ESP32-S3 Bench Guide
ESP32-S3 N16R8 bench guide: live pinout, lessons, simulators and the bench log.
Alice 听写 — 拍照即听写。拍照、粘贴或分享成词表,Edge 朗读,听写中可调语速与重听。内置 290 套中考高考与教材词库,数据保存在本机。
Translation pending
EducationDart · ShellGitHub @vvenvOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
单词听写
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
英文单词听写应用,Flutter 实现,支持 Android / iOS / Web。 0.6.3 之前的版本是 Expo / React Native 实现,仍在 main 分支。 本分支是 Flutter 重写;迁移那一刻的 RN 快照打了 tag rn-final,记录见 docs/migration-from-expo.md。Read the full README ↗
"Down the rabbit-hole of words."
英文单词听写应用,Flutter 实现,支持 Android / iOS / Web。
官网与下载:https://alice.wenfu.dev
0.6.3 之前的版本是 Expo / React Native 实现,仍在
main分支。 本分支是 Flutter 重写;迁移那一刻的 RN 快照打了 tagrn-final,记录见docs/migration-from-expo.md。
| 首页 | 词库 | 听写 | 完成 |
|---|---|---|---|
website/ 子包)git clone https://github.com/vvenv/alice.git
cd alice
cp .env.example .env # 填入自己的密钥(见下方「配置」)
flutter pub get
./do.sh # 交互菜单:运行 / 测试 / 安装 / 发版…
# 或直跑:
./do.sh run # flutter run
./do.sh run --chrome # Web
./do.sh doctor # 环境自检
官网本地开发:
pnpm install
pnpm --filter website dev
底层命令仍可直接用(do.sh 只是薄封装):
flutter run
flutter run -d chrome
./do.sh test --lint # analyze + test + pnpm lint
# 或:
flutter analyze # 0 issue
flutter test # 190 个用例
pnpm lint # scripts/ 的 TypeScript
pnpm --filter website check
敏感配置放在 gitignored 的 .env 中(模板见 .env.example):
| 环境变量 | 说明 | 必填 |
|---|---|---|
ZHIPU_API_KEY | 智谱 API Key(OCR 拍照识词),申请地址 | Android / iOS OCR 需要;Web 不注入 |
自定义发音服务的接口地址与密钥由用户在应用内填写(设置 → 声音 → 发音源),
存在设备本地,不进构建产物。
| DEPLOY_SERVER | 发布脚本的部署目标(user@host) | 仅发版需要 |
| DEPLOY_REMOTE_DIR | 服务器上的站点目录 | 仅发版需要 |
| R2_* / CLOUDFLARE_* | APK / 截图上传用的 Cloudflare R2 配置 | 仅发版需要 |
ZHIPU_API_KEY 走 Dart 的编译期常量,不进仓库:
flutter build apk --release --dart-define=ZHIPU_API_KEY=xxx
Web 构建绝不要传这个参数 —— Web 产物是公开 JS,内嵌共享密钥等于把它发出去。
Web 上 OCR 由用户在设置里自备 API Key。lib/services/config.dart 里还有
ZHIPU_BASE_URL 这个可选的编译期常量,有默认值。
推荐走 ./do.sh(会提示选版本,再调下面的脚本):
./do.sh release # 交互选 keep / patch / minor / major
./do.sh release patch # 升 patch 后发版
./do.sh website # 落地页 + /app/
./do.sh webapp # 仅 /app/
./do.sh screenshots # docs/screenshots/ → R2,并改写 site.ts 里的截图 URL
官网截图放在 R2 上,文件名带内容哈希(缓存一年)。./do.sh website 和 Android
发版在构建官网前都会核对 site.ts 是否指向当前截图;换了截图还没上传时,终端里会
问一句要不要先传,非交互运行则直接报错退出。
pnpm release:android # 保持当前版本发版
pnpm release:android patch # 0.7.2 → 0.7.3
pnpm release:android minor # 0.7.2 → 0.8.0
pnpm release:android major # 0.7.2 → 1.0.0
pnpm release:android 0.7.0 # 指定版本号
流程:可选升版 → flutter build apk --release --split-per-abi --target-platform android-arm64 → 上传 arm64-v8a 到 Cloudflare R2 → 更新官网下载链接 → 构建并 rsync 部署官网。
详见 scripts/release.sh。没有 android/key.properties 时会警告并继续用 debug 签名。
版本号只有 pubspec.yaml 里 version: 0.7.2+14 这一处,
versionName / versionCode 与 iOS 的 MARKETING_VERSION /
CURRENT_PROJECT_VERSION 都由 Flutter 从它派生。versionCode 只增不减 ——
Android 拒绝安装比机器上现有版本低的 versionCode。
pnpm release:website # 落地页 + /app/(推荐)
pnpm release:website -- --skip-webapp # 仅落地页
pnpm release:webapp # 仅更新 /app/
两者无先后顺序要求:落地页 rsync 只排除 app/,不会互相覆盖。APK 托管在
Cloudflare R2,不经过部署服务器。Web 应用入口:https://alice.wenfu.dev/app/。
APK 出来之后按这几项核一遍 —— release.sh 会自动做第一项,其余建议手工看一眼:
aapt2 dump badging build/app/outputs/flutter-apk/app-release.apk | head
classes.dex 里。manifest 写的是相对类名 .MainActivity,
它按 gradle 的 namespace 解析。如果 Kotlin 源码的包名对不上,编译期毫无征兆,
R8 还会把这个「没人引用」的类删掉,装到手机上必然闪退。0.6.2 就栽在这里。com.vvenv.alice、应用名「Alice 听写」、版本号与 pubspec.yaml 一致aapt2 dump xmltree --file AndroidManifest.xml <apk> | grep SEND)——
少了它,别的应用的分享菜单里就没有 Alice体积构成里最大的两块是两个思源宋体(各 14.1 MB,Flutter 只对图标字体做 tree-shaking,正文字体不裁剪)和原生库。发版只打 arm64,官网也只挂这一个:
flutter build apk --release --split-per-abi --target-platform android-arm64
├── lib/ # 应用源码
│ ├── main.dart # 入口
│ ├── screens/ # 首页、听写、设置
│ ├── widgets/ # UI 组件
│ ├── state/ # ChangeNotifier 控制器
│ ├── services/ # 存储、词典、TTS、OCR、老数据迁移
│ ├── models/ # 数据模型
│ └── theme/ # 配色与设计 token
├── test/ # 单元 + widget 测试,golden/ 是行为基线
├── android/ ios/ web/ # 平台目录,由 scripts/bootstrap.sh 生成并配置
├── assets/
│ ├── data/ # library.json、ecdict-meta.json(生成物,打进包)
│ ├── fonts/ sounds/ # 打进包
│ └── icons/ # 图标源文件,不打进包
├── data/ # 内置词库源文件(教材单元 / 中高考词表)
├── scripts/ # 平台脚手架、图标、词库词典生成、发版
├── docs/ # 截图与迁移记录
└── website/ # 官网(Vite + React + Tailwind)
assets/data/ 下两个 JSON 都是生成物,别手改:
pnpm data:check # 校验 data/ 的行格式
pnpm data:gen # data/**/*.txt → assets/data/library.json(290 条词表)
pnpm dict:build # ECDICT(首次会下载) → assets/data/ecdict-meta.json
pnpm fonts:subset # 全量思源宋体 → assets/fonts/web/(仅 Web 发版替换用)
pnpm fonts:subset 需要 fonttools(pip install fonttools)。Android / iOS 仍打全量 TTF;Web 发版脚本会在 flutter build web 之后用子集覆盖产物里的同名文件。
词表的行格式是 word | pos | meaning,1 列或 3 列(全角 | 也认),只有 word
必填。改了 data/ 一定要重新生成并一起提交 —— CI 有一个 job 同时跑格式校验和
一致性比对。
(Expo 时代这一步是 data/ → src/lib/library.ts → 导出 JSON 两跳,中间那份
漏更新过一次,4 个词表的词性标注错了一整个版本。现在直接一跳到 JSON。)
应用图标由 assets/icons/ 里的源图生成,三个平台整套:
pnpm icons:build # 需要 Pillow
平台目录(android/、ios/、web/)需要重新生成时走 bootstrap,不要直接跑
flutter create —— 包名、权限、应用名、图标都得再写回去:
bash scripts/bootstrap.sh # 幂等
按重要性排序。
Web 产物在浏览器里手动走过完整流程(首页 → 设置 → 听写 → 完成,深浅色都看
了)。Android 装到真机上过:0.6.2 一启动就闪退(启动 Activity 被 R8 删了),
0.6.3 起动得来,但两遍发音的第一遍会被截掉尾音(playerStateStream 会把上一次
的 completed 重放给新订阅者)。两个都修了。
iOS 一次都没构建、更没装过。
原生侧仍然只能靠真机验证的:TTS 发音与语速、自定义发音服务、音频会话 (静音键 / 后台播放)、拍照与相册 OCR、老数据迁移。这些在 Web 上要么是空实现 要么走不到,测试环境里插件直接抛 MissingPluginException。
lib/services/tts_config.dart 配的 OpenAI 兼容 TTS 需要把生成的音频落盘,
而 Web 端的缓存实现是空操作,所以 Web 一律走系统 TTS。Expo 版在 Web 上用
blob URL 顶了一下,这边没跟 —— 为一个次要目标改缓存接口形状不划算。
Edge 发音在 Web 上还多一层拦路:浏览器不允许自定义 WebSocket 握手的
Origin / User-Agent,而那正是服务端要校验的,所以 tts_edge_noop.dart
在 Web 上直接报「不支持」,发音回落有道 / 系统 TTS。
lib/services/tts_edge_api.dart 对接的是 Edge 浏览器「大声朗读」用的
WebSocket 接口:免费、不需要账号,但没有公开文档、没有 SLA。微软改了握手
要求(比如 2024 年底加的 Sec-MS-GEC 令牌、以及会被校验的 Chromium 版本号)
这条路就会整条失效 —— 所以调用方永远保留回落:Edge → 有道 → 系统 TTS,
协议细节也全部钉进了 test/tts_edge_test.dart。
lib/services/tts.dart 的 _normalizedRate() 把用户的 0.5–1.5 区间映射到各
平台:iOS 走 AVSpeechUtterance 的 0..1,Android 走
TextToSpeech.setSpeechRate 的 1.0 = 正常。这组映射是按文档推的,没在真机上
听过。听写页底部现在有语速入口(改动下一个词生效),校准时不用来回退页面。
Android 侧靠 manifest 的 ACTION_SEND intent-filter + MainActivity.kt 接收,
逻辑很薄。iOS 要一个独立的 Share Extension(自己的 target、App Group、
签名配置),是另一件事,没做 —— lib/services/share_intake.dart 在 iOS 上
拿不到通道,当作没有分享。
scripts/bootstrap.sh 把 applicationId / bundleIdentifier 固定成
com.vvenv.alice,与 Expo 版一致。改了包名就是另一个沙箱,
lib/services/legacy_migration_io.dart 会读不到任何东西,从老版本升上来的
用户,错词本 / 历史 / 收藏 / Credits 全部丢失。
android/app/build.gradle.kts 在存在 android/key.properties 时用正式签名,否则 release 仍走 debug 签名(方便 flutter run --release)。pnpm release:android 只打并上传 arm64-v8a。上架商店之前请在本机放好 keystore 与 key.properties(均已 gitignore)。
真实应用内购买尚未接入:Credits 是演示积分,点领取即到账。
Gradle 依赖拉不动。到 Maven Central / dl.google.com 的连接会「建立成功但
零字节」地挂死。已在 android/build.gradle.kts、android/settings.gradle.kts
配好阿里云镜像(官方源兜底),android/gradle.properties 里加了 HTTP 超时
让挂死连接快速失败重试。Flutter SDK 自带的 gradle composite build 把仓库硬写死
且设了 FAIL_ON_PROJECT_REPOS,项目侧覆盖不了,只能改 SDK 里的
flutter_tools/gradle/settings.gradle.kts(同目录留了 .orig 备份)。
NDK 反复重下 1 GB。flutter.ndkVersion 指向 28.2.13676358,而本机那份是
中断下载留下的空目录,AGP 每次判定未安装。已把 ndkVersion 钉到本机完整安装的
27.1.12297006 —— app 模块和所有插件子模块都要钉(jni 这类插件自己声明
ndkVersion flutter.ndkVersion),根 build.gradle.kts 里的 hook 必须注册在
evaluationDependsOn(":app") 之前,否则抛 already evaluated。
gen_snapshot 被 macOS 杀掉并删除。手动 curl 下载 SDK zip 会让整个 SDK 带上
com.apple.quarantine,Gatekeeper 对未签名可执行文件先 SIGKILL(表现为
AOT snapshotter exited with code -9)再移除文件。解法:
xattr -dr com.apple.quarantine <flutter-sdk> 然后 flutter precache --force --android
把被删的产物补回来。用 brew install --cask flutter 正常安装不会有这问题。
Web 上的中文字体。CanvasKit 够不着系统字体,默认字族只有 Roboto。引擎
「缺字就去 fonts.gstatic.com 下 Noto」的兜底会被应用自己注册的思源宋体骗过 ——
判定已覆盖便不下载,而它又不在默认字族的兜底链里,结果中文全是豆腐块
(何况 gstatic 国内也拉不到)。lib/main.dart 的 Web 分支显式写了
fontFamily: 'Roboto' + fontFamilyFallback: [NotoSerifSC] ——
两个都要写,只给 fallback 不给 family 不生效。原生不加,否则正文中文
会变成衬线。
欢迎 Issue 和 PR!请先阅读 贡献指南。
MIT © 2026 vvenv