Telegram Android fork。直接基于 Telegram official Android client (DrKLO/Telegram 上游),跟随 upstream master@official tag 周期 merge;包名 xyz.nextalone.nnngram。
历史上 fork 链是
Telegram → Nullgram → Nnngram,但目前 不再依赖 Nullgram 仓库,所有 fork-only 代码在本仓库内独立维护,merge 只对接 upstream Telegram。
| 路径 | 角色 |
|---|---|
TMessagesProj/ |
主 module (APK 产物来源)。namespace=org.telegram.messenger, applicationId=xyz.nextalone.nnngram |
TMessagesProj/src/main/java/org/telegram/** |
上游代码 (merge 时尽量按 upstream,不主动改) |
TMessagesProj/src/main/java/xyz/nextalone/nnngram/** |
fork-only Java/Kotlin |
TMessagesProj/src/main/res/values{,-zh,-zh-rTW}/strings_nullgram.xml |
fork-only 字符串 (3 语言对齐) |
libs/{tcp2ws,pangu,ksp} |
子 module;libs/ksp 是配置代码生成器 |
.github/scripts/upload_ci.py |
CI 发包到 Telegram 的脚本 (caption / changelog 渲染) |
.github/workflows/{ci,pr,revert-stable-commit}.yml |
构建/发布管线 |
- JDK 21+, Android SDK 36 (build-tools 36.0.0), NDK 27+ (CMake 3.22+)
- Gradle 9.x via
./gradlewwrapper - adb (
adb devices必须见序列号才能installDebug)
- Debug APK + 安装:
./gradlew :TMessagesProj:installDebug - Debug APK (仅编译):
./gradlew :TMessagesProj:assembleDebug - Release APK:
./gradlew :TMessagesProj:assembleRelease - 产物:
TMessagesProj/build/outputs/apk/{debug,release}/Nnngram-v<ver>-<sha>-<abi>.apk - 卸载:
adb uninstall xyz.nextalone.nnngram - 启动:
adb shell monkey -p xyz.nextalone.nnngram -c android.intent.category.LAUNCHER 1 - 清理:
./gradlew clean - Lint:
./gradlew :TMessagesProj:lintDebug - 任务列表:
./gradlew :TMessagesProj:tasks
gradle.properties 是 single source of truth:
APP_VERSION_CODE(整数)APP_VERSION_NAME(semver)
merge upstream 后这两个值需要同步成 upstream 的 vsersion。
fork 配置项不手写 boilerplate,全部走 libs/ksp 自动生成:
TMessagesProj/src/main/java/xyz/nextalone/nnngram/utils/Defines.kt加常量:@BooleanConfig const val myToggle = "myToggle" // 默认 false @BooleanConfig(true) const val myToggleOn = "myToggleOn" // 默认 true @IntConfig(default = 5) const val myInt = "myInt" @StringConfig const val myStr = "myStr"
- build 后
xyz.nextalone.gen.Config自动包含myTogglegetter +toggleMyToggle()mutator (boolean) /setMyInt(int)(int) 等。 - UI 入口:对应分类的
xyz/nextalone/nnngram/activity/*SettingActivity.java里加 row, 调 generated mutator。 - 字符串:
strings_nullgram.xml三语言全配。
- upstream-touching 改动:能不改
org.telegram.**就别改 — merge 冲突成本高。优先把逻辑放到xyz.nextalone.nnngram.*帮助类,在 upstream 文件里只插一行调用。 - upstream listener 覆盖陷阱:上游用
setOnXxxListener多次注册时,最后一次胜出。fork 在同 view 已注册 listener,merge 后必复查是否被上游新增的同 setter 覆盖(典型例:MainTabsActivity长按 chat tab)。 - drawable / icon 系列:fork 用
nagram_*自定义图标,不引入 upstream 新增的icon_2/4/5/6启动器系列。 - CMakeLists:保留 fork 的 cpp20/ccache/-Bsymbolic;NDK 27+ warning flag 跟 upstream。
- 3 语言强约束:
values/、values-zh/、values-zh-rTW/三份strings_nullgram.xml必须同步新增/删除字符串,缺一会被 lint 拒绝。
Conventional Commits:subject 首词必须是 feat|fix|perf|refactor|docs|test|build|ci|style|chore|revert|merge 之一 (CI 按这个分组渲染发布消息)。
格式:
<type>(<scope>)?: <subject>
<空行>
<body — 多段, 解释 why, 含失败案例 / 关联 issue>
<空行>
<trailer 区, 类似 Co-Authored-By 风格>
加新 Config toggle / setting 时,commit body 末尾必须加 Setting-Path: trailer 指明 in-app 路径与 deep-link。CI 会把它直接渲染到发到 Telegram 的 caption / changelog 里,给用户一个一眼定位开关、且可点击直接跳转的索引。
格式:
Setting-Path: <UI 文案路径> | <deep-link URL>
|前后两段都 trim, anchor 给用户看, URL 给点击跳转- 一个 commit 可以加多条
Setting-Path:(一个功能开多个 toggle 时) - 路径用
→分隔层级 - URL 可省略(向后兼容):仅
Setting-Path: <text>→ 渲染为斜体不带链接
fork 已通过 SettingsHelper.processDeepLink (xyz.nextalone.nnngram.helpers) 暴露设置项 deep-link 路由。两种等价 URL 都可用:
| 形式 | 例子 | 说明 |
|---|---|---|
https://t.me/nnnsettings/<cat>?r=<row_key> |
https://t.me/nnnsettings/c?r=showRecentChatsOnTabLongPress |
推荐。Bot API HTML 模式必过 filter,点击通过 t.me intent filter 被 Telegram 客户端接管,转给 LaunchActivity → SettingsHelper |
tg://nnn/<cat>?r=<row_key> |
tg://nnn/c?r=showRecentChatsOnTabLongPress |
直接 scheme,LaunchActivity 处会改写到 nnnsettings。tg:// link 在 Bot API HTML 模式的兼容性较差,不推荐发到频道 |
<cat> 由目标 SettingActivity.getKey() 决定:
| key | Activity |
|---|---|
c (或 chat / chats) |
ChatSettingActivity |
g (或 general) |
GeneralSettingActivity |
e (或 experimental) |
ExperimentSettingActivity |
| `` (空) | MainSettingActivity (nnn 设置主页) |
<passcode key> |
PasscodeSettingActivity |
<row_key> = 在对应 SettingActivity.updateRows() 中 addRow("<key>") 传入的 key,等同 Defines.kt 里的 const name(因为 KSP 把 Defines 的常量值用作 row key)。SettingsHelper 解析 query 参数后调 BaseActivity.scrollToRow(key) 自动滚到对应位置。
feat(chat-menu): 长按聊天标签弹最近会话 toggle
恢复 12.7 merge 后被 openFoldersSelector 覆盖的 fork 入口,
新增 Config 网关让用户在两套行为间切换.
Setting-Path: nnn 设置 → 聊天 → 长按聊天标签显示最近会话 | https://t.me/nnnsettings/c?r=showRecentChatsOnTabLongPress
渲染后 caption (Telegram) 形如:
✨ Features
• [abc1234] feat(chat-menu): 长按聊天标签弹最近会话 toggle
↳ ⚙ nnn 设置 → 聊天 → 长按聊天标签显示最近会话 ← 可点击, 直接跳进 app 对应开关
非"加新开关"的 commit (fix/refactor/ci/...) 不要加 Setting-Path:,避免噪音。
git log LAST_SENT_SHA..HEAD拿待发 commits (含失败 build 残留)- POST 完整 changelog 到
METADATA_CHANNEL - POST arm64 APK + caption 到
CHAT_ID(主频道) - POST 同 APK 到
APK_CHANNEL, 记message_id - 1作为start_id关联 changelog - 缓存 HEAD SHA → 下次 build 用
caption budget = 1024B (减去 chip / footer 余量), 超出按 group 倒序丢尾条。Setting-Path 渲染在 subject 同 entry 内,被丢掉时连开关路径一起丢——所以控制 commit 数量保证不溢出。