generated from dellevin/template
搜索优化,首页云备份
This commit is contained in:
@@ -2,10 +2,13 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../providers/app_provider.dart';
|
import '../providers/app_provider.dart';
|
||||||
import '../utils/user_prefs.dart';
|
import '../utils/user_prefs.dart';
|
||||||
|
import '../utils/sync/webdav_service.dart';
|
||||||
|
import '../utils/toast_util.dart';
|
||||||
import 'movies/movie_tab_page.dart';
|
import 'movies/movie_tab_page.dart';
|
||||||
import 'book/book_tab_page.dart';
|
import 'book/book_tab_page.dart';
|
||||||
import 'note/note_tab_page.dart';
|
import 'note/note_tab_page.dart';
|
||||||
import 'search_page.dart';
|
import 'search_page.dart';
|
||||||
|
import 'sync/webdav_sync_page.dart';
|
||||||
|
|
||||||
/// 主内容页 - 观影/阅读/笔记标签页
|
/// 主内容页 - 观影/阅读/笔记标签页
|
||||||
class MainContentPage extends StatefulWidget {
|
class MainContentPage extends StatefulWidget {
|
||||||
@@ -87,6 +90,8 @@ class _MainContentPageState extends State<MainContentPage> {
|
|||||||
return AppBar(
|
return AppBar(
|
||||||
title: Text(_getAppBarTitle(provider)),
|
title: Text(_getAppBarTitle(provider)),
|
||||||
actions: [
|
actions: [
|
||||||
|
// 云备份按钮
|
||||||
|
_buildCloudSyncButton(context),
|
||||||
// 搜索按钮
|
// 搜索按钮
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.search),
|
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 标题
|
/// 获取 AppBar 标题
|
||||||
String _getAppBarTitle(AppProvider provider) {
|
String _getAppBarTitle(AppProvider provider) {
|
||||||
switch (provider.mainTabIndex) {
|
switch (provider.mainTabIndex) {
|
||||||
|
|||||||
@@ -87,15 +87,19 @@ class _SearchPageState extends State<SearchPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _matchNote(Note note, String keyword) {
|
bool _matchNote(Note note, String keyword) {
|
||||||
|
// 搜索框为空且不选标签时,不显示任何笔记
|
||||||
|
if (keyword.isEmpty && _selectedTag == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// 如果有选中的标签,先按标签筛选
|
// 如果有选中的标签,先按标签筛选
|
||||||
if (_selectedTag != null) {
|
if (_selectedTag != null) {
|
||||||
if (!note.tags.contains(_selectedTag)) {
|
if (!note.tags.contains(_selectedTag)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
// 有标签但关键词为空时,只按标签筛选
|
||||||
// 关键词为空时,只按标签筛选
|
if (keyword.isEmpty) {
|
||||||
if (keyword.isEmpty) {
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
// 按内容搜索
|
// 按内容搜索
|
||||||
return note.content.toLowerCase().contains(keyword.toLowerCase());
|
return note.content.toLowerCase().contains(keyword.toLowerCase());
|
||||||
|
|||||||
Reference in New Issue
Block a user