generated from dellevin/template
优化备份逻辑,连同头像一起备份
This commit is contained in:
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)),
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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')) {
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user