新增本地自动备份

This commit is contained in:
DelLevin-Home
2026-03-08 13:34:57 +08:00
parent bb7b2c0191
commit c894921472
7 changed files with 744 additions and 174 deletions

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../utils/backup_service.dart';
import '../utils/auto_backup_service.dart';
import '../utils/toast_util.dart';
/// 本地备份页面
@@ -16,6 +17,30 @@ class BackupPage extends StatefulWidget {
class _BackupPageState extends State<BackupPage> {
bool _isExporting = false;
bool _isImporting = false;
bool _autoBackupEnabled = false;
bool _isLoading = true;
List<File> _autoBackupFiles = [];
String? _backupDirPath;
@override
void initState() {
super.initState();
_loadAutoBackupStatus();
}
Future<void> _loadAutoBackupStatus() async {
final enabled = await AutoBackupService.instance.getEnabled();
final files = await AutoBackupService.instance.getBackupFiles();
final dirPath = await AutoBackupService.instance.getBackupDirectoryPath();
if (mounted) {
setState(() {
_autoBackupEnabled = enabled;
_autoBackupFiles = files;
_backupDirPath = dirPath;
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
@@ -24,9 +49,22 @@ class _BackupPageState extends State<BackupPage> {
appBar: AppBar(
title: const Text('本地备份'),
),
body: ListView(
body: _isLoading
? const Center(child: CircularProgressIndicator())
: ListView(
padding: const EdgeInsets.all(24),
children: [
// 自动备份开关
_buildAutoBackupSection(),
const SizedBox(height: 32),
// 自动备份文件列表
if (_autoBackupFiles.isNotEmpty) ...[
_buildBackupFilesSection(),
const SizedBox(height: 32),
],
// 导出数据
_buildSection(
title: '导出数据',
@@ -298,4 +336,189 @@ class _BackupPageState extends State<BackupPage> {
}
}
}
/// 构建自动备份区域
Widget _buildAutoBackupSection() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE5E5E5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(
Icons.schedule,
size: 24,
color: Color(0xFF1A1A1A),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
'自动本地备份',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF1A1A1A),
),
),
),
Switch(
value: _autoBackupEnabled,
onChanged: (value) async {
setState(() => _autoBackupEnabled = value);
await AutoBackupService.instance.setEnabled(value);
if (value) {
ToastUtil.show(context, '自动备份已开启每2分钟备份一次');
} else {
ToastUtil.show(context, '自动备份已关闭');
}
// 刷新文件列表
await _loadAutoBackupStatus();
},
activeColor: const Color(0xFF1A1A1A),
),
],
),
const SizedBox(height: 8),
Text(
'每隔2分钟自动备份到下载目录/mooknote文件夹最多保留10个备份文件',
style: const TextStyle(
fontSize: 13,
color: Color(0xFF666666),
height: 1.5,
),
),
if (_backupDirPath != null) ...[
const SizedBox(height: 8),
Text(
'备份位置: $_backupDirPath',
style: const TextStyle(
fontSize: 11,
color: Color(0xFF999999),
),
),
],
],
),
);
}
/// 构建备份文件列表区域
Widget _buildBackupFilesSection() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE5E5E5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(
Icons.folder_outlined,
size: 24,
color: Color(0xFF1A1A1A),
),
const SizedBox(width: 12),
Expanded(
child: Text(
'自动备份文件 (${_autoBackupFiles.length}/10)',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF1A1A1A),
),
),
),
],
),
const SizedBox(height: 12),
..._autoBackupFiles.asMap().entries.map((entry) {
final index = entry.key;
final file = entry.value;
final fileName = file.path.split('/').last;
final stat = file.statSync();
final size = _formatFileSize(stat.size);
final modified = _formatDateTime(stat.modified);
return Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
decoration: BoxDecoration(
border: Border(
bottom: index < _autoBackupFiles.length - 1
? const BorderSide(color: Color(0xFFE5E5E5))
: BorderSide.none,
),
),
child: Row(
children: [
const Icon(
Icons.backup,
size: 18,
color: Color(0xFF666666),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
fileName,
style: const TextStyle(
fontSize: 13,
color: Color(0xFF1A1A1A),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
'$modified · $size',
style: const TextStyle(
fontSize: 11,
color: Color(0xFF999999),
),
),
],
),
),
if (index == 0)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
border: Border.all(color: const Color(0xFFE5E5E5)),
),
child: const Text(
'最新',
style: TextStyle(
fontSize: 10,
color: Color(0xFF666666),
),
),
),
],
),
);
}),
],
),
);
}
/// 格式化文件大小
String _formatFileSize(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
}
/// 格式化日期时间
String _formatDateTime(DateTime dateTime) {
return '${dateTime.month}/${dateTime.day} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
}
}