From 9ad9eb1e94137664130bd178dd9da057256829cf Mon Sep 17 00:00:00 2001 From: DelLevin-Home Date: Fri, 13 Mar 2026 13:10:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E4=BC=98=E5=8C=96=EF=BC=8C?= =?UTF-8?q?=E9=A6=96=E9=A1=B5=E4=BA=91=E5=A4=87=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/main_content_page.dart | 300 +++++++++++++++++++++++++++++++ lib/pages/search_page.dart | 12 +- 2 files changed, 308 insertions(+), 4 deletions(-) diff --git a/lib/pages/main_content_page.dart b/lib/pages/main_content_page.dart index 1b5cc5b..76f59f4 100644 --- a/lib/pages/main_content_page.dart +++ b/lib/pages/main_content_page.dart @@ -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 { return AppBar( title: Text(_getAppBarTitle(provider)), actions: [ + // 云备份按钮 + _buildCloudSyncButton(context), // 搜索按钮 IconButton( icon: const Icon(Icons.search), @@ -105,6 +110,301 @@ class _MainContentPageState extends State { ); } + /// 构建云备份按钮 + Widget _buildCloudSyncButton(BuildContext context) { + return PopupMenuButton( + 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 _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? 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) { diff --git a/lib/pages/search_page.dart b/lib/pages/search_page.dart index 983cd1b..3542ed1 100644 --- a/lib/pages/search_page.dart +++ b/lib/pages/search_page.dart @@ -87,15 +87,19 @@ class _SearchPageState extends State { } 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());