import 'dart:io'; import 'package:flutter/material.dart'; 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:package_info_plus/package_info_plus.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'; import 'recycle_bin_page.dart'; import 'sync/backup_page.dart'; import 'statistics_page.dart'; import 'sync/cloud_sync_page.dart'; /// 个人中心页面 - 极简主义设计 class ProfilePage extends StatefulWidget { const ProfilePage({super.key}); @override State createState() => _ProfilePageState(); } class _ProfilePageState extends State { final ImagePicker _picker = ImagePicker(); final UserPrefs _userPrefs = UserPrefs(); // 用户数据 String _nickname = 'Mook'; String _motto = '好运不会眷顾一无所有之人。'; String? _avatarPath; String _version = '0.1.5'; bool _isLoading = true; @override void initState() { super.initState(); _loadUserData(); _loadVersionInfo(); } /// 加载版本信息 Future _loadVersionInfo() async { final packageInfo = await PackageInfo.fromPlatform(); setState(() { _version = packageInfo.version; }); } /// 加载用户数据 Future _loadUserData() async { setState(() => _isLoading = true); try { await UserPrefs.init(); setState(() { _nickname = _userPrefs.nickname; _motto = _userPrefs.motto; _avatarPath = _userPrefs.avatarPath; _isLoading = false; }); } catch (e) { setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { if (_isLoading) { return const Center( child: CircularProgressIndicator( strokeWidth: 2, color: Color(0xFF1A1A1A), ), ); } return Column( children: [ // 标题栏 AppBar( title: const Text('我的'), actions: [ IconButton( icon: const Icon(Icons.settings_outlined), onPressed: () => _showSettings(context), ), ], ), // 内容 Expanded( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 顶部用户信息 _buildUserHeader(), const SizedBox(height: 8), // 数据统计 _buildStatsSection(), const SizedBox(height: 8), // 功能菜单 _buildMenuSection(), const SizedBox(height: 40), // 版本信息 Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(20), ), child: Text( 'MookNote v$_version', style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF666666), ), ), ), ), // 底部留白,避免被 dock 栏遮挡 const SizedBox(height: 100), ], ), ), ), ], ); } /// 用户头部信息 Widget _buildUserHeader() { return Container( padding: const EdgeInsets.all(24), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // 左侧头像 GestureDetector( onTap: _pickAvatar, child: Container( width: 72, height: 72, decoration: BoxDecoration( color: const Color(0xFFF5F5F5), border: Border.all(color: const Color(0xFFE5E5E5), width: 0.5), shape: BoxShape.circle, ), child: _avatarPath != null && _avatarPath!.isNotEmpty ? ClipOval( child: Image.file( File(_avatarPath!), fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildAvatarPlaceholder(), ), ) : _buildAvatarPlaceholder(), ), ), const SizedBox(width: 20), // 右侧信息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // 昵称 GestureDetector( onTap: () => _editNickname(context), child: Text( _nickname, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), ), ), ), const SizedBox(height: 6), // 座右铭 GestureDetector( onTap: () => _editMotto(context), child: Text( _motto, style: const TextStyle( fontSize: 14, color: Color(0xFF666666), ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), ), ], ), ); } Widget _buildAvatarPlaceholder() { return const Center( child: Icon( Icons.person_outline, size: 32, color: Color(0xFFCCCCCC), ), ); } /// 数据统计区域 Widget _buildStatsSection() { return Consumer( builder: (context, provider, child) { final movies = provider.movies; final books = provider.books; final notes = provider.notes; final movieCount = movies.where((m) => !m.isDeleted).length; final watchedCount = movies.where((m) => m.status == 'watched' && !m.isDeleted).length; final watchingCount = movies.where((m) => m.status == 'watching' && !m.isDeleted).length; final wantToWatchCount = movies.where((m) => m.status == 'want_to_watch' && !m.isDeleted).length; final bookCount = books.length; final readCount = books.where((b) => b.status == 'read').length; final readingCount = books.where((b) => b.status == 'reading').length; final wantToReadCount = books.where((b) => b.status == 'want_to_read').length; final noteCount = notes.length; final movieRatings = movies .where((m) => m.rating != null && !m.isDeleted) .map((m) => m.rating!); final avgMovieRating = movieRatings.isNotEmpty ? movieRatings.reduce((a, b) => a + b) / movieRatings.length : 0.0; final bookRatings = books .where((b) => b.rating != null) .map((b) => b.rating!); final avgBookRating = bookRatings.isNotEmpty ? bookRatings.reduce((a, b) => a + b) / bookRatings.length : 0.0; return Padding( padding: const EdgeInsets.fromLTRB(24, 16, 24, 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 主统计卡片 Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: const Color(0xFFF8F8F8), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildStatCard('观影', movieCount, '场', Icons.movie_outlined), _buildStatCard('阅读', bookCount, '本', Icons.menu_book_outlined), _buildStatCard('笔记', noteCount, '条', Icons.note_outlined), ], ), ), const SizedBox(height: 24), // 详情统计 _buildSectionTitle('观影详情'), const SizedBox(height: 12), _buildDetailGrid([ _buildDetailItem('已看', watchedCount, Icons.check_circle_outline), _buildDetailItem('在看', watchingCount, Icons.play_circle_outline), _buildDetailItem('想看', wantToWatchCount, Icons.bookmark_border), _buildDetailItem('均分', avgMovieRating > 0 ? avgMovieRating.toStringAsFixed(1) : '-', Icons.star_outline), ]), const SizedBox(height: 20), _buildSectionTitle('阅读详情'), const SizedBox(height: 12), _buildDetailGrid([ _buildDetailItem('已读', readCount, Icons.check_circle_outline), _buildDetailItem('在读', readingCount, Icons.play_circle_outline), _buildDetailItem('想读', wantToReadCount, Icons.bookmark_border), _buildDetailItem('均分', avgBookRating > 0 ? avgBookRating.toStringAsFixed(1) : '-', Icons.star_outline), ]), ], ), ); }, ); } /// 统计卡片 Widget _buildStatCard(String label, int count, String unit, IconData icon) { return Column( children: [ Icon( icon, size: 24, color: const Color(0xFF666666), ), const SizedBox(height: 8), Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [ Text( '$count', style: const TextStyle( fontSize: 28, fontWeight: FontWeight.w700, color: Color(0xFF1A1A1A), ), ), const SizedBox(width: 2), Text( unit, style: const TextStyle( fontSize: 13, color: Color(0xFF666666), ), ), ], ), const SizedBox(height: 2), Text( label, style: const TextStyle( fontSize: 12, color: Color(0xFF999999), ), ), ], ); } /// 详情网格 Widget _buildDetailGrid(List children) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFFAFAFA), borderRadius: BorderRadius.circular(10), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: children, ), ); } /// 详情项 Widget _buildDetailItem(String label, dynamic value, IconData icon) { return Column( children: [ Icon( icon, size: 18, color: const Color(0xFF999999), ), const SizedBox(height: 6), Text( '$value', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), ), ), const SizedBox(height: 2), Text( label, style: const TextStyle( fontSize: 11, color: Color(0xFF999999), ), ), ], ); } /// 区块标题 Widget _buildSectionTitle(String title) { return Text( title, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF999999), ), ); } /// 功能菜单 Widget _buildMenuSection() { return Container( margin: const EdgeInsets.symmetric(horizontal: 24), decoration: BoxDecoration( color: const Color(0xFFFAFAFA), borderRadius: BorderRadius.circular(12), ), child: Column( children: [ _buildMenuItem( icon: Icons.analytics_outlined, title: '数据统计', onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const StatisticsPage()), ); }, ), const Divider(height: 1, indent: 72, endIndent: 16, color: Color(0xFFE8E8E8)), _buildMenuItem( icon: Icons.backup_outlined, title: '本地备份', onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const BackupPage()), ).then((_) { // 返回时刷新用户数据 _loadUserData(); }); }, ), const Divider(height: 1, indent: 72, endIndent: 16, color: Color(0xFFE8E8E8)), _buildMenuItem( icon: Icons.cloud_sync_outlined, title: '云备份', onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const CloudSyncPage()), ); }, ), const Divider(height: 1, indent: 72, endIndent: 16, color: Color(0xFFE8E8E8)), _buildMenuItem( icon: Icons.delete_outline, title: '回收站', onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const RecycleBinPage()), ); }, ), ], ), ); } /// 菜单项 Widget _buildMenuItem({ required IconData icon, required String title, required VoidCallback onTap, }) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ // 图标背景 Container( width: 40, height: 40, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), ), child: Icon( icon, size: 20, color: const Color(0xFF666666), ), ), const SizedBox(width: 16), // 标题 Expanded( child: Text( title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A), ), ), ), // 箭头 const Icon( Icons.chevron_right, size: 20, color: Color(0xFFCCCCCC), ), ], ), ), ); } /// 显示提示 void _showToast(String message) { ToastUtil.show(context, message); } /// 选择头像 Future _pickAvatar() async { try { final XFile? pickedFile = await _picker.pickImage( source: ImageSource.gallery, maxWidth: 400, maxHeight: 400, imageQuality: 85, ); if (pickedFile != null) { final appDir = await getApplicationDocumentsDirectory(); final fileName = 'avatar_${DateTime.now().millisecondsSinceEpoch}.jpg'; final savedPath = path.join(appDir.path, 'avatars', fileName); final avatarDir = Directory(path.join(appDir.path, 'avatars')); if (!await avatarDir.exists()) { await avatarDir.create(recursive: true); } await File(pickedFile.path).copy(savedPath); // 保存到本地存储 await _userPrefs.setAvatarPath(savedPath); setState(() => _avatarPath = savedPath); } } catch (e) { if (mounted) { _showToast('选择头像失败: $e'); } } } /// 编辑昵称 void _editNickname(BuildContext context) { final controller = TextEditingController(text: _nickname); showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: Colors.white, elevation: 0, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), title: const Text( '修改昵称', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), ), ), content: TextField( controller: controller, decoration: const InputDecoration( hintText: '输入昵称', border: UnderlineInputBorder( borderSide: BorderSide(color: Color(0xFFE5E5E5)), ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消', style: TextStyle(color: Color(0xFF666666))), ), TextButton( onPressed: () async { final newNickname = controller.text.trim(); if (newNickname.isNotEmpty) { await _userPrefs.setNickname(newNickname); setState(() => _nickname = newNickname); } Navigator.pop(context); }, child: const Text('确定'), ), ], ), ); } /// 编辑座右铭 void _editMotto(BuildContext context) { final controller = TextEditingController(text: _motto); showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: Colors.white, elevation: 0, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), title: const Text( '修改座右铭', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), ), ), content: TextField( controller: controller, maxLines: 2, decoration: const InputDecoration( hintText: '输入座右铭', border: UnderlineInputBorder( borderSide: BorderSide(color: Color(0xFFE5E5E5)), ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消', style: TextStyle(color: Color(0xFF666666))), ), TextButton( onPressed: () async { final newMotto = controller.text.trim(); await _userPrefs.setMotto(newMotto); setState(() => _motto = newMotto); Navigator.pop(context); }, child: const Text('确定'), ), ], ), ); } /// 显示设置 void _showSettings(BuildContext context) { 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: [ // 数据管理 _buildSectionHeader('数据管理'), _buildActionItem( icon: Icons.cleaning_services_outlined, title: '清除缓存数据', subtitle: '清理未在数据库中引用的图片文件', onTap: () => _showClearCacheDialog(context), ), const Divider(height: 0.5, indent: 24, endIndent: 24), // 主界面功能显示入口 _buildSectionHeader('主界面显示'), _buildNavigationItem( icon: Icons.view_list_outlined, title: '主界面功能显示', subtitle: '控制观影、阅读、笔记的显示', onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const MainContentSettingsPage(), ), ); }, ), const Divider(height: 0.5, indent: 24, endIndent: 24), // 使用说明 _buildSectionHeader('帮助'), _buildLinkItem( context: context, icon: Icons.help_outline, title: '使用说明', subtitle: '查看应用使用指南', url: 'https://mooknote.iletter.top/#/guide', ), 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 _buildSectionHeader(String title) { return Padding( padding: const EdgeInsets.fromLTRB(24, 32, 24, 12), child: Text( title, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), ), ), ); } /// 构建导航项 Widget _buildNavigationItem({ required IconData icon, required String title, required String subtitle, required VoidCallback onTap, }) { return InkWell( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), child: Row( children: [ // 图标背景 Container( width: 44, height: 44, decoration: BoxDecoration( color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10), ), child: Icon( icon, color: const Color(0xFF666666), size: 22, ), ), 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: 2), Text( subtitle, style: const TextStyle( fontSize: 12, color: Color(0xFF999999), ), ), ], ), ), // 箭头 const Icon( Icons.chevron_right, color: Color(0xFFCCCCCC), size: 20, ), ], ), ), ); } /// 构建链接项 Widget _buildLinkItem({ required BuildContext context, required IconData icon, required String title, required String subtitle, required String url, }) { return InkWell( onTap: () => _launchUrl(context, url), child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), child: Row( children: [ // 图标背景 Container( width: 44, height: 44, decoration: BoxDecoration( color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10), ), child: Icon( icon, color: const Color(0xFF666666), size: 22, ), ), 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: 2), Text( subtitle, style: const TextStyle( fontSize: 12, color: Color(0xFF999999), ), ), ], ), ), // 外部链接图标 const Icon( Icons.open_in_new, color: Color(0xFFCCCCCC), size: 18, ), ], ), ), ); } /// 构建操作项(无箭头) Widget _buildActionItem({ required IconData icon, required String title, required String subtitle, required VoidCallback onTap, }) { return InkWell( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), child: Row( children: [ // 图标背景 Container( width: 44, height: 44, decoration: BoxDecoration( color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10), ), child: Icon( icon, color: const Color(0xFF666666), size: 22, ), ), 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: 2), Text( subtitle, style: const TextStyle( fontSize: 12, color: Color(0xFF999999), ), ), ], ), ), ], ), ), ); } /// 显示清除缓存对话框 void _showClearCacheDialog(BuildContext context) { showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: Colors.white, elevation: 0, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), title: const Text('清除缓存数据'), content: const Text('这将删除所有未在数据库中引用的图片文件。确定要继续吗?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消', style: TextStyle(color: Color(0xFF666666))), ), TextButton( onPressed: () async { Navigator.pop(context); await _clearCacheData(context); }, child: const Text('确定', style: TextStyle(color: Colors.red)), ), ], ), ); } /// 清除缓存数据 Future _clearCacheData(BuildContext context) async { try { // 显示进度提示 showDialog( context: context, barrierDismissible: false, builder: (context) => const Center( child: CircularProgressIndicator(), ), ); // 获取所有数据库中的图片路径 final appProvider = context.read(); final dbImagePaths = await _getAllDbImagePaths(appProvider); // 清理图片目录 final deletedCount = await _cleanImageDirectory(dbImagePaths); // 关闭进度提示 Navigator.pop(context); // 显示结果 if (context.mounted) { ToastUtil.show(context, '已清理 $deletedCount 个缓存文件'); } } catch (e) { // 关闭进度提示 Navigator.pop(context); if (context.mounted) { ToastUtil.show(context, '清理失败: $e'); } } } /// 获取数据库中所有图片路径 Future> _getAllDbImagePaths(AppProvider provider) async { final paths = {}; // 获取所有影视的封面路径 final movies = provider.movies; for (final movie in movies) { final posterPath = movie.posterPath; if (posterPath != null && posterPath.isNotEmpty) { paths.add(posterPath); } } // 获取所有书籍的封面路径 final books = provider.books; for (final book in books) { final coverPath = book.coverPath; if (coverPath != null && coverPath.isNotEmpty) { paths.add(coverPath); } } // 获取所有笔记中的图片路径 final notes = provider.notes; for (final note in notes) { for (final imagePath in note.images) { if (imagePath.isNotEmpty) { paths.add(imagePath); } } } // 获取所有海报墙图片路径 final movieIds = movies.map((m) => m.id).toList(); for (final movieId in movieIds) { final posters = await provider.getMoviePosters(movieId); for (final poster in posters) { final posterPath = poster.posterPath; if (posterPath.isNotEmpty) { paths.add(posterPath); } } } return paths; } /// 清理图片目录 Future _cleanImageDirectory(Set dbImagePaths) async { int deletedCount = 0; try { // 获取应用文档目录 final appDir = await getApplicationDocumentsDirectory(); final imagesDir = Directory('${appDir.path}/images'); if (!await imagesDir.exists()) { return 0; } // 递归遍历所有文件 await for (final entity in imagesDir.list(recursive: true, followLinks: false)) { if (entity is File) { final filePath = entity.path; // 如果文件不在数据库中,删除它 if (!dbImagePaths.contains(filePath)) { try { await entity.delete(); deletedCount++; } catch (e) { // 忽略单个文件删除错误 } } } } } catch (e) { debugPrint('清理图片目录失败: $e'); } return deletedCount; } /// 打开链接(应用内打开) void _launchUrl(BuildContext context, String url) { Navigator.push( context, MaterialPageRoute( builder: (context) => WebViewPage(url: url), ), ); } } /// 主界面功能显示设置页面 class MainContentSettingsPage extends StatefulWidget { const MainContentSettingsPage({super.key}); @override State createState() => _MainContentSettingsPageState(); } class _MainContentSettingsPageState extends State { final UserPrefs _userPrefs = UserPrefs(); bool _showMovieTab = true; bool _showBookTab = true; bool _showNoteTab = true; @override void initState() { super.initState(); _loadSettings(); } /// 加载设置 void _loadSettings() { setState(() { _showMovieTab = _userPrefs.showMovieTab; _showBookTab = _userPrefs.showBookTab; _showNoteTab = _userPrefs.showNoteTab; }); } /// 获取已启用的标签数量 int get _enabledTabCount { int count = 0; if (_showMovieTab) count++; if (_showBookTab) count++; if (_showNoteTab) count++; return count; } /// 切换观影标签显示 Future _toggleMovieTab(bool value) async { if (!value && _enabledTabCount <= 1) { _showToast('至少保留一个标签页'); return; } await _userPrefs.setShowMovieTab(value); setState(() => _showMovieTab = value); } /// 切换阅读标签显示 Future _toggleBookTab(bool value) async { if (!value && _enabledTabCount <= 1) { _showToast('至少保留一个标签页'); return; } await _userPrefs.setShowBookTab(value); setState(() => _showBookTab = value); } /// 切换笔记标签显示 Future _toggleNoteTab(bool value) async { if (!value && _enabledTabCount <= 1) { _showToast('至少保留一个标签页'); return; } await _userPrefs.setShowNoteTab(value); setState(() => _showNoteTab = value); } /// 显示提示 void _showToast(String message) { ToastUtil.show(context, message); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: const Text('主界面功能显示'), ), body: ListView( children: [ // 说明文字 Container( padding: const EdgeInsets.all(24), child: const Text( '选择要在主界面显示的功能模块,至少保留一个。', style: TextStyle( fontSize: 14, color: Color(0xFF666666), ), ), ), const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)), // 观影开关 _buildSwitchItem( icon: Icons.movie_outlined, title: '观影', subtitle: '记录和管理观影记录', value: _showMovieTab, onChanged: _toggleMovieTab, ), const Divider(height: 0.5, indent: 24, endIndent: 24), // 阅读开关 _buildSwitchItem( icon: Icons.menu_book_outlined, title: '阅读', subtitle: '记录和管理阅读记录', value: _showBookTab, onChanged: _toggleBookTab, ), const Divider(height: 0.5, indent: 24, endIndent: 24), // 笔记开关 _buildSwitchItem( icon: Icons.note_outlined, title: '笔记', subtitle: '记录和管理笔记', value: _showNoteTab, onChanged: _toggleNoteTab, ), const Divider(height: 0.5, indent: 24, endIndent: 24), ], ), ); } /// 构建开关项 Widget _buildSwitchItem({ required IconData icon, required String title, required String subtitle, required bool value, required ValueChanged onChanged, }) { return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), leading: Container( width: 48, height: 48, decoration: BoxDecoration( color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(8), ), child: Icon( icon, color: const Color(0xFF666666), size: 24, ), ), title: Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A), ), ), subtitle: Text( subtitle, style: const TextStyle( fontSize: 13, color: Color(0xFF999999), ), ), trailing: Switch( value: value, onChanged: onChanged, activeColor: const Color(0xFF1A1A1A), activeTrackColor: const Color(0xFF1A1A1A).withOpacity(0.3), inactiveThumbColor: Colors.white, inactiveTrackColor: const Color(0xFFE5E5E5), ), ); } } /// WebView 页面 class WebViewPage extends StatefulWidget { final String url; const WebViewPage({super.key, required this.url}); @override State createState() => _WebViewPageState(); } class _WebViewPageState extends State { 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), ), ), ], ), ); } }