优化备份逻辑,连同头像一起备份

This commit is contained in:
DelLevin-Home
2026-03-13 16:18:25 +08:00
parent 9ad9eb1e94
commit bd9b82f6e1
9 changed files with 197 additions and 50 deletions

View File

@@ -65,29 +65,36 @@ class BookTabPage extends StatelessWidget {
/// 构建空状态提示
Widget _buildEmptyState(BuildContext context, int statusIndex) {
final statusText = ['', '在读', '准备'][statusIndex];
final statusText = ['', '在读', ''][statusIndex];
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.menu_book_outlined,
size: 80,
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.3),
),
const SizedBox(height: 16),
Text(
'暂无$statusText的书籍',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(20),
),
child: const Icon(
Icons.menu_book_outlined,
size: 40,
color: Color(0xFFCCCCCC),
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
icon: const Icon(Icons.add),
label: const Text('添加记录'),
onPressed: () {
const SizedBox(height: 20),
Text(
'暂无$statusText的书籍',
style: const TextStyle(
fontSize: 16,
color: Color(0xFF999999),
),
),
const SizedBox(height: 24),
InkWell(
onTap: () {
final statusMap = {
0: 'read',
1: 'reading',
@@ -100,6 +107,21 @@ class BookTabPage extends StatelessWidget {
arguments: {'initialStatus': currentStatus},
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFF1A1A1A),
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'添加记录',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
],
),

View File

@@ -71,22 +71,30 @@ class MovieTabPage extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.movie_outlined,
size: 48,
color: Color(0xFFCCCCCC),
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(20),
),
child: const Icon(
Icons.movie_outlined,
size: 40,
color: Color(0xFFCCCCCC),
),
),
const SizedBox(height: 16),
const SizedBox(height: 20),
Text(
'暂无$statusText的影片',
style: const TextStyle(
fontSize: 15,
fontSize: 16,
color: Color(0xFF999999),
),
),
const SizedBox(height: 24),
TextButton(
onPressed: () {
InkWell(
onTap: () {
final statusMap = {
0: 'watched',
1: 'watching',
@@ -99,7 +107,21 @@ class MovieTabPage extends StatelessWidget {
arguments: {'initialStatus': currentStatus},
);
},
child: const Text('添加记录'),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFF1A1A1A),
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'添加记录',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
],
),

View File

@@ -213,14 +213,6 @@ class _NoteTabPageState extends State<NoteTabPage> {
color: Color(0xFF999999),
),
),
const SizedBox(height: 8),
const Text(
'记录你的想法和灵感',
style: TextStyle(
fontSize: 13,
color: Color(0xFFBBBBBB),
),
),
const SizedBox(height: 24),
InkWell(
onTap: () {
@@ -233,7 +225,7 @@ class _NoteTabPageState extends State<NoteTabPage> {
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'添加',
'添加记',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,

View File

@@ -438,7 +438,10 @@ class _ProfilePageState extends State<ProfilePage> {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const BackupPage()),
);
).then((_) {
// 返回时刷新用户数据
_loadUserData();
});
},
),
const Divider(height: 1, indent: 72, endIndent: 16, color: Color(0xFFE8E8E8)),

View File

@@ -477,7 +477,7 @@ class _BackupPageState extends State<BackupPage> {
),
SizedBox(height: 4),
Text(
'每2分钟自动备份保留最近10个备份',
'每2分钟自动备份保留最近5个备份',
style: TextStyle(
fontSize: 13,
color: Color(0xFF666666),

View File

@@ -16,8 +16,8 @@ class AutoBackupService {
static const String _prefsKey = 'auto_backup_enabled';
static const String _backupDirName = 'mooknote';
static const int _maxBackups = 10;
static const Duration _backupInterval = Duration(minutes: 2);
static const int _maxBackups = 5;
static const Duration _backupInterval = Duration(minutes: 5);
/// 是否正在运行
bool get isRunning => _isRunning;

View File

@@ -8,6 +8,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'package:share_plus/share_plus.dart';
import '../database_helper.dart';
import '../user_prefs.dart';
/// 数据备份服务 - 支持导出和导入数据(包含图片)
class BackupService {
@@ -71,12 +72,27 @@ class BackupService {
}
}
// 获取用户个人信息
final userPrefs = UserPrefs();
final userInfo = {
'nickname': userPrefs.nickname,
'motto': userPrefs.motto,
'avatarPath': userPrefs.avatarPath,
};
// 如果有头像,添加到图片路径
final avatarPath = userPrefs.avatarPath;
if (avatarPath != null && avatarPath.isNotEmpty) {
imagePaths.add(avatarPath);
}
// 构建备份数据
final backupData = {
'version': 2,
'exportTime': DateTime.now().toIso8601String(),
'appName': 'MookNote',
'hasImages': true,
'userInfo': userInfo,
'data': {
'movies': movies,
'books': books,
@@ -238,12 +254,27 @@ class BackupService {
}
}
// 获取用户个人信息
final userPrefs = UserPrefs();
final userInfo = {
'nickname': userPrefs.nickname,
'motto': userPrefs.motto,
'avatarPath': userPrefs.avatarPath,
};
// 如果有头像,添加到图片路径
final avatarPath = userPrefs.avatarPath;
if (avatarPath != null && avatarPath.isNotEmpty) {
imagePaths.add(avatarPath);
}
// 构建备份数据
final backupData = {
'version': 2,
'exportTime': DateTime.now().toIso8601String(),
'appName': 'MookNote',
'hasImages': true,
'userInfo': userInfo,
'data': {
'movies': movies,
'books': books,
@@ -443,6 +474,29 @@ class BackupService {
}
});
// 恢复用户个人信息
if (backupData.containsKey('userInfo')) {
final userInfo = backupData['userInfo'] as Map<String, dynamic>;
final userPrefs = UserPrefs();
if (userInfo.containsKey('nickname')) {
await userPrefs.setNickname(userInfo['nickname'] as String);
}
if (userInfo.containsKey('motto')) {
await userPrefs.setMotto(userInfo['motto'] as String);
}
if (userInfo.containsKey('avatarPath')) {
final avatarPath = userInfo['avatarPath'] as String?;
if (avatarPath != null && avatarPath.isNotEmpty) {
// 更新头像路径为新的路径
final fileName = path.basename(avatarPath);
if (imagePathMap.containsKey(fileName)) {
await userPrefs.setAvatarPath(imagePathMap[fileName]!);
}
}
}
}
// 统计导入数量
final stats = <String, int>{};
if (data.containsKey('movies')) {

View File

@@ -133,23 +133,50 @@ class BookListItem extends StatelessWidget {
builder: (context) => AlertDialog(
backgroundColor: Colors.white,
elevation: 0,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
title: const Text('确认删除'),
content: Text('确定要删除《${book.title}》吗?'),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: const Text(
'确认删除',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
content: Text(
'确定要删除《${book.title}》吗?删除后可在回收站恢复。',
style: const TextStyle(
fontSize: 14,
color: Color(0xFF666666),
height: 1.5,
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消', style: TextStyle(color: Color(0xFF666666))),
style: TextButton.styleFrom(
foregroundColor: const Color(0xFF666666),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
child: const Text('取消'),
),
TextButton(
ElevatedButton(
onPressed: () async {
await context.read<AppProvider>().removeBook(book.id);
Navigator.pop(context);
ToastUtil.show(context, '已删除');
},
child: const Text('删除', style: TextStyle(color: Colors.red)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
child: const Text('删除'),
),
],
actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
);
}

View File

@@ -133,23 +133,50 @@ class MovieListItem extends StatelessWidget {
builder: (context) => AlertDialog(
backgroundColor: Colors.white,
elevation: 0,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
title: const Text('确认删除'),
content: Text('确定要删除《${movie.title}》吗?'),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: const Text(
'确认删除',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
content: Text(
'确定要删除《${movie.title}》吗?删除后可在回收站恢复。',
style: const TextStyle(
fontSize: 14,
color: Color(0xFF666666),
height: 1.5,
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消', style: TextStyle(color: Color(0xFF666666))),
style: TextButton.styleFrom(
foregroundColor: const Color(0xFF666666),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
child: const Text('取消'),
),
TextButton(
ElevatedButton(
onPressed: () async {
await context.read<AppProvider>().removeMovie(movie.id);
Navigator.pop(context);
ToastUtil.show(context, '已删除');
},
child: const Text('删除', style: TextStyle(color: Colors.red)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
child: const Text('删除'),
),
],
actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
);
}