本次提交包含多项迭代优化和问题修复: 1. 新增缩略图图片组件、数字格式化工具类,补充多语言翻译类型与本地化支持 2. 优化底部导航栏主题色统一使用动态accent色值 3. 修复多处图表动画、路由跳转、API请求相关问题 4. 简化服务器公告文案,调整默认分屏状态为关闭 5. 新增安卓/iOS桌面快捷方式配置 6. 重构多处状态管理类使用SafeNotifierInit统一异常保护 7. 替换硬编码蓝色为主题色,更新版本号获取方式为动态读取 8. 优化缓存预加载逻辑,移除无用代码 9. 调整默认设置项,优化用户体验细节
264 lines
7.6 KiB
Dart
264 lines
7.6 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 图片缓存模型与工具类
|
||
/// 创建时间: 2026-05-30
|
||
/// 更新时间: 2026-05-30
|
||
/// 作用: 图片缓存页面的数据模型、枚举、格式化工具、分类映射
|
||
/// 上次更新: 新增ImageCacheState/CacheCleanLogEntry/CacheImageExtensions,扩展CacheItem字段
|
||
/// ============================================================
|
||
|
||
import 'package:flutter/cupertino.dart';
|
||
|
||
import '../../../../core/services/data/image_cache_metadata_service.dart';
|
||
import '../../../../l10n/translations.dart';
|
||
|
||
/// 视图模式:网格 / 列表
|
||
enum ViewMode { grid, list }
|
||
|
||
/// 排序模式:按时间 / 按大小 / 按类型
|
||
enum SortMode { date, size, type }
|
||
|
||
/// 缓存条目数据模型
|
||
class CacheItem {
|
||
const CacheItem({
|
||
required this.path,
|
||
required this.size,
|
||
required this.modified,
|
||
this.category,
|
||
this.sourceUrl,
|
||
this.expiresAt,
|
||
});
|
||
|
||
final String path;
|
||
final int size;
|
||
final DateTime modified;
|
||
final String? category;
|
||
final String? sourceUrl;
|
||
final DateTime? expiresAt;
|
||
}
|
||
|
||
/// 缓存格式化工具
|
||
class CacheFormatter {
|
||
CacheFormatter._();
|
||
|
||
/// 格式化文件大小
|
||
static String formatSize(int bytes) {
|
||
if (bytes <= 0) return '0 B';
|
||
if (bytes < 1024) return '$bytes B';
|
||
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||
if (bytes < 1024 * 1024 * 1024) {
|
||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||
}
|
||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
|
||
}
|
||
|
||
/// 格式化日期时间
|
||
static String formatDate(DateTime dt) {
|
||
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} '
|
||
'${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
|
||
}
|
||
|
||
/// 计算缓存占比
|
||
static double cacheRatio(int part, int total) {
|
||
if (total <= 0) return 0;
|
||
return part / total;
|
||
}
|
||
}
|
||
|
||
/// 缓存图片扩展名常量
|
||
class CacheImageExtensions {
|
||
CacheImageExtensions._();
|
||
|
||
static const List<String> image = [
|
||
'jpg',
|
||
'jpeg',
|
||
'png',
|
||
'gif',
|
||
'webp',
|
||
'bmp',
|
||
];
|
||
|
||
static bool isImage(String path) {
|
||
final ext = path.split('.').last.toLowerCase();
|
||
return image.contains(ext);
|
||
}
|
||
}
|
||
|
||
/// 缓存清理日志条目
|
||
class CacheCleanLogEntry {
|
||
const CacheCleanLogEntry({
|
||
required this.timestamp,
|
||
required this.type,
|
||
required this.count,
|
||
required this.size,
|
||
this.category,
|
||
});
|
||
|
||
final DateTime timestamp;
|
||
final String type;
|
||
final int count;
|
||
final int size;
|
||
final String? category;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'timestamp': timestamp.toIso8601String(),
|
||
'type': type,
|
||
'count': count,
|
||
'size': size,
|
||
'category': category,
|
||
};
|
||
|
||
factory CacheCleanLogEntry.fromJson(Map<String, dynamic> json) {
|
||
return CacheCleanLogEntry(
|
||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||
type: json['type'] as String,
|
||
count: json['count'] as int,
|
||
size: json['size'] as int,
|
||
category: json['category'] as String?,
|
||
);
|
||
}
|
||
|
||
String typeLabel(TSettingsCache ct) => switch (type) {
|
||
'expired' => '🗑️ ${ct.clearExpiredCache}',
|
||
'all' => '⚠️ ${ct.clearAllCache}',
|
||
'single' => '📄 ${ct.delete}',
|
||
'batch' => '📋 ${ct.batchDelete}',
|
||
'category' => '📁 ${ct.category}',
|
||
_ => '🧹 ${ct.cleanLog}',
|
||
};
|
||
}
|
||
|
||
/// 图片缓存页面状态
|
||
class ImageCacheState {
|
||
const ImageCacheState({
|
||
this.cacheItems = const [],
|
||
this.totalSize = 0,
|
||
this.imageCacheCount = 0,
|
||
this.expiredCount = 0,
|
||
this.expiredSize = 0,
|
||
this.categoryStats = const {},
|
||
this.categoryCounts = const {},
|
||
this.autoCleanPolicy = AutoCleanPolicy.days7,
|
||
this.cacheSizeLimit = 100,
|
||
this.isLoading = true,
|
||
this.isCleaning = false,
|
||
this.cleanProgress = 0,
|
||
this.cleanTotal = 0,
|
||
this.isBatchMode = false,
|
||
this.selectedPaths = const {},
|
||
this.viewMode = ViewMode.grid,
|
||
this.sortMode = SortMode.date,
|
||
this.selectedCategory,
|
||
this.error,
|
||
});
|
||
|
||
final List<CacheItem> cacheItems;
|
||
final int totalSize;
|
||
final int imageCacheCount;
|
||
final int expiredCount;
|
||
final int expiredSize;
|
||
final Map<String, int> categoryStats;
|
||
final Map<String, int> categoryCounts;
|
||
final String autoCleanPolicy;
|
||
final int cacheSizeLimit;
|
||
final bool isLoading;
|
||
final bool isCleaning;
|
||
final int cleanProgress;
|
||
final int cleanTotal;
|
||
final bool isBatchMode;
|
||
final Set<String> selectedPaths;
|
||
final ViewMode viewMode;
|
||
final SortMode sortMode;
|
||
final String? selectedCategory;
|
||
final String? error;
|
||
|
||
int get expiredDays => AutoCleanPolicy.toDays(autoCleanPolicy) ?? 7;
|
||
|
||
List<CacheItem> get filteredItems {
|
||
if (selectedCategory == null) return cacheItems;
|
||
return cacheItems.where((i) => i.category == selectedCategory).toList();
|
||
}
|
||
|
||
Map<String, List<CacheItem>> get groupedByDate {
|
||
final items = filteredItems;
|
||
final groups = <String, List<CacheItem>>{};
|
||
for (final item in items) {
|
||
final group = DateGroup.groupOf(item.modified);
|
||
groups.putIfAbsent(group, () => []).add(item);
|
||
}
|
||
return groups;
|
||
}
|
||
|
||
ImageCacheState copyWith({
|
||
List<CacheItem>? cacheItems,
|
||
int? totalSize,
|
||
int? imageCacheCount,
|
||
int? expiredCount,
|
||
int? expiredSize,
|
||
Map<String, int>? categoryStats,
|
||
Map<String, int>? categoryCounts,
|
||
String? autoCleanPolicy,
|
||
int? cacheSizeLimit,
|
||
bool? isLoading,
|
||
bool? isCleaning,
|
||
int? cleanProgress,
|
||
int? cleanTotal,
|
||
bool? isBatchMode,
|
||
Set<String>? selectedPaths,
|
||
ViewMode? viewMode,
|
||
SortMode? sortMode,
|
||
String? Function()? selectedCategory,
|
||
String? Function()? error,
|
||
}) {
|
||
return ImageCacheState(
|
||
cacheItems: cacheItems ?? this.cacheItems,
|
||
totalSize: totalSize ?? this.totalSize,
|
||
imageCacheCount: imageCacheCount ?? this.imageCacheCount,
|
||
expiredCount: expiredCount ?? this.expiredCount,
|
||
expiredSize: expiredSize ?? this.expiredSize,
|
||
categoryStats: categoryStats ?? this.categoryStats,
|
||
categoryCounts: categoryCounts ?? this.categoryCounts,
|
||
autoCleanPolicy: autoCleanPolicy ?? this.autoCleanPolicy,
|
||
cacheSizeLimit: cacheSizeLimit ?? this.cacheSizeLimit,
|
||
isLoading: isLoading ?? this.isLoading,
|
||
isCleaning: isCleaning ?? this.isCleaning,
|
||
cleanProgress: cleanProgress ?? this.cleanProgress,
|
||
cleanTotal: cleanTotal ?? this.cleanTotal,
|
||
isBatchMode: isBatchMode ?? this.isBatchMode,
|
||
selectedPaths: selectedPaths ?? this.selectedPaths,
|
||
viewMode: viewMode ?? this.viewMode,
|
||
sortMode: sortMode ?? this.sortMode,
|
||
selectedCategory: selectedCategory != null
|
||
? selectedCategory()
|
||
: this.selectedCategory,
|
||
error: error != null ? error() : this.error,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 缓存分类图标与颜色映射
|
||
class CacheCategoryStyle {
|
||
CacheCategoryStyle._();
|
||
|
||
/// 分类对应图标
|
||
static IconData icon(String category) {
|
||
return switch (category) {
|
||
CacheCategory.avatar => CupertinoIcons.person_fill,
|
||
CacheCategory.wallpaper => CupertinoIcons.photo_fill,
|
||
CacheCategory.feed => CupertinoIcons.doc_text_fill,
|
||
CacheCategory.card => CupertinoIcons.square_stack_3d_up_fill,
|
||
_ => CupertinoIcons.archivebox_fill,
|
||
};
|
||
}
|
||
|
||
/// 分类对应颜色
|
||
static Color color(String category) {
|
||
return switch (category) {
|
||
CacheCategory.avatar => CupertinoColors.systemPurple,
|
||
CacheCategory.wallpaper => CupertinoColors.systemTeal,
|
||
CacheCategory.feed => CupertinoColors.systemBlue,
|
||
CacheCategory.card => CupertinoColors.systemPink,
|
||
_ => CupertinoColors.systemGrey,
|
||
};
|
||
}
|
||
}
|