This commit is contained in:
DelLevin-Home
2026-03-08 01:43:26 +08:00
parent 901d6e27d6
commit 0c63242ca0
19 changed files with 531 additions and 364 deletions

View File

@@ -5,15 +5,6 @@ import 'movie_tab_page.dart';
import 'book_tab_page.dart';
import 'note_tab_page.dart';
import 'search_page.dart';
import 'webdav_sync_page.dart';
import '../utils/webdav_service.dart';
import '../utils/toast_util.dart';
/// 云备份模式
enum SyncMode {
uploadOnly, // 上传
downloadOnly, // 下载
}
/// 主内容页 - 观影/阅读/笔记标签页
class MainContentPage extends StatelessWidget {
@@ -44,12 +35,6 @@ class MainContentPage extends StatelessWidget {
return AppBar(
title: Text(_getAppBarTitle(provider)),
actions: [
// 云备份按钮
IconButton(
icon: const Icon(Icons.cloud_sync_outlined),
onPressed: () => _showCloudSyncDialog(context, provider),
tooltip: '云备份',
),
// 搜索按钮
IconButton(
icon: const Icon(Icons.search),
@@ -183,250 +168,6 @@ class MainContentPage extends StatelessWidget {
);
}
/// 显示云同步对话框
Future<void> _showCloudSyncDialog(BuildContext context, AppProvider provider) async {
// 检查是否已配置 WebDAV
final config = await WebDAVService.instance.getConfig();
if (!context.mounted) return;
// 如果没有配置,直接跳转到配置页面
if (config == null || config.isEmpty) {
// 关闭云同步对话框
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
// 等待对话框关闭完成
await Future.delayed(const Duration(milliseconds: 100));
if (!context.mounted) return;
// 跳转到配置页面
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const WebDAVSyncPage()),
);
// 配置页面返回后,重新检查配置
if (!context.mounted) return;
final newConfig = await WebDAVService.instance.getConfig();
if (newConfig != null && newConfig.isNotEmpty) {
// 配置成功,显示同步选项
_showSyncOptionsDialog(context, provider);
}
return;
}
// 已配置,显示同步选项
_showSyncOptionsDialog(context, provider);
}
/// 显示同步选项对话框
void _showSyncOptionsDialog(BuildContext context, AppProvider provider) {
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 标题
Container(
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFE5E5E5), width: 0.5),
),
),
child: const Text(
'云备份',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: Color(0xFF1A1A1A),
),
),
),
// 同步选项
Column(
children: [
_buildSyncOption(
context,
icon: Icons.cloud_upload,
iconColor: const Color(0xFF1A1A1A),
title: '上传',
subtitle: '将本地数据备份到云端',
onTap: () async {
Navigator.pop(context);
// 等待对话框关闭
await Future.delayed(const Duration(milliseconds: 100));
if (context.mounted) {
_navigateToSync(context, SyncMode.uploadOnly);
}
},
),
const Divider(height: 0.5, indent: 56, color: Color(0xFFE5E5E5)),
_buildSyncOption(
context,
icon: Icons.cloud_download,
iconColor: const Color(0xFF1A1A1A),
title: '下载',
subtitle: '从云端恢复数据到本地',
onTap: () async {
Navigator.pop(context);
// 等待对话框关闭
await Future.delayed(const Duration(milliseconds: 100));
if (context.mounted) {
_navigateToSync(context, SyncMode.downloadOnly);
}
},
),
],
),
// 取消按钮
Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(color: Color(0xFFE5E5E5), width: 0.5),
),
),
child: InkWell(
onTap: () => Navigator.pop(context),
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(12)),
child: Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: const Text(
'取消',
style: TextStyle(
fontSize: 15,
color: Color(0xFF999999),
),
),
),
),
),
],
),
),
);
},
);
}
/// 构建同步选项
Widget _buildSyncOption(
BuildContext context, {
required IconData icon,
required Color iconColor,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(16),
child: Row(
children: [
// 图标
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: const Color(0xFF1A1A1A),
size: 20,
),
),
const SizedBox(width: 16),
// 文字
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Color(0xFF1A1A1A),
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: const TextStyle(
fontSize: 13,
color: Color(0xFF999999),
),
),
],
),
),
// 箭头
const Icon(
Icons.chevron_right,
color: Color(0xFFCCCCCC),
size: 20,
),
],
),
),
);
}
/// 执行云备份操作
Future<void> _navigateToSync(BuildContext context, SyncMode mode) async {
// 执行同步
final direction = mode == SyncMode.uploadOnly
? SyncDirection.upload
: SyncDirection.download;
final result = await WebDAVService.instance.syncData(direction: direction);
if (!context.mounted) return;
if (result.success) {
// 如果需要重新加载数据(下载模式)
if (result.needReload) {
final provider = context.read<AppProvider>();
await provider.loadMovies();
await provider.loadBooks();
await provider.loadNotes();
}
ToastUtil.show(context, '${_getSyncModeText(mode)}成功');
} else {
ToastUtil.show(context, '${_getSyncModeText(mode)}失败: ${result.message}');
}
}
/// 获取同步模式文本
String _getSyncModeText(SyncMode mode) {
switch (mode) {
case SyncMode.uploadOnly:
return '上传';
case SyncMode.downloadOnly:
return '下载';
}
}
/// 显示添加对话框
void _showAddDialog(BuildContext context, AppProvider provider) {
showModalBottomSheet(

View File

@@ -4,6 +4,8 @@ import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'package:provider/provider.dart';
// import 'package:url_launcher/url_launcher.dart'; // 改为应用内打开
import 'package:webview_flutter/webview_flutter.dart';
import '../providers/app_provider.dart';
import '../utils/user_prefs.dart';
import '../utils/toast_util.dart';
@@ -593,6 +595,176 @@ class _ProfilePageState extends State<ProfilePage> {
/// 显示设置
void _showSettings(BuildContext context) {
_showToast('设置功能开发中');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsPage(),
),
);
}
}
/// 设置页面
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('设置'),
),
body: ListView(
children: [
// 使用说明
_buildLinkItem(
context: context,
icon: Icons.help_outline,
title: '使用说明',
subtitle: '查看应用使用指南',
url: 'https://www.iletter.top/',
),
const Divider(height: 0.5, indent: 24, endIndent: 24),
// 关于作者
_buildLinkItem(
context: context,
icon: Icons.person_outline,
title: '关于作者',
subtitle: '了解更多信息',
url: 'https://www.iletter.top/',
),
const Divider(height: 0.5, indent: 24, endIndent: 24),
],
),
);
}
/// 构建链接项
Widget _buildLinkItem({
required BuildContext context,
required IconData icon,
required String title,
required String subtitle,
required String url,
}) {
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
leading: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: const Color(0xFF666666),
size: 20,
),
),
title: Text(
title,
style: const TextStyle(
fontSize: 15,
color: Color(0xFF1A1A1A),
),
),
subtitle: Text(
subtitle,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF999999),
),
),
trailing: const Icon(
Icons.open_in_new,
color: Color(0xFFCCCCCC),
size: 18,
),
onTap: () => _launchUrl(context, url),
);
}
/// 打开链接(应用内打开)
void _launchUrl(BuildContext context, String url) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WebViewPage(url: url),
),
);
}
}
/// WebView 页面
class WebViewPage extends StatefulWidget {
final String url;
const WebViewPage({super.key, required this.url});
@override
State<WebViewPage> createState() => _WebViewPageState();
}
class _WebViewPageState extends State<WebViewPage> {
late final WebViewController _controller;
bool _isLoading = true;
@override
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (String url) {
setState(() {
_isLoading = true;
});
},
onPageFinished: (String url) {
setState(() {
_isLoading = false;
});
},
onWebResourceError: (WebResourceError error) {
setState(() {
_isLoading = false;
});
},
),
)
..loadRequest(Uri.parse(widget.url));
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text(''),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => _controller.reload(),
),
],
),
body: Stack(
children: [
WebViewWidget(controller: _controller),
if (_isLoading)
const Center(
child: CircularProgressIndicator(
color: Color(0xFF999999),
),
),
],
),
);
}
}

View File

@@ -270,11 +270,11 @@ class _StatisticsPageState extends State<StatisticsPage> with SingleTickerProvid
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildLegendItem('影视', const Color(0xFF1A1A1A)),
_buildLegendItem('影视', const Color(0xFFE53935)),
const SizedBox(width: 24),
_buildLegendItem('书籍', const Color(0xFF666666)),
_buildLegendItem('书籍', const Color(0xFF1E88E5)),
const SizedBox(width: 24),
_buildLegendItem('笔记', const Color(0xFF999999)),
_buildLegendItem('笔记', const Color(0xFF43A047)),
],
),
const SizedBox(height: 32),
@@ -590,35 +590,44 @@ class _StatisticsPageState extends State<StatisticsPage> with SingleTickerProvid
),
borderData: FlBorderData(show: false),
lineBarsData: [
// 影视折线
// 影视折线 - 红色
LineChartBarData(
spots: spotsMovie,
isCurved: true,
color: const Color(0xFF1A1A1A),
barWidth: 2,
color: const Color(0xFFE53935),
barWidth: 2.5,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: false),
belowBarData: BarAreaData(
show: true,
color: const Color(0xFFE53935).withOpacity(0.1),
),
),
// 书籍折线
// 书籍折线 - 蓝色
LineChartBarData(
spots: spotsBook,
isCurved: true,
color: const Color(0xFF666666),
barWidth: 2,
color: const Color(0xFF1E88E5),
barWidth: 2.5,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: false),
belowBarData: BarAreaData(
show: true,
color: const Color(0xFF1E88E5).withOpacity(0.1),
),
),
// 笔记折线
// 笔记折线 - 绿色
LineChartBarData(
spots: spotsNote,
isCurved: true,
color: const Color(0xFF999999),
barWidth: 2,
color: const Color(0xFF43A047),
barWidth: 2.5,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: false),
belowBarData: BarAreaData(
show: true,
color: const Color(0xFF43A047).withOpacity(0.1),
),
),
],
),
@@ -654,11 +663,11 @@ class _StatisticsPageState extends State<StatisticsPage> with SingleTickerProvid
),
child: Column(
children: [
_buildWeeklyRow('影视新增', totalMovies, const Color(0xFF1A1A1A)),
_buildWeeklyRow('影视新增', totalMovies, const Color(0xFFE53935)),
const Divider(height: 24),
_buildWeeklyRow('书籍新增', totalBooks, const Color(0xFF666666)),
_buildWeeklyRow('书籍新增', totalBooks, const Color(0xFF1E88E5)),
const Divider(height: 24),
_buildWeeklyRow('笔记新增', totalNotes, const Color(0xFF999999)),
_buildWeeklyRow('笔记新增', totalNotes, const Color(0xFF43A047)),
const Divider(height: 24),
_buildWeeklyRow('总计', totalMovies + totalBooks + totalNotes, const Color(0xFF1A1A1A), isTotal: true),
],