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:
@@ -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();
|
||||
|
||||
158
lib/shared/widgets/plugin/pinyin_annotation_text.dart
Normal file
158
lib/shared/widgets/plugin/pinyin_annotation_text.dart
Normal 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user