import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:path_provider/path_provider.dart'; import '../../../constants/app_constants.dart'; /// 时间: 2026-03-27 /// 功能: 应用数据管理页面 /// 介绍: 显示软件包、软件数据,提供清空缓存、清空数据等功能 /// 最新变化: 新建页面 class AppDataPage extends StatefulWidget { const AppDataPage({super.key}); @override State createState() => _AppDataPageState(); } class _AppDataPageState extends State { int _sharedPrefsCount = 0; bool _isLoading = true; String _packageSize = '计算中...'; String _cacheSize = '计算中...'; String _dataSize = '计算中...'; String _totalSize = '计算中...'; int _packageBytes = 0; int _cacheBytes = 0; int _dataBytes = 0; @override void initState() { super.initState(); _loadDataInfo(); } Future _loadDataInfo() async { final prefs = await SharedPreferences.getInstance(); final keys = prefs.getKeys(); await Future.wait([ _calculatePackageSize(), _calculateCacheSize(), _calculateDataSize(), ]); _calculateTotalSize(); setState(() { _sharedPrefsCount = keys.length; _isLoading = false; }); } Future _getDirectorySize(Directory dir) async { int totalSize = 0; try { if (await dir.exists()) { await for (FileSystemEntity entity in dir.list(recursive: true)) { if (entity is File) { try { totalSize += await entity.length(); } catch (e) { debugPrint('获取文件大小失败: ${entity.path}, $e'); } } } } } catch (e) { debugPrint('计算目录大小失败: ${dir.path}, $e'); } return totalSize; } String _formatBytes(int bytes) { if (bytes <= 0) return '0 B'; const suffixes = ['B', 'KB', 'MB', 'GB', 'TB']; int i = (bytes.bitLength - 1) ~/ 10; double size = bytes / (1 << (i * 10)); return '${size.toStringAsFixed(i == 0 ? 0 : 2)} ${suffixes[i]}'; } Future _calculatePackageSize() async { try { int totalSize = 0; final tempDir = await getTemporaryDirectory(); final appDocDir = await getApplicationDocumentsDirectory(); final supportDir = await getApplicationSupportDirectory(); final parentDir = tempDir.parent; if (await parentDir.exists()) { await for (FileSystemEntity entity in parentDir.list()) { if (entity is Directory) { final dirName = entity.path.split(Platform.pathSeparator).last; if (!dirName.startsWith('.')) { totalSize += await _getDirectorySize(entity); } } } } if (mounted) { setState(() { _packageBytes = totalSize; _packageSize = _formatBytes(totalSize); }); } } catch (e) { debugPrint('计算软件包大小失败: $e'); if (mounted) { setState(() { _packageSize = '获取失败'; }); } } } Future _calculateCacheSize() async { try { final tempDir = await getTemporaryDirectory(); final size = await _getDirectorySize(tempDir); if (mounted) { setState(() { _cacheBytes = size; _cacheSize = _formatBytes(size); }); } } catch (e) { debugPrint('计算缓存大小失败: $e'); if (mounted) { setState(() { _cacheSize = '获取失败'; }); } } } Future _calculateDataSize() async { try { int totalSize = 0; final appDocDir = await getApplicationDocumentsDirectory(); final supportDir = await getApplicationSupportDirectory(); totalSize += await _getDirectorySize(appDocDir); totalSize += await _getDirectorySize(supportDir); if (mounted) { setState(() { _dataBytes = totalSize; _dataSize = _formatBytes(totalSize); }); } } catch (e) { debugPrint('计算数据大小失败: $e'); if (mounted) { setState(() { _dataSize = '获取失败'; }); } } } void _calculateTotalSize() { const int appBaseSize = 10 * 1024 * 1024; // 10MB final totalBytes = _packageBytes + _cacheBytes + _dataBytes + appBaseSize; setState(() { _totalSize = _formatBytes(totalBytes); }); } Future _clearSharedPreferences() async { final confirmed = await _showConfirmDialog( '清空配置数据', '确定要清空所有配置数据吗?\n\n这将重置所有用户设置,包括:\n• 协议同意状态\n• 用户体验计划\n• 启动设置\n• 其他偏好设置\n\n此操作不可撤销。', ); if (confirmed != true) return; try { final prefs = await SharedPreferences.getInstance(); // 先获取所有键 final keys = prefs.getKeys().toList(); debugPrint('清空前配置项: $keys'); // 清空所有数据 final success = await prefs.clear(); debugPrint('清空结果: $success'); // 验证是否清空成功 final remainingKeys = prefs.getKeys(); debugPrint('清空后剩余配置项: $remainingKeys'); if (mounted) { // 先关闭当前页面,避免状态问题 Navigator.of(context).pop(); // 延迟显示提示,确保页面已关闭 Future.delayed(const Duration(milliseconds: 300), () { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('配置数据已清空,请重新启动应用'), backgroundColor: AppConstants.primaryColor, duration: const Duration(seconds: 3), ), ); } }); } } catch (e) { debugPrint('清空配置数据失败: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('清空失败: $e'), backgroundColor: Colors.red), ); } } } Future _clearAllData() async { final confirmed = await _showConfirmDialog( '清空所有数据', '⚠️ 危险操作 ⚠️\n\n确定要清空所有数据吗?\n\n这将删除:\n• 所有配置数据\n• 所有历史记录\n• 所有收藏数据\n• 所有笔记数据\n• 所有缓存数据\n\n应用将恢复到初始状态,此操作不可撤销!', ); if (confirmed != true) return; final doubleConfirmed = await _showConfirmDialog( '再次确认', '这是最后一次确认!\n\n清空后数据将无法恢复,确定继续吗?', ); if (doubleConfirmed != true) return; try { final prefs = await SharedPreferences.getInstance(); // 先获取所有键 final keys = prefs.getKeys().toList(); debugPrint('清空前配置项: $keys'); // 清空所有数据 final success = await prefs.clear(); debugPrint('清空结果: $success'); // 验证是否清空成功 final remainingKeys = prefs.getKeys(); debugPrint('清空后剩余配置项: $remainingKeys'); if (mounted) { // 先关闭当前页面,避免状态问题 Navigator.of(context).pop(); // 延迟显示提示,确保页面已关闭 Future.delayed(const Duration(milliseconds: 300), () { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('所有数据已清空,请重新启动应用'), backgroundColor: Colors.orange, duration: const Duration(seconds: 3), ), ); } }); } } catch (e) { debugPrint('清空所有数据失败: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('清空失败: $e'), backgroundColor: Colors.red), ); } } } Future _nativeClearData() async { final confirmed = await _showConfirmDialog( '系统层清理数据', '⚠️ 危险操作 ⚠️\n\n确定要系统层清理数据吗?\n\n这将删除:\n• 应用文档目录所有文件\n• 应用支持目录所有文件\n• 应用缓存目录所有文件\n\n此操作将直接删除文件系统数据,不可撤销!', ); if (confirmed != true) return; final doubleConfirmed = await _showConfirmDialog( '再次确认', '这是系统层面文件系统删除!\n\n删除后数据将无法恢复,确定继续吗?', ); if (doubleConfirmed != true) return; try { final tempDir = await getTemporaryDirectory(); final appDocDir = await getApplicationDocumentsDirectory(); final supportDir = await getApplicationSupportDirectory(); debugPrint('开始清理...'); debugPrint('临时目录: ${tempDir.path}'); debugPrint('文档目录: ${appDocDir.path}'); debugPrint('支持目录: ${supportDir.path}'); await _deleteDirectory(tempDir); await _deleteDirectory(appDocDir); await _deleteDirectory(supportDir); debugPrint('原生清理完成'); if (mounted) { Navigator.of(context).pop(); Future.delayed(const Duration(milliseconds: 300), () { if (mounted) { _showExitConfirmDialog(); } }); } } catch (e) { debugPrint('原生清理失败: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('清理失败: $e'), backgroundColor: Colors.red), ); } } } Future _clearCache() async { final confirmed = await _showConfirmDialog( '清空缓存', '确定要清空缓存数据吗?\n\n这将清除临时文件和缓存,不会影响用户数据。', ); if (confirmed != true) return; try { final tempDir = await getTemporaryDirectory(); if (await tempDir.exists()) { await _deleteDirectory(tempDir); } await _calculateCacheSize(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('缓存已清空'), backgroundColor: AppConstants.primaryColor, ), ); } } catch (e) { debugPrint('清空缓存失败: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('清空失败: $e'), backgroundColor: Colors.red), ); } } } Future _deleteDirectory(Directory dir) async { try { if (await dir.exists()) { await for (FileSystemEntity entity in dir.list()) { if (entity is File) { try { await entity.delete(); } catch (e) { debugPrint('删除文件失败: ${entity.path}, $e'); } } else if (entity is Directory) { await _deleteDirectory(entity); } } } } catch (e) { debugPrint('删除目录失败: ${dir.path}, $e'); } } Future _exportData() async { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('数据导出功能开发中...'), backgroundColor: Colors.grey[600], ), ); } } Future _importData() async { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('数据导入功能开发中...'), backgroundColor: Colors.grey[600], ), ); } } Future _showConfirmDialog(String title, String content) { return showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Row( children: [ Icon( title.contains('⚠️') ? Icons.warning : Icons.help_outline, color: title.contains('⚠️') ? Colors.orange : AppConstants.primaryColor, ), const SizedBox(width: 8), Expanded(child: Text(title, style: const TextStyle(fontSize: 18))), ], ), content: Text(content), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: Text('取消', style: TextStyle(color: Colors.grey[600])), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( backgroundColor: title.contains('危险') || title.contains('再次') ? Colors.red : AppConstants.primaryColor, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text('确定'), ), ], ), ); } Future _showExitConfirmDialog() async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Row( children: [ Icon(Icons.power_settings_new, color: Colors.deepOrange[700]), const SizedBox(width: 8), const Expanded(child: Text('关闭应用', style: TextStyle(fontSize: 18))), ], ), content: const Text('数据清理完成!\n\n建议关闭应用后重新启动,\n以确保应用正常运行。\n\n是否现在关闭应用?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: Text('稍后关闭', style: TextStyle(color: Colors.grey[600])), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( backgroundColor: Colors.deepOrange[700], foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text('立即关闭'), ), ], ), ); if (confirmed == true) { if (mounted) { SystemNavigator.pop(); } } } void _showSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: AppConstants.primaryColor, ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F5F5), appBar: AppBar( title: Text( '应用数据', style: TextStyle( color: AppConstants.primaryColor, fontWeight: FontWeight.bold, ), ), backgroundColor: Colors.white, elevation: 0, centerTitle: true, leading: IconButton( icon: Icon(Icons.arrow_back, color: AppConstants.primaryColor), onPressed: () => Navigator.of(context).pop(), ), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : ListView( padding: const EdgeInsets.all(16), children: [ _buildDataOverviewCard(), const SizedBox(height: 16), _buildDataManagementCard(), const SizedBox(height: 16), _buildDataBackupCard(), const SizedBox(height: 16), _buildDangerZoneCard(), const SizedBox(height: 24), _buildBottomInfo(), ], ), ); } Widget _buildDataOverviewCard() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppConstants.primaryColor.withValues(alpha: 0.1), borderRadius: const BorderRadius.vertical( top: Radius.circular(16), ), ), child: Row( children: [ Icon(Icons.storage, color: AppConstants.primaryColor), const SizedBox(width: 8), const Text( '数据概览', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ], ), ), _buildDataItem('📦 软件包', _packageSize), _buildDivider(), _buildDataItem('📱 配置项数量', '$_sharedPrefsCount 项'), _buildDivider(), _buildDataItem('💾 缓存大小', _cacheSize), _buildDivider(), _buildDataItem('📊 数据大小', _dataSize), _buildDivider(), _buildDataItem('📁 占用空间', _totalSize), ], ), ); } Widget _buildDataItem(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: const TextStyle(fontSize: 14)), Text(value, style: TextStyle(fontSize: 14, color: Colors.grey[600])), ], ), ); } Widget _buildDivider() { return Divider( height: 1, indent: 16, endIndent: 16, color: Colors.grey[200], ); } Widget _buildDataManagementCard() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue.withValues(alpha: 0.1), borderRadius: const BorderRadius.vertical( top: Radius.circular(16), ), ), child: Row( children: [ Icon(Icons.manage_accounts, color: Colors.blue[700]), const SizedBox(width: 8), const Text( '数据管理', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ], ), ), _buildActionButton( '🗑️ 清空缓存', '清除临时文件和缓存数据', Icons.cleaning_services, Colors.blue, _clearCache, ), _buildDivider(), _buildActionButton( '⚙️ 清空配置数据', '重置所有用户设置和偏好', Icons.settings_backup_restore, Colors.orange, _clearSharedPreferences, ), _buildDivider(), _buildActionButton( '📋 查看配置详情', '查看所有存储的配置项', Icons.list_alt, Colors.green, () => _showSnackBar('功能开发中...'), ), ], ), ); } Widget _buildDataBackupCard() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.green.withValues(alpha: 0.1), borderRadius: const BorderRadius.vertical( top: Radius.circular(16), ), ), child: Row( children: [ Icon(Icons.backup, color: Colors.green[700]), const SizedBox(width: 8), const Text( '数据备份', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ], ), ), _buildActionButton( '📤 导出数据', '将数据导出到本地文件', Icons.file_upload, Colors.green, _exportData, ), _buildDivider(), _buildActionButton( '📥 导入数据', '从本地文件导入数据', Icons.file_download, Colors.green, _importData, ), _buildDivider(), _buildActionButton( '☁️ 云端同步', '同步数据到云端', Icons.cloud_sync, Colors.purple, () => _showSnackBar('云端同步功能开发中...'), ), ], ), ); } Widget _buildDangerZoneCard() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.red.withValues(alpha: 0.1), borderRadius: const BorderRadius.vertical( top: Radius.circular(16), ), ), child: Row( children: [ Icon(Icons.warning_amber, color: Colors.red[700]), const SizedBox(width: 8), const Text( '危险区域', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.red, ), ), ], ), ), _buildActionButton( '⚠️ 清空所有数据', '删除所有数据,恢复初始状态', Icons.delete_forever, Colors.red, _clearAllData, ), _buildDivider(), _buildActionButton( '🔧 原生清理数据', '删除应用数据目录所有文件', Icons.system_security_update_warning, Colors.orange, _nativeClearData, ), ], ), ); } Widget _buildActionButton( String title, String subtitle, IconData icon, Color color, VoidCallback onTap, ) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: color, size: 22), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 2), Text( subtitle, style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ), Icon(Icons.chevron_right, color: Colors.grey[400]), ], ), ), ); } Widget _buildBottomInfo() { return Center( child: Column( children: [ Icon(Icons.info_outline, color: Colors.grey[400], size: 32), const SizedBox(height: 8), Text( '数据安全提示', style: TextStyle( fontSize: 14, color: Colors.grey[600], fontWeight: FontWeight.w500, ), ), const SizedBox(height: 4), Text( '清空数据前请先备份重要数据', style: TextStyle(fontSize: 12, color: Colors.grey[500]), ), ], ), ); } }