主要变更: 1. 重构多处Provider初始化逻辑,使用Future.microtask避免build阶段修改state 2. 重命名"灵感"模块为"工作流","天气诗词"改为"情景诗词" 3. 新增插件系统页面与路由,添加speech_to_text_windows插件支持 4. 优化玻璃容器性能,添加性能节流与模糊值适配 5. 新增离线横幅、阅读体验控制器、手势控制器等组件 6. 完善头像审核状态展示与缓存管理 7. 修复键盘弹出与页面路由问题 8. 更新依赖与项目配置,优化widget默认数据
16 KiB
16 KiB
闲言APP — 插件系统开发规格文档
创建时间: 2026-05-25 版本: v1.0 状态: 待确认
一、功能概述
在通用设置页新增「插件」入口,点击进入插件管理页面。插件页面参考微信插件列表风格,每个插件卡片包含预览图、启用开关、功能说明和配置项。
两个插件
| 插件 | 图标 | 功能 | 数据源 |
|---|---|---|---|
| 翻译守护 | 🌐 | 一键翻译句子内容 | 复用现有 TranslateApiService(Bing/Google/MyMemory等) |
| 文本朗读 | 🔊 | 语音朗读句子 | 系统TTS(flutter_tts)+ Edge TTS(在线WebSocket) |
启用后的UI注入
| 位置 | 翻译守护 | 文本朗读 |
|---|---|---|
| 主页每日推荐卡片 | 🌐 按钮(爱心左侧) | 🔊 按钮(爱心左侧) |
| 句子广场列表卡片 | 🌐 按钮(收藏左侧) | 🔊 按钮(收藏左侧) |
二、页面结构
2.1 通用设置 — 新增入口
位置: 在现有分组之间新增「功能扩展」分组
交互设置 (现有)
通知设置 (现有)
功能扩展 (新增) ← 新分组
└ 插件 → 导航到插件列表页
显示设置 (现有)
性能设置 (现有)
隐私与权限 (现有)
高级 (现有)
决策点:
- 放在通知设置和显示设置之间(功能扩展属于增强功能,不是基础设置)
- 或者放在高级分组内作为子项?
2.2 插件列表页
CupertinoPageScaffold
└ CupertinoSliverNavigationBar (title: "插件")
└ SliverList
├ 分组标题: "已启用的插件"
├ 插件卡片: 翻译守护 (图标+名称+简介+启用状态+箭头)
├ 插件卡片: 文本朗读 (图标+名称+简介+启用状态+箭头)
├ 分组标题: "关于插件"
└ 说明文字
交互: 点击插件卡片 → 进入插件详情页
2.3 翻译守护 — 插件详情页
CupertinoPageScaffold
└ CupertinoSliverNavigationBar (title: "翻译守护")
└ SliverList
├ 头部区域 (图标 + 名称 + 版本号)
├ 预览Banner (渐变背景 + 标语)
├ 启用开关行
├ 配置分组: 翻译设置
│ ├ 翻译引擎 → 导航到引擎选择页
│ ├ 目标语言 → 导航到语言选择页
│ ├ 自动检测源语言 → Toggle
│ └ 降级策略 → 显示当前降级链
├ 配置分组: 显示设置
│ ├ 按钮位置 → 选择器
│ └ 自动翻译 → Toggle
└ 配置分组: 数据管理
├ 翻译缓存 → 显示缓存大小
└ 清除缓存 → Action
决策点:
- 翻译引擎选择页:复用翻译助手的引擎列表,还是简化为仅展示?
- 复用翻译助手的引擎选择逻辑,共享
translateSettingsProvider - 独立一套引擎配置
- 复用翻译助手的引擎选择逻辑,共享
2.4 文本朗读 — 插件详情页
CupertinoPageScaffold
└ CupertinoSliverNavigationBar (title: "文本朗读")
└ SliverList
├ 头部区域 (图标 + 名称 + 版本号)
├ 预览Banner (渐变背景 + 标语)
├ 启用开关行
├ 配置分组: 朗读引擎
│ ├ 系统TTS (推荐·离线) → Radio选择
│ └ 在线语音合成 → Radio选择 + 子项
├ 配置分组: 朗读设置
│ ├ 语速 → Slider (0.1-2.0)
│ ├ 音调 → Slider (0.5-2.0)
│ ├ 音量 → Slider (0.0-1.0)
│ └ 朗读语言 → 选择器
└ 配置分组: 在线引擎设置 (仅在线引擎时显示)
├ 语音选择 → 选择器
└ 试听 → Action
决策点:
- 在线语音合成引擎选择:
- Edge TTS(免费、无需Key、高音质、8种中文语音)← 推荐
- 百度语音合成(需申请API Key)
- 同时支持两者
2.5 翻译结果弹窗
使用 AppBottomSheet.showHalf 展示:
StupidSimpleGlassSheetRoute (半屏面板)
├ Handle
├ 标题: "🌐 翻译结果"
├ 翻译结果卡片 (蓝色背景)
│ ├ 标签: "🌐 英文翻译 · Bing"
│ └ 翻译文本
├ 原文卡片 (灰色背景)
│ ├ 标签: "📝 原文 · 自动检测: 中文"
│ └ 原文文本
└ 操作按钮行
├ 复制翻译 → 复制到剪贴板 + Toast
└ 朗读翻译 → 调用TTS朗读翻译结果
决策点:
- 弹窗样式:
- 使用
AppBottomSheet.showHalf(半屏面板,多级吸附 [0.4, 0.7, 1.0]) - 使用
AppBottomSheet.showCustom(自定义高度)
- 使用
2.6 朗读控制弹窗
使用 AppBottomSheet.showCustom 展示:
StupidSimpleGlassSheetRoute (自定义面板)
├ Handle
├ 标题: "🔊 文本朗读"
├ 朗读播放器卡片 (紫色渐变背景)
│ ├ 状态标题 + 语速标签
│ ├ 当前朗读文本 (最多2行)
│ └ 播放控制行
│ ├ 上一句按钮
│ ├ 播放/暂停按钮 (大圆)
│ ├ 下一句按钮
│ └ 进度条 + 时间
├ 设置行
│ ├ 语速滑块
│ └ 引擎切换
└ 关闭按钮
决策点:
- 朗读弹窗是否需要"上一句/下一句"功能?
- 仅播放当前句子,不需要上下句切换(简化实现)
- 支持上下句切换
三、数据模型
3.1 插件状态模型
/// 插件启用状态
class PluginState {
/// 翻译守护是否启用
final bool translateEnabled;
/// 文本朗读是否启用
final bool ttsEnabled;
/// 翻译目标语言 (默认: en)
final String translateTargetLang;
/// 翻译引擎ID (默认: bing)
final String translateEngineId;
/// 是否自动检测源语言
final bool autoDetectLang;
/// 是否自动翻译 (滑动到新卡片时自动翻译)
final bool autoTranslate;
/// TTS引擎类型: 'system' | 'online'
final String ttsEngineType;
/// TTS在线引擎语音名称 (默认: zh-CN-XiaoxiaoNeural)
final String ttsOnlineVoice;
/// TTS语速 (0.1-2.0, 默认0.5)
final double ttsSpeed;
/// TTS音调 (0.5-2.0, 默认1.0)
final double ttsPitch;
/// TTS音量 (0.0-1.0, 默认0.8)
final double ttsVolume;
}
3.2 持久化键
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
plugin_translate_enabled |
bool | false | 翻译守护开关 |
plugin_tts_enabled |
bool | false | 文本朗读开关 |
plugin_translate_target_lang |
String | 'en' | 翻译目标语言 |
plugin_translate_engine_id |
String | 'bing' | 翻译引擎 |
plugin_auto_detect_lang |
bool | true | 自动检测源语言 |
plugin_auto_translate |
bool | false | 自动翻译 |
plugin_tts_engine_type |
String | 'system' | TTS引擎类型 |
plugin_tts_online_voice |
String | 'zh-CN-XiaoxiaoNeural' | 在线TTS语音 |
plugin_tts_speed |
double | 0.5 | TTS语速 |
plugin_tts_pitch |
double | 1.0 | TTS音调 |
plugin_tts_volume |
double | 0.8 | TTS音量 |
3.3 翻译缓存模型
/// 翻译缓存条目
class TranslateCacheEntry {
final String sourceText;
final String sourceLang;
final String targetLang;
final String translatedText;
final String engineId;
final DateTime cachedAt;
}
决策点:
- 翻译缓存策略:
- 内存缓存 + KvStorage 持久化(简单高效,同一句子不重复请求)
- 使用 Hive 数据库缓存(更强大但增加复杂度)
- 不缓存,每次都请求
四、服务层设计
4.1 PluginProvider (Riverpod)
/// 插件状态管理
///
/// 职责:
/// 1. 管理插件启用/禁用状态
/// 2. 管理插件配置参数
/// 3. 持久化到 KvStorage
/// 4. 提供便捷方法供卡片组件查询
@riverpod
class PluginNotifier extends _$PluginNotifier {
@override
PluginState build() { /* 从KvStorage恢复状态 */ }
// 翻译守护
void setTranslateEnabled(bool enabled);
void setTranslateTargetLang(String lang);
void setTranslateEngine(String engineId);
void setAutoDetectLang(bool auto);
void setAutoTranslate(bool auto);
// 文本朗读
void setTtsEnabled(bool enabled);
void setTtsEngineType(String type);
void setTtsOnlineVoice(String voice);
void setTtsSpeed(double speed);
void setTtsPitch(double pitch);
void setTtsVolume(double volume);
}
4.2 翻译插件服务 (复用 TranslateApiService)
/// 翻译插件服务
///
/// 复用现有翻译API体系:
/// - BingTranslateService (默认首选)
/// - GoogleTranslateService
/// - MyMemoryTranslateService
/// - AppWorldsTranslateService
/// - LibreTranslateService
/// - CustomTranslateService
///
/// 降级顺序: 用户选择 → bing → mymemory → appworlds → google → libre
class TranslatePluginService {
/// 翻译单个句子
Future<TranslateResult> translateSentence(String text, {String? targetLang});
/// 获取翻译(优先缓存)
Future<String> getTranslation(String text, {String? targetLang});
/// 清除翻译缓存
void clearCache();
}
4.3 在线TTS服务 (Edge TTS)
/// Edge TTS 在线语音合成服务
///
/// 通过 WebSocket 连接 speech.platform.bing.com
/// 支持 100+ 语音,音质接近真人
/// 无需 API Key
///
/// 协议流程:
/// 1. WebSocket 握手 (带 TrustedClientToken)
/// 2. 发送 speech.config 配置消息
/// 3. 发送 SSML 合成请求
/// 4. 接收音频数据流 (MP3格式)
/// 5. 播放音频
class OnlineTtsService {
/// 合成语音并播放
Future<void> speak(String text, {String? voice});
/// 停止播放
Future<void> stop();
/// 暂停播放
Future<void> pause();
/// 获取可用语音列表
Future<List<VoiceInfo>> getAvailableVoices();
/// 状态流
Stream<TtsState> get onStateChanged;
Stream<(int, int, String)> get onProgress;
}
Edge TTS WebSocket 协议细节:
- 端点:
wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1 - 认证: URL参数
TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4 - 请求格式: SSML + 配置消息(二进制帧头 + 音频数据)
- 输出格式:
audio-24khz-48kbitrate-mono-mp3 - 中文语音: XiaoxiaoNeural(女), YunxiNeural(男), YunjianNeural(男), YunyangNeural(男新闻), XiaoyiNeural(女), YunxiaNeural(男), XiaobeiNeural(东北), XiaoniNeural(陕西)
决策点:
- Edge TTS 音频播放方式:
- WebSocket接收MP3 → 临时文件 → audioplayers播放(项目已有audioplayers依赖)
- WebSocket接收MP3 → 内存缓冲 → audioplayers播放(更高效但更复杂)
五、UI组件设计
5.1 卡片按钮注入方式
DailyCard (home_daily_card.dart):
当前 buildAuthorRow() 布局:
Row [作者(Expanded), ⭐收藏, ❤️点赞]
修改后:
Row [作者(Expanded), 🌐翻译(条件), 🔊朗读(条件), ⭐收藏, ❤️点赞]
条件渲染逻辑:
if (ref.watch(pluginProvider.select((s) => s.translateEnabled)))
_buildTranslateButton(sentence),
if (ref.watch(pluginProvider.select((s) => s.ttsEnabled)))
_buildTtsButton(sentence),
SentenceCard (home_sentence_card.dart):
当前 _buildActionRow() 布局:
Row [作者(Expanded), ⭐收藏, ❤️点赞]
修改后:
Row [作者(Expanded), 🌐翻译(条件), 🔊朗读(条件), ⭐收藏, ❤️点赞]
5.2 按钮样式
| 属性 | 翻译按钮 | 朗读按钮 |
|---|---|---|
| 图标 | CupertinoIcons.globe |
CupertinoIcons.speaker_2_fill |
| 颜色 | 主题色 ext.primary |
紫色 Color(0xFFAF52DE) |
| 大小 | 18px | 18px |
| 间距 | 与收藏按钮间距 AppSpacing.sm |
与翻译按钮间距 AppSpacing.sm |
| 动画 | 点击缩放0.9 | 点击缩放0.9 |
决策点:
-
按钮图标选择:
- CupertinoIcons(iOS风格统一)
- Emoji(🌐/🔊,与现有收藏⭐风格一致)
→ 推荐 CupertinoIcons,因为项目规则要求"优先使用iOS风格组件"。但现有收藏按钮用的是emoji ⭐,点赞用的是 CupertinoIcons.heart。
最终方案: 翻译用
CupertinoIcons.globe,朗读用CupertinoIcons.speaker_2_fill,与点赞按钮风格统一。
5.3 翻译弹窗组件
class TranslateSheet extends ConsumerStatefulWidget {
final String text; // 原文
final String? sourceLang; // 源语言(可选)
final AppThemeExtension ext;
static void show(BuildContext context, {...}) {
AppBottomSheet.showHalf(
context: context,
builder: (context) => TranslateSheet(...),
);
}
}
5.4 朗读控制弹窗组件
class TtsPlayerSheet extends ConsumerStatefulWidget {
final String text; // 朗读文本
final AppThemeExtension ext;
static void show(BuildContext context, {...}) {
AppBottomSheet.showCustom(
context: context,
builder: (context) => TtsPlayerSheet(...),
);
}
}
六、文件清单
新增文件
| 文件路径 | 类型 | 行数估算 | 说明 |
|---|---|---|---|
lib/features/mine/settings/presentation/plugin/plugin_page.dart |
页面 | ~200 | 插件列表页 |
lib/features/mine/settings/presentation/plugin/translate_plugin_page.dart |
页面 | ~350 | 翻译守护详情页 |
lib/features/mine/settings/presentation/plugin/tts_plugin_page.dart |
页面 | ~350 | 文本朗读详情页 |
lib/features/mine/settings/providers/plugin_provider.dart |
状态 | ~250 | 插件状态管理 |
lib/core/services/audio/online_tts_service.dart |
服务 | ~300 | Edge TTS在线语音合成 |
lib/shared/widgets/plugin/translate_sheet.dart |
组件 | ~150 | 翻译结果弹窗 |
lib/shared/widgets/plugin/tts_player_sheet.dart |
组件 | ~200 | 朗读控制弹窗 |
修改文件
| 文件路径 | 修改内容 |
|---|---|
lib/features/mine/settings/presentation/general/general_settings_sections.dart |
新增「功能扩展」分组 + 插件导航项 |
lib/features/mine/settings/presentation/general/general_settings_page.dart |
_onNavigate 增加 plugin case |
lib/core/router/app_routes.dart |
新增 plugin、translatePlugin、ttsPlugin 路由常量 |
lib/core/router/settings_routes.dart |
新增3条路由注册 |
lib/core/router/ohos_nav_bridge.dart |
鸿蒙端路由注册 |
lib/features/home/presentation/home_daily_card.dart |
buildAuthorRow 注入翻译/朗读按钮 |
lib/features/home/presentation/home_sentence_card.dart |
_buildActionRow 注入翻译/朗读按钮 |
CHANGELOG.md |
记录功能变更 |
七、跨平台兼容
| 平台 | 翻译守护 | 文本朗读(系统TTS) | 文本朗读(Edge TTS) |
|---|---|---|---|
| Android | ✅ HTTP API | ✅ flutter_tts | ✅ WebSocket |
| iOS | ✅ HTTP API | ✅ flutter_tts | ✅ WebSocket |
| macOS | ✅ HTTP API | ✅ flutter_tts | ✅ WebSocket |
| Windows | ✅ HTTP API | ✅ flutter_tts | ✅ WebSocket |
| Linux | ✅ HTTP API | ✅ flutter_tts | ✅ WebSocket |
| 鸿蒙 | ✅ HTTP API | ✅ flutter_tts(本地化) | ✅ WebSocket |
| Web | ✅ HTTP API | ⚠️ 有限支持 | ✅ WebSocket |
注意事项:
- Web端 flutter_tts 支持有限,在线引擎作为备选
- 鸿蒙端 flutter_tts 已本地化适配(pubspec.yaml中有path引用)
- Edge TTS 的 WebSocket 在所有平台均可用(基于 dart:io 的 WebSocket)
八、待确认决策项
- 插件入口位置: 功能扩展独立分组 vs 放在高级分组内?
- 翻译引擎配置: 复用翻译助手配置 vs 独立配置?
- 在线TTS引擎: 仅Edge TTS vs 同时支持百度?
- 翻译缓存: 内存缓存 vs Hive缓存 vs 不缓存?
- 朗读弹窗: 仅播放当前句 vs 支持上下句?
- Edge TTS播放: 临时文件播放 vs 内存缓冲播放?
- 按钮图标: CupertinoIcons vs Emoji?