Bump AboutLibraries plugin to 15.0.4 -> 15.2.0; parse
aboutlibraries.json on Dispatchers.IO via produceState (header renders
immediately, rows fill in); drop animateContentSize from rows.
Paging result rows had no keys and were matched by position: appended
pages and query diffs could not reuse row state. Keys derived from
dictionaryPath+offset+word are stable per row, and the paging source
collapses duplicate entries for one dictionary card: case and diacritic
alias variants of a headword share the normalized word, so entries are
deduplicated by dictionaryPath+word+offset before emitting.
IndexSearcher.compareEntryGroups / IndexComparisonSummary /
IndexComparisonException / readEntryOrNull and AboutLibrariesData.decode
have no call sites left in the app.
monotonicTargetProgress is held in remember and only ever grows via
maxOf, so when a new indexing starts after a previous one reached a
higher value the bar is left at the old level while the percent label
starts at 0. Snap it to the current target when the section becomes
visible so the bar follows the new operation from its first frame.
Notifications belong to the package and outlive the process: after the
system kills the foreground service mid-download the frozen progress
notification stays in the shade forever. A fresh process means no
service is running, so any leftover notification is stale.
Compose compiler reports showed DownloadState's sealed root as Uncertain
and DownloadProgressState/SettingsUiState as runtime stability, so
skippability checks were deferred to runtime. The annotations make them
compile-time stable and turn any future mutable field into an explicit
contract break.
perFileProgress was never populated and its progress branch was
unreachable; currentFile, currentIndex, currentFileProgress and label
were write-only (no consumer read them); progressPercent had no
callers; the -1f aggregate sentinel is gone along with the fallback
branches that only fired when the tracker was absent.
The early return skipped the terminal-state delay and reset, leaving
a phantom 100% Success (and a stuck "indexing" bar plus a success
notification) for downloads that never happened.
Instant server failures (404/HTML error page) no longer flash a 0%
progress bar; the check mirrors the downloader's previous-day fallback
and tolerates transient network errors, which still go through the
downloader's retry path.
Downloads killed by WiFi/mobile transitions failed with the generic
"Dictionary download failed" message because UnknownHostException,
ConnectException, unexpected end of stream, and closed-socket errors
were not classified as network errors and were never retried.
Classify them by exception type and message so the retry loop
engages and exhaustion reports the network-specific message.
Bump OkHttp 5.4.0 to 5.5.0: a throwing system proxy selector now
falls back to a direct connection instead of failing the call,
complementing the download resilience fix.
- The dictionary update card now returns after a failed download
- Fixed headwords and articles with escaped characters being displayed incorrectly
- Fixed slow rendering of articles containing many bracket characters
- Blocked swipe-to-delete on dictionary rows while a download, extract,
import or indexing pass is running. Deleting mid-update removed the
dictionary's source from preferences while the pipeline kept its own
snapshot, so the post-update rescan re-created the dictionary with its
update source permanently lost.
- Fixed the dictionary icon leaving the row while swiping to delete: the
item now clips to its bounds and the delete button is a static overlay
instead of a counter-translated child of the sliding row.
- Fixed the dictionary update card taking too long to reappear after
cancelling a download (DownloadManager).
- Bump agp 9.3.0 -> 9.3.1; migrate the release build type from
isMinifyEnabled/isShrinkResources/proguardFiles to
optimization { enable = true } with keep rules moved to
app/src/release/keepRules/proguard-rules.keep.
- Fixed a crash on broad queries (e.g. a single letter across several
dictionaries): the results list keyed items by dictionary
path/offset/word, and duplicate keys are illegal in a Compose list;
drop the explicit key, let Paging identify items by position instead
(SearchResultsList).
- Fixed a disabled dictionary reactivating itself on the next app
launch: the active flag is now persisted through
PreferencesManager/DataStore and reapplied whenever the dictionary
list is rebuilt (LocalDictionaryRepository).
- Search results no longer stop at the first ~100-200 matches: the
ranked head page hands back a cursor per unfinished prefix range,
and IndexEntryPagingSource pages the rest straight from the index,
merged alphabetically across dictionaries, until every match is
surfaced (IndexSearcher, IndexEntryPagingSource).
- Added diacritic-insensitive search: folded index entries are merged
into the primary index as aliases, so accented and plain spellings
find each other (DslIndexer, IndexSearcher).
- Fixed indexing failures on dictionaries with dense non-Latin text: a
truncated multi-byte sequence at a sample boundary was misread as
invalid UTF-8 and the whole file fell back to UTF-16LE
(DslCharsetDetector).
Fixed the update card reappearing for a moment after tapping it: the tap
re-ran the status check and republished NeedsUpdate before the download
service reported Loading, so the card flashed back in that gap.
Fixed the update card never returning after cancelling a download: both the
Cancelled branch and cancelDownload() forced the status to UpToDate, and the
card's tap is the only thing that re-evaluates it. The status is now
re-derived on the Cancelled to Idle transition, once the partially downloaded
archive - which carries today's date and would otherwise be read as a current
dictionary - has been cleaned up.
Fixed the download progress bar staying at 0% far too long: progress was
published in 3-percentage-point buckets and then scaled by the download stage
weight, so the bar only moved after about 7% of the archive had transferred.
Rebuilt the search field on the Material 3 TextField instead of a hand-rolled
BasicTextField inside a Surface, so it picks up the standard container, cursor
and icon treatment. Dictionary entry titles in the search field, the results
list and the article bar now share a dictionaryTitleLarge style that keeps the
font padding, so their line height lines up with the Material 3 text field.
- Fixed handling of an HTML "not found" payload served with HTTP 200 for a
missing daily build: DictionaryDownloader validates Content-Type and the
gzip magic bytes and falls back to the previous day's build.
- Fixed the update check counting a text/html HEAD response as an available
build (DictionaryChecker).
- Fixed dictionary source matching: sources are matched by normalized identity
key and the normalized template is handed to the downloader in
downloadSpecificSources (DictionaryRepository).
- Fixed the dictionary progress indicator animating backwards to 0% during its
exit animation; it now stays frozen while it fades out
(DictionaryProgressSection).
- Fixed slow first composition of every screen by extending the checked-in
baseline profile to the settings/dictionary flow and the Compose framework:
the Compose alpha AARs ship near-empty profiles, so every first composition
ran interpreted.
AboutLibraries fetched license text over the network at build time, which is unavailable on F-Droid's isolated buildserver and produced different license identifiers (SPDX id vs. hash fallback) between environments, breaking APK reproducibility.