Merge upstream LeanType v3.8.8 before 3.10.0 release#111
Open
AsafMah wants to merge 159 commits into
Open
Conversation
Parse Gboard format header dynamically to fix missing words and swapped values. Optimize using bulkInsert to reduce database insertion IPC overhead.
Set versionCode to 3840 and versionName to 3.8.4 in build.gradle.kts. Create Fastlane changelog metadata at 3840.txt.
Add responsive search filtering for text expansion shortcuts. Add quick placeholder selection row with rich cursor-based insert support to Add/Edit Dialog.
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.
Remove redundant template placeholder explanation while retaining the list of supported placeholder tags.
Add support and UI representations for %month%, %month_short%, %year%, and %week% placeholders.
Add and integrate support for %battery%, %device%, and %android% placeholders.
Add and integrate support for %language% placeholder, which expands to the current active keyboard display language.
- 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
- 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.
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.
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.
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.
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.
- 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.
- 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.
…alog
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.
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.
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
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).
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.
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.
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.
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.
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.
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.
…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.
Align provider preference source and observe changes dynamically to update UI fields immediately. Wrap preferences in key() to prevent Compose state reuse.
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.
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.
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
…ift-symbols fix: number row keeps digits when keyboard is shifted to uppercase
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Merges latest
upstream/mainfrom LeanBitLab/LeanType intodevafter PR #109. Upstream advanced from the previously merged v3.8.6 tip to v3.8.8 + two post-tag docs/badge commits (v3.8.8-2-ge2763bf9c).This keeps the fork from shipping the 3.10.0 release on a stale upstream baseline.
Upstream highlights included
standardOptimisedflavorFork-specific conflict decisions
applicationId = "com.asafmah.leantypedual", version3.10.0/4000, LeanTypeDual README branding/download links.standardOptimised; scrubbedstandardOptimisedchecks from build and source.AUTOSPACE,AUTO_CAP,FORCE_AUTO_CAP,JOIN_NEXT,FORCE_NEXT_SPACE,UNDO_WORD, shortcut rows) while adopting upstream's split toolbar exclusion model.mHandwritingView.isShown()beforestopHandwriting()).AsafMah/LeanType; the upstream workflow rewrites README links to LeanBitLab.Verification
./gradlew.bat compileOfflineRunTestsKotlin --no-daemon --console=plain✅./gradlew.bat compileStandardRunTestsKotlin --no-daemon --console=plain✅./gradlew.bat compileOfflineliteRunTestsKotlin --no-daemon --console=plain✅./gradlew.bat :app:assembleStandardDebug :app:assembleOfflineDebug :app:assembleOfflineliteDebug --no-daemon --console=plain✅git diff --cached --checkclean before commit ✅INTERNETremains Standard-only ✅standardOptimisedreferences removed ✅Release note
PR #110 (
dev→mainrelease 3.10.0) should wait until this merge lands, then be refreshed/updated before taggingv3.10.0.