Skip to content

Commit e26b283

Browse files
committed
FEAT: 增加欧路网页词典
1 parent 21e52a9 commit e26b283

4 files changed

Lines changed: 59 additions & 4 deletions

File tree

‎TASKS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
## 本次完成
66

7+
- [x] 网页词典新增欧路词典(2026-10-10):新增可配置网页源 `eudic`,使用欧路英汉词条页 URL,支持词组空格编码;来源自动出现在词典切换器与设置列表,默认可见且可禁用,默认源仍为本地词典。新增 URL、注册表可见性及禁用行为回归测试。两个定向测试文件共 58 项通过,3 个 Dart 文件定向分析通过,`git diff --check` 通过。未运行 `scripts/check.sh`:改动限于网页词典配置与来源可见性,定向检查覆盖相关行为。未运行 Maestro:沙箱内 ADB 守护进程启动失败(`Operation not permitted`),现有首启 smoke flow 会清空应用数据;未在设备验证 WebView 实际加载。**完成时间**: 2026-10-10 19:05
78
- [x] 限制共享后台文件下载进度更新频率(2026-10-10):百度网盘和播客共用的 `BackgroundFileDownloadService` 按任务将应用层进度回调限制为最多每 100ms 一次;初始进度、已知总量下的完成进度,以及任务终态的最终字节数仍会及时发布,失败/异常路径会冲刷最后待发布进度。新增 1000 次突发进度回调回归测试,确认通知被合并且最终字节数保留;完整下载服务测试 10 项通过,改动的 2 个 Dart 文件定向分析通过,`git diff --check` 通过。未运行 `scripts/check.sh`:改动仅限共享下载进度通知,定向测试覆盖修改范围;尚未做真机下载速度前后对比。**完成时间**: 2026-10-10 09:54
89
- [x] 定位并压制 ASR 下载进度回调造成的吞吐下降(2026-10-10):日志中的 App 是 sidecar 优化后重新构建的版本;同 URL 的 Dart/Dio 流式写盘复测为 2.01 秒、约 31.0 MB/s,收到 11,414 个数据块;App 日志为 62,337,248 字节、14.110 秒、约 4.42 MB/s。根因是每个数据块都触发 ASR provider 状态更新,而设置页订阅整个状态;限频前的 10,000 次回调回归用例产生了 10,000 次状态更新。现将 UI 进度状态更新限制为每 100ms 至多一次,100% 仍立即发布。Dio 同 URL 无重定向且无内容编码,直连 curl 复测约 24.9 MB/s。Provider 与设置页定向测试 15 项通过,2 个 Dart 文件定向分析通过,`git diff --check` 通过。未运行 `scripts/check.sh`:改动限于 ASR 进度通知频率,定向测试覆盖相关状态流。**完成时间**: 2026-10-10 09:16
910
- [x] 复核限频后 ASR 下载速度差距(2026-10-10):最新 `flutter_run.log` 中 95,051,629 字节下载耗时 7.717 秒,约 12.32 MB/s;同 URL 的 `curl --noproxy '*'` 为 3.56 秒。查到启动 shell 的小写 `http_proxy`、`https_proxy`、`all_proxy` 均指向 `127.0.0.1:7897`,Dart TCP 连接也确实经过该端口;`scutil --proxy` 为空不能覆盖 shell 环境变量。普通 Dart Dio 探针保留变量为 5.14 秒,清除变量直连为 3.61 秒;curl 走本地代理仍能达到 2.34 秒,因此已确认 App 与此前直连对比的路径不同,但代理因素尚不足以解释全部差值。未改代码强制绕过代理,避免破坏用户显式代理配置;需在清除变量后重新启动 App 并用现有设置流程验证一次新下载。只做日志与网络诊断,未运行 `scripts/check.sh`。**完成时间**: 2026-10-10 09:32

‎lib/services/dictionary/web_dictionary_source.dart‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
///
33
/// 网页词典(Cambridge / Oxford / Longman / Britannica / Merriam-Webster / Collins /
44
/// Vocabulary.com / Wiktionary / OZDIC / PlayPhrase / YouGlish / Forvo /
5-
/// WordReference / Etymonline / 有道)本质相同——不抓取/解析 HTML,只按词构造 URL,
6-
/// 交给内置 WebView 显示。差异仅在 URL 模板与品牌展示,故抽象为一份 [WebDictConfig]
5+
/// WordReference / Etymonline / 有道 / 欧路词典)本质相同——不抓取/解析 HTML,
6+
/// 只按词构造 URL,交给内置 WebView 显示。差异仅在 URL 模板与品牌展示,故抽象为一份 [WebDictConfig]
77
/// 配置 + 一个通用 [WebDictionarySource]:新增一个网页词典只需往 [kWebDictConfigs]
88
/// 加一行配置。
99
library;
@@ -193,6 +193,13 @@ const List<WebDictConfig> kWebDictConfigs = [
193193
color: Color(0xFFEA4B35), // 有道暖红(与 M-W 冷红拉开)
194194
buildUrl: _youdaoUrl,
195195
),
196+
WebDictConfig(
197+
id: 'eudic',
198+
displayName: '欧路词典',
199+
icon: Icons.menu_book_rounded,
200+
color: Color(0xFF2D6FA3), // 欧路词典蓝
201+
buildUrl: _eudicUrl,
202+
),
196203
];
197204

198205
// URL 模板(顶层函数,便于 const 配置引用)。`w` 为已 URL 编码的查询词。
@@ -216,3 +223,4 @@ String _wordReferenceUrl(String w) =>
216223
'https://www.wordreference.com/definition/$w';
217224
String _etymonlineUrl(String w) => 'https://www.etymonline.com/search?q=$w';
218225
String _youdaoUrl(String w) => 'https://m.youdao.com/dict?le=eng&q=$w';
226+
String _eudicUrl(String w) => 'https://dict.eudic.net/dicts/en/$w';

‎test/providers/dictionary/visible_sources_provider_test.dart‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ void main() {
4949
return c;
5050
}
5151

52+
ProviderContainer makeRegistryContainer() {
53+
final c = ProviderContainer(
54+
overrides: [sharedPreferencesProvider.overrideWithValue(prefs)],
55+
);
56+
addTearDown(c.dispose);
57+
return c;
58+
}
59+
5260
setUp(() async {
5361
SharedPreferences.setMockInitialValues({});
5462
prefs = await SharedPreferences.getInstance();
@@ -60,6 +68,28 @@ void main() {
6068
expect(visible.map((s) => s.id), ['local', 'ai', 'cambridge']);
6169
});
6270

71+
test('欧路词典默认可见并可禁用', () async {
72+
final c = makeRegistryContainer();
73+
expect(
74+
c
75+
.read(visibleDictionarySourcesProvider)
76+
.any((source) => source.id == 'eudic'),
77+
isTrue,
78+
);
79+
80+
await c
81+
.read(dictionarySettingsNotifierProvider.notifier)
82+
.setDisabled('eudic', true);
83+
84+
expect(
85+
c
86+
.read(visibleDictionarySourcesProvider)
87+
.any((source) => source.id == 'eudic'),
88+
isFalse,
89+
);
90+
expect(c.read(dictionarySettingsNotifierProvider).defaultSourceId, 'local');
91+
});
92+
6393
test('禁用 cambridge → 可见列表排除它', () async {
6494
final c = makeContainer();
6595
await c

‎test/services/dictionary/web_dictionary_source_test.dart‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ void main() {
1717
expect(ids.indexOf('britannica'), greaterThan(ids.indexOf('collins')));
1818
});
1919

20-
test('已确认的 15 个网页源都在、Macmillan/欧陆 不在', () {
20+
test('已确认的 16 个网页源都在、Macmillan 不在', () {
2121
final ids = kWebDictConfigs.map((c) => c.id).toSet();
2222
expect(ids, {
2323
'cambridge',
@@ -35,9 +35,9 @@ void main() {
3535
'wordReference',
3636
'etymonline',
3737
'youdao',
38+
'eudic',
3839
});
3940
expect(ids.contains('macmillan'), isFalse);
40-
expect(ids.contains('eudic'), isFalse);
4141
});
4242

4343
test('Britannica Dictionary 使用词条页 URL 模板', () async {
@@ -55,6 +55,22 @@ void main() {
5555
);
5656
});
5757

58+
test('欧路词典使用英汉词条 URL 并编码词组空格', () async {
59+
final source = WebDictionarySource(
60+
kWebDictConfigs.singleWhere((config) => config.id == 'eudic'),
61+
);
62+
final result = await source.lookup(
63+
const DictionaryLookupRequest(word: 'ice cream'),
64+
);
65+
66+
if (result case WebDictResult(:final sourceId, :final url)) {
67+
expect(sourceId, 'eudic');
68+
expect(url.toString(), 'https://dict.eudic.net/dicts/en/ice%20cream');
69+
} else {
70+
fail('Expected an Eudic web dictionary result.');
71+
}
72+
});
73+
5874
for (final config in kWebDictConfigs) {
5975
group('WebDictionarySource(${config.id})', () {
6076
final source = WebDictionarySource(config);

0 commit comments

Comments
 (0)