ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기单词听写
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
英文单词听写应用,Flutter 实现,支持 Android / iOS / Web。 0.6.3 之前的版本是 Expo / React Native 实现,仍在 main 分支。 本分支是 Flutter 重写;迁移那一刻的 RN 快照打了 tag rn-final,记录见 docs/migration-from-expo.md。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