refactor: 重构项目路由与模块结构,统一发现页命名与路径

1. 全局替换tool_center/inspiration为discover模块,统一路由路径
2. 调整AppRoutes路由常量,将discover作为主Tab页,inspiration作为子页面
3. 更新页面注册表与路由配置,修正跳转目标
4. 调整启动页可选配置项,修正路由ID对应关系
5. 新增翻译服务、内容发现、热搜相关工具类与数据模型
6. 修复缓存清理后未刷新统计的问题,调整x86_64架构注释
7. 更新AGENTS.md文档约束规则
8. 新增一批调试用截图资源文件
This commit is contained in:
Developer
2026-05-28 06:42:20 +08:00
parent 355191aaf6
commit 63a0559721
178 changed files with 43990 additions and 3936 deletions

View File

@@ -1,10 +1,10 @@
/// ============================================================
/// 闲言APP — 安全缓存图片组件 + 容错缓存管理器
/// 创建时间: 2026-05-23
/// 更新时间: 2026-05-27
/// 更新时间: 2026-05-28
/// 作用: 替代 CachedNetworkImage增加URL校验+数据库异常降级处理
/// 防止超长/非法URL导致 flutter_cache_manager 内部 SQLite 只读异常
/// 上次更新: 集成ShimmerPlaceholder骨架屏占位+错误占位shimmer风格
/// 上次更新: 修复11-octo_image断言失败(移除placeholder与progressIndicatorBuilder冲突)
/// ============================================================
import 'dart:convert';
@@ -145,11 +145,6 @@ class _SafeCachedImageState extends State<SafeCachedImage> {
fadeOutDuration: widget.fadeOutDuration ?? Duration.zero,
memCacheWidth: _effectiveCacheWidth,
memCacheHeight: _effectiveCacheHeight,
placeholder: widget.placeholder ??
(context, url) => ShimmerPlaceholder.image(
width: widget.width,
height: widget.height,
),
progressIndicatorBuilder: (context, url, progress) {
final downloaded = progress.downloaded;
final totalSize = progress.totalSize;
@@ -168,10 +163,11 @@ class _SafeCachedImageState extends State<SafeCachedImage> {
],
);
}
return ShimmerPlaceholder.image(
width: widget.width,
height: widget.height,
);
return widget.placeholder?.call(context, url) ??
ShimmerPlaceholder.image(
width: widget.width,
height: widget.height,
);
},
imageBuilder: (context, imageProvider) {
widget.onLoaded?.call();

View File

@@ -0,0 +1,158 @@
/// ============================================================
/// 闲言APP — 拼音注音文本组件
/// 创建时间: 2026-05-28
/// 更新时间: 2026-05-28
/// 作用: 在汉字上方显示带音调的拼音标注,支持逐字注音
/// 上次更新: 初始创建
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:pinyin/pinyin.dart';
import '../../../core/theme/app_typography.dart';
// ============================================================
// 拼音缓存
// ============================================================
final Map<String, String> _pinyinCache = {};
String _getPinyinWithTone(String char) {
return _pinyinCache.putIfAbsent(
char,
() => PinyinHelper.getPinyin(char, format: PinyinFormat.WITH_TONE_MARK),
);
}
bool _isChinese(String char) {
final code = char.codeUnitAt(0);
return code >= 0x4E00 && code <= 0x9FFF;
}
// ============================================================
// 拼音注音文本组件
// ============================================================
class PinyinAnnotationText extends StatelessWidget {
const PinyinAnnotationText({
super.key,
required this.text,
required this.showPinyin,
this.textStyle,
this.pinyinStyle,
this.maxLines,
this.overflow,
});
final String text;
final bool showPinyin;
final TextStyle? textStyle;
final TextStyle? pinyinStyle;
final int? maxLines;
final TextOverflow? overflow;
@override
Widget build(BuildContext context) {
if (!showPinyin || text.isEmpty) {
return Text(
text,
style: textStyle ?? AppTypography.body,
maxLines: maxLines,
overflow: overflow,
);
}
return _PinyinAnnotatedContent(
text: text,
textStyle: textStyle ?? AppTypography.body,
pinyinStyle: pinyinStyle ??
AppTypography.caption2.copyWith(
fontSize: 9,
height: 1.1,
letterSpacing: 0.2,
),
);
}
}
// ============================================================
// 逐字注音内容渲染
// ============================================================
class _PinyinAnnotatedContent extends StatelessWidget {
const _PinyinAnnotatedContent({
required this.text,
required this.textStyle,
required this.pinyinStyle,
});
final String text;
final TextStyle textStyle;
final TextStyle pinyinStyle;
@override
Widget build(BuildContext context) {
final children = <Widget>[];
final buffer = <String>[];
for (int i = 0; i < text.length; i++) {
final char = text[i];
if (_isChinese(char)) {
if (buffer.isNotEmpty) {
children.add(Text(buffer.join(), style: textStyle));
buffer.clear();
}
children.add(_AnnotatedChar(
char: char,
textStyle: textStyle,
pinyinStyle: pinyinStyle,
));
} else {
buffer.add(char);
}
}
if (buffer.isNotEmpty) {
children.add(Text(buffer.join(), style: textStyle));
}
return Wrap(
crossAxisAlignment: WrapCrossAlignment.end,
runSpacing: 2,
children: children,
);
}
}
// ============================================================
// 单个注音字符 — 拼音在上,汉字在下
// ============================================================
class _AnnotatedChar extends StatelessWidget {
const _AnnotatedChar({
required this.char,
required this.textStyle,
required this.pinyinStyle,
});
final String char;
final TextStyle textStyle;
final TextStyle pinyinStyle;
@override
Widget build(BuildContext context) {
final pinyin = _getPinyinWithTone(char);
return Padding(
padding: const EdgeInsets.only(right: 0.5),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(pinyin, style: pinyinStyle),
const SizedBox(height: 0),
Text(char, style: textStyle),
],
),
);
}
}

View File

@@ -22,8 +22,8 @@ import 'package:xianyan/core/services/audio/tts_service.dart';
import 'package:xianyan/core/services/audio/online_tts_service.dart';
import 'package:xianyan/core/storage/database/app_database.dart';
import 'package:drift/drift.dart' show Value;
import 'package:xianyan/features/tool_center/inspiration/services/translate_api_service.dart';
import 'package:xianyan/features/tool_center/inspiration/models/translate_language.dart';
import 'package:xianyan/features/discover/services/translate_api_service.dart';
import 'package:xianyan/features/discover/models/translate_language.dart';
import 'package:xianyan/features/mine/settings/providers/plugin_provider.dart';
import 'package:xianyan/features/mine/settings/providers/translate_engine_provider.dart';
import 'package:xianyan/shared/widgets/containers/bottom_sheet.dart';

View File

@@ -1,10 +1,10 @@
/// ============================================================
/// 闲言APP — 壁纸公共组件主体
/// 创建时间: 2026-05-04
/// 更新时间: 2026-05-25
/// 更新时间: 2026-05-28
/// 作用: 壁纸图库公共视图 — 支持drawer(编辑器抽屉)/fullscreen(发现页)双模式
/// 统一数据源 + 瀑布流 + 已加载优先 + 分类"全部" + URL三级回退 + 无限下拉加载
/// 上次更新: 修复9-壁纸卡死(移除_sortLoadedFirst阻塞式缓存检查+防抖排序+并发限制)
/// 上次更新: 修复10-壁纸ANR卡死(全局超时20s+渐进式加载+增强异常处理+快速失败)
/// ============================================================
import 'dart:async';
@@ -121,9 +121,31 @@ class _WallpaperGalleryViewState extends State<WallpaperGalleryView> {
try {
if (_isAllSources) {
await _loadAllSources(reset: reset);
await _loadAllSources(reset: reset).timeout(
const Duration(seconds: 20),
onTimeout: () {
if (!mounted) return;
setState(() {
_isLoading = false;
if (_items.isEmpty) {
_errorMessage = '加载超时,部分源可能不可用';
}
});
},
);
} else {
await _loadSingleSource(reset: reset);
await _loadSingleSource(reset: reset).timeout(
const Duration(seconds: 15),
onTimeout: () {
if (!mounted) return;
setState(() {
_isLoading = false;
if (_items.isEmpty) {
_errorMessage = '加载超时,请检查网络后重试';
}
});
},
);
}
} catch (e) {
Log.e('壁纸加载异常', e);
@@ -131,7 +153,9 @@ class _WallpaperGalleryViewState extends State<WallpaperGalleryView> {
setState(() {
_isLoading = false;
if (reset) _items = [];
_errorMessage = '加载失败,请重试';
if (_items.isEmpty) {
_errorMessage = '加载失败,请重试';
}
});
}
}
@@ -194,57 +218,76 @@ class _WallpaperGalleryViewState extends State<WallpaperGalleryView> {
Future<void> _loadAllSources({bool reset = true}) async {
final page = _allSourcesPageIndex;
// 修复7: 快速源第1批
final batch1Results = await _fetchBatch(_fastBatch1, page);
if (!mounted) return;
try {
// 修复7: 快速源第1批核心源必须快速返回
final batch1Results = await _fetchBatch(_fastBatch1, page);
if (!mounted) return;
final batch1Items = _extractNewItems(batch1Results);
batch1Items.shuffle();
final batch1Items = _extractNewItems(batch1Results);
batch1Items.shuffle();
final batch1HasMore = _hasMorePages(batch1Results);
final batch1HasMore = _hasMorePages(batch1Results);
setState(() {
if (reset) {
_items = batch1Items;
} else {
_items.addAll(batch1Items);
}
_isLoading = false;
_hasMore = batch1HasMore;
});
_sortLoadedFirst();
// 修复7: 快速源第2批
final batch2Results = await _fetchBatch(_fastBatch2, page);
if (!mounted) return;
final batch2Items = _extractNewItems(batch2Results);
if (batch2Items.isNotEmpty) {
batch2Items.shuffle();
final batch2HasMore = _hasMorePages(batch2Results);
// 立即更新UI只要有数据就显示不要等待其他批次
setState(() {
_items.addAll(batch2Items);
if (batch2HasMore) _hasMore = true;
if (reset) {
_items = batch1Items;
} else {
_items.addAll(batch1Items);
}
_isLoading = false;
_hasMore = batch1HasMore;
});
_sortLoadedFirst();
}
// 修复6.1: 快速源全部超时/失败时设置错误提示
final allFastItems = [...batch1Items, ...batch2Items];
if (allFastItems.isEmpty) {
if (batch1Items.isNotEmpty) {
_errorMessage = null;
_sortLoadedFirst();
}
// 修复7: 快速源第2批后台加载
try {
final batch2Results = await _fetchBatch(_fastBatch2, page);
if (!mounted) return;
final batch2Items = _extractNewItems(batch2Results);
if (batch2Items.isNotEmpty) {
batch2Items.shuffle();
final batch2HasMore = _hasMorePages(batch2Results);
setState(() {
_items.addAll(batch2Items);
if (batch2HasMore) _hasMore = true;
});
_sortLoadedFirst();
if (_items.isNotEmpty) _errorMessage = null;
}
} catch (e) {
Log.w('快速源第2批加载失败(非致命)', e);
}
// 修复6.1: 检查是否所有快速源都失败
if (_items.isEmpty && !_isLoading) {
if (!mounted) return;
setState(() {
_errorMessage = '网络连接超时,请检查网络后重试';
});
}
_allSourcesPageIndex++;
// 慢源后台加载不阻塞UI
_loadSlowSources(page: page).catchError((Object e) {
Log.e('慢源加载异常(已捕获)', e);
});
} catch (e) {
Log.e('快速源加载异常', e);
if (!mounted) return;
setState(() {
_errorMessage = '网络连接超时,请检查网络后重试';
_isLoading = false;
if (_items.isEmpty) {
_errorMessage = '核心壁纸源不可用,请稍后重试';
}
});
} else {
_errorMessage = null;
}
_allSourcesPageIndex++;
_loadSlowSources(page: page).catchError((Object e) {
Log.e('慢源加载异常(已捕获)', e);
});
}
Future<void> _loadSlowSources({required int page}) async {