← Blog · Development log · einkbro-ios · 開發紀錄 · einkbro-ios

Zero to a full EinkBro App on iOS in three days

This week the EinkBro iOS port went from an empty repository to a working WKWebView browser with near-complete feature parity with the Android original — 57 commits and 53,470 lines between Thursday morning and Saturday, built with Compose Multiplatform and Kotlin.

57
commits
+53,470
lines added (−1,781)
408
files in the tree
3
days, Jul 16–18

Lines added per day

The shape is the story: big-bang port, parity marathon, then surgical gap-closing.
Thu, Jul 1612 commits
+35,193 12 commits · +35,193 / −559 · 397 file changes
Fri, Jul 1722 commits
+14,817 22 commits · +14,817 / −929 · 228 file changes
Sat, Jul 1823 commits
+3,460 23 commits · +3,460 / −293 · 68 file changes

The build, stage by stage

Each layer landed on top of the previous one — UI first, then the engine, actions, settings, and finally polish.
Thu

Compose UI port

The entire Android Compose UI compiling and running on iOS via Compose Multiplatform — first as a UI catalog of every screen and dialog.
258 filesandroid.* shimsUI catalog
Thu

Browser corePhases 1–8

A real WKWebView engine behind the ported UI: persistence, the reader pipeline, privacy, services, and file output.
WKWebView shellRoom KMPreader modeadblocktranslate · TTS · GPTPDF & web archives
Fri

Toolbar & menu actionsPhases A–B

A central BrowserAction dispatcher mirroring Android's toolbar/menu handlers, plus a much deeper engine delegate.
BrowserAction dispatcherlong-press menusengine delegate
Fri

Interaction & power featuresPhases C–J

Core settings start taking real effect, then the heavyweight features arrive one phase at a time.
find on pagegesture FABsplit screenGM_* userscriptsEPUB exportbackup · Instapaper
Fri

ServicesPhases K–M

The AI, speech, and translation layers reach parity with Android.
chat-with-webOpenAI · Edge TTSimage OCRdual captions
Fri

Settings implementationPhase N + batches

Every settings screen wired to real behavior — pickers, sub-editors, privacy enforcement, and a 4,400-line batch round.
enum pickerstoolbar layout editorlocale pickerPDF paper sizeauto-hide toolbar
Sat

Final fixes & updates

A four-domain FEATURE_GAPS audit, then an overnight run closing the remaining gaps and polishing the details.
AI task runnernative chat tabslock-screen TTSdark-mode fixesx.com fixCJK mojibake fix

Thursday — the port, then a real browser by nightfall

12 commits · +35,193 / −559

The day opened with the initial port fa15096: the entire EinkBro Compose UI — 258 files, ~24,000 lines — compiling and running on iOS via Compose Multiplatform, first as a UI catalog of every ported screen and dialog. Tiny source-compatible android.*/androidx.* shims let the original Android files compile unchanged, keeping the package layout diffable against the Android tree.

An eight-phase migration plan was adopted — and all eight phases shipped the same day:

Friday — the feature-parity marathon, Phases A–N

22 commits · +14,817 / −929

With the browser working, Friday was a systematic sweep through fourteen parity phases, each one mapped against specific Android files and line references:

The phases were followed by same-day audit rounds: favicons and a live tab bar, URL-bar search suggestions, a hide/reorder menu editor, removal of the system edit menu in favor of the custom selection menu, and a 4,400-line settings batch (locale picker, PDF paper size, auto-hide toolbar, vertical toolbar, reader settings, zoom reflow, external-link consent).

Saturday — audit-driven gap closing and polish

23 commits · +3,460 / −293

Saturday started with FEATURE_GAPS.md 47ae073, a four-domain audit of what still differed from Android — then an overnight session closed most of it:


Under the hood

The app is a single shared Kotlin module consumed by a thin SwiftUI host. All UI, ViewModels, the preference layer, the Room KMP database, and Ktor networking live in commonMain; the platform seam is a set of expect/actual interfaces — most importantly WebViewEngine, whose iOS actual wraps WKWebView with user scripts and JS message handlers. TTS rides on AVFoundation, adblock on WKContentRuleList, preferences on NSUserDefaults.

SwiftUI host — iosApp
thin wrapper: entry point, einkbro:// scheme, external hand-offs
ComposeApp — commonMain
Compose UI · ViewModels · ConfigManager · Room KMP · Ktor
expect/actual seam
WebViewEngine · PrefsStore · TtsManager · ContentBlocker · FilePicker
iOS frameworks
WKWebView · AVFoundation · NSUserDefaults · WKContentRuleList

Two decisions did a lot of quiet work this week: keeping the original Android package layout so every file diffs cleanly against its Android counterpart, and treating the Android app as the behavioral reference — features were ported by reading the original implementation first, not re-imagined. With no test suite, the loop was a fast Kotlin compile check plus driving each feature in the iOS simulator.

三天內從零打造出完整的 iOS 版 EinkBro

本週 EinkBro iOS 移植版從一個空的 repository,發展成以 WKWebView 為核心、功能幾乎與 Android 原版完全對等的瀏覽器 —— 從週四早上到週六共 57 個 commit53,470 行程式碼,以 Compose Multiplatform 與 Kotlin 打造。

57
個 commit
+53,470
新增行數(−1,781)
408
個檔案
3
天(7/16–18)

每日新增行數

圖表的形狀就是故事本身:一次性大規模移植、功能對等馬拉松、然後是精準補洞。
週四 7/1612 個 commit
+35,193 12 個 commit · +35,193 / −559 · 397 個檔案變更
週五 7/1722 個 commit
+14,817 22 個 commit · +14,817 / −929 · 228 個檔案變更
週六 7/1823 個 commit
+3,460 23 個 commit · +3,460 / −293 · 68 個檔案變更

開發歷程 —— 一層一層疊出瀏覽器

每一層都疊在前一層之上:先有 UI,再接引擎、動作與設定,最後收尾打磨。
週四

Compose UI 移植

整套 Android Compose UI 透過 Compose Multiplatform 在 iOS 上編譯執行 —— 先以 UI 型錄呈現所有畫面與對話框。
258 個檔案android.* shimUI 型錄
週四

瀏覽器核心Phase 1–8

在移植好的 UI 背後接上真正的 WKWebView 引擎:持久化、閱讀管線、隱私、服務與檔案輸出。
WKWebView 外殼Room KMP閱讀模式擋廣告翻譯 · TTS · GPTPDF 與網頁封存
週五

工具列與選單動作Phase A–B

對應 Android 工具列/選單處理器的中央 BrowserAction 派發器,加上更深入的 engine delegate。
BrowserAction 派發器長按選單engine delegate
週五

互動與進階功能Phase C–J

核心設定開始真正生效,重量級功能一個階段一個階段到位。
頁內搜尋手勢 FAB分割畫面GM_* userscriptEPUB 匯出備份 · Instapaper
週五

服務層Phase K–M

AI、語音與翻譯追上 Android 的完整度。
與網頁對話OpenAI · Edge TTS圖片 OCR雙語字幕
週五

設定完整實作Phase N + 批次

每個設定頁都接上實際行為 —— 選單、子編輯器、隱私強制執行,以及 4,400 行的批次補完。
列舉型選單工具列版面編輯器語系選擇PDF 紙張大小自動隱藏工具列
週六

最終修正與更新

四大領域的 FEATURE_GAPS 稽核,接著通宵補完剩餘缺口並打磨細節。
AI 任務執行器原生聊天分頁鎖定畫面 TTS深色模式修正x.com 修正CJK 亂碼修正

週四 —— 完成移植,入夜前跑起真正的瀏覽器

12 個 commit · +35,193 / −559

這一天從初始移植 fa15096 開始:整個 EinkBro Compose UI —— 258 個檔案、約 24,000 行 —— 透過 Compose Multiplatform 在 iOS 上編譯執行, 最初以 UI 型錄的形式呈現所有移植過的畫面與對話框。少量源碼相容的 android.*/androidx.* shim 讓原始 Android 檔案不需修改即可編譯,並保留可直接與 Android 原始碼樹 diff 的套件結構。

接著採用了八階段移植計畫 —— 而且八個階段全數在同一天完成:

週五 —— 功能對等馬拉松:Phase A–N

22 個 commit · +14,817 / −929

瀏覽器能動之後,週五是系統性地推進十四個對等階段,每一階段都對照到具體的 Android 檔案與行號:

各階段之後同日又接著幾輪稽核修補:網站圖示與即時分頁列、網址列搜尋建議、 選單隱藏/排序編輯器、移除系統編輯選單改用自訂選字選單,以及一批 4,400 行的設定(語系選擇、PDF 紙張大小、自動隱藏工具列、直式工具列、閱讀設定、 縮放重排、外部連結確認)。

週六 —— 依稽核清單補洞與打磨

23 個 commit · +3,460 / −293

週六從 FEATURE_GAPS.md 47ae073 開始 —— 一份與 Android 對照、涵蓋四大領域的功能差異稽核 —— 接著在通宵的一輪作業中補完了大部分缺口:


架構筆記

整個 App 是單一共用的 Kotlin 模組,由輕薄的 SwiftUI 殼層載入。所有 UI、 ViewModel、偏好設定層、Room KMP 資料庫與 Ktor 網路層都放在 commonMain;平台接縫是一組 expect/actual 介面 —— 其中最關鍵的是 WebViewEngine,它的 iOS actual 以 user script 與 JS message handler 包裝 WKWebView。TTS 建立在 AVFoundation 上、擋廣告用 WKContentRuleList、偏好設定存於 NSUserDefaults。

SwiftUI 殼層 — iosApp
輕薄包裝:程式入口、einkbro:// scheme、對外交接
ComposeApp — commonMain
Compose UI · ViewModel · ConfigManager · Room KMP · Ktor
expect/actual 接縫
WebViewEngine · PrefsStore · TtsManager · ContentBlocker · FilePicker
iOS 框架
WKWebView · AVFoundation · NSUserDefaults · WKContentRuleList

本週有兩個決定默默發揮了很大的作用:一是保留原始 Android 套件結構, 讓每個檔案都能直接與 Android 對應檔 diff;二是把 Android 版當作行為基準 —— 每個功能都先讀原始實作再移植,而不是重新發明。專案沒有測試套件, 開發迴圈就是快速的 Kotlin 編譯檢查,加上在 iOS 模擬器中實際操作每個功能。