415 lines
12 KiB
Dart
415 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
import '../theme/app_theme.dart';
|
|
import '../database/app_usage_dao.dart';
|
|
import '../database/daily_stats_dao.dart';
|
|
|
|
class DataPrivacyScreen extends StatefulWidget {
|
|
const DataPrivacyScreen({super.key});
|
|
|
|
@override
|
|
State<DataPrivacyScreen> createState() => _DataPrivacyScreenState();
|
|
}
|
|
|
|
class _DataPrivacyScreenState extends State<DataPrivacyScreen> {
|
|
final AppUsageDao _appUsageDao = AppUsageDao();
|
|
final DailyStatsDao _dailyStatsDao = DailyStatsDao();
|
|
bool _isDeleting = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('数据与隐私'),
|
|
),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 隐私说明
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.security, color: AppTheme.primaryColor),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'隐私保护',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildPrivacyItem(
|
|
theme,
|
|
Icons.storage,
|
|
'本地存储',
|
|
'所有数据仅存储在您的设备本地,不会上传到任何服务器。',
|
|
),
|
|
_buildPrivacyItem(
|
|
theme,
|
|
Icons.lock,
|
|
'数据加密',
|
|
'敏感数据在存储时进行加密处理,确保数据安全。',
|
|
),
|
|
_buildPrivacyItem(
|
|
theme,
|
|
Icons.visibility_off,
|
|
'隐私保护',
|
|
'我们不会收集您的个人信息,也不会追踪您的具体操作内容。',
|
|
),
|
|
_buildPrivacyItem(
|
|
theme,
|
|
Icons.delete_forever,
|
|
'完全控制',
|
|
'您可以随时删除所有数据,完全掌控您的隐私。',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 数据管理
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'数据管理',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildDataAction(
|
|
context,
|
|
theme,
|
|
Icons.delete_outline,
|
|
'删除旧数据',
|
|
'删除 30 天前的数据',
|
|
Colors.orange,
|
|
() => _showDeleteOldDataDialog(context),
|
|
),
|
|
const SizedBox(height: 12),
|
|
_buildDataAction(
|
|
context,
|
|
theme,
|
|
Icons.delete_forever,
|
|
'清空所有数据',
|
|
'删除所有使用记录和统计数据',
|
|
Colors.red,
|
|
() => _showDeleteAllDataDialog(context),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 数据使用说明
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'数据使用说明',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'• 应用使用数据仅用于统计和分析\n'
|
|
'• 数据不会离开您的设备\n'
|
|
'• 不会与第三方分享任何数据\n'
|
|
'• 不会用于广告或营销目的\n'
|
|
'• 您可以随时导出或删除数据',
|
|
style: theme.textTheme.bodyMedium,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPrivacyItem(ThemeData theme, IconData icon, String title, String description) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 16),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(icon, color: AppTheme.primaryColor, size: 24),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: theme.textTheme.bodyLarge?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
description,
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDataAction(
|
|
BuildContext context,
|
|
ThemeData theme,
|
|
IconData icon,
|
|
String title,
|
|
String subtitle,
|
|
Color color,
|
|
VoidCallback onTap,
|
|
) {
|
|
return InkWell(
|
|
onTap: _isDeleting ? null : onTap,
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: color.withOpacity(0.3)),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, color: color),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: theme.textTheme.bodyLarge?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
color: color,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
subtitle,
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (_isDeleting)
|
|
const SizedBox(
|
|
width: 20,
|
|
height: 20,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
else
|
|
const Icon(Icons.chevron_right),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showDeleteOldDataDialog(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('删除旧数据'),
|
|
content: const Text('确定要删除 30 天前的所有数据吗?此操作不可恢复。'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: const Text('取消'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
Navigator.of(context).pop();
|
|
await _deleteOldData(context);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.orange,
|
|
),
|
|
child: const Text('删除'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showDeleteAllDataDialog(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('清空所有数据'),
|
|
content: const Text(
|
|
'确定要删除所有使用记录和统计数据吗?\n\n'
|
|
'此操作将:\n'
|
|
'• 删除所有应用使用记录\n'
|
|
'• 删除所有统计数据\n'
|
|
'• 删除所有分类设置\n'
|
|
'• 删除所有目标设置\n\n'
|
|
'此操作不可恢复!',
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: const Text('取消'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
Navigator.of(context).pop();
|
|
await _deleteAllData(context);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.red,
|
|
),
|
|
child: const Text('确认删除'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _deleteOldData(BuildContext context) async {
|
|
if (kIsWeb) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Web 平台不支持数据删除功能'),
|
|
backgroundColor: AppTheme.warningColor,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isDeleting = true;
|
|
});
|
|
|
|
try {
|
|
final thirtyDaysAgo = DateTime.now().subtract(const Duration(days: 30));
|
|
|
|
await _appUsageDao.deleteBeforeDate(thirtyDaysAgo);
|
|
await _dailyStatsDao.deleteBeforeDate(thirtyDaysAgo);
|
|
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('已删除 30 天前的数据'),
|
|
backgroundColor: AppTheme.successColor,
|
|
),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('删除失败: $e'),
|
|
backgroundColor: AppTheme.errorColor,
|
|
),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isDeleting = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _deleteAllData(BuildContext context) async {
|
|
if (kIsWeb) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Web 平台不支持数据删除功能'),
|
|
backgroundColor: AppTheme.warningColor,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isDeleting = true;
|
|
});
|
|
|
|
try {
|
|
// 删除所有应用使用记录
|
|
final allUsages = await _appUsageDao.getAppUsages(
|
|
startTime: DateTime(2000),
|
|
endTime: DateTime.now(),
|
|
);
|
|
for (final usage in allUsages) {
|
|
if (usage.id != null) {
|
|
await _appUsageDao.deleteAppUsage(usage.id!);
|
|
}
|
|
}
|
|
|
|
// 删除所有统计数据
|
|
final allStats = await _dailyStatsDao.getStatsRange(
|
|
startDate: DateTime(2000),
|
|
endDate: DateTime.now(),
|
|
);
|
|
for (final stat in allStats) {
|
|
if (stat.id != null) {
|
|
await _dailyStatsDao.deleteDailyStats(stat.id!);
|
|
}
|
|
}
|
|
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('已清空所有数据'),
|
|
backgroundColor: AppTheme.successColor,
|
|
),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('删除失败: $e'),
|
|
backgroundColor: AppTheme.errorColor,
|
|
),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isDeleting = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|