Dự án là gì
FlorisBoard là bàn phím ảo (IME) Android mã nguồn mở. Toàn bộ UI dùng Jetpack Compose — không có màn XML song song, chỉ vài cầu nối AbstractComposeView/FrameLayout.
- Ngôn ngữ & UI: Kotlin + Jetpack Compose,
minSdk 26, target/compile36. - Đóng gói: multi-module Gradle —
:app(bàn phím + UI settings) và:lib:{android,compose,color,kotlin,snygg,native}. - Package: app code
dev.patrickgold.florisboard.*; lib codeorg.florisboard.lib.*. Bật type-safe project accessors (projects.lib.snygg). - Không ViewModel: màn Compose đọc/ghi trực tiếp JetPref datastore + observe
StateFlowdo Manager phát.
Pattern trung tâm — Manager singleton + Context extension
FlorisApplication sở hữu mọi manager dưới dạng field lazy {}. Đây chính là "DI container" của dự án — không có Hilt/Dagger.
// FlorisApplication sở hữu, lazy khởi tạo keyboardManager, nlpManager, subtypeManager, themeManager, clipboardManager, editorInstance, extensionManager, glideTypingManager, cacheManager // Truy cập ở bất kỳ đâu qua Context extension → resolve về singleton context.keyboardManager() context.themeManager()
WeakReference toàn cục
FlorisApplication và FlorisImeService mỗi cái giữ một WeakReference tĩnh tới chính nó, để framework lấy tham chiếu trước khi setup xong. Service expose helper tĩnh (currentInputConnection(), launchSettings()) qua đó.
Direct-boot aware
Trước khi user mở khoá máy, FlorisApplication chỉ init ExtensionManager, rồi chờ ACTION_USER_UNLOCKED mới init phần còn lại.
Rủi ro [W1]: WeakReference toàn cục có thể gây NPE/leak. Chi tiết trong .code_index/issues.md.
Điểm vào của ứng dụng
| Entry point | Vai trò |
|---|---|
FlorisApplication | Init app, DI container lazy cho mọi manager, gating direct-boot, load native lib. |
FlorisImeService | IME Service chính (LifecycleInputMethodService); nối manager ↔ Compose UI, vòng đời cửa sổ nhập, hardware key, inline suggestion, insets. |
FlorisSpellCheckerService | Service spell-check, delegate NlpManager.spell. |
FlorisAppActivity | Single-activity host cho UI settings; Routes.AppNavHost. |
KeyboardManager | HUB Điều phối key event tới editor / state / service. |
State machine — các trục chính
KeyboardState
Packed ULong (bit mask tay), sở hữu bởi KeyboardManager.activeState. Chứa keyboardMode, inputShiftState, imeUiMode, incognito, cờ selection/overflow. Mutate qua batchEdit.
ImeUiMode
Panel nào đang hiện trong ImeWindow: TEXT / MEDIA / CLIPBOARD.
ThemeMode
ALWAYS_DAY / ALWAYS_NIGHT / FOLLOW_SYSTEM / FOLLOW_TIME.
Bản đồ module
Multi-module Gradle. :app là bàn phím + UI settings; các :lib:* là thư viện tách riêng.
| Module | Package | Vai trò |
|---|---|---|
:app | dev.patrickgold.florisboard.* | Bàn phím + UI settings (245 file .kt, không ViewModel). |
:lib:snygg | org.florisboard.lib.snygg | Engine theming/styling giống CSS. |
:lib:android | org.florisboard.lib.android | Wrapper/extension framework Android. |
:lib:compose | org.florisboard.lib.compose | Helper Compose dùng chung (Floris*). |
:lib:color | org.florisboard.lib.color | Helper màu / Material You. |
:lib:kotlin | org.florisboard.lib.kotlin | Util Kotlin thuần (không dep Android). |
:lib:native | org.florisboard.libnative | Rust → fl_native .so (CMake→NDK); hiện là stub dummyAdd. |
Có hai package tên lib: dev.patrickgold.florisboard.lib.* (app-internal) khác với các Gradle module :lib:*. Đừng nhầm.
12 tính năng cốt lõi
Mỗi thẻ trỏ tới tài liệu chi tiết trong docs/features/, đối chiếu với .code_index/ để định vị chính xác symbol.
Input pipeline
Touch/hardware key → EditorInstance → InputConnection + vòng lặp suggestion.
Code index:features/input_pipeline.md
Đọc chi tiết
02
Render bàn phím
TextKeyboardLayout, TextKey, glide typing, popup.
Code index:modules/app_ime_text.md
Đọc chi tiết
03
Gợi ý / NLP
NlpManager, provider, dictionary, spell, editor engine.
Code index:modules/app_ime_nlp.md
Đọc chi tiết
04
Emoji & Media
Emoji palette, history, EmojiCompat, emoticon.
Code index:modules/app_ime_media.md
Đọc chi tiết
05
Clipboard
ClipboardManager, Room storage, media provider.
Code index:modules/app_ime_media.md
Đọc chi tiết
06
Smartbar & Quick actions
Smartbar, candidates row, sắp xếp quick action.
Code index:modules/app_ime_smartbar.md
Đọc chi tiết
07
IME Window
ImeWindowController, spec/constraint, insets (resize/floating).
Code index:modules/app_ime_window.md
Đọc chi tiết
08
Theming (Snygg)
ThemeManager + Snygg engine: load → compile → render.
Code index:features/theming.md
Đọc chi tiết
09
Extensions (.flex)
ExtensionManager, model, import/export, 3 loại ext.
Code index:modules/app_ext.md
Đọc chi tiết
10
Subtype / Localization
SubtypeManager, locale, language pack.
Code index:modules/app_core.md
Đọc chi tiết
11
Settings UI & Navigation
FlorisScreen DSL, Routes, deep link.
Code index:graph/navigation.md
Đọc chi tiết
12
Preferences / Data / IO
AppPrefs (JetPref), FlorisRef, ZipUtils, CacheManager, log/crash.
Code index:modules/app_data_core.md
Đọc chi tiết
Quy ước xuyên suốt
- Serialization: kotlinx.serialization JSON cho mọi data (layout, theme, extension, subtype); nhiều
Serializertuỳ biến. - Room cho DB on-device (clipboard history, dictionary); compiler KSP; schema ở
app/schemas. - JetPref (lib của patrickgold, KSP) cho preferences — model ở
app/AppPrefs.kt. - Compose opt-in bật toàn dự án (context parameters, explicit backing fields,
-Xwhen-guards). - UI Compose-only — không có màn XML song song.
- Translation do Crowdin quản lý upstream —
res/values-*/sync từ Crowdin, upstream không nhận PR dịch.
Cảnh báo build & nguyên tắc bất biến
[W1] WeakReference
WeakReference toàn cục → nguy cơ NPE / memory leak.
[W2] Layer-skip
KeyboardManager gọi thẳng UI tĩnh của service; SelectSubtypePanel mutate thẳng activeState.
[W3] runBlocking
runBlocking trên nhiều path main-thread → jank / ANR.
AI policy: upstream AI_POLICY.md cấm code do AI sinh trong đóng góp cho repo public. Đây là fork của Solar — xác nhận đích đến trước khi push code AI-authored lên upstream.
Native lib: fl_native build qua CMake→NDK cần toolchain Rust/NDK. Trên Windows thiếu toolchain có thể fail; System.loadLibrary bọc try/catch nên app vẫn chạy. Nhưng UnsatisfiedLinkError là Error chứ không phải Exception → thoát khỏi catch [W-native].
Nguyên tắc bất biến (đừng phá khi tích hợp)
- Manager chỉ truy cập qua
Context.<manager>()ext. KeyboardManager.activeStatelàKeyboardStatemutable duy nhất — mutate quabatchEdit.ImeWindowControllerphải giữ không phụ thuộc Android framework (đang unit-test trên JVM).- Không giữ
InputConnection— luôn lấy per-call quaFlorisImeService.currentInputConnection(). - Không có tầng ViewModel: màn hình đọc/ghi JetPref + observe StateFlow của Manager.