feat: 新增壁纸图库组件和编辑器功能优化

- 新增壁纸图库相关组件(WallpaperGalleryView/WallpaperSearchBar等)
- 优化编辑器主题服务和系统UI管理
- 新增虚线边框和拖拽描边风格支持
- 完善今日诗词服务和阅读报告功能
- 修复多个UI问题和空指针异常
- 更新依赖库版本和SVG资源
- 优化交互动画和状态管理
- 补充文档和API测试脚本
This commit is contained in:
Developer
2026-05-05 05:03:33 +08:00
parent 839e118cdb
commit b5157c19f4
230 changed files with 44325 additions and 19116 deletions

View File

@@ -0,0 +1,173 @@
/// ============================================================
/// 闲言APP — 壁纸模板数据模型
/// 创建时间: 2026-05-01
/// 更新时间: 2026-05-04
// 作用: 壁纸图库数据结构 — 壁纸项/分类/来源/模板
// 上次更新: tags字段类型保护(is List检查)防API返回非List崩溃
/// ============================================================
// ============================================================
// 壁纸来源
// ============================================================
enum WallpaperSource {
unsplash('api_unsplash', '📷', 'photo', 'Unsplash', 138),
wallstreet('api_wallstreet', '🏙', 'square_grid_2x2', 'WallSt', 140),
pexels('api_pexels', '🎞', 'photo', 'Pexels', 136),
pixabay('api_pixabay', '🔍', 'circle_grid_3x3', 'Pixabay', 140),
bing360('api_360_bing', '🖼', 'photo', '360壁纸', 264),
bing('api_360_bing', '🌍', 'globe', '必应壁纸', 245),
nasa('api_nasa', '🚀', 'sparkles', 'NASA', 133),
haoWallpaper('api_hao_wallpaper', '🏔', 'square_grid_2x2', '精选壁纸', 345),
bizhiduoduo('api_bizhiduoduo', '🎲', 'square_grid_2x2', '多多壁纸', 2102),
bingEnhanced('api_bing_enhanced', '🔮', 'sparkles', '增强必应', 371),
animePictures('api_anime_pictures', '🎨', 'paintpalette', '动漫插画', 2432),
dmoe('api_dmoe', '🌸', 'sparkles', '二次元', 2807);
const WallpaperSource(
this.path,
this.emoji,
this.iconName,
this.label,
this.avgMs,
);
final String path;
final String emoji;
final String iconName;
final String label;
final int avgMs;
String get baseUrl => 'http://bz.wktyl.com';
String buildUrl({
int limit = 20,
int page = 1,
String? category,
String sort = 'hot',
String? search,
}) {
final params = <String, String>{'limit': '$limit', 'page': '$page'};
if (this == WallpaperSource.bing360) {
params['source'] = '360';
} else if (this == WallpaperSource.bing) {
params['source'] = 'bing';
} else if (this == WallpaperSource.bizhiduoduo) {
params['action'] = 'random';
}
if (category != null && category != 'all') params['category'] = category;
if (sort != 'hot') params['sort'] = sort;
if (search != null && search.isNotEmpty) params['search'] = search;
final query = params.entries.map((e) => '${e.key}=${e.value}').join('&');
return '$baseUrl/$path.php?$query';
}
}
// ============================================================
// 壁纸数据项
// ============================================================
class WallpaperItem {
const WallpaperItem({
required this.id,
required this.title,
required this.imageUrl,
required this.thumbnailUrl,
required this.previewUrl,
this.downloadUrl,
this.width = 0,
this.height = 0,
this.resolution = '',
this.format = '',
this.fileSize = 0,
this.views = 0,
this.downloads = 0,
this.likes = 0,
this.popularityScore = 0.0,
this.tags = const [],
this.primaryCategory = '',
this.source = '',
this.sourceType = '',
});
final String id;
final String title;
final String imageUrl;
final String thumbnailUrl;
final String previewUrl;
final String? downloadUrl;
final int width;
final int height;
final String resolution;
final String format;
final int fileSize;
final int views;
final int downloads;
final int likes;
final double popularityScore;
final List<String> tags;
final String primaryCategory;
final String source;
final String sourceType;
double get aspectRatio => width > 0 && height > 0 ? width / height : 9 / 16;
factory WallpaperItem.fromApi(Map<String, dynamic> json) {
final resourceInfo = json['resource_info'] as Map<String, dynamic>? ?? {};
final media = json['media'] as Map<String, dynamic>? ?? {};
final classification =
json['classification'] as Map<String, dynamic>? ?? {};
final statistics = json['statistics'] as Map<String, dynamic>? ?? {};
final metadata = json['metadata'] as Map<String, dynamic>? ?? {};
return WallpaperItem(
id: json['id']?.toString() ?? '',
title: resourceInfo['title'] as String? ?? '',
imageUrl: media['image_url'] as String? ?? '',
thumbnailUrl: media['thumbnail_url'] as String? ?? '',
previewUrl: media['preview_url'] as String? ?? '',
downloadUrl: media['download_url'] as String?,
width: (media['width'] as num?)?.toInt() ?? 0,
height: (media['height'] as num?)?.toInt() ?? 0,
resolution: media['resolution'] as String? ?? '',
format: media['format'] as String? ?? '',
fileSize: (media['file_size'] as num?)?.toInt() ?? 0,
views: (statistics['views'] as num?)?.toInt() ?? 0,
downloads: (statistics['downloads'] as num?)?.toInt() ?? 0,
likes: (statistics['likes'] as num?)?.toInt() ?? 0,
popularityScore:
(statistics['popularity_score'] as num?)?.toDouble() ?? 0.0,
tags: (classification['tags'] is List<dynamic>)
? (classification['tags'] as List<dynamic>)
.map((e) => e.toString())
.toList()
: [],
primaryCategory: classification['primary_category'] as String? ?? '',
source: metadata['source'] as String? ?? '',
sourceType: resourceInfo['source_type'] as String? ?? '',
);
}
}
// ============================================================
// 壁纸分类
// ============================================================
enum WallpaperCategory {
all('all', '🌐', 'globe', '全部'),
nature('nature', '🌿', 'circle', '自然'),
animal('animal', '🐾', 'smiley', '动物'),
architecture('architecture', '🏛', 'square_grid_2x2', '建筑'),
technology('technology', '💻', 'pencil_ellipsis', '科技'),
abstract('abstract', '🎨', 'paintpalette', '抽象'),
people('people', '👤', 'smiley', '人物'),
travel('travel', '✈️', 'globe', '旅行'),
food('food', '🍜', 'drop', '美食'),
anime('anime', '🌸', 'sparkles', '动漫');
const WallpaperCategory(this.id, this.emoji, this.iconName, this.label);
final String id;
final String emoji;
final String iconName;
final String label;
}