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

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) { Widget _buildEmptyState(BuildContext context, int statusIndex) {
final statusText = ['', '在读', '准备'][statusIndex]; final statusText = ['', '在读', ''][statusIndex];
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Container(
Icons.menu_book_outlined, width: 80,
size: 80, height: 80,
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.3), decoration: BoxDecoration(
), color: const Color(0xFFF5F5F5),
const SizedBox(height: 16), borderRadius: BorderRadius.circular(20),
Text( ),
'暂无$statusText的书籍', child: const Icon(
style: Theme.of(context).textTheme.titleMedium?.copyWith( Icons.menu_book_outlined,
color: Theme.of(context).colorScheme.onSurfaceVariant, size: 40,
color: Color(0xFFCCCCCC),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 20),
ElevatedButton.icon( Text(
icon: const Icon(Icons.add), '暂无$statusText的书籍',
label: const Text('添加记录'), style: const TextStyle(
onPressed: () { fontSize: 16,
color: Color(0xFF999999),
),
),
const SizedBox(height: 24),
InkWell(
onTap: () {
final statusMap = { final statusMap = {
0: 'read', 0: 'read',
1: 'reading', 1: 'reading',
@@ -100,6 +107,21 @@ class BookTabPage extends StatelessWidget {
arguments: {'initialStatus': currentStatus}, 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( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon( Container(
Icons.movie_outlined, width: 80,
size: 48, height: 80,
color: Color(0xFFCCCCCC), 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( Text(
'暂无$statusText的影片', '暂无$statusText的影片',
style: const TextStyle( style: const TextStyle(
fontSize: 15, fontSize: 16,
color: Color(0xFF999999), color: Color(0xFF999999),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
TextButton( InkWell(
onPressed: () { onTap: () {
final statusMap = { final statusMap = {
0: 'watched', 0: 'watched',
1: 'watching', 1: 'watching',
@@ -99,7 +107,21 @@ class MovieTabPage extends StatelessWidget {
arguments: {'initialStatus': currentStatus}, 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), color: Color(0xFF999999),
), ),
), ),
const SizedBox(height: 8),
const Text(
'记录你的想法和灵感',
style: TextStyle(
fontSize: 13,
color: Color(0xFFBBBBBB),
),
),
const SizedBox(height: 24), const SizedBox(height: 24),
InkWell( InkWell(
onTap: () { onTap: () {
@@ -233,7 +225,7 @@ class _NoteTabPageState extends State<NoteTabPage> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Text( child: const Text(
'添加', '添加记',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,

View File

@@ -438,7 +438,10 @@ class _ProfilePageState extends State<ProfilePage> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => const BackupPage()), MaterialPageRoute(builder: (context) => const BackupPage()),
); ).then((_) {
// 返回时刷新用户数据
_loadUserData();
});
}, },
), ),
const Divider(height: 1, indent: 72, endIndent: 16, color: Color(0xFFE8E8E8)), 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), SizedBox(height: 4),
Text( Text(
'每2分钟自动备份保留最近10个备份', '每2分钟自动备份保留最近5个备份',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: Color(0xFF666666), color: Color(0xFF666666),

View File

@@ -16,8 +16,8 @@ class AutoBackupService {
static const String _prefsKey = 'auto_backup_enabled'; static const String _prefsKey = 'auto_backup_enabled';
static const String _backupDirName = 'mooknote'; static const String _backupDirName = 'mooknote';
static const int _maxBackups = 10; static const int _maxBackups = 5;
static const Duration _backupInterval = Duration(minutes: 2); static const Duration _backupInterval = Duration(minutes: 5);
/// 是否正在运行 /// 是否正在运行
bool get isRunning => _isRunning; 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:path/path.dart' as path;
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import '../database_helper.dart'; import '../database_helper.dart';
import '../user_prefs.dart';
/// 数据备份服务 - 支持导出和导入数据(包含图片) /// 数据备份服务 - 支持导出和导入数据(包含图片)
class BackupService { 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 = { final backupData = {
'version': 2, 'version': 2,
'exportTime': DateTime.now().toIso8601String(), 'exportTime': DateTime.now().toIso8601String(),
'appName': 'MookNote', 'appName': 'MookNote',
'hasImages': true, 'hasImages': true,
'userInfo': userInfo,
'data': { 'data': {
'movies': movies, 'movies': movies,
'books': books, '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 = { final backupData = {
'version': 2, 'version': 2,
'exportTime': DateTime.now().toIso8601String(), 'exportTime': DateTime.now().toIso8601String(),
'appName': 'MookNote', 'appName': 'MookNote',
'hasImages': true, 'hasImages': true,
'userInfo': userInfo,
'data': { 'data': {
'movies': movies, 'movies': movies,
'books': books, '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>{}; final stats = <String, int>{};
if (data.containsKey('movies')) { if (data.containsKey('movies')) {

View File

@@ -133,23 +133,50 @@ class BookListItem extends StatelessWidget {
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: const Text('确认删除'), title: const Text(
content: Text('确定要删除《${book.title}》吗?'), '确认删除',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
content: Text(
'确定要删除《${book.title}》吗?删除后可在回收站恢复。',
style: const TextStyle(
fontSize: 14,
color: Color(0xFF666666),
height: 1.5,
),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), 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 { onPressed: () async {
await context.read<AppProvider>().removeBook(book.id); await context.read<AppProvider>().removeBook(book.id);
Navigator.pop(context); Navigator.pop(context);
ToastUtil.show(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( builder: (context) => AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: const Text('确认删除'), title: const Text(
content: Text('确定要删除《${movie.title}》吗?'), '确认删除',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
content: Text(
'确定要删除《${movie.title}》吗?删除后可在回收站恢复。',
style: const TextStyle(
fontSize: 14,
color: Color(0xFF666666),
height: 1.5,
),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), 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 { onPressed: () async {
await context.read<AppProvider>().removeMovie(movie.id); await context.read<AppProvider>().removeMovie(movie.id);
Navigator.pop(context); Navigator.pop(context);
ToastUtil.show(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),
), ),
); );
} }