我修改了一个自己的只读笔记基于GitJournal

small parking
目录

起因

我需要一个"只能看、不能改"的笔记应用。

场景很简单:把技术文档、工作日志、参考资料归档到一个地方,偶尔翻出来看,但绝不希望误触误改。手机上的备忘录点一下就能编辑,太容易手滑;而我只想要一个纯粹的阅读仓库。

找了一圈没有完全合适的,于是决定自己改一个 —— 目标锁定在 GitJournal:一个基于 Git 的 Markdown 笔记应用,笔记以标准 Markdown + YAML 头格式存储,数据完全归自己所有(GitHub / Gitea / 自建服务都行)。完美契合"数据自有"的需求。

第一关:编译环境

Fork 下来第一件事就是编译,结果环境全空:没有 Flutter、没有 Android SDK、JDK 版本也不对。补环境的过程踩了几个有意思的坑:

Flutter 版本陷阱

项目要求 Flutter >= 3.41.5,我直接装了当时最新的 stable 3.44.9,编译报错:

Error: The class 'IconData' can't be extended outside of its library because it's a final class.

Flutter 3.44 把 IconData 变成了 final class,而项目依赖的 font_awesome_flutter 10.x 还在 extends IconData,全线不兼容。查了 flutter/flutter 的提交记录,这个改动是 2026-03 的 PR #181345,首次进入 3.44.0,3.41.x 没有。而项目约束是 >=3.41.5,所以正确解法是降级而不是改依赖:

git clone -b stable https://github.com/flutter/flutter.git ~/flutter
cd ~/flutter && git fetch origin tag 3.41.9 --depth 1 && git checkout 3.41.9

JDK 版本陷阱

系统装的是 OpenJDK 26,但 Gradle 8.12(AGP 8.9.1 配套)最高支持 Java 23,构建直接失败。装一个 JDK 17 给 Gradle 专用,不污染系统默认 Java:

sudo pacman -S --needed jdk17-openjdk ninja
echo "org.gradle.java.home=/usr/lib/jvm/java-17-openjdk" >> ~/.gradle/gradle.properties

Gradle 还不读环境变量代理,得显式写进 ~/.gradle/gradle.properties

systemProp.http.proxyHost=192.168.31.2
systemProp.http.proxyPort=8080
systemProp.https.proxyHost=192.168.31.2
systemProp.https.proxyPort=8080

Android SDK

项目固定了 NDK 版本 28.2.13676358,用 sdkmanager 把组件装齐并接受许可:

sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0" \
  "ndk;28.2.13676358" "cmake;3.22.1"
yes | sdkmanager --licenses

环境齐了,编译一次通过。

第二关:只读模式

核心需求。先读代码理清所有写入路径,发现一个好消息:这个应用的所有写操作都收敛在 repository.dart 的 10 个方法里(addNote / updateNote / removeNotes / renameNote / moveNotes / removeFolder / renameFolder / saveNoteToDisk / undoRemoveNote / discardChanges),UI 各处(编辑器、列表、重命名、新建)全部调用它们。

于是设计了三层防线:

① 设置开关

Settings 类加 readOnlyMode 字段(SharedPreferences 持久化),放在"设置 → 编辑器"页顶部:

SwitchListTile(
  title: Text(context.loc.settingsEditorsReadOnlyMode),
  subtitle: Text(context.loc.settingsEditorsReadOnlyModeSubtitle),
  value: settings.readOnlyMode,
  onChanged: (bool newVal) {
    settings.readOnlyMode = newVal;
    settings.save();
    setState(() {});
  },
)

② 数据层强制拦截(核心)

所有写方法开头统一检查,只读模式下直接抛异常 —— 就算有 UI 漏网之鱼,也改不了任何东西:

void _checkWriteAllowed() {
  if (settings.readOnlyMode) {
    throw Exception("Read-only mode is enabled");
  }
}

覆盖范围包括容易被忽略的路径:编辑器切后台时的自动保存、返回键触发的保存、discardChanges(git checkout 单文件,也是改文件)。

③ UI 层禁用(体验)

  • 编辑框 readOnly(正文、标题、清单勾选、日记日期选择全禁)
  • 左滑删除:Dismissible 直接不挂载 —— 这很关键,因为 Dismissible 是"先滑出动画、后调回调",只靠数据层拦截会导致笔记视觉上消失再报错
  • 删除 / 重命名 / 移动 / 编辑标签按钮隐藏,新建入口(FAB、底部栏、深链路由)全部隐藏
  • 删除整个仓库的按钮也拦截

同步策略

只读模式下 git 同步做了差异化处理:pull 保留(拉取远端更新没问题),但本地 _commitUnTrackedChanges(commit)和 push 跳过 —— 保证本地零提交零推送。

顺手做的三件事

1. 简体中文全量翻译

发现一个离谱的事实:GitJournal 的简体中文翻译文件 app_zh_Hans.arb 从来没被翻译过 —— 471 条字符串只有 1 条是中文(“根目录”),其余全是英文模板占位,上游 master 也一样。所以配置中文后界面还是英文。

我把 471 条全部翻译成了简体中文,还修了 2 个隐藏的 locale 匹配 bug:

  • gitJournalSupportedLocales 同时包含 Locale('zh')(英文模板数据)和 Locale.fromSubtags('zh','Hans')(真中文),解析时同分平局先命中英文条目
  • app.dart"zh_Hans" 拆成 Locale('zh', 'Hans')Hans 被当成国家码而不是 script,永远匹配不到简体中文类

修完系统语言切中文,界面立刻全中文。

2. Pro 功能解锁

GitJournal 的 Pro(内购解锁层)在代码里就是 AppConfig.proMode 一个 bool,ProOverlay 组件做门禁。自己的 fork,直接改默认值 + 跳过 IAP 验证:

// Fork 版:Pro 功能默认解锁,不进行 IAP 验证
bool proMode = true;
var validateProMode = false;

主屏幕选择、外部存储、自定义元数据、KaTeX 渲染等 Pro 功能全部可用。

3. build.sh 一键构建

之前每次都要敲一串命令,封装成 build.sh

./build.sh            # 编译 prod release 并安装到手机
./build.sh build      # 仅编译
./build.sh install    # 仅安装
./build.sh dev        # dev debug 版(包名带 .dev,可与正式版共存)

自动检测设备、处理小米的 USB 安装授权弹窗、提示版本降级问题。

使用体验

用了一周,很满意:

  • 只读模式:笔记库成了纯阅读仓库,左滑、长按、编辑全无反应,彻底放心;想改的时候关掉开关即可
  • 中文界面:设置项、菜单、对话框全部中文,比上游原版体验好
  • 同步:手机和电脑之间用 Git 同步,pull 照常工作,只读模式下也不会产生垃圾提交

效果截图

设置 → 编辑器 里的只读模式开关:

只读模式开关

全中文界面(日记视图):

中文界面

仓库

改动都在我的 fork:github.com/xtccc/GitJournal,包含只读模式、全量中文翻译、Pro 解锁和构建脚本,欢迎围观。