v0.6.0-alpha · Fork của Solar

Bàn phím ảo FlorisBoard
cho Android, mã nguồn mở

IME (input method) viết bằng Kotlin + Jetpack Compose. Kiến trúc manager-singleton không dùng Hilt/Dagger, engine theming Snygg giống CSS, và hệ extension .flex. Tài liệu này mô tả chi tiết kiến trúc, bản đồ module và 12 tính năng cốt lõi.

Kotlin + Compose minSdk 26 · target 36 Multi-module Gradle Material You dev.patrickgold.florisboard
245
file .kt trong :app
6
module :lib:*
12
tính năng cốt lõi
~24
màn settings (Compose)
Tổng quan

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/compile 36.
  • Đó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 code org.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 StateFlow do Manager phát.
Kiến trúc

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

FlorisApplicationFlorisImeService 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.

Entry point

Điểm vào của ứng dụng

Entry pointVai trò
FlorisApplicationInit app, DI container lazy cho mọi manager, gating direct-boot, load native lib.
FlorisImeServiceIME Service chính (LifecycleInputMethodService); nối manager ↔ Compose UI, vòng đời cửa sổ nhập, hardware key, inline suggestion, insets.
FlorisSpellCheckerServiceService spell-check, delegate NlpManager.spell.
FlorisAppActivitySingle-activity host cho UI settings; Routes.AppNavHost.
KeyboardManagerHUB Điều phối key event tới editor / state / service.
State

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.

Module

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.

ModulePackageVai trò
:appdev.patrickgold.florisboard.*Bàn phím + UI settings (245 file .kt, không ViewModel).
:lib:snyggorg.florisboard.lib.snyggEngine theming/styling giống CSS.
:lib:androidorg.florisboard.lib.androidWrapper/extension framework Android.
:lib:composeorg.florisboard.lib.composeHelper Compose dùng chung (Floris*).
:lib:colororg.florisboard.lib.colorHelper màu / Material You.
:lib:kotlinorg.florisboard.lib.kotlinUtil Kotlin thuần (không dep Android).
:lib:nativeorg.florisboard.libnativeRust → fl_native .so (CMake→NDK); hiện là stub dummyAdd.

hai package tên lib: dev.patrickgold.florisboard.lib.* (app-internal) khác với các Gradle module :lib:*. Đừng nhầm.

Tính năng

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.

01

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
Convention

Quy ước xuyên suốt

  • Serialization: kotlinx.serialization JSON cho mọi data (layout, theme, extension, subtype); nhiều Serializer tuỳ 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.
Rủi ro & bất biến

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 UnsatisfiedLinkErrorError 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.activeStateKeyboardState mutable duy nhất — mutate qua batchEdit.
  • ImeWindowController phả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 qua FlorisImeService.currentInputConnection().
  • Không có tầng ViewModel: màn hình đọc/ghi JetPref + observe StateFlow của Manager.