本次提交包含多项迭代优化和问题修复: 1. 新增缩略图图片组件、数字格式化工具类,补充多语言翻译类型与本地化支持 2. 优化底部导航栏主题色统一使用动态accent色值 3. 修复多处图表动画、路由跳转、API请求相关问题 4. 简化服务器公告文案,调整默认分屏状态为关闭 5. 新增安卓/iOS桌面快捷方式配置 6. 重构多处状态管理类使用SafeNotifierInit统一异常保护 7. 替换硬编码蓝色为主题色,更新版本号获取方式为动态读取 8. 优化缓存预加载逻辑,移除无用代码 9. 调整默认设置项,优化用户体验细节
734 lines
25 KiB
Dart
734 lines
25 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 图片缓存管理页面
|
||
/// 创建时间: 2026-05-30
|
||
/// 更新时间: 2026-05-30
|
||
/// 作用: 图片缓存详情查看、按日期/类型智能分组、图片预览、自动清理策略
|
||
/// 上次更新: 全面接入Riverpod Provider,Sliver布局,批量操作,错误边界,动态过期
|
||
/// ============================================================
|
||
|
||
import 'dart:io';
|
||
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:photo_view/photo_view.dart';
|
||
|
||
import '../../../../core/services/data/image_cache_metadata_service.dart';
|
||
import '../../../../core/theme/app_radius.dart';
|
||
import '../../../../core/theme/app_spacing.dart';
|
||
import '../../../../core/theme/app_theme.dart';
|
||
import '../../../../core/theme/app_typography.dart';
|
||
import '../../../../l10n/translations.dart';
|
||
import '../../../../shared/widgets/adaptive/adaptive_back_button.dart';
|
||
import '../../../../shared/widgets/adaptive/responsive_layout.dart';
|
||
import '../../../../shared/widgets/feedback/app_toast.dart';
|
||
import '../providers/image_cache_provider.dart';
|
||
import 'image_cache_detail_page.dart';
|
||
import 'image_cache_grid.dart';
|
||
import 'image_cache_log_page.dart';
|
||
import 'image_cache_models.dart';
|
||
import 'image_cache_widgets.dart';
|
||
|
||
class ImageCachePage extends ConsumerWidget {
|
||
const ImageCachePage({super.key});
|
||
|
||
// ============================================================
|
||
// 图片预览
|
||
// ============================================================
|
||
|
||
void _previewImage(BuildContext context, CacheItem item) {
|
||
final file = File(item.path);
|
||
if (!file.existsSync()) return;
|
||
if (!CacheImageExtensions.isImage(item.path)) return;
|
||
|
||
showCupertinoDialog<void>(
|
||
context: context,
|
||
barrierColor: Colors.black87,
|
||
builder: (ctx) => GestureDetector(
|
||
onTap: () => Navigator.pop(ctx),
|
||
child: Scaffold(
|
||
backgroundColor: Colors.transparent,
|
||
body: Stack(
|
||
children: [
|
||
Center(
|
||
child: PhotoView(
|
||
imageProvider: FileImage(file),
|
||
minScale: PhotoViewComputedScale.contained,
|
||
maxScale: PhotoViewComputedScale.covered * 3.0,
|
||
backgroundDecoration: const BoxDecoration(
|
||
color: Colors.transparent,
|
||
),
|
||
loadingBuilder: (_, __) =>
|
||
const Center(child: CupertinoActivityIndicator()),
|
||
),
|
||
),
|
||
Positioned(
|
||
top: MediaQuery.of(ctx).padding.top + 8,
|
||
right: 16,
|
||
child: CupertinoButton(
|
||
padding: EdgeInsets.zero,
|
||
onPressed: () => Navigator.pop(ctx),
|
||
child: Container(
|
||
padding: const EdgeInsets.all(8),
|
||
decoration: BoxDecoration(
|
||
color: CupertinoColors.black.withValues(alpha: 0.4),
|
||
borderRadius: AppRadius.fullBorder,
|
||
),
|
||
child: const Icon(
|
||
CupertinoIcons.xmark,
|
||
color: CupertinoColors.white,
|
||
size: 20,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
Positioned(
|
||
bottom: MediaQuery.of(ctx).padding.bottom + 24,
|
||
left: 0,
|
||
right: 0,
|
||
child: Column(
|
||
children: [
|
||
Text(
|
||
CacheFormatter.formatSize(item.size),
|
||
style: AppTypography.subhead.copyWith(
|
||
color: CupertinoColors.white,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
CacheFormatter.formatDate(item.modified),
|
||
style: AppTypography.caption1.copyWith(
|
||
color: CupertinoColors.systemGrey5,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 删除确认
|
||
// ============================================================
|
||
|
||
void _confirmDeleteSingle(
|
||
BuildContext context,
|
||
WidgetRef ref,
|
||
CacheItem item,
|
||
) {
|
||
final ct = ProviderScope.containerOf(
|
||
context,
|
||
).read(translationsProvider).settings.cache;
|
||
showCupertinoDialog<void>(
|
||
context: context,
|
||
builder: (ctx) => CupertinoAlertDialog(
|
||
title: Text('🗑️ ${ct.cacheFiles}'),
|
||
content: Text(
|
||
'${ct.confirmDelete}(${CacheFormatter.formatSize(item.size)})',
|
||
),
|
||
actions: [
|
||
CupertinoDialogAction(
|
||
child: Text(ct.cancel),
|
||
onPressed: () => Navigator.pop(ctx),
|
||
),
|
||
CupertinoDialogAction(
|
||
isDestructiveAction: true,
|
||
child: Text(ct.delete),
|
||
onPressed: () {
|
||
Navigator.pop(ctx);
|
||
ref.read(imageCacheProvider.notifier).deleteSingleItem(item);
|
||
AppToast.showSuccess(ct.deleted);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 清除过期缓存确认
|
||
// ============================================================
|
||
|
||
void _confirmClearExpired(
|
||
BuildContext context,
|
||
WidgetRef ref,
|
||
ImageCacheState state,
|
||
) {
|
||
final ct = ProviderScope.containerOf(
|
||
context,
|
||
).read(translationsProvider).settings.cache;
|
||
final expiredDays = state.expiredDays;
|
||
showCupertinoDialog<void>(
|
||
context: context,
|
||
builder: (ctx) => CupertinoAlertDialog(
|
||
title: Text('🗑️ ${ct.clearExpiredCache}'),
|
||
content: Text(
|
||
'${ct.confirmClearExpired}(${expiredDays}${ct.beforeDays} ${state.expiredCount} ${ct.filesUnit} / ${CacheFormatter.formatSize(state.expiredSize)})',
|
||
),
|
||
actions: [
|
||
CupertinoDialogAction(
|
||
child: Text(ct.cancel),
|
||
onPressed: () => Navigator.pop(ctx),
|
||
),
|
||
CupertinoDialogAction(
|
||
isDestructiveAction: true,
|
||
child: Text(ct.clear),
|
||
onPressed: () {
|
||
Navigator.pop(ctx);
|
||
ref.read(imageCacheProvider.notifier).clearExpiredCache();
|
||
AppToast.showSuccess(ct.clearingExpired);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 清除全部缓存确认
|
||
// ============================================================
|
||
|
||
void _confirmClearAll(
|
||
BuildContext context,
|
||
WidgetRef ref,
|
||
ImageCacheState state,
|
||
) {
|
||
final ct = ProviderScope.containerOf(
|
||
context,
|
||
).read(translationsProvider).settings.cache;
|
||
showCupertinoDialog<void>(
|
||
context: context,
|
||
builder: (ctx) => CupertinoAlertDialog(
|
||
title: Text('⚠️ ${ct.clearAllCache}'),
|
||
content: Text(
|
||
'${ct.confirmClearAll}(${state.cacheItems.length} ${ct.filesUnit} / ${CacheFormatter.formatSize(state.totalSize)})',
|
||
),
|
||
actions: [
|
||
CupertinoDialogAction(
|
||
child: Text(ct.cancel),
|
||
onPressed: () => Navigator.pop(ctx),
|
||
),
|
||
CupertinoDialogAction(
|
||
isDestructiveAction: true,
|
||
child: Text(ct.clear),
|
||
onPressed: () {
|
||
Navigator.pop(ctx);
|
||
ref.read(imageCacheProvider.notifier).clearAllCache();
|
||
AppToast.showSuccess(ct.clearingAll);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 批量删除确认
|
||
// ============================================================
|
||
|
||
void _confirmDeleteSelected(
|
||
BuildContext context,
|
||
WidgetRef ref,
|
||
ImageCacheState state,
|
||
) {
|
||
final ct = ProviderScope.containerOf(
|
||
context,
|
||
).read(translationsProvider).settings.cache;
|
||
final count = state.selectedPaths.length;
|
||
showCupertinoDialog<void>(
|
||
context: context,
|
||
builder: (ctx) => CupertinoAlertDialog(
|
||
title: Text('📋 ${ct.batchDelete}'),
|
||
content: Text(
|
||
'${ct.confirmBatchDelete}($count ${ct.filesUnit})${ct.irreversible}',
|
||
),
|
||
actions: [
|
||
CupertinoDialogAction(
|
||
child: Text(ct.cancel),
|
||
onPressed: () => Navigator.pop(ctx),
|
||
),
|
||
CupertinoDialogAction(
|
||
isDestructiveAction: true,
|
||
child: Text(ct.delete),
|
||
onPressed: () {
|
||
Navigator.pop(ctx);
|
||
ref.read(imageCacheProvider.notifier).deleteSelected();
|
||
AppToast.showSuccess(ct.clearingBatch);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 自动清理策略
|
||
// ============================================================
|
||
|
||
void _showAutoCleanPolicySheet(
|
||
BuildContext context,
|
||
WidgetRef ref,
|
||
ImageCacheState state,
|
||
) {
|
||
final ct = ProviderScope.containerOf(
|
||
context,
|
||
).read(translationsProvider).settings.cache;
|
||
final ext = AppTheme.ext(context);
|
||
showCupertinoModalPopup<void>(
|
||
context: context,
|
||
builder: (ctx) => CupertinoActionSheet(
|
||
title: Text('🔄 ${ct.autoCleanPolicy}'),
|
||
message: Text(ct.autoCleanPolicyDesc),
|
||
actions: AutoCleanPolicy.all.map((policy) {
|
||
return CupertinoActionSheetAction(
|
||
onPressed: () {
|
||
Navigator.pop(ctx);
|
||
ref.read(imageCacheProvider.notifier).setAutoCleanPolicy(policy);
|
||
AppToast.showSuccess(
|
||
'${ct.autoCleanPolicy}: ${AutoCleanPolicy.label(policy)}',
|
||
);
|
||
},
|
||
isDefaultAction: policy == state.autoCleanPolicy,
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text(AutoCleanPolicy.label(policy)),
|
||
if (policy == state.autoCleanPolicy) ...[
|
||
const SizedBox(width: 8),
|
||
Icon(
|
||
CupertinoIcons.checkmark_alt,
|
||
size: 18,
|
||
color: ext.accent,
|
||
),
|
||
],
|
||
],
|
||
),
|
||
);
|
||
}).toList(),
|
||
cancelButton: CupertinoActionSheetAction(
|
||
isDestructiveAction: true,
|
||
onPressed: () => Navigator.pop(ctx),
|
||
child: Text(ct.cancel),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 缓存上限设置
|
||
// ============================================================
|
||
|
||
void _showCacheSizeLimitSheet(
|
||
BuildContext context,
|
||
WidgetRef ref,
|
||
ImageCacheState state,
|
||
) {
|
||
final ct = ProviderScope.containerOf(
|
||
context,
|
||
).read(translationsProvider).settings.cache;
|
||
final ext = AppTheme.ext(context);
|
||
const limits = [50, 100, 200, 500];
|
||
showCupertinoModalPopup<void>(
|
||
context: context,
|
||
builder: (ctx) => CupertinoActionSheet(
|
||
title: Text('📏 ${ct.cacheSizeLimit}'),
|
||
message: Text(ct.cacheSizeLimitDesc),
|
||
actions: limits.map((limit) {
|
||
return CupertinoActionSheetAction(
|
||
onPressed: () {
|
||
Navigator.pop(ctx);
|
||
ref.read(imageCacheProvider.notifier).setCacheSizeLimit(limit);
|
||
AppToast.showSuccess('${ct.cacheSizeLimit}: ${limit}MB');
|
||
},
|
||
isDefaultAction: limit == state.cacheSizeLimit,
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text('${limit} MB'),
|
||
if (limit == state.cacheSizeLimit) ...[
|
||
const SizedBox(width: 8),
|
||
Icon(
|
||
CupertinoIcons.checkmark_alt,
|
||
size: 18,
|
||
color: ext.accent,
|
||
),
|
||
],
|
||
],
|
||
),
|
||
);
|
||
}).toList(),
|
||
cancelButton: CupertinoActionSheetAction(
|
||
isDestructiveAction: true,
|
||
onPressed: () => Navigator.pop(ctx),
|
||
child: Text(ct.cancel),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 排序选择
|
||
// ============================================================
|
||
|
||
void _showSortSheet(
|
||
BuildContext context,
|
||
WidgetRef ref,
|
||
ImageCacheState state,
|
||
) {
|
||
final ct = ProviderScope.containerOf(
|
||
context,
|
||
).read(translationsProvider).settings.cache;
|
||
final ext = AppTheme.ext(context);
|
||
showCupertinoModalPopup<void>(
|
||
context: context,
|
||
builder: (ctx) => CupertinoActionSheet(
|
||
title: Text('📊 ${ct.sortBy}'),
|
||
actions: [
|
||
CupertinoActionSheetAction(
|
||
onPressed: () {
|
||
Navigator.pop(ctx);
|
||
ref.read(imageCacheProvider.notifier).setSortMode(SortMode.date);
|
||
},
|
||
isDefaultAction: state.sortMode == SortMode.date,
|
||
child: _sortLabel(
|
||
ct.sortByDate,
|
||
SortMode.date,
|
||
state.sortMode,
|
||
ext,
|
||
),
|
||
),
|
||
CupertinoActionSheetAction(
|
||
onPressed: () {
|
||
Navigator.pop(ctx);
|
||
ref.read(imageCacheProvider.notifier).setSortMode(SortMode.size);
|
||
},
|
||
isDefaultAction: state.sortMode == SortMode.size,
|
||
child: _sortLabel(
|
||
ct.sortBySize,
|
||
SortMode.size,
|
||
state.sortMode,
|
||
ext,
|
||
),
|
||
),
|
||
CupertinoActionSheetAction(
|
||
onPressed: () {
|
||
Navigator.pop(ctx);
|
||
ref.read(imageCacheProvider.notifier).setSortMode(SortMode.type);
|
||
},
|
||
isDefaultAction: state.sortMode == SortMode.type,
|
||
child: _sortLabel(
|
||
ct.sortByType,
|
||
SortMode.type,
|
||
state.sortMode,
|
||
ext,
|
||
),
|
||
),
|
||
],
|
||
cancelButton: CupertinoActionSheetAction(
|
||
isDestructiveAction: true,
|
||
onPressed: () => Navigator.pop(ctx),
|
||
child: Text(ct.cancel),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _sortLabel(
|
||
String label,
|
||
SortMode mode,
|
||
SortMode current,
|
||
AppThemeExtension ext,
|
||
) {
|
||
return Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text(label),
|
||
if (current == mode) ...[
|
||
const SizedBox(width: 8),
|
||
Icon(CupertinoIcons.checkmark_alt, size: 18, color: ext.accent),
|
||
],
|
||
],
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 清理进度对话框
|
||
// ============================================================
|
||
|
||
void _showCleaningDialog(BuildContext context, ImageCacheState state) {
|
||
if (!state.isCleaning) return;
|
||
final ct = ProviderScope.containerOf(
|
||
context,
|
||
).read(translationsProvider).settings.cache;
|
||
showCupertinoDialog<void>(
|
||
context: context,
|
||
builder: (ctx) => CupertinoAlertDialog(
|
||
title: Text('🧹 ${ct.cleaningProgress}'),
|
||
content: Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: AppSpacing.md),
|
||
child: Column(
|
||
children: [
|
||
const CupertinoActivityIndicator(),
|
||
const SizedBox(height: AppSpacing.sm),
|
||
Text(
|
||
'${state.cleanProgress} / ${state.cleanTotal}',
|
||
style: AppTypography.footnote.copyWith(
|
||
color: AppTheme.ext(context).textSecondary,
|
||
),
|
||
),
|
||
const SizedBox(height: AppSpacing.sm),
|
||
ClipRRect(
|
||
borderRadius: AppRadius.xsBorder,
|
||
child: LinearProgressIndicator(
|
||
value: state.cleanTotal > 0
|
||
? state.cleanProgress / state.cleanTotal
|
||
: 0,
|
||
backgroundColor: AppTheme.ext(
|
||
context,
|
||
).textHint.withValues(alpha: 0.1),
|
||
valueColor: AlwaysStoppedAnimation<Color>(
|
||
AppTheme.ext(context).accent,
|
||
),
|
||
minHeight: 4,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 构建 UI
|
||
// ============================================================
|
||
|
||
@override
|
||
Widget build(BuildContext context, WidgetRef ref) {
|
||
final state = ref.watch(imageCacheProvider);
|
||
final notifier = ref.read(imageCacheProvider.notifier);
|
||
final ext = AppTheme.ext(context);
|
||
final t = ref.watch(translationsProvider);
|
||
final ct = t.settings.cache;
|
||
|
||
if (state.isCleaning) {
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
_showCleaningDialog(context, state);
|
||
});
|
||
}
|
||
|
||
return CupertinoPageScaffold(
|
||
backgroundColor: ext.bgPrimary,
|
||
navigationBar: CupertinoNavigationBar(
|
||
leading: const AdaptiveBackButton(),
|
||
middle: Text(
|
||
'🖼️ ${ct.cacheManagement}',
|
||
style: AppTypography.title3.copyWith(color: ext.textPrimary),
|
||
),
|
||
trailing: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
if (!state.isBatchMode)
|
||
CupertinoButton(
|
||
padding: EdgeInsets.zero,
|
||
onPressed: () => _showSortSheet(context, ref, state),
|
||
child: Icon(
|
||
CupertinoIcons.sort_down,
|
||
color: ext.accent,
|
||
size: 22,
|
||
),
|
||
),
|
||
if (!state.isBatchMode)
|
||
CupertinoButton(
|
||
padding: EdgeInsets.zero,
|
||
onPressed: () => notifier.setViewMode(
|
||
state.viewMode == ViewMode.grid
|
||
? ViewMode.list
|
||
: ViewMode.grid,
|
||
),
|
||
child: Icon(
|
||
state.viewMode == ViewMode.grid
|
||
? CupertinoIcons.list_bullet
|
||
: CupertinoIcons.square_grid_2x2,
|
||
color: ext.accent,
|
||
size: 22,
|
||
),
|
||
),
|
||
CupertinoButton(
|
||
padding: EdgeInsets.zero,
|
||
onPressed: () => notifier.toggleBatchMode(),
|
||
child: Text(
|
||
state.isBatchMode ? ct.done : ct.edit,
|
||
style: AppTypography.footnote.copyWith(color: ext.accent),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
backgroundColor: ext.bgElevated.withValues(alpha: 0.85),
|
||
border: null,
|
||
),
|
||
child: ResponsiveMaxWidth(
|
||
maxWidth: 900,
|
||
child: SafeArea(
|
||
bottom: false,
|
||
child: Stack(
|
||
children: [
|
||
if (state.isLoading && !state.isCleaning)
|
||
const Center(child: CupertinoActivityIndicator())
|
||
else if (state.error != null && state.cacheItems.isEmpty)
|
||
_buildErrorState(context, ref, state, ext, ct)
|
||
else
|
||
CustomScrollView(
|
||
physics: const BouncingScrollPhysics(),
|
||
slivers: [
|
||
CupertinoSliverRefreshControl(
|
||
onRefresh: () => notifier.loadCacheData(),
|
||
),
|
||
SliverPadding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: AppSpacing.md,
|
||
vertical: AppSpacing.sm,
|
||
),
|
||
sliver: SliverList(
|
||
delegate: SliverChildListDelegate([
|
||
StorageOverviewSection(
|
||
state: state,
|
||
ext: ext,
|
||
ct: ct,
|
||
),
|
||
const SizedBox(height: AppSpacing.md),
|
||
CategoryChipsSection(
|
||
state: state,
|
||
ext: ext,
|
||
ct: ct,
|
||
onCategorySelected: (cat) =>
|
||
notifier.setSelectedCategory(cat),
|
||
),
|
||
const SizedBox(height: AppSpacing.md),
|
||
CacheBreakdownSection(state: state, ext: ext, ct: ct),
|
||
const SizedBox(height: AppSpacing.md),
|
||
]),
|
||
),
|
||
),
|
||
SliverToBoxAdapter(
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: AppSpacing.md,
|
||
),
|
||
child: CacheGridSection(
|
||
state: state,
|
||
ext: ext,
|
||
ct: ct,
|
||
onPreviewImage: (item) => _previewImage(context, item),
|
||
onDeleteItem: (item) =>
|
||
_confirmDeleteSingle(context, ref, item),
|
||
onToggleSelect: (path) =>
|
||
notifier.toggleSelectPath(path),
|
||
onTapItem: (item) => Navigator.push<void>(
|
||
context,
|
||
CupertinoPageRoute<void>(
|
||
builder: (_) => ImageCacheDetailPage(item: item),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
SliverPadding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: AppSpacing.md,
|
||
),
|
||
sliver: SliverList(
|
||
delegate: SliverChildListDelegate([
|
||
const SizedBox(height: AppSpacing.md),
|
||
CacheActionsSection(
|
||
state: state,
|
||
ext: ext,
|
||
ct: ct,
|
||
onClearExpired: () =>
|
||
_confirmClearExpired(context, ref, state),
|
||
onClearAll: () =>
|
||
_confirmClearAll(context, ref, state),
|
||
onShowAutoCleanPolicy: () =>
|
||
_showAutoCleanPolicySheet(context, ref, state),
|
||
onShowCacheSizeLimit: () =>
|
||
_showCacheSizeLimitSheet(context, ref, state),
|
||
onShowCleanLog: () => Navigator.push<void>(
|
||
context,
|
||
CupertinoPageRoute<void>(
|
||
builder: (_) => const ImageCacheLogPage(),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: AppSpacing.xxl),
|
||
]),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
if (state.isBatchMode)
|
||
Positioned(
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
child: BatchActionBar(
|
||
state: state,
|
||
ext: ext,
|
||
ct: ct,
|
||
onSelectAll: () => notifier.selectAll(),
|
||
onDeselectAll: () => notifier.deselectAll(),
|
||
onDeleteSelected: () =>
|
||
_confirmDeleteSelected(context, ref, state),
|
||
onCancel: () => notifier.toggleBatchMode(),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 错误状态UI
|
||
// ============================================================
|
||
|
||
Widget _buildErrorState(
|
||
BuildContext context,
|
||
WidgetRef ref,
|
||
ImageCacheState state,
|
||
AppThemeExtension ext,
|
||
TSettingsCache ct,
|
||
) {
|
||
return Center(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(AppSpacing.xl),
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Text('⚠️', style: TextStyle(fontSize: 48)),
|
||
const SizedBox(height: AppSpacing.sm),
|
||
Text(
|
||
state.error ?? ct.loadFailed,
|
||
style: AppTypography.subhead.copyWith(color: ext.textHint),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
const SizedBox(height: AppSpacing.md),
|
||
CupertinoButton(
|
||
onPressed: () =>
|
||
ref.read(imageCacheProvider.notifier).loadCacheData(),
|
||
child: Text(
|
||
ct.retry,
|
||
style: AppTypography.subhead.copyWith(color: ext.accent),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|