搜索优化,首页云备份

This commit is contained in:
DelLevin-Home
2026-03-13 13:10:43 +08:00
parent e181ebf9c6
commit 9ad9eb1e94
2 changed files with 308 additions and 4 deletions

View File

@@ -2,10 +2,13 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../utils/user_prefs.dart';
import '../utils/sync/webdav_service.dart';
import '../utils/toast_util.dart';
import 'movies/movie_tab_page.dart';
import 'book/book_tab_page.dart';
import 'note/note_tab_page.dart';
import 'search_page.dart';
import 'sync/webdav_sync_page.dart';
/// 主内容页 - 观影/阅读/笔记标签页
class MainContentPage extends StatefulWidget {
@@ -87,6 +90,8 @@ class _MainContentPageState extends State<MainContentPage> {
return AppBar(
title: Text(_getAppBarTitle(provider)),
actions: [
// 云备份按钮
_buildCloudSyncButton(context),
// 搜索按钮
IconButton(
icon: const Icon(Icons.search),
@@ -105,6 +110,301 @@ class _MainContentPageState extends State<MainContentPage> {
);
}
/// 构建云备份按钮
Widget _buildCloudSyncButton(BuildContext context) {
return PopupMenuButton<String>(
icon: const Icon(Icons.cloud_sync_outlined),
tooltip: '云备份',
offset: const Offset(0, 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
itemBuilder: (context) => [
PopupMenuItem(
value: 'upload',
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.cloud_upload_outlined,
size: 18,
color: Color(0xFF666666),
),
),
const SizedBox(width: 12),
const Text(
'上传数据',
style: TextStyle(
fontSize: 14,
color: Color(0xFF1A1A1A),
),
),
],
),
),
PopupMenuItem(
value: 'download',
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.cloud_download_outlined,
size: 18,
color: Color(0xFF666666),
),
),
const SizedBox(width: 12),
const Text(
'下载数据',
style: TextStyle(
fontSize: 14,
color: Color(0xFF1A1A1A),
),
),
],
),
),
const PopupMenuDivider(),
PopupMenuItem(
value: 'settings',
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.settings_outlined,
size: 18,
color: Color(0xFF666666),
),
),
const SizedBox(width: 12),
const Text(
'WebDAV设置',
style: TextStyle(
fontSize: 14,
color: Color(0xFF1A1A1A),
),
),
],
),
),
],
onSelected: (value) async {
switch (value) {
case 'upload':
await _performSync(context, SyncDirection.upload);
break;
case 'download':
await _performSync(context, SyncDirection.download);
break;
case 'settings':
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const WebDAVSyncPage(),
),
);
break;
}
},
);
}
/// 执行同步操作
Future<void> _performSync(BuildContext context, SyncDirection direction) async {
// 检查是否已配置
final config = await WebDAVService.instance.getConfig();
if (config == null) {
if (context.mounted) {
_showResultDialog(
context,
title: '同步失败',
message: '请先配置 WebDAV 服务器',
isSuccess: false,
);
}
return;
}
// 显示加载对话框
if (context.mounted) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(
child: CircularProgressIndicator(
color: Color(0xFF1A1A1A),
),
),
);
}
// 执行同步
final result = await WebDAVService.instance.syncData(direction: direction);
// 关闭加载对话框
if (context.mounted) {
Navigator.pop(context);
}
// 显示结果
if (context.mounted) {
final isSuccess = result.success;
final message = result.message.isNotEmpty ? result.message : (isSuccess ? '同步成功' : '同步失败');
_showResultDialog(
context,
title: isSuccess ? '同步成功' : '同步失败',
message: message,
isSuccess: isSuccess,
details: {
'uploaded': result.uploadedFiles + result.uploadedImages,
'downloaded': result.downloadedFiles + result.downloadedImages,
},
);
}
}
/// 显示同步结果对话框
void _showResultDialog(
BuildContext context, {
required String title,
required String message,
required bool isSuccess,
Map<String, dynamic>? details,
}) {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: isSuccess ? const Color(0xFFE8F5E9) : const Color(0xFFFFEBEE),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
isSuccess ? Icons.check_circle : Icons.error,
color: isSuccess ? const Color(0xFF4CAF50) : const Color(0xFFE57373),
size: 24,
),
),
const SizedBox(width: 12),
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
message,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF666666),
height: 1.5,
),
),
if (details != null) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (details['uploaded'] != null)
_buildDetailRow('上传文件', '${details['uploaded']}'),
if (details['downloaded'] != null)
_buildDetailRow('下载文件', '${details['downloaded']}'),
if (details['conflicts'] != null)
_buildDetailRow('冲突处理', '${details['conflicts']}'),
if (details['errors'] != null && details['errors'] > 0)
_buildDetailRow('错误', '${details['errors']}', isError: true),
],
),
),
],
],
),
actions: [
ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1A1A1A),
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
child: const Text('确定'),
),
],
actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
);
}
/// 构建详情行
Widget _buildDetailRow(String label, String value, {bool isError = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
fontSize: 13,
color: Color(0xFF999999),
),
),
Text(
value,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: isError ? const Color(0xFFE57373) : const Color(0xFF1A1A1A),
),
),
],
),
);
}
/// 获取 AppBar 标题
String _getAppBarTitle(AppProvider provider) {
switch (provider.mainTabIndex) {

View File

@@ -87,15 +87,19 @@ class _SearchPageState extends State<SearchPage> {
}
bool _matchNote(Note note, String keyword) {
// 搜索框为空且不选标签时,不显示任何笔记
if (keyword.isEmpty && _selectedTag == null) {
return false;
}
// 如果有选中的标签,先按标签筛选
if (_selectedTag != null) {
if (!note.tags.contains(_selectedTag)) {
return false;
}
}
// 关键词为空时,只按标签筛选
if (keyword.isEmpty) {
return true;
// 有标签但关键词为空时,只按标签筛选
if (keyword.isEmpty) {
return true;
}
}
// 按内容搜索
return note.content.toLowerCase().contains(keyword.toLowerCase());