From 73f3795e6580da661d2d47d0d84a8f55418c9c14 Mon Sep 17 00:00:00 2001 From: DelLevin-Home Date: Tue, 26 May 2026 05:10:21 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=82=E9=85=8D=E6=B7=B1=E8=89=B2=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 131 +-- lib/pages/app_icon_picker_page.dart | 22 +- lib/pages/book/book_detail_page.dart | 261 +++--- lib/pages/book/book_excerpt_form_page.dart | 26 +- lib/pages/book/book_excerpts_page.dart | 113 +-- lib/pages/book/book_form_page.dart | 535 +++++------ lib/pages/book/book_review_detail_page.dart | 24 +- lib/pages/book/book_review_form_page.dart | 106 +-- lib/pages/book/book_reviews_page.dart | 100 ++- lib/pages/book/book_share_page.dart | 63 +- lib/pages/book/book_tab_page.dart | 57 +- lib/pages/home_page.dart | 6 +- lib/pages/main_content_page.dart | 119 +-- .../markdown_reader/md_reader_tab_page.dart | 327 ++++--- lib/pages/markdown_reader/md_viewer_page.dart | 70 +- lib/pages/movies/douban_webview_page.dart | 189 ++-- lib/pages/movies/movie_detail_page.dart | 281 +++--- lib/pages/movies/movie_form_page.dart | 845 +++++++++--------- lib/pages/movies/movie_posters_page.dart | 305 ++++--- .../movies/movie_review_detail_page.dart | 24 +- lib/pages/movies/movie_review_form_page.dart | 107 +-- lib/pages/movies/movie_reviews_page.dart | 102 ++- lib/pages/movies/movie_share_page.dart | 63 +- lib/pages/movies/movie_tab_page.dart | 62 +- lib/pages/note/note_detail_page.dart | 98 +- lib/pages/note/note_form_page.dart | 260 +++--- lib/pages/note/note_share_page.dart | 45 +- lib/pages/note/note_tab_page.dart | 130 ++- lib/pages/profile_page.dart | 318 ++++--- lib/pages/recycle_bin_page.dart | 96 +- lib/pages/search_page.dart | 101 ++- lib/pages/statistics_page.dart | 66 +- lib/pages/stroll_page.dart | 41 +- lib/pages/sync/backup_page.dart | 402 +++++---- lib/pages/sync/cloud_sync_page.dart | 124 ++- lib/pages/sync/server_sync_page.dart | 221 +++-- lib/pages/sync/webdav_sync_page.dart | 417 +++++---- lib/pages/tag_management_page.dart | 197 ++-- lib/providers/app_provider.dart | 26 +- lib/utils/theme/app_theme.dart | 106 +++ lib/utils/usage_stats_service.dart | 4 +- lib/utils/user_prefs.dart | 11 +- lib/widgets/animated_star_rating.dart | 3 +- lib/widgets/app_refresh_indicator.dart | 5 +- lib/widgets/book_list_item.dart | 42 +- lib/widgets/book_status_bar.dart | 19 +- lib/widgets/bottom_nav_bar.dart | 84 +- lib/widgets/custom_drawer.dart | 115 ++- lib/widgets/fade_in_local_image.dart | 18 +- lib/widgets/movie_list_item.dart | 44 +- lib/widgets/movie_status_bar.dart | 19 +- lib/widgets/note_list_item.dart | 101 +-- lib/widgets/shimmer_skeleton.dart | 6 +- 53 files changed, 3846 insertions(+), 3211 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 68b993b..28b114d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,9 @@ +import 'dart:async'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; -import 'dart:async'; import 'pages/home_page.dart'; import 'utils/theme/app_theme.dart'; import 'utils/app_router.dart'; @@ -13,31 +14,17 @@ import 'providers/app_provider.dart'; import 'package:flutter/widget_previews.dart'; void main() async { - // 确保 Flutter 绑定初始化完成 WidgetsFlutterBinding.ensureInitialized(); - - // 设置系统导航栏颜色(与App主题一致) - SystemChrome.setSystemUIOverlayStyle( - const SystemUiOverlayStyle( - systemNavigationBarColor: Colors.white, - systemNavigationBarIconBrightness: Brightness.dark, - ), - ); - - // 初始化用户偏好设置 await UserPrefs.init(); final appProvider = AppProvider(); - // 先显示界面,后台加载数据 runApp(MyApp(appProvider: appProvider)); unawaited(appProvider.initDatabase().then((_) => appProvider.initMainTabIndex())); unawaited(_initAutoBackup()); unawaited(_initUsageStats()); } -/// 初始化自动备份 Future _initAutoBackup() async { try { - // 初始化本地自动备份 final isLocalAutoBackupEnabled = await AutoBackupService.instance.getEnabled(); if (isLocalAutoBackupEnabled) { await AutoBackupService.instance.start(); @@ -47,7 +34,6 @@ Future _initAutoBackup() async { } } -/// 初始化匿名用户统计 Future _initUsageStats() async { try { await UsageStatsService.instance.start(); @@ -56,51 +42,92 @@ Future _initUsageStats() async { } } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { final AppProvider appProvider; - + const MyApp({super.key, required this.appProvider}); + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State with WidgetsBindingObserver { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + widget.appProvider.loadThemeMode(); + _updateSystemUI(widget.appProvider.themeMode); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _updateSystemUI(widget.appProvider.themeMode); + } + } + + void _updateSystemUI(ThemeMode mode) { + final Brightness brightness; + switch (mode) { + case ThemeMode.light: + brightness = Brightness.light; + case ThemeMode.dark: + brightness = Brightness.dark; + case ThemeMode.system: + brightness = PlatformDispatcher.instance.platformBrightness; + } + final isDark = brightness == Brightness.dark; + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + systemNavigationBarColor: isDark ? const Color(0xFF1A1A1A) : Colors.white, + systemNavigationBarIconBrightness: isDark ? Brightness.light : Brightness.dark, + )); + } + @override Widget build(BuildContext context) { - // 获取当前选中的图标名称 final iconName = UserPrefs().appIconName; - + return MultiProvider( providers: [ - ChangeNotifierProvider.value(value: appProvider), + ChangeNotifierProvider.value(value: widget.appProvider), ], - child: MaterialApp( - title: 'MookNote', - debugShowCheckedModeBanner: false, - theme: AppTheme.lightTheme, - darkTheme: AppTheme.darkTheme, - themeMode: ThemeMode.system, - localizationsDelegates: [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [ - Locale('zh', 'CN'), - Locale('en', 'US'), - ], - home: const HomePage(), - onGenerateRoute: AppRouter.generateRoute, - builder: (context, child) { - // 尝试动态设置应用图标(Android 13+ 支持动态图标,但 Flutter 目前主要通过静态配置) - // 这里我们主要实现逻辑上的切换,实际生效通常需要重启应用或配合原生插件 - return _AppIconWrapper(iconName: iconName, child: child!); + child: Consumer( + builder: (context, provider, _) { + _updateSystemUI(provider.themeMode); + return MaterialApp( + title: 'MookNote', + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: provider.themeMode, + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('zh', 'CN'), + Locale('en', 'US'), + ], + home: const HomePage(), + onGenerateRoute: AppRouter.generateRoute, + builder: (context, child) { + return _AppIconWrapper(iconName: iconName, child: child!); + }, + ); }, ), ); } } -/// 应用图标包装器 -/// 注意:Flutter 默认不支持运行时动态更换桌面图标。 -/// 这里的实现主要是为了在应用内记录用户的选择,并为未来可能的动态图标功能做准备。 -/// 如果需要真正的动态图标,通常需要引入 flutter_app_icon_changer 等插件并配置多套图标资源。 class _AppIconWrapper extends StatefulWidget { final Widget child; final String iconName; @@ -118,11 +145,7 @@ class _AppIconWrapperState extends State<_AppIconWrapper> { _updateSystemIcon(); } - Future _updateSystemIcon() async { - // 目前 Flutter 官方不支持直接通过代码更换 Launcher Icon。 - // 这一步主要用于记录日志或在未来集成第三方库时使用。 - // print('Current selected icon: ${widget.iconName}'); - } + Future _updateSystemIcon() async {} @override Widget build(BuildContext context) { @@ -130,11 +153,9 @@ class _AppIconWrapperState extends State<_AppIconWrapper> { } } -/// 用于预览 MyApp 的 Widget -/// 添加 @Preview 注解 @Preview(name: "MookNote App Preview") Widget previewMyApp() { - final appProvider = AppProvider(); + final appProvider = AppProvider(); return MultiProvider( providers: [ @@ -142,4 +163,4 @@ Widget previewMyApp() { ], child: MyApp(appProvider: appProvider), ); -} \ No newline at end of file +} diff --git a/lib/pages/app_icon_picker_page.dart b/lib/pages/app_icon_picker_page.dart index 7983203..5d823de 100644 --- a/lib/pages/app_icon_picker_page.dart +++ b/lib/pages/app_icon_picker_page.dart @@ -15,7 +15,6 @@ class _AppIconPickerPageState extends State { final UserPrefs _userPrefs = UserPrefs(); String _currentIconName = 'app_icon'; - // 预定义的图标列表 final List> _icons = [ {'name': 'app_icon', 'label': '默认图标'}, {'name': 'app_icon2', 'label': '风格二'}, @@ -28,7 +27,6 @@ class _AppIconPickerPageState extends State { } Future _loadCurrentIcon() async { - // 先从原生层获取当前实际启用的图标(更准确) final nativeIcon = await AppIconChannel.getCurrentIcon(); setState(() { _currentIconName = nativeIcon; @@ -39,7 +37,6 @@ class _AppIconPickerPageState extends State { if (iconName == _currentIconName) return; try { - // 调用原生层切换桌面图标 final success = await AppIconChannel.switchIcon(iconName); if (success) { @@ -65,8 +62,9 @@ class _AppIconPickerPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: const Text('应用图标'), ), @@ -82,22 +80,21 @@ class _AppIconPickerPageState extends State { itemBuilder: (context, index) { final icon = _icons[index]; final isSelected = _currentIconName == icon['name']; - + return GestureDetector( onTap: () => _selectIcon(icon['name']!), child: Container( decoration: BoxDecoration( - color: isSelected ? const Color(0xFFF0F0F0) : const Color(0xFFFAFAFA), + color: isSelected ? colors.outlineVariant : colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(16), border: Border.all( - color: isSelected ? const Color(0xFF1A1A1A) : const Color(0xFFE8E8E8), + color: isSelected ? colors.primary : colors.outlineVariant, width: isSelected ? 2 : 1, ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - // 图标预览 Image.asset( 'assets/icon/${icon['name']}.png', width: 64, @@ -107,26 +104,25 @@ class _AppIconPickerPageState extends State { width: 64, height: 64, decoration: BoxDecoration( - color: const Color(0xFFEEEEEE), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(12), ), - child: const Icon(Icons.image_not_supported, color: Color(0xFF999999)), + child: Icon(Icons.image_not_supported, color: colors.onSurface.withValues(alpha: 0.4)), ); }, ), const SizedBox(height: 12), - // 标签 Text( icon['label']!, style: TextStyle( fontSize: 14, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, - color: const Color(0xFF1A1A1A), + color: colors.onSurface, ), ), if (isSelected) ...[ const SizedBox(height: 4), - const Icon(Icons.check_circle, size: 18, color: Color(0xFF1A1A1A)), + Icon(Icons.check_circle, size: 18, color: colors.primary), ], ], ), diff --git a/lib/pages/book/book_detail_page.dart b/lib/pages/book/book_detail_page.dart index 005c98c..3ef872d 100644 --- a/lib/pages/book/book_detail_page.dart +++ b/lib/pages/book/book_detail_page.dart @@ -13,9 +13,9 @@ import 'book_share_page.dart'; /// 书籍详情页 - 极简主义设计 class BookDetailPage extends StatefulWidget { final Book book; - + const BookDetailPage({super.key, required this.book}); - + @override State createState() => _BookDetailPageState(); } @@ -24,80 +24,54 @@ class _BookDetailPageState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - // 页面获得焦点时刷新数据 _refreshBookData(); } void _refreshBookData() { final provider = context.read(); - // 强制刷新当前书籍数据 provider.loadBooks(); } @override Widget build(BuildContext context) { - // 从 Provider 获取最新的 book 数据,实现动态刷新 + final colors = Theme.of(context).colorScheme; final book = context.watch().books .where((b) => b.id == widget.book.id) .firstOrNull ?? widget.book; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, body: Stack( children: [ CustomScrollView( slivers: [ - // 顶部封面区域 _buildSliverAppBar(book), - - // 内容区域 SliverToBoxAdapter( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 基本信息 _buildBasicInfo(book), - - const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)), - - // 作者信息 + Divider(height: 0.5, thickness: 0.5, color: colors.outline), _buildAuthorsSection(book), - - // 类型 if (book.genres.isNotEmpty) _buildGenresSection(book), - - // ISBN if (book.isbn != null && book.isbn!.isNotEmpty) _buildIsbnSection(book), - - // 出版社 if (book.publisher != null && book.publisher!.isNotEmpty) _buildPublisherSection(book), - - // 出版时间 if (book.publishDate != null) _buildPublishDateSection(book), - - const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)), - - // 简介 + Divider(height: 0.5, thickness: 0.5, color: colors.outline), if (book.summary != null && book.summary!.isNotEmpty) _buildSummarySection(book), - - const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)), - - // 书评和摘抄入口 + Divider(height: 0.5, thickness: 0.5, color: colors.outline), _buildExtraSections(book), - const SizedBox(height: 120), ], ), ), ], ), - - // 右下角悬浮按钮组 Positioned( right: 16, bottom: 24, @@ -107,9 +81,9 @@ class _BookDetailPageState extends State { ), ); } - - /// 构建右下角悬浮按钮组 + Widget _buildFloatingActionButtons(Book book) { + final colors = Theme.of(context).colorScheme; return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -117,13 +91,16 @@ class _BookDetailPageState extends State { icon: Icons.edit_outlined, onPressed: () => _navigateToEdit(context), tooltip: '编辑', + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, ), const SizedBox(height: 12), _buildFloatingButton( icon: Icons.delete_outline, onPressed: () => _showDeleteDialog(context), tooltip: '删除', - backgroundColor: Colors.red, + backgroundColor: colors.error, + foregroundColor: colors.onError, ), const SizedBox(height: 12), _buildFloatingButton( @@ -131,17 +108,18 @@ class _BookDetailPageState extends State { onPressed: () => _showSharePoster(book), tooltip: '分享海报', backgroundColor: const Color(0xFF4CAF50), + foregroundColor: Colors.white, ), ], ); } - /// 构建单个悬浮按钮 Widget _buildFloatingButton({ required IconData icon, required VoidCallback onPressed, required String tooltip, - Color backgroundColor = const Color(0xFF1A1A1A), + required Color backgroundColor, + required Color foregroundColor, }) { return Material( color: Colors.transparent, @@ -160,15 +138,15 @@ class _BookDetailPageState extends State { ], ), child: IconButton( - icon: Icon(icon, size: 18, color: Colors.white), + icon: Icon(icon, size: 18, color: foregroundColor), onPressed: onPressed, + padding: EdgeInsets.zero, tooltip: tooltip, ), ), ); } - /// 构建带背景的返回按钮 Widget _buildBackButton() { return Container( margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), @@ -194,21 +172,19 @@ class _BookDetailPageState extends State { ); } - /// 构建顶部 AppBar Widget _buildSliverAppBar(Book book) { + final colors = Theme.of(context).colorScheme; return SliverAppBar( expandedHeight: 320, pinned: true, - backgroundColor: const Color(0xFFF5F5F5), + backgroundColor: colors.surfaceContainerHighest, leading: _buildBackButton(), flexibleSpace: FlexibleSpaceBar( background: _buildCoverSection(book), ), - // 右上角按钮已移到右下角悬浮按钮 ); } - /// 构建封面区域 Widget _buildCoverSection(Book book) { return SizedBox.expand( child: book.coverPath != null && book.coverPath!.isNotEmpty @@ -220,79 +196,74 @@ class _BookDetailPageState extends State { : _buildCoverPlaceholder(), ); } - + Widget _buildCoverPlaceholder() { - return const Center( + final colors = Theme.of(context).colorScheme; + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.menu_book, size: 64, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), - SizedBox(height: 16), + const SizedBox(height: 16), Text( '暂无封面', style: TextStyle( fontSize: 14, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], ), ); } - - /// 构建基本信息 + Widget _buildBasicInfo(Book book) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 书名 Text( book.title, - style: const TextStyle( + style: TextStyle( fontSize: 24, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.3, ), ), - - // 别名(显示在主名称下面,用 / 分隔) if (book.alternateTitles.isNotEmpty) ...[ const SizedBox(height: 8), Text( book.alternateTitles.join(' / '), - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), height: 1.4, ), ), ], - const SizedBox(height: 16), - - // 评分和状态 Row( children: [ if (book.rating != null) ...[ - const Icon( + Icon( Icons.star, size: 20, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), const SizedBox(width: 4), Text( book.rating!.toStringAsFixed(1), - style: const TextStyle( + style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), const SizedBox(width: 16), @@ -300,15 +271,12 @@ class _BookDetailPageState extends State { _buildStatusTag(book), ], ), - const SizedBox(height: 8), - - // 时间信息 Text( '添加于 ${_formatDate(book.createdAt)}', - style: const TextStyle( + style: TextStyle( fontSize: 12, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], @@ -316,33 +284,33 @@ class _BookDetailPageState extends State { ); } - /// 构建状态标签 Widget _buildStatusTag(Book book) { + final colors = Theme.of(context).colorScheme; String label; Color bgColor; Color textColor; switch (book.status) { case 'read': label = '已读'; - bgColor = const Color(0xFF1A1A1A); - textColor = Colors.white; + bgColor = colors.primary; + textColor = colors.onPrimary; break; case 'reading': label = '在读'; - bgColor = const Color(0xFFF0F0F0); - textColor = const Color(0xFF666666); + bgColor = colors.outlineVariant; + textColor = colors.onSurface.withValues(alpha: 0.6); break; case 'want_to_read': label = '想读'; - bgColor = const Color(0xFFF5F5F5); - textColor = const Color(0xFF999999); + bgColor = colors.surfaceContainerHighest; + textColor = colors.onSurface.withValues(alpha: 0.4); break; default: label = '未知'; - bgColor = const Color(0xFFEEEEEE); - textColor = const Color(0xFFCCCCCC); + bgColor = colors.outlineVariant; + textColor = colors.onSurface.withValues(alpha: 0.25); } - + return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( @@ -359,30 +327,30 @@ class _BookDetailPageState extends State { ), ); } - - /// 构建作者区域 + Widget _buildAuthorsSection(Book book) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 64, child: Text( '作者', style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), Expanded( child: Text( book.authors.join(','), - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.5, ), ), @@ -392,29 +360,29 @@ class _BookDetailPageState extends State { ); } - /// 构建ISBN区域 Widget _buildIsbnSection(Book book) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 64, child: Text( 'ISBN', style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), Expanded( child: Text( book.isbn!, - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.5, ), ), @@ -424,29 +392,29 @@ class _BookDetailPageState extends State { ); } - /// 构建出版社区域 Widget _buildPublisherSection(Book book) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 64, child: Text( '出版社', style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), Expanded( child: Text( book.publisher!, - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.5, ), ), @@ -456,29 +424,29 @@ class _BookDetailPageState extends State { ); } - /// 构建出版时间区域 Widget _buildPublishDateSection(Book book) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 64, child: Text( '出版时间', style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), Expanded( child: Text( '${book.publishDate!.year}年${book.publishDate!.month.toString().padLeft(2, '0')}月', - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.5, ), ), @@ -488,20 +456,20 @@ class _BookDetailPageState extends State { ); } - /// 构建类型区域 Widget _buildGenresSection(Book book) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 64, child: Text( '类型', style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), @@ -513,14 +481,14 @@ class _BookDetailPageState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(4), ), child: Text( genre, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), ); @@ -531,9 +499,9 @@ class _BookDetailPageState extends State { ), ); } - - /// 构建简介区域 + Widget _buildSummarySection(Book book) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.all(24), child: Column( @@ -545,17 +513,17 @@ class _BookDetailPageState extends State { width: 4, height: 16, decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.onSurface, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), - const Text( + Text( '简介', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ], @@ -564,14 +532,14 @@ class _BookDetailPageState extends State { Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), ), child: Text( book.summary!, - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.8, ), ), @@ -581,8 +549,8 @@ class _BookDetailPageState extends State { ); } - /// 构建额外功能区域(书评、摘抄) Widget _buildExtraSections(Book book) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.all(24), child: Column( @@ -594,23 +562,22 @@ class _BookDetailPageState extends State { width: 4, height: 16, decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.onSurface, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), - const Text( + Text( '更多', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ], ), const SizedBox(height: 16), - // 书评入口 _buildExtraSectionItem( icon: Icons.rate_review_outlined, title: '书评', @@ -620,7 +587,6 @@ class _BookDetailPageState extends State { onTap: () => _navigateToReviews(book), ), const SizedBox(height: 12), - // 摘抄入口 _buildExtraSectionItem( icon: Icons.format_quote_outlined, title: '摘抄', @@ -634,7 +600,6 @@ class _BookDetailPageState extends State { ); } - /// 构建更多区域项 Widget _buildExtraSectionItem({ required IconData icon, required String title, @@ -643,14 +608,15 @@ class _BookDetailPageState extends State { required String unit, required VoidCallback onTap, }) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), child: Row( children: [ @@ -658,14 +624,14 @@ class _BookDetailPageState extends State { width: 40, height: 40, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), child: Icon( icon, size: 20, - color: const Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 12), @@ -675,10 +641,10 @@ class _BookDetailPageState extends State { children: [ Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), const SizedBox(height: 4), @@ -688,9 +654,9 @@ class _BookDetailPageState extends State { final count = snapshot.data ?? 0; return Text( count > 0 ? '$count $unit' : emptyText, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ); }, @@ -698,9 +664,9 @@ class _BookDetailPageState extends State { ], ), ), - const Icon( + Icon( Icons.chevron_right, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), ], ), @@ -726,39 +692,37 @@ class _BookDetailPageState extends State { ); } - - /// 格式化日期 String _formatDate(DateTime date) { return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; } - - /// 跳转到编辑页面 + void _navigateToEdit(BuildContext context) { Navigator.pushNamed(context, '/book-form', arguments: widget.book).then((_) { context.read().loadBooks(); }); } - - /// 显示删除对话框 + void _showDeleteDialog(BuildContext context) { + final colors = Theme.of(context).colorScheme; showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text( + title: Text( '确认删除', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, + color: colors.onSurface, ), ), content: Text( '确定要删除"${widget.book.title}"吗?删除后可在回收站恢复。', - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.5, ), ), @@ -766,7 +730,7 @@ class _BookDetailPageState extends State { TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( - foregroundColor: const Color(0xFF666666), + foregroundColor: colors.onSurface.withValues(alpha: 0.6), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), child: const Text('取消'), @@ -780,8 +744,8 @@ class _BookDetailPageState extends State { ToastUtil.show(context, '已删除'); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + backgroundColor: colors.error, + foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -796,7 +760,6 @@ class _BookDetailPageState extends State { ); } - /// 下载封面到本地 Future _downloadCover(Book book) async { if (book.coverPath == null || book.coverPath!.isEmpty) { ToastUtil.show(context, '没有可下载的封面'); @@ -810,16 +773,13 @@ class _BookDetailPageState extends State { return; } - // 生成文件名:书籍名称_时间戳_封面.jpg final timestamp = DateTime.now().millisecondsSinceEpoch; final fileName = '${book.title}_${timestamp}_封面.jpg'; - // 获取临时目录路径 final tempDir = await Directory.systemTemp.createTemp(); final tempFile = File('${tempDir.path}/$fileName'); await sourceFile.copy(tempFile.path); - // 使用分享功能让用户选择保存位置 await Share.shareXFiles( [XFile(tempFile.path)], subject: '${book.title} 封面', @@ -830,7 +790,6 @@ class _BookDetailPageState extends State { } } - /// 显示分享海报页面 void _showSharePoster(Book book) { Navigator.push( context, diff --git a/lib/pages/book/book_excerpt_form_page.dart b/lib/pages/book/book_excerpt_form_page.dart index a568fc0..b658fae 100644 --- a/lib/pages/book/book_excerpt_form_page.dart +++ b/lib/pages/book/book_excerpt_form_page.dart @@ -50,8 +50,9 @@ class _BookExcerptFormPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: Text(_isEditing ? '编辑摘抄' : '添加摘抄'), actions: [ @@ -63,10 +64,10 @@ class _BookExcerptFormPageState extends State { height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) - : const Text( + : Text( '保存', style: TextStyle( - color: Color(0xFF1A1A1A), + color: colors.onSurface, fontWeight: FontWeight.w600, ), ), @@ -82,15 +83,15 @@ class _BookExcerptFormPageState extends State { // 章节 TextFormField( controller: _chapterController, - decoration: const InputDecoration( + decoration: InputDecoration( labelText: '章节(可选)', hintText: '例如:第一章、第3节等', - border: OutlineInputBorder( + border: const OutlineInputBorder( borderRadius: BorderRadius.zero, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.zero, - borderSide: BorderSide(color: Color(0xFF1A1A1A)), + borderSide: BorderSide(color: colors.primary), ), ), ), @@ -101,16 +102,16 @@ class _BookExcerptFormPageState extends State { TextFormField( controller: _contentController, maxLines: 8, - decoration: const InputDecoration( + decoration: InputDecoration( labelText: '摘抄内容', hintText: '输入你想要摘抄的内容...', alignLabelWithHint: true, - border: OutlineInputBorder( + border: const OutlineInputBorder( borderRadius: BorderRadius.zero, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.zero, - borderSide: BorderSide(color: Color(0xFF1A1A1A)), + borderSide: BorderSide(color: colors.primary), ), ), validator: (value) { @@ -127,16 +128,16 @@ class _BookExcerptFormPageState extends State { TextFormField( controller: _commentController, maxLines: 5, - decoration: const InputDecoration( + decoration: InputDecoration( labelText: '我的感悟(可选)', hintText: '记录你对这段内容的思考和感悟...', alignLabelWithHint: true, - border: OutlineInputBorder( + border: const OutlineInputBorder( borderRadius: BorderRadius.zero, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.zero, - borderSide: BorderSide(color: Color(0xFF1A1A1A)), + borderSide: BorderSide(color: colors.primary), ), ), ), @@ -185,4 +186,3 @@ class _BookExcerptFormPageState extends State { } } } - diff --git a/lib/pages/book/book_excerpts_page.dart b/lib/pages/book/book_excerpts_page.dart index 758ea2e..3fb4d42 100644 --- a/lib/pages/book/book_excerpts_page.dart +++ b/lib/pages/book/book_excerpts_page.dart @@ -41,8 +41,9 @@ class _BookExcerptsPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: const Text('摘抄'), actions: [ @@ -62,6 +63,7 @@ class _BookExcerptsPageState extends State { } Widget _buildEmptyState() { + final colors = Theme.of(context).colorScheme; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -70,21 +72,21 @@ class _BookExcerptsPageState extends State { width: 80, height: 80, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), ), - child: const Icon( + child: Icon( Icons.format_quote_outlined, size: 40, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), ), const SizedBox(height: 20), - const Text( + Text( '暂无摘抄', style: TextStyle( fontSize: 16, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), const SizedBox(height: 24), @@ -93,15 +95,15 @@ class _BookExcerptsPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(8), ), - child: const Text( + child: Text( '添加记录', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: Colors.white, + color: colors.onPrimary, ), ), ), @@ -114,7 +116,7 @@ class _BookExcerptsPageState extends State { /// 按章节分组摘抄数据 Map> _groupExcerptsByChapter() { final Map> groups = {}; - + for (final excerpt in _excerpts) { final chapter = excerpt.chapter.isEmpty ? '未分类' : excerpt.chapter; if (!groups.containsKey(chapter)) { @@ -122,19 +124,19 @@ class _BookExcerptsPageState extends State { } groups[chapter]!.add(excerpt); } - + // 每个章节内的摘抄按时间排序(新的在前) for (final chapter in groups.keys) { groups[chapter]!.sort((a, b) => b.createdAt.compareTo(a.createdAt)); } - + return groups; } Widget _buildExcerptList() { final groups = _groupExcerptsByChapter(); final chapters = groups.keys.toList(); - + return ListView.builder( padding: const EdgeInsets.all(16), itemCount: chapters.length, @@ -147,6 +149,7 @@ class _BookExcerptsPageState extends State { } Widget _buildChapterSection(String chapter, List excerpts) { + final colors = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -154,18 +157,18 @@ class _BookExcerptsPageState extends State { Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - decoration: const BoxDecoration( - color: Color(0xFFF5F5F5), + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, border: Border( - left: BorderSide(color: Color(0xFF1A1A1A), width: 4), + left: BorderSide(color: colors.primary, width: 4), ), ), child: Text( chapter, - style: const TextStyle( + style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ), @@ -178,11 +181,12 @@ class _BookExcerptsPageState extends State { } Widget _buildExcerptItem(BookExcerpt excerpt) { + final colors = Theme.of(context).colorScheme; return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - border: Border.all(color: const Color(0xFFE5E5E5)), + border: Border.all(color: colors.outline), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -192,9 +196,9 @@ class _BookExcerptsPageState extends State { excerpt.content, maxLines: 3, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.5, ), ), @@ -205,16 +209,16 @@ class _BookExcerptsPageState extends State { Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - decoration: const BoxDecoration( - color: Color(0xFFF5F5F5), + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, ), child: Text( excerpt.comment, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontSize: 12, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), ), @@ -227,29 +231,29 @@ class _BookExcerptsPageState extends State { children: [ Text( _formatDate(excerpt.createdAt), - style: const TextStyle( + style: TextStyle( fontSize: 11, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), const Spacer(), // 编辑按钮 GestureDetector( onTap: () => _navigateToEditExcerpt(excerpt), - child: const Icon( + child: Icon( Icons.edit_outlined, size: 16, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), const SizedBox(width: 12), // 删除按钮 GestureDetector( onTap: () => _showDeleteDialog(excerpt), - child: const Icon( + child: Icon( Icons.delete_outline, size: 16, - color: Colors.red, + color: colors.error, ), ), ], @@ -287,28 +291,31 @@ class _BookExcerptsPageState extends State { void _showDeleteDialog(BookExcerpt excerpt) { 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 { - await context.read().removeBookExcerpt(excerpt.id); - Navigator.pop(context); - _loadExcerpts(); - ToastUtil.show(context, '已删除'); - }, - child: const Text('删除', style: TextStyle(color: Colors.red)), - ), - ], - ), + builder: (context) { + final colors = Theme.of(context).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: const Text('确认删除'), + content: const Text('确定要删除这条摘抄吗?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))), + ), + TextButton( + onPressed: () async { + await context.read().removeBookExcerpt(excerpt.id); + Navigator.pop(context); + _loadExcerpts(); + ToastUtil.show(context, '已删除'); + }, + child: Text('删除', style: TextStyle(color: colors.error)), + ), + ], + ); + }, ); } } diff --git a/lib/pages/book/book_form_page.dart b/lib/pages/book/book_form_page.dart index cc72e86..e65a5bc 100644 --- a/lib/pages/book/book_form_page.dart +++ b/lib/pages/book/book_form_page.dart @@ -16,7 +16,7 @@ class BookFormPage extends StatefulWidget { final String? initialStatus; // 添加时的默认状态 const BookFormPage({super.key, this.book, this.initialStatus}); - + @override State createState() => _BookFormPageState(); } @@ -24,17 +24,17 @@ class BookFormPage extends StatefulWidget { class _BookFormPageState extends State { final _formKey = GlobalKey(); final ImagePicker _picker = ImagePicker(); - + // 输入框控制器 late TextEditingController _titleController; late TextEditingController _publisherController; late TextEditingController _summaryController; late TextEditingController _ratingController; late TextEditingController _isbnController; - + // 多值字段的临时输入控制器 final Map _tagControllers = {}; - + // 数据 List _authors = []; List _alternateTitles = []; @@ -42,7 +42,7 @@ class _BookFormPageState extends State { String? _coverPath; String _status = 'want_to_read'; DateTime? _publishDate; - + @override void initState() { super.initState(); @@ -80,7 +80,7 @@ class _BookFormPageState extends State { _status = widget.initialStatus!; } } - + @override void dispose() { _titleController.dispose(); @@ -91,22 +91,24 @@ class _BookFormPageState extends State { _tagControllers.values.forEach((c) => c.dispose()); super.dispose(); } - + TextEditingController _getTagController(String key) { return _tagControllers.putIfAbsent(key, () => TextEditingController()); } - + /// 构建右上角操作按钮 Widget _buildActionButton({ required IconData icon, required VoidCallback onPressed, required String tooltip, - Color color = const Color(0xFF1A1A1A), + Color? color, }) { + final colors = Theme.of(context).colorScheme; + final iconColor = color ?? colors.onSurface; return Container( margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.9), + color: colors.surface.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(8), ), child: Material( @@ -118,7 +120,7 @@ class _BookFormPageState extends State { padding: const EdgeInsets.all(8), child: Icon( icon, - color: color, + color: iconColor, size: 22, ), ), @@ -129,10 +131,11 @@ class _BookFormPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; final isEdit = widget.book != null; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: Text(isEdit ? '编辑书籍' : '添加书籍'), actions: [ @@ -205,15 +208,15 @@ class _BookFormPageState extends State { ), ), ), - + // 第二行:作者 + 出版社 SizedBox( width: (MediaQuery.of(context).size.width - 52) / 2, height: 90, child: _buildInfoCard( label: '作者', - value: _authors.isEmpty - ? '' + value: _authors.isEmpty + ? '' : '${_authors.length}人:${_authors.join('、')}', icon: Icons.person_outline, onTap: () => _showMultiValueDialog( @@ -239,7 +242,7 @@ class _BookFormPageState extends State { ), ), ), - + // 第三行:类型 + ISBN SizedBox( width: (MediaQuery.of(context).size.width - 52) / 2, @@ -281,21 +284,21 @@ class _BookFormPageState extends State { ), ), ), - + // 第四行:出版时间(独占一行) SizedBox( width: double.infinity, height: 90, child: _buildInfoCard( label: '出版时间', - value: _publishDate != null - ? '${_publishDate!.year}.${_publishDate!.month.toString().padLeft(2, '0')}.${_publishDate!.day.toString().padLeft(2, '0')}' + value: _publishDate != null + ? '${_publishDate!.year}.${_publishDate!.month.toString().padLeft(2, '0')}.${_publishDate!.day.toString().padLeft(2, '0')}' : '', icon: Icons.date_range_outlined, onTap: () => _selectPublishDate(), ), ), - + // 第五行:书籍简介(独占一行) SizedBox( width: double.infinity, @@ -316,22 +319,23 @@ class _BookFormPageState extends State { ), ], ), - + const SizedBox(height: 48), ], ), ), ); } - + /// 构建表单条目(标签 + 内容)- 书籍简介使用 Widget _buildFormItem({required String label, required Widget child}) { + final colors = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, - style: const TextStyle(fontSize: 13, color: Color(0xFF666666)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.6)), ), const SizedBox(height: 8), child, @@ -345,6 +349,7 @@ class _BookFormPageState extends State { required Widget child, bool required = false, }) { + final colors = Theme.of(context).colorScheme; return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -354,7 +359,7 @@ class _BookFormPageState extends State { required ? '$label *' : label, style: TextStyle( fontSize: 13, - color: required ? const Color(0xFF1A1A1A) : const Color(0xFF999999), + color: required ? colors.onSurface : colors.onSurface.withValues(alpha: 0.4), fontWeight: required ? FontWeight.w500 : FontWeight.normal, ), ), @@ -375,6 +380,7 @@ class _BookFormPageState extends State { required Function(int) onRemove, }) { final controller = _getTagController(controllerKey); + final colors = Theme.of(context).colorScheme; return Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -385,7 +391,7 @@ class _BookFormPageState extends State { padding: const EdgeInsets.only(top: 8), child: Text( label, - style: const TextStyle(fontSize: 13, color: Color(0xFF999999)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)), ), ), ), @@ -400,7 +406,7 @@ class _BookFormPageState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(4), ), child: Row( @@ -408,12 +414,12 @@ class _BookFormPageState extends State { children: [ Text( entry.value, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 14, color: colors.onSurface), ), const SizedBox(width: 4), GestureDetector( onTap: () => onRemove(entry.key), - child: const Icon(Icons.close, size: 14, color: Color(0xFF999999)), + child: Icon(Icons.close, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), ), ], ), @@ -424,10 +430,10 @@ class _BookFormPageState extends State { constraints: const BoxConstraints(minWidth: 80, maxWidth: 120), child: TextField( controller: controller, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 14, color: colors.onSurface), decoration: InputDecoration( hintText: values.isEmpty ? hint : '', - hintStyle: const TextStyle(fontSize: 13, color: Color(0xFFCCCCCC)), + hintStyle: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.25)), border: InputBorder.none, isDense: true, contentPadding: const EdgeInsets.symmetric(vertical: 6), @@ -447,7 +453,7 @@ class _BookFormPageState extends State { ], ); } - + /// 构建多值条目 Widget _buildMultiValueItem({ required String label, @@ -458,7 +464,8 @@ class _BookFormPageState extends State { required Function(int) onRemove, }) { final controller = _getTagController(controllerKey); - + final colors = Theme.of(context).colorScheme; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -467,7 +474,7 @@ class _BookFormPageState extends State { children: [ Text( label, - style: const TextStyle(fontSize: 13, color: Color(0xFF666666)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.6)), ), const Spacer(), // 添加按钮(当输入框有内容时显示) @@ -489,7 +496,7 @@ class _BookFormPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( border: Border.all( - color: hasText ? const Color(0xFF1A1A1A) : const Color(0xFFE5E5E5), + color: hasText ? colors.primary : colors.outline, ), ), child: Row( @@ -498,14 +505,14 @@ class _BookFormPageState extends State { Icon( Icons.add, size: 14, - color: hasText ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), + color: hasText ? colors.primary : colors.onSurface.withValues(alpha: 0.25), ), const SizedBox(width: 2), Text( '添加', style: TextStyle( fontSize: 12, - color: hasText ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), + color: hasText ? colors.primary : colors.onSurface.withValues(alpha: 0.25), ), ), ], @@ -527,20 +534,20 @@ class _BookFormPageState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), - border: Border.all(color: const Color(0xFFE5E5E5)), + color: colors.surfaceContainerHighest, + border: Border.all(color: colors.outline), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( entry.value, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 14, color: colors.onSurface), ), const SizedBox(width: 4), GestureDetector( onTap: () => onRemove(entry.key), - child: const Icon(Icons.close, size: 14, color: Color(0xFF999999)), + child: Icon(Icons.close, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), ), ], ), @@ -551,10 +558,10 @@ class _BookFormPageState extends State { constraints: const BoxConstraints(minWidth: 100, maxWidth: 150), child: TextField( controller: controller, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 14, color: colors.onSurface), decoration: InputDecoration( hintText: values.isEmpty ? hint : '', - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.25)), border: InputBorder.none, isDense: true, contentPadding: const EdgeInsets.symmetric(vertical: 5), @@ -573,7 +580,7 @@ class _BookFormPageState extends State { ], ); } - + /// 构建信息卡片(用于网格布局) Widget _buildInfoCard({ @@ -586,7 +593,8 @@ class _BookFormPageState extends State { bool scrollable = false, }) { final hasValue = value.isNotEmpty; - + final colors = Theme.of(context).colorScheme; + // 构建值显示部分 Widget buildContent() { if (scrollable && height != null) { @@ -598,7 +606,7 @@ class _BookFormPageState extends State { hasValue ? value : '点击填写', style: TextStyle( fontSize: 15, - color: hasValue ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), + color: hasValue ? colors.onSurface : colors.onSurface.withValues(alpha: 0.25), fontWeight: hasValue ? FontWeight.w500 : FontWeight.normal, ), ), @@ -610,7 +618,7 @@ class _BookFormPageState extends State { hasValue ? value : '点击填写', style: TextStyle( fontSize: 15, - color: hasValue ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), + color: hasValue ? colors.onSurface : colors.onSurface.withValues(alpha: 0.25), fontWeight: hasValue ? FontWeight.w500 : FontWeight.normal, ), maxLines: 1, @@ -618,19 +626,19 @@ class _BookFormPageState extends State { ); } } - + return GestureDetector( onTap: onTap, child: Container( height: height, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFE8E8E8)), + border: Border.all(color: colors.outline), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.03), + color: colors.onSurface.withValues(alpha: 0.018), blurRadius: 8, offset: const Offset(0, 2), ), @@ -644,14 +652,14 @@ class _BookFormPageState extends State { Row( children: [ if (icon != null) ...[ - Icon(icon, size: 14, color: const Color(0xFF999999)), + Icon(icon, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), const SizedBox(width: 6), ], Text( required ? '$label *' : label, style: TextStyle( fontSize: 12, - color: required ? const Color(0xFF1A1A1A) : const Color(0xFF999999), + color: required ? colors.onSurface : colors.onSurface.withValues(alpha: 0.4), fontWeight: required ? FontWeight.w500 : FontWeight.normal, ), ), @@ -665,20 +673,21 @@ class _BookFormPageState extends State { ), ); } - + /// 构建状态选择器(简洁风格) Widget _buildStatusSelector() { + final colors = Theme.of(context).colorScheme; return Row( children: [ - const Text( + Text( '状态', - style: TextStyle(fontSize: 13, color: Color(0xFF999999)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)), ), const SizedBox(width: 16), Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - color: const Color(0xFFF0F0F0), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(6), ), child: Row( @@ -696,11 +705,12 @@ class _BookFormPageState extends State { /// 构建星星评分(支持手动输入) Widget _buildStarRating() { + final colors = Theme.of(context).colorScheme; return Row( children: [ - const Text( + Text( '评分', - style: TextStyle(fontSize: 13, color: Color(0xFF999999)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)), ), const SizedBox(width: 16), // 星星选择 @@ -716,6 +726,7 @@ class _BookFormPageState extends State { Widget _buildStarSelector() { final currentRating = double.tryParse(_ratingController.text) ?? 0; final starRating = currentRating / 2; + final colors = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.symmetric(vertical: 8), @@ -745,7 +756,7 @@ class _BookFormPageState extends State { size: 24, color: isFilled || isHalf ? const Color(0xFFFFB800) - : const Color(0xFFE5E5E5), + : colors.outline, ), ), ); @@ -756,27 +767,28 @@ class _BookFormPageState extends State { /// 构建评分输入框(支持0-10,保留1位小数) Widget _buildRatingInputField() { + final colors = Theme.of(context).colorScheme; return Container( width: 56, height: 32, decoration: BoxDecoration( - color: const Color(0xFFF0F0F0), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(6), ), child: TextFormField( controller: _ratingController, keyboardType: const TextInputType.numberWithOptions(decimal: true), textAlign: TextAlign.center, - style: const TextStyle( + style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), - decoration: const InputDecoration( + decoration: InputDecoration( hintText: '-', - hintStyle: TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.25)), border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 4, vertical: 6), + contentPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), ), onChanged: (value) { if (value.isNotEmpty) { @@ -802,12 +814,13 @@ class _BookFormPageState extends State { required VoidCallback onTap, required VoidCallback onClear, }) { + final colors = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, - style: const TextStyle(fontSize: 13, color: Color(0xFF999999)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)), ), const SizedBox(height: 8), GestureDetector( @@ -822,15 +835,15 @@ class _BookFormPageState extends State { style: TextStyle( fontSize: 15, color: date != null - ? const Color(0xFF1A1A1A) - : const Color(0xFFCCCCCC), + ? colors.onSurface + : colors.onSurface.withValues(alpha: 0.25), ), ), ), if (date != null) GestureDetector( onTap: onClear, - child: const Icon(Icons.close, size: 18, color: Color(0xFF999999)), + child: Icon(Icons.close, size: 18, color: colors.onSurface.withValues(alpha: 0.4)), ), ], ), @@ -846,16 +859,7 @@ class _BookFormPageState extends State { initialDate: _publishDate ?? DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime.now().add(const Duration(days: 365 * 5)), - builder: (context, child) { - return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light( - primary: Color(0xFF1A1A1A), - ), - ), - child: child!, - ); - }, + builder: (context, child) => child!, ); if (picked != null) { @@ -866,6 +870,7 @@ class _BookFormPageState extends State { /// 构建状态选项 Widget _buildStatusOption(String label, String value) { final isSelected = _status == value; + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: () => setState(() => _status = value), @@ -873,12 +878,12 @@ class _BookFormPageState extends State { duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), decoration: BoxDecoration( - color: isSelected ? Colors.white : Colors.transparent, + color: isSelected ? colors.surface : Colors.transparent, borderRadius: BorderRadius.circular(6), boxShadow: isSelected ? [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: colors.onSurface.withValues(alpha: 0.03), blurRadius: 4, offset: const Offset(0, 2), ), @@ -890,7 +895,7 @@ class _BookFormPageState extends State { style: TextStyle( fontSize: 14, fontWeight: isSelected ? FontWeight.w500 : FontWeight.normal, - color: isSelected ? const Color(0xFF1A1A1A) : const Color(0xFF999999), + color: isSelected ? colors.onSurface : colors.onSurface.withValues(alpha: 0.4), ), ), ), @@ -900,6 +905,7 @@ class _BookFormPageState extends State { /// 构建封面选择器 Widget _buildCoverPicker() { final hasCover = _coverPath != null && _coverPath!.isNotEmpty; + final colors = Theme.of(context).colorScheme; return Column( mainAxisSize: MainAxisSize.min, @@ -910,7 +916,7 @@ class _BookFormPageState extends State { width: 120, height: 170, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), clipBehavior: Clip.antiAlias, @@ -932,7 +938,7 @@ class _BookFormPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), ), child: Row( @@ -941,14 +947,14 @@ class _BookFormPageState extends State { Icon( Icons.delete_outline, size: 14, - color: Colors.grey[600], + color: colors.onSurface.withValues(alpha: 0.6), ), const SizedBox(width: 4), Text( '移除封面', style: TextStyle( fontSize: 12, - color: Colors.grey[600], + color: colors.onSurface.withValues(alpha: 0.6), ), ), ], @@ -959,89 +965,94 @@ class _BookFormPageState extends State { ], ); } - + Widget _buildCoverPlaceholder() { - return const Column( + final colors = Theme.of(context).colorScheme; + return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.image_outlined, size: 32, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), - SizedBox(height: 8), + const SizedBox(height: 8), Text( '封面', style: TextStyle( fontSize: 12, - color: Color(0xFFAAAAAA), + color: colors.onSurface.withValues(alpha: 0.35), ), ), ], ); } - + /// 选择封面 Future _pickCover() async { + final colors = Theme.of(context).colorScheme; // 显示选择对话框 final result = await showModalBottomSheet( context: context, - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - builder: (context) => SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 顶部指示条 - Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: const Color(0xFFE0E0E0), - borderRadius: BorderRadius.circular(2), + builder: (context) { + final colors = Theme.of(context).colorScheme; + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 顶部指示条 + Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: colors.outline, + borderRadius: BorderRadius.circular(2), + ), ), - ), - const SizedBox(height: 20), - // 标题 - const Padding( - padding: EdgeInsets.symmetric(horizontal: 24), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - '添加封面', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + const SizedBox(height: 20), + // 标题 + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + '添加封面', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: colors.onSurface, + ), ), ), ), - ), - const SizedBox(height: 16), - // 本地图片选项 - _buildCoverOption( - icon: Icons.photo_library_outlined, - title: '从相册选择', - onTap: () { - Navigator.pop(context, 0); - }, - ), - // 网络链接选项 - _buildCoverOption( - icon: Icons.link_outlined, - title: '网络链接', - onTap: () { - Navigator.pop(context, 1); - }, - ), - ], + const SizedBox(height: 16), + // 本地图片选项 + _buildCoverOption( + icon: Icons.photo_library_outlined, + title: '从相册选择', + onTap: () { + Navigator.pop(context, 0); + }, + ), + // 网络链接选项 + _buildCoverOption( + icon: Icons.link_outlined, + title: '网络链接', + onTap: () { + Navigator.pop(context, 1); + }, + ), + ], + ), ), - ), - ), + ); + }, ); if (result == null) return; @@ -1062,23 +1073,23 @@ class _BookFormPageState extends State { maxHeight: 1200, imageQuality: 85, ); - + if (pickedFile != null) { // 生成文件名 final fileName = 'cover_${DateTime.now().millisecondsSinceEpoch}.jpg'; - + // 如果是编辑模式,使用现有书籍ID;如果是新建模式,使用临时ID(保存时会替换) final bookId = widget.book?.id ?? DateTime.now().millisecondsSinceEpoch.toString(); - + // 保存到新的路径结构: images/books/{bookId}/{fileName} final targetPath = await ImagePathHelper.instance.getBookCoverPath( - bookId, + bookId, fileName ); await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath)); - + await File(pickedFile.path).copy(targetPath); - + setState(() => _coverPath = targetPath); } } catch (e) { @@ -1091,55 +1102,58 @@ class _BookFormPageState extends State { /// 从网络链接选择封面 Future _pickCoverFromUrl() async { final urlController = TextEditingController(); - + final confirmed = await showDialog( context: context, - builder: (context) => AlertDialog( - backgroundColor: Colors.white, - elevation: 0, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - title: const Text('添加网络图片'), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - '请输入图片链接地址', - style: TextStyle( - fontSize: 14, - color: Color(0xFF666666), + builder: (context) { + final colors = Theme.of(context).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: const Text('添加网络图片'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '请输入图片链接地址', + style: TextStyle( + fontSize: 14, + color: colors.onSurface.withValues(alpha: 0.6), + ), ), + const SizedBox(height: 12), + TextField( + controller: urlController, + decoration: InputDecoration( + hintText: 'https://book.doban.com/image.jpg', + hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.25)), + border: const UnderlineInputBorder(), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: colors.outline), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: colors.primary, width: 1.5), + ), + ), + style: const TextStyle(fontSize: 14), + keyboardType: TextInputType.url, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))), ), - const SizedBox(height: 12), - TextField( - controller: urlController, - decoration: const InputDecoration( - hintText: 'https://book.doban.com/image.jpg', - hintStyle: TextStyle(color: Color(0xFFCCCCCC)), - border: UnderlineInputBorder(), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color.fromARGB(255, 58, 49, 49)), - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFF1A1A1A)), - ), - ), - style: const TextStyle(fontSize: 14), - keyboardType: TextInputType.url, + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text('确定', style: TextStyle(color: colors.onSurface)), ), ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('取消', style: TextStyle(color: Color(0xFF666666))), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: const Text('确定', style: TextStyle(color: Color(0xFF1A1A1A))), - ), - ], - ), + ); + }, ); if (confirmed != true) return; @@ -1160,7 +1174,7 @@ class _BookFormPageState extends State { 'Referer': 'https://book.douban.com/', }, ); - + if (response.statusCode != 200) { throw Exception('下载失败: HTTP ${response.statusCode}'); } @@ -1178,13 +1192,13 @@ class _BookFormPageState extends State { // 生成文件名 final fileName = 'cover_${DateTime.now().millisecondsSinceEpoch}.jpg'; - + // 如果是编辑模式,使用现有书籍ID;如果是新建模式,使用临时ID final bookId = widget.book?.id ?? DateTime.now().millisecondsSinceEpoch.toString(); - + // 保存到新的路径结构 final targetPath = await ImagePathHelper.instance.getBookCoverPath( - bookId, + bookId, fileName ); await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath)); @@ -1193,7 +1207,7 @@ class _BookFormPageState extends State { await File(targetPath).writeAsBytes(response.bodyBytes); setState(() => _coverPath = targetPath); - + if (mounted) { ToastUtil.show(context, '添加成功'); } @@ -1210,6 +1224,7 @@ class _BookFormPageState extends State { required String title, required VoidCallback onTap, }) { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: onTap, child: Container( @@ -1220,27 +1235,27 @@ class _BookFormPageState extends State { width: 44, height: 44, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10), ), child: Icon( icon, size: 22, - color: const Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 16), Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 16, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), const Spacer(), - const Icon( + Icon( Icons.chevron_right, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), size: 20, ), ], @@ -1248,29 +1263,29 @@ class _BookFormPageState extends State { ), ); } - + /// 保存书籍 Future _saveBook() async { if (!_formKey.currentState!.validate()) { return; } - - final rating = _ratingController.text.isNotEmpty - ? double.tryParse(_ratingController.text) + + final rating = _ratingController.text.isNotEmpty + ? double.tryParse(_ratingController.text) : null; - + final now = DateTime.now(); - + if (widget.book == null) { // 生成新的书籍ID final newBookId = now.millisecondsSinceEpoch.toString(); - + // 如果有封面,需要移动到正确的ID目录 String? finalCoverPath; if (_coverPath != null && _coverPath!.isNotEmpty) { finalCoverPath = await _moveCoverToNewId(_coverPath!, newBookId); } - + final newBook = Book( id: newBookId, title: _titleController.text.trim(), @@ -1287,7 +1302,7 @@ class _BookFormPageState extends State { createdAt: now, updatedAt: now, ); - + await context.read().addBook(newBook); } else { final updatedBook = widget.book!.copyWith( @@ -1304,17 +1319,17 @@ class _BookFormPageState extends State { publishDate: _publishDate, updatedAt: now, ); - + await context.read().updateBook(updatedBook); } - + if (!mounted) return; - + ToastUtil.show(context, widget.book == null ? '添加成功' : '更新成功'); - + Navigator.pop(context); } - + /// 将封面从临时ID目录移动到新的书籍ID目录 Future _moveCoverToNewId(String currentPath, String newBookId) async { // 检查是否已经在正确的目录中(兼容 Windows 路径分隔符) @@ -1322,24 +1337,24 @@ class _BookFormPageState extends State { if (normalizedPath.contains('/books/$newBookId/')) { return currentPath; } - + // 提取文件名 final fileName = p.basename(currentPath); - + // 获取新路径 final newPath = await ImagePathHelper.instance.getBookCoverPath( - newBookId, + newBookId, fileName ); - + // 确保目标目录存在 await ImagePathHelper.instance.ensureDirExists(p.dirname(newPath)); - + // 移动文件 final currentFile = File(currentPath); if (await currentFile.exists()) { await currentFile.rename(newPath); - + // 删除空的临时目录 final tempDir = Directory(p.dirname(currentPath)); if (await tempDir.exists()) { @@ -1349,10 +1364,10 @@ class _BookFormPageState extends State { // 忽略删除目录失败的情况 } } - + return newPath; } - + return null; } @@ -1373,7 +1388,7 @@ class _BookFormPageState extends State { maxLines: maxLines, ), ); - + if (result != null) { onConfirm(result); } @@ -1438,8 +1453,9 @@ class _TextInputDialogState extends State<_TextInputDialog> { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Text( widget.title, @@ -1449,21 +1465,21 @@ class _TextInputDialogState extends State<_TextInputDialog> { controller: controller, maxLines: widget.maxLines, autofocus: true, - style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 15, color: colors.onSurface), decoration: InputDecoration( hintText: widget.hint, - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFAAAAAA)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.35)), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE0E0E0)), + borderSide: BorderSide(color: colors.outline), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE0E0E0)), + borderSide: BorderSide(color: colors.outline), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFF1A1A1A)), + borderSide: BorderSide(color: colors.primary, width: 1.5), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), @@ -1471,13 +1487,13 @@ class _TextInputDialogState extends State<_TextInputDialog> { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('取消', style: TextStyle(color: Color(0xFF999999))), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4))), ), ElevatedButton( onPressed: () => Navigator.pop(context, controller.text.trim()), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF1A1A1A), - foregroundColor: Colors.white, + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: const Text('确定'), @@ -1549,12 +1565,13 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; final availableTags = widget.existingTags .where((t) => !values.contains(t)) .toList(); return AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Text( widget.title, @@ -1580,9 +1597,9 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { children: [ // 已选择的标签 if (values.isNotEmpty) ...[ - const Text( + Text( '已选择', - style: TextStyle(fontSize: 12, color: Color(0xFFAAAAAA)), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.35)), ), const SizedBox(height: 8), Wrap( @@ -1592,7 +1609,7 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { return Container( padding: const EdgeInsets.only(left: 12, right: 6, top: 7, bottom: 7), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(16), ), child: Row( @@ -1600,10 +1617,10 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { children: [ Text( v, - style: const TextStyle( + style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, - color: Colors.white, + color: colors.onPrimary, ), ), const SizedBox(width: 4), @@ -1615,10 +1632,10 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { width: 18, height: 18, decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.3), + color: colors.onPrimary.withValues(alpha: 0.3), shape: BoxShape.circle, ), - child: const Icon(Icons.close, size: 12, color: Colors.white), + child: Icon(Icons.close, size: 12, color: colors.onPrimary), ), ), ], @@ -1628,15 +1645,15 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { ), if (availableTags.isNotEmpty) ...[ const SizedBox(height: 16), - const Divider(height: 0.5, color: Color(0xFFEEEEEE)), + Divider(height: 0.5, color: colors.outlineVariant), const SizedBox(height: 16), ], ], // 已有类型 if (availableTags.isNotEmpty) ...[ - const Text( + Text( '已有类型', - style: TextStyle(fontSize: 12, color: Color(0xFFAAAAAA)), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.35)), ), const SizedBox(height: 8), Wrap( @@ -1650,24 +1667,24 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), border: Border.all( - color: const Color(0xFFE8E8E8), + color: colors.outline, width: 0.5, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.add, size: 14, color: Color(0xFF999999)), + Icon(Icons.add, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), const SizedBox(width: 4), Text( tag, - style: const TextStyle( + style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, - color: Color(0xFF555555), + color: colors.onSurface.withValues(alpha: 0.7), ), ), ], @@ -1684,7 +1701,7 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { // 分隔线 + 固定底部输入框 if (values.isNotEmpty || availableTags.isNotEmpty) ...[ const SizedBox(height: 12), - const Divider(height: 0.5, color: Color(0xFFEEEEEE)), + Divider(height: 0.5, color: colors.outlineVariant), const SizedBox(height: 12), ], Row( @@ -1693,21 +1710,21 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { child: TextField( controller: controller, autofocus: true, - style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 15, color: colors.onSurface), decoration: InputDecoration( hintText: widget.hint, - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFAAAAAA)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.35)), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE0E0E0)), + borderSide: BorderSide(color: colors.outline), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE0E0E0)), + borderSide: BorderSide(color: colors.outline), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFF1A1A1A)), + borderSide: BorderSide(color: colors.primary, width: 1.5), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), @@ -1720,10 +1737,10 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(8), ), - child: const Icon(Icons.add, size: 20, color: Colors.white), + child: Icon(Icons.add, size: 20, color: colors.onPrimary), ), ), ], @@ -1735,7 +1752,7 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('取消', style: TextStyle(color: Color(0xFF999999))), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4))), ), ElevatedButton( onPressed: () { @@ -1743,8 +1760,8 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { Navigator.pop(context, values); }, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF1A1A1A), - foregroundColor: Colors.white, + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: const Text('确定'), diff --git a/lib/pages/book/book_review_detail_page.dart b/lib/pages/book/book_review_detail_page.dart index ce473dc..de66ab0 100644 --- a/lib/pages/book/book_review_detail_page.dart +++ b/lib/pages/book/book_review_detail_page.dart @@ -49,8 +49,9 @@ class _BookReviewDetailPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: const Text('书评详情'), actions: [ @@ -70,9 +71,9 @@ class _BookReviewDetailPageState extends State { // 书评内容 Text( _review.content, - style: const TextStyle( + style: TextStyle( fontSize: 16, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.8, ), ), @@ -82,7 +83,7 @@ class _BookReviewDetailPageState extends State { // 分隔线 Container( height: 0.5, - color: const Color(0xFFE5E5E5), + color: colors.outline, ), const SizedBox(height: 24), @@ -92,6 +93,7 @@ class _BookReviewDetailPageState extends State { icon: Icons.person_outline, label: '书评人:', value: _review.reviewer.isNotEmpty ? _review.reviewer : '匿名', + colors: colors, ), const SizedBox(height: 16), @@ -102,6 +104,7 @@ class _BookReviewDetailPageState extends State { icon: Icons.source_outlined, label: '来源:', value: _review.source, + colors: colors, ), if (_review.source.isNotEmpty) const SizedBox(height: 16), @@ -111,6 +114,7 @@ class _BookReviewDetailPageState extends State { icon: Icons.category_outlined, label: '类型:', value: _review.typeText, + colors: colors, ), const SizedBox(height: 16), @@ -120,6 +124,7 @@ class _BookReviewDetailPageState extends State { icon: Icons.access_time, label: '时间:', value: _formatDate(_review.createdAt), + colors: colors, ), ], ), @@ -132,29 +137,30 @@ class _BookReviewDetailPageState extends State { required IconData icon, required String label, required String value, + required ColorScheme colors, }) { return Row( children: [ Icon( icon, size: 20, - color: const Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), const SizedBox(width: 12), Text( label, - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), const SizedBox(width: 16), Expanded( child: Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ), diff --git a/lib/pages/book/book_review_form_page.dart b/lib/pages/book/book_review_form_page.dart index c9cc4ef..a32ad9a 100644 --- a/lib/pages/book/book_review_form_page.dart +++ b/lib/pages/book/book_review_form_page.dart @@ -47,10 +47,11 @@ class _BookReviewFormPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; final isEdit = widget.review != null; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: Text(isEdit ? '编辑书评' : '写书评'), actions: [ @@ -74,24 +75,24 @@ class _BookReviewFormPageState extends State { // 顶部信息栏 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: const BoxDecoration( + decoration: BoxDecoration( border: Border( - bottom: BorderSide(color: Color(0xFFE5E5E5), width: 0.5), + bottom: BorderSide(color: colors.outline, width: 0.5), ), ), child: Row( children: [ // 类型选择 - _buildTypeSelector(), + _buildTypeSelector(colors), const SizedBox(width: 16), // 评论人 Expanded( child: TextField( controller: _reviewerController, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), - decoration: const InputDecoration( + style: TextStyle(fontSize: 14, color: colors.onSurface), + decoration: InputDecoration( hintText: '评论人', - hintStyle: TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.25)), border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, @@ -104,10 +105,10 @@ class _BookReviewFormPageState extends State { width: 100, child: TextField( controller: _sourceController, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), - decoration: const InputDecoration( + style: TextStyle(fontSize: 14, color: colors.onSurface), + decoration: InputDecoration( hintText: '来源', - hintStyle: TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.25)), border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, @@ -125,19 +126,19 @@ class _BookReviewFormPageState extends State { maxLines: null, expands: true, textAlignVertical: TextAlignVertical.top, - style: const TextStyle( + style: TextStyle( fontSize: 16, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.7, ), - decoration: const InputDecoration( + decoration: InputDecoration( hintText: '写下你的书评...', hintStyle: TextStyle( fontSize: 16, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), border: InputBorder.none, - contentPadding: EdgeInsets.all(16), + contentPadding: const EdgeInsets.all(16), ), validator: (value) { if (value == null || value.trim().isEmpty) { @@ -154,29 +155,29 @@ class _BookReviewFormPageState extends State { } /// 构建类型选择器 - Widget _buildTypeSelector() { + Widget _buildTypeSelector(ColorScheme colors) { return GestureDetector( onTap: () => _showTypeSelector(), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - border: Border.all(color: const Color(0xFFE5E5E5)), + border: Border.all(color: colors.outline), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( _reviewType == 1 ? '短评' : '长评', - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 4), - const Icon( + Icon( Icons.arrow_drop_down, size: 16, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ], ), @@ -188,36 +189,42 @@ class _BookReviewFormPageState extends State { void _showTypeSelector() { showModalBottomSheet( context: context, - backgroundColor: Colors.white, + backgroundColor: Colors.transparent, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - builder: (context) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: const Text('短评'), - trailing: _reviewType == 1 - ? const Icon(Icons.check, color: Color(0xFF1A1A1A)) - : null, - onTap: () { - setState(() => _reviewType = 1); - Navigator.pop(context); - }, + builder: (context) { + final colors = Theme.of(context).colorScheme; + return Container( + color: colors.surface, + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: const Text('短评'), + trailing: _reviewType == 1 + ? Icon(Icons.check, color: colors.onSurface) + : null, + onTap: () { + setState(() => _reviewType = 1); + Navigator.pop(context); + }, + ), + Divider(height: 0.5, color: colors.outline), + ListTile( + title: const Text('长评'), + trailing: _reviewType == 2 + ? Icon(Icons.check, color: colors.onSurface) + : null, + onTap: () { + setState(() => _reviewType = 2); + Navigator.pop(context); + }, + ), + ], ), - const Divider(height: 0.5), - ListTile( - title: const Text('长评'), - trailing: _reviewType == 2 - ? const Icon(Icons.check, color: Color(0xFF1A1A1A)) - : null, - onTap: () { - setState(() => _reviewType = 2); - Navigator.pop(context); - }, - ), - ], - ), - ), + ), + ); + }, ); } @@ -259,4 +266,3 @@ class _BookReviewFormPageState extends State { Navigator.pop(context); } } - diff --git a/lib/pages/book/book_reviews_page.dart b/lib/pages/book/book_reviews_page.dart index 578f34d..ba906dc 100644 --- a/lib/pages/book/book_reviews_page.dart +++ b/lib/pages/book/book_reviews_page.dart @@ -73,19 +73,20 @@ class _BookReviewsPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: _isSearching ? TextField( controller: _searchController, autofocus: true, - decoration: const InputDecoration( + decoration: InputDecoration( hintText: '搜索书评内容、书评人、来源...', - hintStyle: TextStyle(color: Color(0xFF999999)), + hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.4)), border: InputBorder.none, ), - style: const TextStyle(color: Color(0xFF1A1A1A)), + style: TextStyle(color: colors.onSurface), onChanged: _onSearchChanged, ) : const Text('书评'), @@ -112,6 +113,7 @@ class _BookReviewsPageState extends State { } Widget _buildEmptyState() { + final colors = Theme.of(context).colorScheme; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -120,21 +122,21 @@ class _BookReviewsPageState extends State { width: 80, height: 80, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), ), - child: const Icon( + child: Icon( Icons.rate_review_outlined, size: 40, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), ), const SizedBox(height: 20), - const Text( + Text( '暂无书评', style: TextStyle( fontSize: 16, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), const SizedBox(height: 24), @@ -143,15 +145,15 @@ class _BookReviewsPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(8), ), - child: const Text( + child: Text( '添加记录', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: Colors.white, + color: colors.onPrimary, ), ), ), @@ -176,13 +178,14 @@ class _BookReviewsPageState extends State { } Widget _buildReviewCard(BookReview review) { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: () => _navigateToReviewDetail(review), onLongPress: () => _showDeleteDialog(review), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), child: Column( @@ -193,8 +196,8 @@ class _BookReviewsPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: review.reviewType == 1 - ? Colors.white - : const Color(0xFF1A1A1A), + ? colors.surface + : colors.primary, borderRadius: BorderRadius.circular(4), ), child: Text( @@ -202,8 +205,8 @@ class _BookReviewsPageState extends State { style: TextStyle( fontSize: 10, color: review.reviewType == 1 - ? const Color(0xFF666666) - : Colors.white, + ? colors.onSurface.withValues(alpha: 0.6) + : colors.onPrimary, ), ), ), @@ -215,9 +218,9 @@ class _BookReviewsPageState extends State { review.content, maxLines: review.reviewType == 1 ? 4 : 8, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.5, ), ), @@ -232,9 +235,9 @@ class _BookReviewsPageState extends State { Expanded( child: Text( review.reviewer, - style: const TextStyle( + style: TextStyle( fontSize: 11, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), overflow: TextOverflow.ellipsis, ), @@ -252,9 +255,9 @@ class _BookReviewsPageState extends State { Expanded( child: Text( review.source, - style: const TextStyle( + style: TextStyle( fontSize: 10, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), overflow: TextOverflow.ellipsis, ), @@ -262,9 +265,9 @@ class _BookReviewsPageState extends State { // 日期 Text( _formatDate(review.createdAt), - style: const TextStyle( + style: TextStyle( fontSize: 10, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], @@ -315,28 +318,31 @@ class _BookReviewsPageState extends State { void _showDeleteDialog(BookReview review) { 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 { - await context.read().removeBookReview(review.id); - Navigator.pop(context); - _loadReviews(); - ToastUtil.show(context, '已删除'); - }, - child: const Text('删除', style: TextStyle(color: Colors.red)), - ), - ], - ), + builder: (context) { + final colors = Theme.of(context).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: const Text('确认删除'), + content: const Text('确定要删除这条书评吗?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))), + ), + TextButton( + onPressed: () async { + await context.read().removeBookReview(review.id); + Navigator.pop(context); + _loadReviews(); + ToastUtil.show(context, '已删除'); + }, + child: Text('删除', style: TextStyle(color: colors.error)), + ), + ], + ); + }, ); } } diff --git a/lib/pages/book/book_share_page.dart b/lib/pages/book/book_share_page.dart index 4f0d6db..29d12dd 100644 --- a/lib/pages/book/book_share_page.dart +++ b/lib/pages/book/book_share_page.dart @@ -23,21 +23,22 @@ class _BookSharePageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: const Color(0xFFF5F5F5), + backgroundColor: colors.surfaceContainerHighest, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, leading: IconButton( - icon: const Icon(Icons.close, color: Color(0xFF1A1A1A)), + icon: Icon(Icons.close, color: colors.onSurface), onPressed: () => Navigator.pop(context), ), - title: const Text( + title: Text( '分享海报', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), centerTitle: true, @@ -50,12 +51,12 @@ class _BookSharePageState extends State { height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) - : const Text( + : Text( '分享', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ), @@ -76,13 +77,14 @@ class _BookSharePageState extends State { /// 构建海报 Widget Widget _buildPosterWidget() { + final colors = Theme.of(context).colorScheme; final book = widget.book; final hasCover = book.coverPath != null && book.coverPath!.isNotEmpty; return Container( width: 320, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( @@ -117,10 +119,10 @@ class _BookSharePageState extends State { // 书名 Text( book.title, - style: const TextStyle( + style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), @@ -129,9 +131,9 @@ class _BookSharePageState extends State { const SizedBox(height: 8), Text( book.alternateTitles.join(' / '), - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), ], @@ -157,11 +159,11 @@ class _BookSharePageState extends State { ), ), const SizedBox(width: 4), - const Text( + Text( '/ 10', style: TextStyle( fontSize: 12, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], @@ -171,44 +173,45 @@ class _BookSharePageState extends State { // 作者 if (book.authors.isNotEmpty) - _buildInfoRow('作者', book.authors.join(' / ')), + _buildInfoRow('作者', book.authors.join(' / '), colors), // 出版社 if (book.publisher != null && book.publisher!.isNotEmpty) - _buildInfoRow('出版社', book.publisher!), + _buildInfoRow('出版社', book.publisher!, colors), // 出版时间 if (book.publishDate != null) _buildInfoRow( '出版', '${book.publishDate!.year}.${book.publishDate!.month.toString().padLeft(2, '0')}.${book.publishDate!.day.toString().padLeft(2, '0')}', + colors, ), // 类型 if (book.genres.isNotEmpty) - _buildInfoRow('类型', book.genres.join(' / ')), + _buildInfoRow('类型', book.genres.join(' / '), colors), // ISBN if (book.isbn != null && book.isbn!.isNotEmpty) - _buildInfoRow('ISBN', book.isbn!), + _buildInfoRow('ISBN', book.isbn!, colors), const SizedBox(height: 16), // 简介 if (book.summary != null && book.summary!.isNotEmpty) ...[ - const Text( + Text( '简介', style: TextStyle( fontSize: 12, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), const SizedBox(height: 8), Text( book.summary!, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.6, ), maxLines: 5, @@ -219,7 +222,7 @@ class _BookSharePageState extends State { const SizedBox(height: 20), // 底部标识 - const Divider(height: 1, color: Color(0xFFE8E8E8)), + Divider(height: 1, color: colors.outline), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -227,14 +230,14 @@ class _BookSharePageState extends State { Icon( Icons.book_outlined, size: 14, - color: const Color(0xFF1A1A1A).withOpacity(0.5), + color: colors.onSurface.withValues(alpha: 0.5), ), const SizedBox(width: 6), Text( '来自 MookNote', style: TextStyle( fontSize: 12, - color: const Color(0xFF1A1A1A).withOpacity(0.5), + color: colors.onSurface.withValues(alpha: 0.5), ), ), ], @@ -248,7 +251,7 @@ class _BookSharePageState extends State { } /// 构建信息行 - Widget _buildInfoRow(String label, String value) { + Widget _buildInfoRow(String label, String value, ColorScheme colors) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( @@ -256,17 +259,17 @@ class _BookSharePageState extends State { children: [ Text( '$label:', - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), Expanded( child: Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF333333), + color: colors.onSurface.withValues(alpha: 0.75), ), ), ), diff --git a/lib/pages/book/book_tab_page.dart b/lib/pages/book/book_tab_page.dart index 12c3d15..1b4f83c 100644 --- a/lib/pages/book/book_tab_page.dart +++ b/lib/pages/book/book_tab_page.dart @@ -17,7 +17,7 @@ class BookTabPage extends StatefulWidget { } class _BookTabPageState extends State { - int _layoutStyle = 0; // 0: 封面网格, 1: 列表 + int _layoutStyle = 0; bool _firstLoad = true; @override @@ -42,6 +42,7 @@ class _BookTabPageState extends State { } Widget _buildBookList(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Consumer( builder: (context, provider, child) { final statusMap = {0: 'read', 1: 'reading', 2: 'want_to_read'}; @@ -55,8 +56,8 @@ class _BookTabPageState extends State { if (books.isEmpty) { return RefreshIndicator( onRefresh: () async => await provider.loadBooks(), - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, child: ListView( physics: const AlwaysScrollableScrollPhysics(), children: [_buildEmptyState(context, provider.bookStatusIndex)], @@ -73,10 +74,11 @@ class _BookTabPageState extends State { } Widget _buildGridView(List books, AppProvider provider) { + final colors = Theme.of(context).colorScheme; return RefreshIndicator( onRefresh: () async => await provider.loadBooks(), - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, child: GridView.builder( padding: const EdgeInsets.fromLTRB(16, 16, 16, 100), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( @@ -92,10 +94,11 @@ class _BookTabPageState extends State { } Widget _buildListView(List books, AppProvider provider) { + final colors = Theme.of(context).colorScheme; return RefreshIndicator( onRefresh: () async => await provider.loadBooks(), - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, child: ListView.builder( padding: const EdgeInsets.fromLTRB(12, 8, 12, 100), itemCount: books.length, @@ -105,6 +108,7 @@ class _BookTabPageState extends State { } Widget _buildListCard(book) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: () => Navigator.pushNamed(context, '/book-detail', arguments: book), onLongPress: () => _showDeleteDialog(context, book), @@ -112,23 +116,22 @@ class _BookTabPageState extends State { margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ - // 封面缩略图 Container( width: 48, height: 64, decoration: BoxDecoration( - color: const Color(0xFFF0F0F0), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(6), ), clipBehavior: Clip.antiAlias, child: book.coverPath != null && book.coverPath!.isNotEmpty ? Image.file(File(book.coverPath!), fit: BoxFit.cover, - errorBuilder: (_, __, ___) => const Icon(Icons.menu_book_outlined, size: 22, color: Color(0xFFCCCCCC))) - : const Icon(Icons.menu_book_outlined, size: 22, color: Color(0xFFCCCCCC)), + errorBuilder: (_, __, ___) => Icon(Icons.menu_book_outlined, size: 22, color: colors.onSurface.withValues(alpha: 0.25))) + : Icon(Icons.menu_book_outlined, size: 22, color: colors.onSurface.withValues(alpha: 0.25)), ), const SizedBox(width: 12), Expanded( @@ -136,11 +139,11 @@ class _BookTabPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(book.title, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface)), if (book.authors.isNotEmpty) ...[ const SizedBox(height: 3), Text(book.authors.take(2).join('、'), maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12, color: Color(0xFFAAAAAA))), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.35))), ], const SizedBox(height: 6), if (book.rating != null) @@ -151,7 +154,7 @@ class _BookTabPageState extends State { ), ), const SizedBox(width: 8), - const Icon(Icons.chevron_right, color: Color(0xFFD0D0D0), size: 20), + Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.2), size: 20), ], ), ), @@ -159,19 +162,20 @@ class _BookTabPageState extends State { } void _showDeleteDialog(BuildContext context, book) { + final colors = Theme.of(context).colorScheme; showDialog( context: context, builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), + title: Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), content: Text('确定要删除《${book.title}》吗?删除后可在回收站恢复。', - style: const TextStyle(fontSize: 14, color: Color(0xFF666666), height: 1.5)), + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.5)), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text('取消', style: TextStyle(color: Color(0xFF666666))), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))), ), ElevatedButton( onPressed: () async { @@ -179,7 +183,7 @@ class _BookTabPageState extends State { Navigator.pop(ctx); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, foregroundColor: Colors.white, elevation: 0, + backgroundColor: colors.error, foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), @@ -193,11 +197,12 @@ class _BookTabPageState extends State { Widget _buildSkeleton() { return _layoutStyle == 1 - ? MovieSkeletonGrid() // reuse same grid skeleton pattern + ? MovieSkeletonGrid() : const BookSkeletonGrid(); } Widget _buildEmptyState(BuildContext context, int statusIndex) { + final colors = Theme.of(context).colorScheme; final statusText = ['已读', '在读', '想读'][statusIndex]; return Center( child: Column( @@ -205,11 +210,11 @@ class _BookTabPageState extends State { children: [ Container( width: 80, height: 80, - decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(20)), - child: const Icon(Icons.menu_book_outlined, size: 40, color: Color(0xFFCCCCCC)), + decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(20)), + child: Icon(Icons.menu_book_outlined, size: 40, color: colors.onSurface.withValues(alpha: 0.25)), ), const SizedBox(height: 20), - Text('暂无$statusText的书籍', style: const TextStyle(fontSize: 16, color: Color(0xFF999999))), + Text('暂无$statusText的书籍', style: TextStyle(fontSize: 16, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(height: 24), InkWell( onTap: () { @@ -218,8 +223,8 @@ class _BookTabPageState extends State { }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - decoration: BoxDecoration(color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(8)), - child: const Text('添加记录', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)), + decoration: BoxDecoration(color: colors.primary, borderRadius: BorderRadius.circular(8)), + child: Text('添加记录', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onPrimary)), ), ), ], diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 887a3cf..aeb18ba 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -99,7 +99,7 @@ class _HomePageState extends State { width: 44, height: 56, decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.horizontal( right: Radius.circular(28)), boxShadow: [ @@ -111,10 +111,10 @@ class _HomePageState extends State { ), ], ), - child: const Center( + child: Center( child: Icon( Icons.chevron_right, - color: Color(0xFF999999), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.4), size: 24, ), ), diff --git a/lib/pages/main_content_page.dart b/lib/pages/main_content_page.dart index 95f9e74..0159752 100644 --- a/lib/pages/main_content_page.dart +++ b/lib/pages/main_content_page.dart @@ -31,7 +31,6 @@ class _MainContentPageState extends State { _loadTabSettings(); } - /// 加载标签显示设置 void _loadTabSettings() { setState(() { _showMovieTab = _userPrefs.showMovieTab; @@ -43,11 +42,9 @@ class _MainContentPageState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - // 当页面重新获得焦点时刷新设置 _loadTabSettings(); } - /// 获取启用的标签列表 List<_TabItem> get _enabledTabs { final tabs = <_TabItem>[]; if (_showMovieTab) tabs.add(_TabItem('影视', 0)); @@ -56,7 +53,6 @@ class _MainContentPageState extends State { return tabs; } - /// 将原始索引映射到启用标签的索引 int _mapToEnabledTabIndex(int originalIndex) { final tabs = _enabledTabs; for (int i = 0; i < tabs.length; i++) { @@ -69,13 +65,8 @@ class _MainContentPageState extends State { Widget build(BuildContext context) { return Column( children: [ - // 顶部 AppBar _buildAppBar(context), - - // 三个标签页的标题栏 _buildTabBar(context), - - // 标签页内容 Expanded( child: _buildTabContent(), ), @@ -83,16 +74,13 @@ class _MainContentPageState extends State { ); } - /// 构建顶部 AppBar Widget _buildAppBar(BuildContext context) { return Consumer( builder: (context, provider, child) { return AppBar( title: Text(_getAppBarTitle(provider)), actions: [ - // 云备份按钮 _buildCloudSyncButton(context), - // 搜索按钮 IconButton( icon: const Icon(Icons.search), onPressed: () { @@ -110,8 +98,8 @@ class _MainContentPageState extends State { ); } - /// 构建云备份按钮 Widget _buildCloudSyncButton(BuildContext context) { + final colors = Theme.of(context).colorScheme; return PopupMenuButton( icon: const Icon(Icons.cloud_sync_outlined), tooltip: '云备份', @@ -128,21 +116,21 @@ class _MainContentPageState extends State { width: 32, height: 32, decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(8), ), - child: const Icon( + child: Icon( Icons.cloud_upload_outlined, size: 18, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 12), - const Text( + Text( '上传数据', style: TextStyle( fontSize: 14, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ], @@ -156,21 +144,21 @@ class _MainContentPageState extends State { width: 32, height: 32, decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(8), ), - child: const Icon( + child: Icon( Icons.cloud_download_outlined, size: 18, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 12), - const Text( + Text( '下载数据', style: TextStyle( fontSize: 14, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ], @@ -185,21 +173,21 @@ class _MainContentPageState extends State { width: 32, height: 32, decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(8), ), - child: const Icon( + child: Icon( Icons.settings_outlined, size: 18, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 12), - const Text( + Text( 'WebDAV设置', style: TextStyle( fontSize: 14, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ], @@ -227,9 +215,8 @@ class _MainContentPageState extends State { ); } - /// 执行同步操作 Future _performSync(BuildContext context, SyncDirection direction) async { - // 检查是否已配置 + final colors = Theme.of(context).colorScheme; final config = await WebDAVService.instance.getConfig(); if (config == null) { if (context.mounted) { @@ -243,28 +230,24 @@ class _MainContentPageState extends State { return; } - // 显示加载对话框 if (context.mounted) { showDialog( context: context, barrierDismissible: false, - builder: (context) => const Center( + builder: (context) => Center( child: CircularProgressIndicator( - color: Color(0xFF1A1A1A), + color: colors.primary, ), ), ); } - // 执行同步 final result = await WebDAVService.instance.syncData(direction: direction); - // 关闭加载对话框 if (context.mounted) { Navigator.pop(context); } - // 下载了数据则刷新界面 if (result.success && result.needReload && context.mounted) { final provider = context.read(); await provider.loadMovies(); @@ -272,11 +255,10 @@ class _MainContentPageState extends State { await provider.loadNotes(); } - // 显示结果 if (context.mounted) { final isSuccess = result.success; final message = result.message.isNotEmpty ? result.message : (isSuccess ? '同步成功' : '同步失败'); - + _showResultDialog( context, title: isSuccess ? '同步成功' : '同步失败', @@ -290,7 +272,6 @@ class _MainContentPageState extends State { } } - /// 显示同步结果对话框 void _showResultDialog( BuildContext context, { required String title, @@ -298,10 +279,11 @@ class _MainContentPageState extends State { required bool isSuccess, Map? details, }) { + final colors = Theme.of(context).colorScheme; showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Row( @@ -322,9 +304,10 @@ class _MainContentPageState extends State { const SizedBox(width: 12), Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, + color: colors.onSurface, ), ), ], @@ -335,9 +318,9 @@ class _MainContentPageState extends State { children: [ Text( message, - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.5, ), ), @@ -346,7 +329,7 @@ class _MainContentPageState extends State { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(8), ), child: Column( @@ -370,8 +353,8 @@ class _MainContentPageState extends State { ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF1A1A1A), - foregroundColor: Colors.white, + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -386,8 +369,8 @@ class _MainContentPageState extends State { ); } - /// 构建详情行 Widget _buildDetailRow(String label, String value, {bool isError = false}) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( @@ -395,9 +378,9 @@ class _MainContentPageState extends State { children: [ Text( label, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), Text( @@ -405,7 +388,7 @@ class _MainContentPageState extends State { style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, - color: isError ? const Color(0xFFE57373) : const Color(0xFF1A1A1A), + color: isError ? const Color(0xFFE57373) : colors.onSurface, ), ), ], @@ -413,7 +396,6 @@ class _MainContentPageState extends State { ); } - /// 获取 AppBar 标题 String _getAppBarTitle(AppProvider provider) { switch (provider.mainTabIndex) { case 0: @@ -440,17 +422,17 @@ class _MainContentPageState extends State { } } - /// 构建标签栏 Widget _buildTabBar(BuildContext context) { return Consumer( builder: (context, provider, child) { + final colors = Theme.of(context).colorScheme; final tabs = _enabledTabs; final currentEnabledIndex = _mapToEnabledTabIndex(provider.mainTabIndex); final safeIndex = currentEnabledIndex < tabs.length ? currentEnabledIndex : 0; return Container( - decoration: const BoxDecoration( - color: Colors.white, + decoration: BoxDecoration( + color: colors.surface, ), child: Column( mainAxisSize: MainAxisSize.min, @@ -474,7 +456,7 @@ class _MainContentPageState extends State { Icon( _getTabIcon(tab.label), size: 22, - color: isSelected ? const Color(0xFF1A1A1A) : const Color(0xFFB0B0B0), + color: isSelected ? colors.primary : colors.onSurface.withValues(alpha: 0.35), ), const SizedBox(height: 6), Text( @@ -482,7 +464,7 @@ class _MainContentPageState extends State { style: TextStyle( fontSize: 13, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, - color: isSelected ? const Color(0xFF1A1A1A) : const Color(0xFFB0B0B0), + color: isSelected ? colors.primary : colors.onSurface.withValues(alpha: 0.35), ), ), ], @@ -513,7 +495,7 @@ class _MainContentPageState extends State { width: indicatorWidth, height: 3, decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(1.5), ), ), @@ -524,7 +506,7 @@ class _MainContentPageState extends State { }, ), ), - const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)), + Divider(height: 0.5, thickness: 0.5, color: colors.outline), ], ), ); @@ -532,12 +514,10 @@ class _MainContentPageState extends State { ); } - /// 构建标签页内容 Widget _buildTabContent() { return Consumer( builder: (context, provider, child) { final tabs = _enabledTabs; - // 找到当前应该显示的标签 _TabItem? currentTab; for (final tab in tabs) { if (tab.originalIndex == provider.mainTabIndex) { @@ -545,10 +525,8 @@ class _MainContentPageState extends State { break; } } - // 如果当前标签被禁用了,显示第一个启用的标签 if (currentTab == null && tabs.isNotEmpty) { currentTab = tabs.first; - // 更新 provider 的索引 WidgetsBinding.instance.addPostFrameCallback((_) { provider.setMainTabIndex(currentTab!.originalIndex); }); @@ -572,22 +550,21 @@ class _MainContentPageState extends State { ); } - /// 显示添加对话框 void _showAddDialog(BuildContext context, AppProvider provider) { + final colors = Theme.of(context).colorScheme; showModalBottomSheet( context: context, - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), builder: (BuildContext context) { return SafeArea( child: Wrap( children: [ ListTile( - leading: const Icon(Icons.movie, color: Color(0xFF1A1A1A)), + leading: Icon(Icons.movie, color: colors.onSurface), title: const Text('添加观影'), onTap: () { Navigator.pop(context); - // 根据当前影视标签页的选中状态设置默认值 final statusMap = { 0: 'watched', 1: 'watching', @@ -601,13 +578,12 @@ class _MainContentPageState extends State { ); }, ), - const Divider(height: 0.5, indent: 56), + Divider(height: 0.5, indent: 56, color: colors.outlineVariant), ListTile( - leading: const Icon(Icons.menu_book, color: Color(0xFF1A1A1A)), + leading: Icon(Icons.menu_book, color: colors.onSurface), title: const Text('添加阅读'), onTap: () { Navigator.pop(context); - // 根据当前阅读标签页的选中状态设置默认值 final statusMap = { 0: 'read', 1: 'reading', @@ -621,9 +597,9 @@ class _MainContentPageState extends State { ); }, ), - const Divider(height: 0.5, indent: 56), + Divider(height: 0.5, indent: 56, color: colors.outlineVariant), ListTile( - leading: const Icon(Icons.note, color: Color(0xFF1A1A1A)), + leading: Icon(Icons.note, color: colors.onSurface), title: const Text('添加笔记'), onTap: () { Navigator.pop(context); @@ -638,7 +614,6 @@ class _MainContentPageState extends State { } } -/// 标签项数据类 class _TabItem { final String label; final int originalIndex; diff --git a/lib/pages/markdown_reader/md_reader_tab_page.dart b/lib/pages/markdown_reader/md_reader_tab_page.dart index 6f93e88..259e2b5 100644 --- a/lib/pages/markdown_reader/md_reader_tab_page.dart +++ b/lib/pages/markdown_reader/md_reader_tab_page.dart @@ -90,26 +90,30 @@ class _MdReaderTabPageState extends State { void _showPermissionDeniedDialog() { showDialog( context: context, - builder: (ctx) => AlertDialog( - title: const Text('需要存储权限', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), - content: const Text( - 'Android 11+ 需要在系统设置中授予"所有文件访问权限"才能读取目录中的 Markdown 文件。\n\n是否前往设置?', - style: TextStyle(fontSize: 14, color: Color(0xFF666666), height: 1.6), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - child: const Text('取消', style: TextStyle(color: Color(0xFF999999))), + builder: (ctx) { + final colors = Theme.of(ctx).colorScheme; + return AlertDialog( + title: const Text('需要存储权限', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + content: Text( + 'Android 11+ 需要在系统设置中授予"所有文件访问权限"才能读取目录中的 Markdown 文件。\n\n是否前往设置?', + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.6), ), - TextButton( - onPressed: () { - Navigator.pop(ctx); - openAppSettings(); - }, - child: const Text('前往设置', style: TextStyle(color: Color(0xFF1A1A1A))), - ), - ], - ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4))), + ), + TextButton( + onPressed: () { + Navigator.pop(ctx); + openAppSettings(); + }, + child: Text('前往设置', style: TextStyle(color: colors.onSurface)), + ), + ], + ); + }, ); } @@ -121,7 +125,10 @@ class _MdReaderTabPageState extends State { for (final entity in dir.listSync(recursive: true, followLinks: false)) { if (entity is File) { final lower = p.basename(entity.path).toLowerCase(); - if (lower.endsWith('.md') || lower.endsWith('.markdown') || lower.endsWith('.mdown') || lower.endsWith('.txt')) { + if (lower.endsWith('.md') || + lower.endsWith('.markdown') || + lower.endsWith('.mdown') || + lower.endsWith('.txt')) { return true; } } @@ -135,13 +142,28 @@ class _MdReaderTabPageState extends State { try { final dir = Directory(dirPath); if (!dir.existsSync()) return false; - const imageExts = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg', '.ico', '.tiff', '.heic', '.webm']; + const imageExts = [ + '.png', + '.jpg', + '.jpeg', + '.gif', + '.webp', + '.bmp', + '.svg', + '.ico', + '.tiff', + '.heic', + '.webm' + ]; bool hasAnyFile = false; for (final entity in dir.listSync(recursive: true, followLinks: false)) { if (entity is File) { hasAnyFile = true; final lower = p.basename(entity.path).toLowerCase(); - if (lower.endsWith('.md') || lower.endsWith('.markdown') || lower.endsWith('.mdown') || lower.endsWith('.txt')) { + if (lower.endsWith('.md') || + lower.endsWith('.markdown') || + lower.endsWith('.mdown') || + lower.endsWith('.txt')) { return false; } if (!imageExts.any((ext) => lower.endsWith(ext))) { @@ -193,9 +215,13 @@ class _MdReaderTabPageState extends State { } } else if (entity is File) { final lower = name.toLowerCase(); - if (lower.endsWith('.md') || lower.endsWith('.markdown') || lower.endsWith('.mdown') || lower.endsWith('.txt')) { + if (lower.endsWith('.md') || + lower.endsWith('.markdown') || + lower.endsWith('.mdown') || + lower.endsWith('.txt')) { final stat = entity.statSync(); - entries.add(_FileEntry(name: name, path: entity.path, isDir: false, size: stat.size)); + entries.add(_FileEntry( + name: name, path: entity.path, isDir: false, size: stat.size)); } } } @@ -236,9 +262,10 @@ class _MdReaderTabPageState extends State { } void _openFile(_FileEntry entry) { - Navigator.push(context, MaterialPageRoute( - builder: (context) => MdViewerPage(filePath: entry.path), - )); + Navigator.push(context, + MaterialPageRoute( + builder: (context) => MdViewerPage(filePath: entry.path), + )); } void _showSettingsSheet() { @@ -249,6 +276,7 @@ class _MdReaderTabPageState extends State { borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (ctx) { + final colors = Theme.of(ctx).colorScheme; return StatefulBuilder( builder: (ctx, setLocalState) => Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), @@ -256,24 +284,31 @@ class _MdReaderTabPageState extends State { mainAxisSize: MainAxisSize.min, children: [ Container( - width: 36, height: 4, + width: 36, + height: 4, decoration: BoxDecoration( - color: const Color(0xFFDDDDDD), + color: colors.onSurface.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 20), - const Align( + Align( alignment: Alignment.centerLeft, - child: Text('目录显示设置', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + child: Text('目录显示设置', + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface)), ), const SizedBox(height: 16), SwitchListTile( contentPadding: EdgeInsets.zero, - title: const Text('显示空目录', style: TextStyle(fontSize: 14, color: Color(0xFF333333))), - subtitle: const Text('关闭后隐藏无 Markdown 文件的目录', style: TextStyle(fontSize: 12, color: Color(0xFF999999))), + title: Text('显示空目录', + style: TextStyle( + fontSize: 14, color: colors.onSurface.withValues(alpha: 0.75))), + subtitle: Text('关闭后隐藏无 Markdown 文件的目录', + style: + TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), value: _showEmptyDirs, - activeColor: const Color(0xFF1A1A1A), + activeTrackColor: colors.primary, onChanged: (val) { setLocalState(() => _showEmptyDirs = val); UserPrefs().setShowEmptyDirs(val); @@ -281,13 +316,17 @@ class _MdReaderTabPageState extends State { _loadDirectory(); }, ), - const Divider(height: 0.5, color: Color(0xFFF0F0F0)), + Divider(height: 0.5, color: colors.outlineVariant), SwitchListTile( contentPadding: EdgeInsets.zero, - title: const Text('显示纯图片目录', style: TextStyle(fontSize: 14, color: Color(0xFF333333))), - subtitle: const Text('关闭后隐藏只含图片、无 Markdown 的目录', style: TextStyle(fontSize: 12, color: Color(0xFF999999))), + title: Text('显示纯图片目录', + style: TextStyle( + fontSize: 14, color: colors.onSurface.withValues(alpha: 0.75))), + subtitle: Text('关闭后隐藏只含图片、无 Markdown 的目录', + style: + TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), value: _showImageOnlyDirs, - activeColor: const Color(0xFF1A1A1A), + activeTrackColor: colors.primary, onChanged: (val) { setLocalState(() => _showImageOnlyDirs = val); UserPrefs().setShowImageOnlyDirs(val); @@ -304,40 +343,26 @@ class _MdReaderTabPageState extends State { ); } - bool get _canGoBack => _currentPath != null && _rootPath != null && _currentPath != _rootPath; + bool get _canGoBack => + _currentPath != null && _rootPath != null && _currentPath != _rootPath; @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, title: Text( _currentPath != null ? p.basename(_currentPath!) : 'Markdown 阅读', - style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface), ), leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Color(0xFF1A1A1A)), + icon: Icon(Icons.arrow_back, color: colors.onSurface), onPressed: () => Navigator.pop(context), ), actions: [ - // if (_canGoBack) - // Padding( - // padding: const EdgeInsets.only(right: 8), - // child: GestureDetector( - // onTap: _goBack, - // child: Container( - // padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - // decoration: BoxDecoration( - // color: const Color(0xFFF5F5F5), - // borderRadius: BorderRadius.circular(14), - // border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), - // ), - // child: const Text('返回上级', style: TextStyle(fontSize: 12, color: Color(0xFF666666))), - // ), - // ), - // ), if (_currentPath != null) Padding( padding: const EdgeInsets.only(right: 4), @@ -346,86 +371,109 @@ class _MdReaderTabPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(14), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outline, width: 0.5), ), - child: const Text('更换目录', style: TextStyle(fontSize: 12, color: Color(0xFF888888))), + child: Text('更换目录', + style: TextStyle( + fontSize: 12, color: colors.onSurface.withValues(alpha: 0.5))), ), ), ), IconButton( - icon: const Icon(Icons.tune, size: 20, color: Color(0xFF888888)), + icon: Icon(Icons.tune, size: 20, color: colors.onSurface.withValues(alpha: 0.5)), onPressed: _showSettingsSheet, ), const SizedBox(width: 4), ], ), - body: _buildBody(), + body: _buildBody(colors), ); } - Widget _buildBody() { + Widget _buildBody(ColorScheme colors) { if (_currentPath == null) { - return _buildWelcome(); + return _buildWelcome(colors); } if (_isLoading) { - return const Center(child: CircularProgressIndicator(color: Color(0xFF1A1A1A))); + return Center(child: CircularProgressIndicator(color: colors.primary)); } if (_error != null) { - return _buildError(); + return _buildError(colors); } return RefreshIndicator( onRefresh: _loadDirectory, - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, - child: _entries.isEmpty ? _buildEmpty() : ListView.separated( - padding: EdgeInsets.zero, - itemCount: (_canGoBack ? 1 : 0) + _entries.length, - separatorBuilder: (_, __) => const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFF0F0F0)), - itemBuilder: (context, index) { - if (_canGoBack && index == 0) { - return ListTile( - leading: Container( - width: 36, height: 36, - decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon(Icons.arrow_upward, size: 18, color: Color(0xFF888888)), - ), - title: const Text('..', style: TextStyle(fontSize: 14, color: Color(0xFF888888))), - onTap: _goBack, - ); - } - final entry = _entries[_canGoBack ? index - 1 : index]; - return ListTile( - leading: Container( - width: 36, height: 36, - decoration: BoxDecoration( - color: entry.isDir ? const Color(0xFFF0F7FF) : const Color(0xFFF5F5F5), - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - entry.isDir ? Icons.folder_outlined : Icons.description_outlined, - size: 18, - color: entry.isDir ? const Color(0xFF4A90D9) : const Color(0xFF666666), - ), + color: colors.primary, + backgroundColor: colors.surface, + child: _entries.isEmpty + ? _buildEmpty(colors) + : ListView.separated( + padding: EdgeInsets.zero, + itemCount: (_canGoBack ? 1 : 0) + _entries.length, + separatorBuilder: (_, __) => + Divider(height: 0.5, thickness: 0.5, color: colors.outlineVariant), + itemBuilder: (context, index) { + if (_canGoBack && index == 0) { + return ListTile( + leading: Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, + borderRadius: BorderRadius.circular(8), + ), + child: Icon(Icons.arrow_upward, + size: 18, color: colors.onSurface.withValues(alpha: 0.5)), + ), + title: Text('..', + style: TextStyle( + fontSize: 14, color: colors.onSurface.withValues(alpha: 0.5))), + onTap: _goBack, + ); + } + final entry = _entries[_canGoBack ? index - 1 : index]; + return ListTile( + leading: Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: entry.isDir + ? const Color(0xFFF0F7FF) + : colors.surfaceContainerHighest, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + entry.isDir ? Icons.folder_outlined : Icons.description_outlined, + size: 18, + color: entry.isDir + ? const Color(0xFF4A90D9) + : colors.onSurface.withValues(alpha: 0.6), + ), + ), + title: Text(entry.name, + style: TextStyle(fontSize: 14, color: colors.onSurface), + maxLines: 1, + overflow: TextOverflow.ellipsis), + subtitle: entry.isDir + ? null + : Text(_formatSize(entry.size), + style: TextStyle( + fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4))), + trailing: Icon(entry.isDir ? Icons.chevron_right : Icons.open_in_new_outlined, + size: 16, color: colors.onSurface.withValues(alpha: 0.25)), + onTap: () => + entry.isDir ? _enterDirectory(entry.path) : _openFile(entry), + ); + }, ), - title: Text(entry.name, style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), maxLines: 1, overflow: TextOverflow.ellipsis), - subtitle: entry.isDir ? null : Text(_formatSize(entry.size), style: const TextStyle(fontSize: 11, color: Color(0xFF999999))), - trailing: Icon(entry.isDir ? Icons.chevron_right : Icons.open_in_new_outlined, size: 16, color: const Color(0xFFCCCCCC)), - onTap: () => entry.isDir ? _enterDirectory(entry.path) : _openFile(entry), - ); - }, - ), ); } - Widget _buildWelcome() { + Widget _buildWelcome(ColorScheme colors) { return Center( child: Padding( padding: const EdgeInsets.all(40), @@ -433,27 +481,34 @@ class _MdReaderTabPageState extends State { mainAxisSize: MainAxisSize.min, children: [ Container( - width: 80, height: 80, + width: 80, + height: 80, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), ), - child: const Icon(Icons.folder_open_outlined, size: 40, color: Color(0xFFCCCCCC)), + child: Icon(Icons.folder_open_outlined, + size: 40, color: colors.onSurface.withValues(alpha: 0.25)), ), const SizedBox(height: 24), - const Text('Markdown 阅读', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text('Markdown 阅读', + style: TextStyle( + fontSize: 20, fontWeight: FontWeight.w600, color: colors.onSurface)), const SizedBox(height: 8), - const Text('选择一个包含 .md 文件的文件夹', style: TextStyle(fontSize: 14, color: Color(0xFF999999))), + Text('选择一个包含 .md 文件的文件夹', + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(height: 32), GestureDetector( onTap: _pickDirectory, child: Container( padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 14), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(24), ), - child: const Text('选择目录', style: TextStyle(fontSize: 15, color: Colors.white, fontWeight: FontWeight.w500)), + child: Text('选择目录', + style: TextStyle( + fontSize: 15, color: colors.onPrimary, fontWeight: FontWeight.w500)), ), ), ], @@ -462,28 +517,31 @@ class _MdReaderTabPageState extends State { ); } - Widget _buildEmpty() { + Widget _buildEmpty(ColorScheme colors) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.folder_open_outlined, size: 64, color: Color(0xFFE0E0E0)), + Icon(Icons.folder_open_outlined, size: 64, color: colors.outline), const SizedBox(height: 16), - const Text('此目录下没有 Markdown 文件', style: TextStyle(fontSize: 15, color: Color(0xFF999999))), + Text('此目录下没有 Markdown 文件', + style: TextStyle(fontSize: 15, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(height: 4), - Text(_currentPath ?? '', style: const TextStyle(fontSize: 12, color: Color(0xFFCCCCCC))), + Text(_currentPath ?? '', + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.25))), const SizedBox(height: 24), GestureDetector( onTap: _canGoBack ? _goBack : () => _pickDirectory(), child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), decoration: BoxDecoration( - border: Border.all(color: const Color(0xFFDDDDDD)), + border: Border.all(color: colors.onSurface.withValues(alpha: 0.15)), borderRadius: BorderRadius.circular(20), ), child: Text( _canGoBack ? '返回上级目录' : '换一个目录', - style: const TextStyle(fontSize: 13, color: Color(0xFF888888)), + style: TextStyle( + fontSize: 13, color: colors.onSurface.withValues(alpha: 0.5)), ), ), ), @@ -492,18 +550,22 @@ class _MdReaderTabPageState extends State { ); } - Widget _buildError() { + Widget _buildError(ColorScheme colors) { return Center( child: Padding( padding: const EdgeInsets.all(40), child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.error_outline, size: 48, color: Color(0xFFCCCCCC)), + Icon(Icons.error_outline, + size: 48, color: colors.onSurface.withValues(alpha: 0.25)), const SizedBox(height: 16), - Text(_error!, style: const TextStyle(fontSize: 14, color: Color(0xFF999999)), textAlign: TextAlign.center), + Text(_error!, + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.4)), + textAlign: TextAlign.center), const SizedBox(height: 8), - Text('路径: ${_currentPath ?? ""}', style: const TextStyle(fontSize: 12, color: Color(0xFFCCCCCC))), + Text('路径: ${_currentPath ?? ""}', + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.25))), const SizedBox(height: 24), Row( mainAxisSize: MainAxisSize.min, @@ -513,10 +575,11 @@ class _MdReaderTabPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(20), ), - child: const Text('重试', style: TextStyle(fontSize: 13, color: Colors.white)), + child: Text('重试', + style: TextStyle(fontSize: 13, color: colors.onPrimary)), ), ), const SizedBox(width: 12), @@ -525,10 +588,12 @@ class _MdReaderTabPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), decoration: BoxDecoration( - border: Border.all(color: const Color(0xFFDDDDDD)), + border: Border.all(color: colors.onSurface.withValues(alpha: 0.15)), borderRadius: BorderRadius.circular(20), ), - child: const Text('更换目录', style: TextStyle(fontSize: 13, color: Color(0xFF888888))), + child: Text('更换目录', + style: TextStyle( + fontSize: 13, color: colors.onSurface.withValues(alpha: 0.5))), ), ), ], diff --git a/lib/pages/markdown_reader/md_viewer_page.dart b/lib/pages/markdown_reader/md_viewer_page.dart index 0a26e04..26f6414 100644 --- a/lib/pages/markdown_reader/md_viewer_page.dart +++ b/lib/pages/markdown_reader/md_viewer_page.dart @@ -50,10 +50,11 @@ class _MdViewerPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; final fileName = widget.filePath.split('/').last; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( elevation: 0, title: Text( @@ -61,69 +62,71 @@ class _MdViewerPageState extends State { style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600), ), ), - body: _buildBody(), + body: _buildBody(colors), ); } - Widget _buildBody() { + Widget _buildBody(ColorScheme colors) { if (_isLoading) { - return const Center(child: CircularProgressIndicator(color: Color(0xFF1A1A1A))); + return Center(child: CircularProgressIndicator(color: colors.primary)); } if (_error != null) { - return _buildErrorState(); + return _buildErrorState(colors); } return Markdown( data: _content, padding: const EdgeInsets.all(20), styleSheet: MarkdownStyleSheet( - h1: const TextStyle( + h1: TextStyle( fontSize: 22, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.4, ), - h2: const TextStyle( + h2: TextStyle( fontSize: 20, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.4, ), - h3: const TextStyle( + h3: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.4, ), - p: const TextStyle( + p: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.8, ), - code: const TextStyle( + code: TextStyle( fontSize: 13, - color: Color(0xFF1A1A1A), - backgroundColor: Color(0xFFF5F5F5), + color: colors.onSurface, + backgroundColor: colors.surfaceContainerHighest, ), codeblockDecoration: BoxDecoration( - color: const Color(0xFFF5F5F5), - border: Border.all(color: const Color(0xFFE5E5E5)), + color: colors.surfaceContainerHighest, + border: Border.all(color: colors.outline), borderRadius: BorderRadius.circular(6), ), codeblockPadding: const EdgeInsets.all(12), - blockquote: const TextStyle( + blockquote: TextStyle( fontSize: 15, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), fontStyle: FontStyle.italic, ), - blockquoteDecoration: const BoxDecoration( - border: Border(left: BorderSide(color: Color(0xFF999999), width: 4)), + blockquoteDecoration: BoxDecoration( + border: Border( + left: BorderSide( + color: colors.onSurface.withValues(alpha: 0.4), width: 4)), ), blockquotePadding: const EdgeInsets.only(left: 12), - listBullet: const TextStyle( + listBullet: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), listIndent: 24, a: const TextStyle( @@ -133,12 +136,12 @@ class _MdViewerPageState extends State { ), ), // ignore: deprecated_member_use - imageBuilder: (uri, title, alt) => _buildImage(uri.toString(), alt), + imageBuilder: (uri, title, alt) => _buildImage(colors, uri.toString(), alt), ); } /// 构建图片显示 - Widget _buildImage(String uri, String? alt) { + Widget _buildImage(ColorScheme colors, String uri, String? alt) { if (uri.isEmpty) return const SizedBox.shrink(); // 处理相对路径:基于 md 文件所在目录 @@ -158,17 +161,19 @@ class _MdViewerPageState extends State { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ - const Icon(Icons.broken_image_outlined, size: 20, color: Color(0xFF999999)), + Icon(Icons.broken_image_outlined, + size: 20, color: colors.onSurface.withValues(alpha: 0.4)), const SizedBox(width: 8), Expanded( child: Text( alt ?? '图片加载失败', - style: const TextStyle(fontSize: 13, color: Color(0xFF999999)), + style: + TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)), ), ), ], @@ -179,16 +184,17 @@ class _MdViewerPageState extends State { ); } - Widget _buildErrorState() { + Widget _buildErrorState(ColorScheme colors) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.error_outline, size: 48, color: Color(0xFFCCCCCC)), + Icon(Icons.error_outline, + size: 48, color: colors.onSurface.withValues(alpha: 0.25)), const SizedBox(height: 16), Text( _error!, - style: const TextStyle(fontSize: 14, color: Color(0xFF999999)), + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.4)), ), ], ), diff --git a/lib/pages/movies/douban_webview_page.dart b/lib/pages/movies/douban_webview_page.dart index e6735db..b67f3f8 100644 --- a/lib/pages/movies/douban_webview_page.dart +++ b/lib/pages/movies/douban_webview_page.dart @@ -5,9 +5,9 @@ import 'package:webview_flutter/webview_flutter.dart'; /// 豆瓣影视WebView页面 - 用于抓取影视信息 class DoubanWebViewPage extends StatefulWidget { final String url; - + const DoubanWebViewPage({super.key, required this.url}); - + @override State createState() => _DoubanWebViewPageState(); } @@ -16,21 +16,21 @@ class _DoubanWebViewPageState extends State { late WebViewController _controller; bool _isLoading = true; bool _canExtract = false; - bool _isExtracting = false; // 防止重复提取 - + bool _isExtracting = false; // 防止重复提取 + @override void initState() { super.initState(); _initWebView(); } - + @override void dispose() { // 清理 WebView 资源 _controller.loadRequest(Uri.parse('about:blank')); super.dispose(); } - + void _initWebView() { _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) @@ -55,23 +55,26 @@ class _DoubanWebViewPageState extends State { ) ..loadRequest(Uri.parse(widget.url)); } - + @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: const Text('豆瓣影视'), leading: _buildBackButton(), actions: [ // 提取按钮 - 始终显示 _buildActionButton( + colors: colors, icon: Icons.auto_fix_high_outlined, onPressed: _showExtractedInfo, tooltip: '提取信息', ), // 刷新按钮 _buildActionButton( + colors: colors, icon: Icons.refresh, onPressed: () => _controller.reload(), tooltip: '刷新', @@ -83,16 +86,14 @@ class _DoubanWebViewPageState extends State { children: [ WebViewWidget(controller: _controller), // 加载指示器 - if (_isLoading) - const Center( - child: CircularProgressIndicator(), - ), + if (_isLoading) const Center( + child: CircularProgressIndicator(), + ), ], ), - ); } - + /// 构建返回按钮 Widget _buildBackButton() { return Container( @@ -121,6 +122,7 @@ class _DoubanWebViewPageState extends State { /// 构建右上角操作按钮 Widget _buildActionButton({ + required ColorScheme colors, required IconData icon, required VoidCallback onPressed, required String tooltip, @@ -138,75 +140,86 @@ class _DoubanWebViewPageState extends State { borderRadius: BorderRadius.circular(8), child: Container( padding: const EdgeInsets.all(8), - child: Icon(icon, color: const Color(0xFF1A1A1A), size: 22), + child: Icon(icon, color: colors.onSurface, size: 22), ), ), ), ); } - + /// 显示提取的信息对话框 Future _showExtractedInfo() async { // 先提取信息 final movieInfo = await _extractMovieInfo(); if (movieInfo == null) return; - + // 显示提取的信息 if (mounted) { showDialog( context: context, - builder: (context) => AlertDialog( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text( - '提取的影视信息', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), - ), - ), - content: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildInfoRow('标题', movieInfo['title']?.toString() ?? '未提取到'), - _buildInfoRow('导演', movieInfo['director']?.toString() ?? '未提取到'), - _buildInfoRow('类型', movieInfo['genres']?.toString() ?? '未提取到'), - _buildInfoRow('上映日期', movieInfo['releaseDate']?.toString() ?? '未提取到'), - if (movieInfo['summary'] != null) - _buildInfoRow('简介', movieInfo['summary'].toString().substring(0, - movieInfo['summary'].toString().length > 100 ? 100 : movieInfo['summary'].toString().length) + '...'), - ], - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text( - '取消', - style: TextStyle(color: Color(0xFF999999)), + builder: (ctx) { + final colors = Theme.of(ctx).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + title: Text( + '提取的影视信息', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: colors.onSurface, ), ), - TextButton( - onPressed: () { - Navigator.pop(context); - Navigator.pop(context, movieInfo); - }, - child: const Text( - '使用此信息', - style: TextStyle(color: Color(0xFF1A1A1A), fontWeight: FontWeight.w600), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow(colors, '标题', movieInfo['title']?.toString() ?? '未提取到'), + _buildInfoRow(colors, '导演', movieInfo['director']?.toString() ?? '未提取到'), + _buildInfoRow(colors, '类型', movieInfo['genres']?.toString() ?? '未提取到'), + _buildInfoRow(colors, '上映日期', movieInfo['releaseDate']?.toString() ?? '未提取到'), + if (movieInfo['summary'] != null) + _buildInfoRow( + colors, + '简介', + movieInfo['summary'].toString().substring( + 0, + movieInfo['summary'].toString().length > 100 + ? 100 + : movieInfo['summary'].toString().length) + + '...'), + ], ), ), - ], - ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: Text( + '取消', + style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4)), + ), + ), + TextButton( + onPressed: () { + Navigator.pop(ctx); + Navigator.pop(context, movieInfo); + }, + child: Text( + '使用此信息', + style: TextStyle( + color: colors.onSurface, fontWeight: FontWeight.w600), + ), + ), + ], + ); + }, ); } } - + /// 构建信息行 - Widget _buildInfoRow(String label, String value) { + Widget _buildInfoRow(ColorScheme colors, String label, String value) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( @@ -216,18 +229,18 @@ class _DoubanWebViewPageState extends State { width: 64, child: Text( label, - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), Expanded( child: Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ), @@ -240,10 +253,10 @@ class _DoubanWebViewPageState extends State { Future?> _extractMovieInfo() async { // 检查是否已提取过,避免重复点击 if (_isExtracting) return null; - + try { _isExtracting = true; - + // 显示加载提示 showDialog( context: context, @@ -252,16 +265,16 @@ class _DoubanWebViewPageState extends State { child: CircularProgressIndicator(), ), ); - + // 执行JavaScript代码提取页面信息 final result = await _controller.runJavaScriptReturningResult(r''' (function() { const info = {}; - + // 获取标题 - 移动版页面 const titleEl = document.querySelector('.sub-title'); info.title = titleEl ? titleEl.textContent.trim() : ''; - + // 获取年份 - 从 original-title 中提取 const originalTitleEl = document.querySelector('.sub-original-title'); if (originalTitleEl) { @@ -270,7 +283,7 @@ class _DoubanWebViewPageState extends State { } else { info.year = ''; } - + // 获取封面图 - 从 sub-cover 中的 img 标签获取 const coverEl = document.querySelector('.sub-cover img'); if (coverEl) { @@ -283,11 +296,11 @@ class _DoubanWebViewPageState extends State { } else { info.coverUrl = ''; } - + // 获取评分 - 移动版可能在 mark-item 中 const ratingEl = document.querySelector('.rating-num') || document.querySelector('.score'); info.rating = ratingEl ? ratingEl.textContent.trim() : ''; - + // 获取导演 - 从演职员列表中找 const directorEl = document.querySelector('.movie-celebrities .item__celebrity .role'); if (directorEl && directorEl.textContent.includes('导演')) { @@ -296,7 +309,7 @@ class _DoubanWebViewPageState extends State { } else { info.director = ''; } - + // 获取编剧 - 从演职员列表中找(匹配"编剧"或"剧本") const writerEls = document.querySelectorAll('.movie-celebrities .item__celebrity'); const writers = []; @@ -308,7 +321,7 @@ class _DoubanWebViewPageState extends State { } }); info.writers = writers; - + // 获取主演- 从演职员列表中找前5个 const actorEls = document.querySelectorAll('.movie-celebrities .item__celebrity'); const actors = []; @@ -316,10 +329,10 @@ class _DoubanWebViewPageState extends State { const roleEl = el .querySelector('.role'); if (roleEl && ( - roleEl.textContent.includes('配音') || - roleEl.textContent.includes('主演') || + roleEl.textContent.includes('配音') || + roleEl.textContent.includes('主演') || roleEl.textContent.includes('演员') || - roleEl.textContent.includes('参演') || + roleEl.textContent.includes('参演') || roleEl.textContent.includes('饰') )) { const nameEl = el.querySelector('.name'); @@ -327,20 +340,20 @@ class _DoubanWebViewPageState extends State { } }); info.actors = actors; - + // 获取类型 - 从 sub-meta 或标签中提取 const metaEl = document.querySelector('.sub-meta'); if (metaEl) { const metaText = metaEl.textContent; const parts = metaText.split('/').map(s => s.trim()); // 过滤出类型(通常是中文,不是日期,不是时长) - info.genres = parts.filter(p => + info.genres = parts.filter(p => p && !p.match(/^\d{4}/) && !p.includes('分钟') && !p.includes('上映') ).join(','); } else { info.genres = ''; } - + // 获取上映日期 if (metaEl) { const dateMatch = metaEl.textContent.match(/(\d{4}-\d{2}-\d{2})/); @@ -348,7 +361,7 @@ class _DoubanWebViewPageState extends State { } else { info.releaseDate = ''; } - + // 获取简介 const summaryEl = document.querySelector('.subject-intro p'); if (summaryEl) { @@ -356,7 +369,7 @@ class _DoubanWebViewPageState extends State { } else { info.summary = ''; } - + // 获取别名 - 从 original-title 中提取(去掉年份) if (originalTitleEl) { const fullText = originalTitleEl.textContent.trim(); @@ -364,14 +377,14 @@ class _DoubanWebViewPageState extends State { } else { info.alternateTitles = []; } - + return JSON.stringify(info); })() '''); - + // 关闭加载提示 if (mounted) Navigator.pop(context); - + // 解析提取的信息 // result 是 JavaScript 执行结果,已经是 JSON 字符串(带引号的) final String jsonStr = result.toString(); @@ -380,12 +393,12 @@ class _DoubanWebViewPageState extends State { ? jsonDecode(jsonStr) as String : jsonStr; final Map movieInfo = jsonDecode(cleanJson) as Map; - + return movieInfo; } catch (e) { // 关闭加载提示 if (mounted) Navigator.pop(context); - + if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('提取信息失败: $e')), diff --git a/lib/pages/movies/movie_detail_page.dart b/lib/pages/movies/movie_detail_page.dart index 3483c86..d94a69a 100644 --- a/lib/pages/movies/movie_detail_page.dart +++ b/lib/pages/movies/movie_detail_page.dart @@ -16,9 +16,9 @@ import 'movie_share_page.dart'; /// 影视详情页 - 极简主义设计 class MovieDetailPage extends StatefulWidget { final Movie movie; - + const MovieDetailPage({super.key, required this.movie}); - + @override State createState() => _MovieDetailPageState(); } @@ -27,77 +27,53 @@ class _MovieDetailPageState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - // 页面获得焦点时刷新数据 _refreshMovieData(); } - + void _refreshMovieData() { final provider = context.read(); - // 强制刷新当前影视数据 provider.loadMovies(); } - + @override Widget build(BuildContext context) { - // 从 Provider 获取最新的 movie 数据,实现动态刷新 + final colors = Theme.of(context).colorScheme; final movie = context.watch().movies .where((m) => m.id == widget.movie.id) .firstOrNull ?? widget.movie; - + return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, body: Stack( children: [ CustomScrollView( slivers: [ - // 顶部海报区域 _buildSliverAppBar(movie), - - // 内容区域 SliverToBoxAdapter( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 基本信息 _buildBasicInfo(movie), - - const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)), - - // 导演 + Divider(height: 0.5, thickness: 0.5, color: colors.outline), if (movie.directors.isNotEmpty) _buildDirectorsSection(movie), - - // 编剧 if (movie.writers.isNotEmpty) _buildWritersSection(movie), - - // 主演 if (movie.actors.isNotEmpty) _buildActorsSection(movie), - - // 类型 if (movie.genres.isNotEmpty) _buildGenresSection(movie), - - const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)), - - // 简介 + Divider(height: 0.5, thickness: 0.5, color: colors.outline), if (movie.summary != null && movie.summary!.isNotEmpty) _buildSummarySection(movie), - - const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)), - - // 影评和海报墙入口 + Divider(height: 0.5, thickness: 0.5, color: colors.outline), _buildExtraSections(movie), - const SizedBox(height: 120), ], ), ), ], ), - - // 右下角悬浮按钮组 Positioned( right: 16, bottom: 24, @@ -107,9 +83,9 @@ class _MovieDetailPageState extends State { ), ); } - - /// 构建右下角悬浮按钮组 + Widget _buildFloatingActionButtons(Movie movie) { + final colors = Theme.of(context).colorScheme; return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -117,13 +93,16 @@ class _MovieDetailPageState extends State { icon: Icons.edit_outlined, onPressed: () => _navigateToEdit(context), tooltip: '编辑', + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, ), const SizedBox(height: 12), _buildFloatingButton( icon: Icons.delete_outline, onPressed: () => _showDeleteDialog(context), tooltip: '删除', - backgroundColor: Colors.red, + backgroundColor: colors.error, + foregroundColor: colors.onError, ), const SizedBox(height: 12), _buildFloatingButton( @@ -131,17 +110,18 @@ class _MovieDetailPageState extends State { onPressed: () => _showSharePoster(movie), tooltip: '分享海报', backgroundColor: const Color(0xFF4CAF50), + foregroundColor: Colors.white, ), ], ); } - /// 构建单个悬浮按钮 Widget _buildFloatingButton({ required IconData icon, required VoidCallback onPressed, required String tooltip, - Color backgroundColor = const Color(0xFF1A1A1A), + required Color backgroundColor, + required Color foregroundColor, }) { return Material( color: Colors.transparent, @@ -160,15 +140,15 @@ class _MovieDetailPageState extends State { ], ), child: IconButton( - icon: Icon(icon, size: 18, color: Colors.white), + icon: Icon(icon, size: 18, color: foregroundColor), onPressed: onPressed, + padding: EdgeInsets.zero, tooltip: tooltip, ), ), ); } - /// 构建带背景的返回按钮 Widget _buildBackButton() { return Container( margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), @@ -194,21 +174,19 @@ class _MovieDetailPageState extends State { ); } - /// 构建顶部 AppBar Widget _buildSliverAppBar(Movie movie) { + final colors = Theme.of(context).colorScheme; return SliverAppBar( expandedHeight: 320, pinned: true, - backgroundColor: const Color(0xFFF5F5F5), + backgroundColor: colors.surfaceContainerHighest, leading: _buildBackButton(), flexibleSpace: FlexibleSpaceBar( background: _buildPosterSection(movie), ), - // 右上角按钮已移到右下角悬浮按钮 ); } - - /// 构建海报区域 + Widget _buildPosterSection(Movie movie) { return SizedBox.expand( child: movie.posterPath != null && movie.posterPath!.isNotEmpty @@ -220,79 +198,74 @@ class _MovieDetailPageState extends State { : _buildPosterPlaceholder(), ); } - + Widget _buildPosterPlaceholder() { - return const Center( + final colors = Theme.of(context).colorScheme; + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.movie_outlined, size: 64, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), - SizedBox(height: 16), + const SizedBox(height: 16), Text( '暂无海报', style: TextStyle( fontSize: 14, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], ), ); } - - /// 构建基本信息 + Widget _buildBasicInfo(Movie movie) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 影视名称 Text( movie.title, - style: const TextStyle( + style: TextStyle( fontSize: 24, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.3, ), ), - - // 别名(显示在主名称下面,用 / 分隔) if (movie.alternateTitles.isNotEmpty) ...[ const SizedBox(height: 8), Text( movie.alternateTitles.join(' / '), - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), height: 1.4, ), ), ], - const SizedBox(height: 16), - - // 评分和状态 Row( children: [ if (movie.rating != null) ...[ - const Icon( + Icon( Icons.star, size: 20, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), const SizedBox(width: 4), Text( movie.rating!.toStringAsFixed(1), - style: const TextStyle( + style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), const SizedBox(width: 16), @@ -300,62 +273,56 @@ class _MovieDetailPageState extends State { _buildStatusTag(movie), ], ), - const SizedBox(height: 8), - - // 上映日期 if (movie.releaseDate != null) Text( '${movie.releaseDate!.year}年${movie.releaseDate!.month.toString().padLeft(2, '0')}月上映', - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), - const SizedBox(height: 8), - - // 观看日期 if (movie.watchDate != null) Text( '观看于 ${_formatDate(movie.watchDate!)}', - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], ), ); } - - /// 构建状态标签 + Widget _buildStatusTag(Movie movie) { + final colors = Theme.of(context).colorScheme; String label; Color bgColor; Color textColor; switch (movie.status) { case 'watched': label = '已看'; - bgColor = const Color(0xFF1A1A1A); - textColor = Colors.white; + bgColor = colors.primary; + textColor = colors.onPrimary; break; case 'watching': label = '在看'; - bgColor = const Color(0xFFF0F0F0); - textColor = const Color(0xFF666666); + bgColor = colors.outlineVariant; + textColor = colors.onSurface.withValues(alpha: 0.6); break; case 'want_to_watch': label = '想看'; - bgColor = const Color(0xFFF5F5F5); - textColor = const Color(0xFF999999); + bgColor = colors.surfaceContainerHighest; + textColor = colors.onSurface.withValues(alpha: 0.4); break; default: label = '未知'; - bgColor = const Color(0xFFEEEEEE); - textColor = const Color(0xFFCCCCCC); + bgColor = colors.outlineVariant; + textColor = colors.onSurface.withValues(alpha: 0.25); } - + return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( @@ -372,30 +339,30 @@ class _MovieDetailPageState extends State { ), ); } - - /// 构建导演区域 + Widget _buildDirectorsSection(Movie movie) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 48, child: Text( '导演', style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), Expanded( child: Text( movie.directors.join(','), - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.5, ), ), @@ -405,29 +372,29 @@ class _MovieDetailPageState extends State { ); } - /// 构建编剧区域 Widget _buildWritersSection(Movie movie) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 48, child: Text( '编剧', style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), Expanded( child: Text( movie.writers.join(','), - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.5, ), ), @@ -437,29 +404,29 @@ class _MovieDetailPageState extends State { ); } - /// 构建主演区域 Widget _buildActorsSection(Movie movie) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 48, child: Text( '主演', style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), Expanded( child: Text( movie.actors.join(','), - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.5, ), ), @@ -469,20 +436,20 @@ class _MovieDetailPageState extends State { ); } - /// 构建类型区域 Widget _buildGenresSection(Movie movie) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 48, child: Text( '类型', style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), @@ -494,14 +461,14 @@ class _MovieDetailPageState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(4), ), child: Text( genre, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), ); @@ -512,9 +479,9 @@ class _MovieDetailPageState extends State { ), ); } - - /// 构建简介区域 + Widget _buildSummarySection(Movie movie) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.all(24), child: Column( @@ -526,17 +493,17 @@ class _MovieDetailPageState extends State { width: 4, height: 16, decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.onSurface, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), - const Text( + Text( '简介', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ], @@ -545,14 +512,14 @@ class _MovieDetailPageState extends State { Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), ), child: Text( movie.summary!, - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.8, ), ), @@ -561,9 +528,9 @@ class _MovieDetailPageState extends State { ), ); } - - /// 构建额外功能区域(影评、海报墙) + Widget _buildExtraSections(Movie movie) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.all(24), child: Column( @@ -575,23 +542,22 @@ class _MovieDetailPageState extends State { width: 4, height: 16, decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.onSurface, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), - const Text( + Text( '更多', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ], ), const SizedBox(height: 16), - // 影评入口 _buildExtraSectionItem( icon: Icons.rate_review_outlined, title: '影评', @@ -601,7 +567,6 @@ class _MovieDetailPageState extends State { onTap: () => _navigateToReviews(movie), ), const SizedBox(height: 12), - // 海报墙入口 _buildExtraSectionItem( icon: Icons.photo_library_outlined, title: '海报墙', @@ -615,7 +580,6 @@ class _MovieDetailPageState extends State { ); } - /// 构建更多区域项 Widget _buildExtraSectionItem({ required IconData icon, required String title, @@ -624,14 +588,15 @@ class _MovieDetailPageState extends State { required String unit, required VoidCallback onTap, }) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), child: Row( children: [ @@ -639,14 +604,14 @@ class _MovieDetailPageState extends State { width: 40, height: 40, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), child: Icon( icon, size: 20, - color: const Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 12), @@ -656,10 +621,10 @@ class _MovieDetailPageState extends State { children: [ Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), const SizedBox(height: 4), @@ -669,9 +634,9 @@ class _MovieDetailPageState extends State { final count = snapshot.data ?? 0; return Text( count > 0 ? '$count $unit' : emptyText, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ); }, @@ -679,16 +644,16 @@ class _MovieDetailPageState extends State { ], ), ), - const Icon( + Icon( Icons.chevron_right, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), ], ), ), ); } - + void _navigateToReviews(Movie movie) { Navigator.push( context, @@ -697,7 +662,7 @@ class _MovieDetailPageState extends State { ), ); } - + void _navigateToPosters(Movie movie) { Navigator.push( context, @@ -706,39 +671,38 @@ class _MovieDetailPageState extends State { ), ); } - - /// 格式化日期 + String _formatDate(DateTime date) { return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; } - - /// 跳转到编辑页面 + void _navigateToEdit(BuildContext context) { Navigator.pushNamed(context, '/movie-form', arguments: widget.movie).then((_) { context.read().loadMovies(); }); } - - /// 显示删除对话框 + void _showDeleteDialog(BuildContext context) { + final colors = Theme.of(context).colorScheme; showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text( + title: Text( '确认删除', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, + color: colors.onSurface, ), ), content: Text( '确定要删除"${widget.movie.title}"吗?删除后可在回收站恢复。', - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.5, ), ), @@ -746,7 +710,7 @@ class _MovieDetailPageState extends State { TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( - foregroundColor: const Color(0xFF666666), + foregroundColor: colors.onSurface.withValues(alpha: 0.6), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), child: const Text('取消'), @@ -760,8 +724,8 @@ class _MovieDetailPageState extends State { ToastUtil.show(context, '已删除'); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + backgroundColor: colors.error, + foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -775,18 +739,14 @@ class _MovieDetailPageState extends State { ), ); } - - /// 请求存储权限 + Future _requestStoragePermission() async { - // Android 13+ 使用新的权限 if (Platform.isAndroid) { final sdkInt = await _getAndroidSdkInt(); if (sdkInt >= 33) { - // Android 13+ 使用 READ_MEDIA_IMAGES final status = await Permission.photos.request(); return status.isGranted; } else { - // Android 12 及以下使用存储权限 var status = await Permission.storage.request(); if (status.isDenied) { status = await Permission.storage.request(); @@ -794,18 +754,13 @@ class _MovieDetailPageState extends State { return status.isGranted; } } - // iOS 不需要额外权限来保存到应用沙盒 return true; } - - /// 获取 Android SDK 版本 + Future _getAndroidSdkInt() async { - // 简化处理,实际可以通过 platform channel 获取 - // 这里默认返回较低版本,使用传统存储权限 return 30; } - /// 显示分享海报页面 void _showSharePoster(Movie movie) { Navigator.push( context, diff --git a/lib/pages/movies/movie_form_page.dart b/lib/pages/movies/movie_form_page.dart index 61c6d49..76dd575 100644 --- a/lib/pages/movies/movie_form_page.dart +++ b/lib/pages/movies/movie_form_page.dart @@ -14,9 +14,9 @@ import '../../utils/image_path_helper.dart'; class MovieFormPage extends StatefulWidget { final Movie? movie; final String? initialStatus; // 添加时的默认状态 - + const MovieFormPage({super.key, this.movie, this.initialStatus}); - + @override State createState() => _MovieFormPageState(); } @@ -24,15 +24,15 @@ class MovieFormPage extends StatefulWidget { class _MovieFormPageState extends State { final _formKey = GlobalKey(); final ImagePicker _picker = ImagePicker(); - + // 输入框控制器 late TextEditingController _titleController; late TextEditingController _summaryController; late TextEditingController _ratingController; - + // 多值字段的临时输入控制器 final Map _tagControllers = {}; - + // 数据 List _directors = []; List _writers = []; @@ -43,13 +43,13 @@ class _MovieFormPageState extends State { String _status = 'want_to_watch'; DateTime? _releaseDate; DateTime? _watchDate; - + @override void initState() { super.initState(); _initializeData(); } - + void _initializeData() { // 如果有传入movie,尝试从Provider获取最新数据 Movie? movie = widget.movie; @@ -62,11 +62,11 @@ class _MovieFormPageState extends State { movie = latestMovie; } } - + _titleController = TextEditingController(text: movie?.title ?? ''); _summaryController = TextEditingController(text: movie?.summary ?? ''); _ratingController = TextEditingController(text: movie?.rating?.toString() ?? ''); - + if (movie != null) { _directors = List.from(movie.directors); _writers = List.from(movie.writers); @@ -82,7 +82,7 @@ class _MovieFormPageState extends State { _status = widget.initialStatus!; } } - + @override void dispose() { // 先 dispose controllers @@ -93,7 +93,7 @@ class _MovieFormPageState extends State { // 最后调用 super.dispose super.dispose(); } - + TextEditingController _getTagController(String key) { return _tagControllers.putIfAbsent(key, () => TextEditingController()); } @@ -103,12 +103,14 @@ class _MovieFormPageState extends State { required IconData icon, required VoidCallback onPressed, required String tooltip, - Color color = const Color(0xFF1A1A1A), + Color? color, }) { + final colors = Theme.of(context).colorScheme; + final iconColor = color ?? colors.onSurface; return Container( margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.9), + color: colors.surface.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(8), ), child: Material( @@ -120,7 +122,7 @@ class _MovieFormPageState extends State { padding: const EdgeInsets.all(8), child: Icon( icon, - color: color, + color: iconColor, size: 22, ), ), @@ -132,54 +134,86 @@ class _MovieFormPageState extends State { /// 显示快捷添加对话框 void _showQuickAddDialog() { final textController = TextEditingController(); - + showDialog( context: context, - builder: (dialogContext) => AlertDialog( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text( - '快捷添加', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + builder: (dialogContext) { + final colors = Theme.of(dialogContext).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + title: Text( + '快捷添加', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: colors.onSurface, + ), ), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - '请输入分享的豆瓣影视链接:', - style: TextStyle( - fontSize: 14, - color: Color(0xFF666666), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '请输入分享的豆瓣影视链接:', + style: TextStyle( + fontSize: 14, + color: colors.onSurface.withValues(alpha: 0.6), + ), + ), + const SizedBox(height: 12), + TextField( + controller: textController, + autofocus: true, + decoration: InputDecoration( + hintText: 'https://m.douban.com/subject/...', + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.25)), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: colors.outline), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: colors.outline), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: colors.primary, width: 1.5), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + ), + onSubmitted: (url) async { + if (url.trim().isNotEmpty) { + // 先移除焦点 + FocusManager.instance.primaryFocus?.unfocus(); + await Future.delayed(const Duration(milliseconds: 50)); + if (dialogContext.mounted) { + Navigator.of(dialogContext).pop(url); + } + } + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () async { + // 先移除焦点,等待一帧确保焦点已释放 + FocusManager.instance.primaryFocus?.unfocus(); + await Future.delayed(const Duration(milliseconds: 50)); + if (dialogContext.mounted) { + Navigator.of(dialogContext).pop(null); + } + }, + child: Text( + '取消', + style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4)), ), ), - const SizedBox(height: 12), - TextField( - controller: textController, - autofocus: true, - decoration: InputDecoration( - hintText: 'https://m.douban.com/subject/...', - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE5E5E5)), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE5E5E5)), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFF1A1A1A)), - ), - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), - ), - onSubmitted: (url) async { - if (url.trim().isNotEmpty) { + TextButton( + onPressed: () async { + final url = textController.text.trim(); + if (url.isNotEmpty) { // 先移除焦点 FocusManager.instance.primaryFocus?.unfocus(); await Future.delayed(const Duration(milliseconds: 50)); @@ -188,43 +222,14 @@ class _MovieFormPageState extends State { } } }, + child: Text( + '确定', + style: TextStyle(color: colors.onSurface, fontWeight: FontWeight.w600), + ), ), ], - ), - actions: [ - TextButton( - onPressed: () async { - // 先移除焦点,等待一帧确保焦点已释放 - FocusManager.instance.primaryFocus?.unfocus(); - await Future.delayed(const Duration(milliseconds: 50)); - if (dialogContext.mounted) { - Navigator.of(dialogContext).pop(null); - } - }, - child: const Text( - '取消', - style: TextStyle(color: Color(0xFF999999)), - ), - ), - TextButton( - onPressed: () async { - final url = textController.text.trim(); - if (url.isNotEmpty) { - // 先移除焦点 - FocusManager.instance.primaryFocus?.unfocus(); - await Future.delayed(const Duration(milliseconds: 50)); - if (dialogContext.mounted) { - Navigator.of(dialogContext).pop(url); - } - } - }, - child: const Text( - '确定', - style: TextStyle(color: Color(0xFF1A1A1A), fontWeight: FontWeight.w600), - ), - ), - ], - ), + ); + }, ).then((result) { // 延迟 dispose,确保 widget tree 已释放 controller WidgetsBinding.instance.addPostFrameCallback((_) { @@ -245,13 +250,13 @@ class _MovieFormPageState extends State { '/douban-webview', arguments: url, ); - + // 处理返回的影视信息 if (result != null && result is Map) { _fillMovieInfo(result); } } - + /// 填充影视信息到表单 void _fillMovieInfo(Map info) { setState(() { @@ -259,42 +264,42 @@ class _MovieFormPageState extends State { if (info['title'] != null && info['title'].toString().isNotEmpty) { _titleController.text = info['title'].toString(); } - + // 填充评分 if (info['rating'] != null && info['rating'].toString().isNotEmpty) { _ratingController.text = info['rating'].toString(); } - + // 填充导演 if (info['director'] != null && info['director'].toString().isNotEmpty) { _directors = [info['director'].toString()]; } - + // 填充编剧 if (info['writers'] != null && info['writers'] is List) { _writers = (info['writers'] as List).map((w) => w.toString()).toList(); } - + // 填充演员 if (info['actors'] != null && info['actors'] is List) { _actors = (info['actors'] as List).map((a) => a.toString()).toList(); } - + // 填充类型 if (info['genres'] != null && info['genres'].toString().isNotEmpty) { _genres = info['genres'].toString().split(',').map((g) => g.trim()).toList(); } - + // 填充别名 if (info['alternateTitles'] != null && info['alternateTitles'] is List) { _alternateTitles = (info['alternateTitles'] as List).map((t) => t.toString()).toList(); } - + // 填充简介 if (info['summary'] != null && info['summary'].toString().isNotEmpty) { _summaryController.text = info['summary'].toString(); } - + // 填充上映日期 if (info['releaseDate'] != null && info['releaseDate'].toString().isNotEmpty) { final dateStr = info['releaseDate'].toString(); @@ -307,14 +312,14 @@ class _MovieFormPageState extends State { // 解析失败则忽略 } } - + // 下载封面图 if (info['coverUrl'] != null && info['coverUrl'].toString().isNotEmpty) { _downloadCoverFromUrl(info['coverUrl'].toString()); } - + }); - + // 显示成功提示 if (mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -322,7 +327,7 @@ class _MovieFormPageState extends State { ); } } - + /// 从URL下载封面图 Future _downloadCoverFromUrl(String url) async { try { @@ -335,7 +340,7 @@ class _MovieFormPageState extends State { 'Referer': 'https://movie.douban.com/', }, ); - + if (response.statusCode != 200) { throw Exception('下载失败: HTTP ${response.statusCode}'); } @@ -353,13 +358,13 @@ class _MovieFormPageState extends State { // 生成文件名 final fileName = 'poster_${DateTime.now().millisecondsSinceEpoch}.jpg'; - + // 如果是编辑模式,使用现有影视ID;如果是新建模式,使用临时ID final movieId = widget.movie?.id ?? DateTime.now().millisecondsSinceEpoch.toString(); - + // 保存到新的路径结构 final targetPath = await ImagePathHelper.instance.getMoviePosterPath( - movieId, + movieId, fileName ); await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath)); @@ -373,13 +378,14 @@ class _MovieFormPageState extends State { debugPrint('封面下载失败: $e'); } } - + @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; final isEdit = widget.movie != null; - + return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: Text(isEdit ? '编辑影视' : '添加影视'), actions: [ @@ -406,19 +412,19 @@ class _MovieFormPageState extends State { children: [ // 封面选择 - 居中显示 Center(child: _buildCoverPicker()), - + const SizedBox(height: 32), - + // 状态选择(靠左显示) _buildStatusSelector(), - + const SizedBox(height: 20), - + // 评分 - 星星选择(靠左显示) _buildStarRating(), - + const SizedBox(height: 32), - + // 信息卡片网格 Wrap( spacing: 12, @@ -459,15 +465,15 @@ class _MovieFormPageState extends State { ), ), ), - + // 第二行:导演 + 编剧 SizedBox( width: (MediaQuery.of(context).size.width - 52) / 2, height: 90, child: _buildInfoCard( label: '导演', - value: _directors.isEmpty - ? '' + value: _directors.isEmpty + ? '' : '${_directors.length}人:${_directors.join('、')}', icon: Icons.videocam_outlined, onTap: () => _showMultiValueDialog( @@ -483,8 +489,8 @@ class _MovieFormPageState extends State { height: 90, child: _buildInfoCard( label: '编剧', - value: _writers.isEmpty - ? '' + value: _writers.isEmpty + ? '' : '${_writers.length}人:${_writers.join('、')}', icon: Icons.edit_note_outlined, onTap: () => _showMultiValueDialog( @@ -495,15 +501,15 @@ class _MovieFormPageState extends State { ), ), ), - + // 第三行:主演 + 类型 SizedBox( width: (MediaQuery.of(context).size.width - 52) / 2, height: 90, child: _buildInfoCard( label: '主演', - value: _actors.isEmpty - ? '' + value: _actors.isEmpty + ? '' : '${_actors.length}人:${_actors.join('、')}', icon: Icons.people_outline, onTap: () => _showMultiValueDialog( @@ -539,15 +545,15 @@ class _MovieFormPageState extends State { }, ), ), - + // 第四行:上映日期 + 观看日期 SizedBox( width: (MediaQuery.of(context).size.width - 52) / 2, height: 90, child: _buildInfoCard( label: '上映日期', - value: _releaseDate != null - ? '${_releaseDate!.year}.${_releaseDate!.month.toString().padLeft(2, '0')}.${_releaseDate!.day.toString().padLeft(2, '0')}' + value: _releaseDate != null + ? '${_releaseDate!.year}.${_releaseDate!.month.toString().padLeft(2, '0')}.${_releaseDate!.day.toString().padLeft(2, '0')}' : '', icon: Icons.theaters_outlined, onTap: () => _selectReleaseDate(), @@ -558,14 +564,14 @@ class _MovieFormPageState extends State { height: 90, child: _buildInfoCard( label: '观看日期', - value: _watchDate != null - ? '${_watchDate!.year}.${_watchDate!.month.toString().padLeft(2, '0')}.${_watchDate!.day.toString().padLeft(2, '0')}' + value: _watchDate != null + ? '${_watchDate!.year}.${_watchDate!.month.toString().padLeft(2, '0')}.${_watchDate!.day.toString().padLeft(2, '0')}' : '', icon: Icons.visibility_outlined, onTap: () => _selectWatchDate(), ), ), - + // 第五行:剧情简介(独占一行) SizedBox( width: double.infinity, @@ -586,14 +592,14 @@ class _MovieFormPageState extends State { ), ], ), - + const SizedBox(height: 48), ], ), ), ); } - + /// 构建信息卡片(用于网格布局) Widget _buildInfoCard({ required String label, @@ -606,6 +612,7 @@ class _MovieFormPageState extends State { bool scrollHorizontal = false, }) { final hasValue = value.isNotEmpty; + final colors = Theme.of(context).colorScheme; // 构建值显示部分 Widget buildContent() { @@ -618,7 +625,7 @@ class _MovieFormPageState extends State { hasValue ? value : '点击填写', style: TextStyle( fontSize: 15, - color: hasValue ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), + color: hasValue ? colors.onSurface : colors.onSurface.withValues(alpha: 0.25), fontWeight: hasValue ? FontWeight.w500 : FontWeight.normal, ), ), @@ -633,7 +640,7 @@ class _MovieFormPageState extends State { hasValue ? value : '点击填写', style: TextStyle( fontSize: 15, - color: hasValue ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), + color: hasValue ? colors.onSurface : colors.onSurface.withValues(alpha: 0.25), fontWeight: hasValue ? FontWeight.w500 : FontWeight.normal, ), ), @@ -644,7 +651,7 @@ class _MovieFormPageState extends State { hasValue ? value : '点击填写', style: TextStyle( fontSize: 15, - color: hasValue ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), + color: hasValue ? colors.onSurface : colors.onSurface.withValues(alpha: 0.25), fontWeight: hasValue ? FontWeight.w500 : FontWeight.normal, ), maxLines: 1, @@ -652,19 +659,19 @@ class _MovieFormPageState extends State { ); } } - + return GestureDetector( onTap: onTap, child: Container( height: height, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFE8E8E8)), + border: Border.all(color: colors.outline), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.03), + color: colors.onSurface.withValues(alpha: 0.018), blurRadius: 8, offset: const Offset(0, 2), ), @@ -678,14 +685,14 @@ class _MovieFormPageState extends State { Row( children: [ if (icon != null) ...[ - Icon(icon, size: 14, color: const Color(0xFF999999)), + Icon(icon, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), const SizedBox(width: 6), ], Text( required ? '$label *' : label, style: TextStyle( fontSize: 12, - color: required ? const Color(0xFF1A1A1A) : const Color(0xFF999999), + color: required ? colors.onSurface : colors.onSurface.withValues(alpha: 0.4), fontWeight: required ? FontWeight.w500 : FontWeight.normal, ), ), @@ -717,7 +724,7 @@ class _MovieFormPageState extends State { maxLines: maxLines, ), ); - + if (result != null) { onConfirm(result); } @@ -753,12 +760,13 @@ class _MovieFormPageState extends State { required VoidCallback onTap, required VoidCallback onClear, }) { + final colors = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, - style: const TextStyle(fontSize: 13, color: Color(0xFF999999)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)), ), const SizedBox(height: 8), GestureDetector( @@ -773,15 +781,15 @@ class _MovieFormPageState extends State { style: TextStyle( fontSize: 15, color: date != null - ? const Color(0xFF1A1A1A) - : const Color(0xFFCCCCCC), + ? colors.onSurface + : colors.onSurface.withValues(alpha: 0.25), ), ), ), if (date != null) GestureDetector( onTap: onClear, - child: const Icon(Icons.close, size: 18, color: Color(0xFF999999)), + child: Icon(Icons.close, size: 18, color: colors.onSurface.withValues(alpha: 0.4)), ), ], ), @@ -796,6 +804,7 @@ class _MovieFormPageState extends State { required Widget child, bool required = false, }) { + final colors = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -803,7 +812,7 @@ class _MovieFormPageState extends State { required ? '$label *' : label, style: TextStyle( fontSize: 13, - color: required ? const Color(0xFF1A1A1A) : const Color(0xFF999999), + color: required ? colors.onSurface : colors.onSurface.withValues(alpha: 0.4), fontWeight: required ? FontWeight.w500 : FontWeight.normal, ), ), @@ -811,9 +820,9 @@ class _MovieFormPageState extends State { Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFD0D0D0)), + border: Border.all(color: colors.onSurface.withValues(alpha: 0.2)), ), child: child, ), @@ -828,12 +837,13 @@ class _MovieFormPageState extends State { required VoidCallback onTap, required VoidCallback onClear, }) { + final colors = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, - style: const TextStyle(fontSize: 13, color: Color(0xFF999999)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)), ), const SizedBox(height: 8), GestureDetector( @@ -841,16 +851,16 @@ class _MovieFormPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFD0D0D0)), + border: Border.all(color: colors.onSurface.withValues(alpha: 0.2)), ), child: Row( children: [ Icon( Icons.calendar_today_outlined, size: 18, - color: date != null ? const Color(0xFF1A1A1A) : const Color(0xFFAAAAAA), + color: date != null ? colors.onSurface : colors.onSurface.withValues(alpha: 0.35), ), const SizedBox(width: 10), Expanded( @@ -861,8 +871,8 @@ class _MovieFormPageState extends State { style: TextStyle( fontSize: 15, color: date != null - ? const Color(0xFF1A1A1A) - : const Color(0xFFAAAAAA), + ? colors.onSurface + : colors.onSurface.withValues(alpha: 0.35), ), ), ), @@ -872,10 +882,10 @@ class _MovieFormPageState extends State { child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: const Color(0xFFEEEEEE), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(4), ), - child: const Icon(Icons.close, size: 14, color: Color(0xFF999999)), + child: Icon(Icons.close, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), ), ), ], @@ -888,12 +898,13 @@ class _MovieFormPageState extends State { /// 构建表单条目(标签 + 内容)- 剧情简介使用 Widget _buildFormItem({required String label, required Widget child}) { + final colors = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, - style: const TextStyle(fontSize: 13, color: Color(0xFF666666)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.6)), ), const SizedBox(height: 8), child, @@ -907,6 +918,7 @@ class _MovieFormPageState extends State { required Widget child, bool required = false, }) { + final colors = Theme.of(context).colorScheme; return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -916,7 +928,7 @@ class _MovieFormPageState extends State { required ? '$label *' : label, style: TextStyle( fontSize: 13, - color: required ? const Color(0xFF1A1A1A) : const Color(0xFF999999), + color: required ? colors.onSurface : colors.onSurface.withValues(alpha: 0.4), fontWeight: required ? FontWeight.w500 : FontWeight.normal, ), ), @@ -937,6 +949,7 @@ class _MovieFormPageState extends State { required Function(int) onRemove, }) { final controller = _getTagController(controllerKey); + final colors = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -946,7 +959,7 @@ class _MovieFormPageState extends State { children: [ Text( label, - style: const TextStyle(fontSize: 13, color: Color(0xFF999999)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)), ), const Spacer(), // 添加按钮 @@ -967,7 +980,7 @@ class _MovieFormPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: hasText ? const Color(0xFF1A1A1A) : const Color(0xFFF5F5F5), + color: hasText ? colors.primary : colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(6), ), child: Row( @@ -976,14 +989,14 @@ class _MovieFormPageState extends State { Icon( Icons.add, size: 14, - color: hasText ? Colors.white : const Color(0xFFCCCCCC), + color: hasText ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.25), ), const SizedBox(width: 4), Text( '添加', style: TextStyle( fontSize: 12, - color: hasText ? Colors.white : const Color(0xFFCCCCCC), + color: hasText ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.25), ), ), ], @@ -1004,7 +1017,7 @@ class _MovieFormPageState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(6), ), child: Row( @@ -1012,12 +1025,12 @@ class _MovieFormPageState extends State { children: [ Text( entry.value, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 14, color: colors.onSurface), ), const SizedBox(width: 4), GestureDetector( onTap: () => onRemove(entry.key), - child: const Icon(Icons.close, size: 14, color: Color(0xFF999999)), + child: Icon(Icons.close, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), ), ], ), @@ -1028,16 +1041,16 @@ class _MovieFormPageState extends State { // 输入框(带边框背景) Container( decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFD0D0D0)), + border: Border.all(color: colors.onSurface.withValues(alpha: 0.2)), ), child: TextField( controller: controller, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 14, color: colors.onSurface), decoration: InputDecoration( hintText: hint, - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFAAAAAA)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.35)), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), @@ -1064,7 +1077,8 @@ class _MovieFormPageState extends State { required Function(int) onRemove, }) { final controller = _getTagController(controllerKey); - + final colors = Theme.of(context).colorScheme; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1073,7 +1087,7 @@ class _MovieFormPageState extends State { children: [ Text( label, - style: const TextStyle(fontSize: 13, color: Color(0xFF666666)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.6)), ), const Spacer(), // 添加按钮(当输入框有内容时显示) @@ -1095,7 +1109,7 @@ class _MovieFormPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( border: Border.all( - color: hasText ? const Color(0xFF1A1A1A) : const Color(0xFFE5E5E5), + color: hasText ? colors.primary : colors.outline, ), ), child: Row( @@ -1104,14 +1118,14 @@ class _MovieFormPageState extends State { Icon( Icons.add, size: 14, - color: hasText ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), + color: hasText ? colors.primary : colors.onSurface.withValues(alpha: 0.25), ), const SizedBox(width: 2), Text( '添加', style: TextStyle( fontSize: 12, - color: hasText ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), + color: hasText ? colors.primary : colors.onSurface.withValues(alpha: 0.25), ), ), ], @@ -1133,20 +1147,20 @@ class _MovieFormPageState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), - border: Border.all(color: const Color(0xFFE5E5E5)), + color: colors.surfaceContainerHighest, + border: Border.all(color: colors.outline), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( entry.value, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 14, color: colors.onSurface), ), const SizedBox(width: 4), GestureDetector( onTap: () => onRemove(entry.key), - child: const Icon(Icons.close, size: 14, color: Color(0xFF999999)), + child: Icon(Icons.close, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), ), ], ), @@ -1157,10 +1171,10 @@ class _MovieFormPageState extends State { constraints: const BoxConstraints(minWidth: 100, maxWidth: 150), child: TextField( controller: controller, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 14, color: colors.onSurface), decoration: InputDecoration( hintText: values.isEmpty ? hint : '', - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.25)), border: InputBorder.none, isDense: true, contentPadding: const EdgeInsets.symmetric(vertical: 5), @@ -1179,20 +1193,21 @@ class _MovieFormPageState extends State { ], ); } - + /// 构建状态选择器(简洁风格) Widget _buildStatusSelector() { + final colors = Theme.of(context).colorScheme; return Row( children: [ - const Text( + Text( '状态', - style: TextStyle(fontSize: 13, color: Color(0xFF999999)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)), ), const SizedBox(width: 16), Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - color: const Color(0xFFF0F0F0), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(6), ), child: Row( @@ -1207,14 +1222,15 @@ class _MovieFormPageState extends State { ], ); } - + /// 构建星星评分(支持手动输入) Widget _buildStarRating() { + final colors = Theme.of(context).colorScheme; return Row( children: [ - const Text( + Text( '评分', - style: TextStyle(fontSize: 13, color: Color(0xFF999999)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4)), ), const SizedBox(width: 16), // 星星选择 @@ -1225,12 +1241,13 @@ class _MovieFormPageState extends State { ], ); } - + /// 构建星星选择器 Widget _buildStarSelector() { final currentRating = double.tryParse(_ratingController.text) ?? 0; final starRating = currentRating / 2; - + final colors = Theme.of(context).colorScheme; + return Container( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( @@ -1240,7 +1257,7 @@ class _MovieFormPageState extends State { final scoreValue = starValue * 2; final isFilled = starValue <= starRating; final isHalf = starValue == starRating.ceil() && starRating % 1 != 0; - + return InkWell( onTap: () { setState(() { @@ -1259,7 +1276,7 @@ class _MovieFormPageState extends State { size: 24, color: isFilled || isHalf ? const Color(0xFFFFB800) - : const Color(0xFFE5E5E5), + : colors.outline, ), ), ); @@ -1267,30 +1284,31 @@ class _MovieFormPageState extends State { ), ); } - + /// 构建评分输入框(支持0-10,保留1位小数) Widget _buildRatingInputField() { + final colors = Theme.of(context).colorScheme; return Container( width: 56, height: 32, decoration: BoxDecoration( - color: const Color(0xFFF0F0F0), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(6), ), child: TextFormField( controller: _ratingController, keyboardType: const TextInputType.numberWithOptions(decimal: true), textAlign: TextAlign.center, - style: const TextStyle( + style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), - decoration: const InputDecoration( + decoration: InputDecoration( hintText: '-', - hintStyle: TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.25)), border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 4, vertical: 6), + contentPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), ), onChanged: (value) { if (value.isNotEmpty) { @@ -1308,23 +1326,24 @@ class _MovieFormPageState extends State { ), ); } - + /// 构建状态选项 Widget _buildStatusOption(String label, String value) { final isSelected = _status == value; - + final colors = Theme.of(context).colorScheme; + return GestureDetector( onTap: () => setState(() => _status = value), child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), decoration: BoxDecoration( - color: isSelected ? Colors.white : Colors.transparent, + color: isSelected ? colors.surface : Colors.transparent, borderRadius: BorderRadius.circular(6), boxShadow: isSelected ? [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: colors.onSurface.withValues(alpha: 0.03), blurRadius: 4, offset: const Offset(0, 2), ), @@ -1336,17 +1355,18 @@ class _MovieFormPageState extends State { style: TextStyle( fontSize: 14, fontWeight: isSelected ? FontWeight.w500 : FontWeight.normal, - color: isSelected ? const Color(0xFF1A1A1A) : const Color(0xFF999999), + color: isSelected ? colors.onSurface : colors.onSurface.withValues(alpha: 0.4), ), ), ), ); } - + /// 构建封面选择器 Widget _buildCoverPicker() { final hasPoster = _posterPath != null && _posterPath!.isNotEmpty; - + final colors = Theme.of(context).colorScheme; + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -1356,7 +1376,7 @@ class _MovieFormPageState extends State { width: 120, height: 170, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), clipBehavior: Clip.antiAlias, @@ -1378,7 +1398,7 @@ class _MovieFormPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), ), child: Row( @@ -1387,14 +1407,14 @@ class _MovieFormPageState extends State { Icon( Icons.delete_outline, size: 14, - color: Colors.grey[600], + color: colors.onSurface.withValues(alpha: 0.6), ), const SizedBox(width: 4), Text( '移除海报', style: TextStyle( fontSize: 12, - color: Colors.grey[600], + color: colors.onSurface.withValues(alpha: 0.6), ), ), ], @@ -1405,78 +1425,83 @@ class _MovieFormPageState extends State { ], ); } - + /// 显示封面选择选项 void _showCoverOptions() { + final colors = Theme.of(context).colorScheme; showModalBottomSheet( context: context, - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - builder: (context) => SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 顶部指示条 - Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: const Color(0xFFE0E0E0), - borderRadius: BorderRadius.circular(2), + builder: (context) { + final colors = Theme.of(context).colorScheme; + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 顶部指示条 + Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: colors.outline, + borderRadius: BorderRadius.circular(2), + ), ), - ), - const SizedBox(height: 20), - // 标题 - const Padding( - padding: EdgeInsets.symmetric(horizontal: 24), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - '添加海报', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + const SizedBox(height: 20), + // 标题 + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + '添加海报', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: colors.onSurface, + ), ), ), ), - ), - const SizedBox(height: 16), - // 本地图片选项 - _buildCoverOption( - icon: Icons.photo_library_outlined, - title: '从相册选择', - onTap: () { - Navigator.pop(context); - _pickCover(); - }, - ), - // 网络链接选项 - _buildCoverOption( - icon: Icons.link_outlined, - title: '网络链接', - onTap: () { - Navigator.pop(context); - _pickCoverFromUrl(); - }, - ), - ], + const SizedBox(height: 16), + // 本地图片选项 + _buildCoverOption( + icon: Icons.photo_library_outlined, + title: '从相册选择', + onTap: () { + Navigator.pop(context); + _pickCover(); + }, + ), + // 网络链接选项 + _buildCoverOption( + icon: Icons.link_outlined, + title: '网络链接', + onTap: () { + Navigator.pop(context); + _pickCoverFromUrl(); + }, + ), + ], + ), ), - ), - ), + ); + }, ); } - + /// 构建封面选项 Widget _buildCoverOption({ required IconData icon, required String title, required VoidCallback onTap, }) { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: onTap, child: Container( @@ -1487,27 +1512,27 @@ class _MovieFormPageState extends State { width: 44, height: 44, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10), ), child: Icon( icon, size: 22, - color: const Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 16), Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 16, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), const Spacer(), - const Icon( + Icon( Icons.chevron_right, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), size: 20, ), ], @@ -1515,28 +1540,29 @@ class _MovieFormPageState extends State { ), ); } - + Widget _buildCoverPlaceholder() { - return const Column( + final colors = Theme.of(context).colorScheme; + return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.image_outlined, size: 32, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), - SizedBox(height: 8), + const SizedBox(height: 8), Text( '海报', style: TextStyle( fontSize: 12, - color: Color(0xFFAAAAAA), + color: colors.onSurface.withValues(alpha: 0.35), ), ), ], ); } - + /// 选择封面 Future _pickCover() async { try { @@ -1546,23 +1572,23 @@ class _MovieFormPageState extends State { maxHeight: 1200, imageQuality: 85, ); - + if (pickedFile != null) { // 生成文件名 final fileName = 'poster_${DateTime.now().millisecondsSinceEpoch}.jpg'; - + // 如果是编辑模式,使用现有影视ID;如果是新建模式,使用临时ID(保存时会替换) final movieId = widget.movie?.id ?? DateTime.now().millisecondsSinceEpoch.toString(); - + // 保存到新的路径结构: images/movies/{movieId}/{fileName} final targetPath = await ImagePathHelper.instance.getMoviePosterPath( - movieId, + movieId, fileName ); await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath)); - + await File(pickedFile.path).copy(targetPath); - + setState(() => _posterPath = targetPath); } } catch (e) { @@ -1571,59 +1597,62 @@ class _MovieFormPageState extends State { } } } - + /// 从网络链接选择封面 Future _pickCoverFromUrl() async { final urlController = TextEditingController(); - + final confirmed = await showDialog( context: context, - builder: (context) => AlertDialog( - backgroundColor: Colors.white, - elevation: 0, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - title: const Text('添加网络图片'), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - '请输入图片链接地址', - style: TextStyle( - fontSize: 14, - color: Color(0xFF666666), + builder: (context) { + final colors = Theme.of(context).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: const Text('添加网络图片'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '请输入图片链接地址', + style: TextStyle( + fontSize: 14, + color: colors.onSurface.withValues(alpha: 0.6), + ), ), + const SizedBox(height: 12), + TextField( + controller: urlController, + decoration: InputDecoration( + hintText: 'https://movie.douban.com/image.jpg', + hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.25)), + border: const UnderlineInputBorder(), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: colors.outline), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: colors.primary, width: 1.5), + ), + ), + style: const TextStyle(fontSize: 14), + keyboardType: TextInputType.url, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))), ), - const SizedBox(height: 12), - TextField( - controller: urlController, - decoration: const InputDecoration( - hintText: 'https://movie.douban.com/image.jpg', - hintStyle: TextStyle(color: Color(0xFFCCCCCC)), - border: UnderlineInputBorder(), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFFE5E5E5)), - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFF1A1A1A)), - ), - ), - style: const TextStyle(fontSize: 14), - keyboardType: TextInputType.url, + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text('确定', style: TextStyle(color: colors.onSurface)), ), ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('取消', style: TextStyle(color: Color(0xFF666666))), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: const Text('确定', style: TextStyle(color: Color(0xFF1A1A1A))), - ), - ], - ), + ); + }, ); if (confirmed != true) return; @@ -1644,7 +1673,7 @@ class _MovieFormPageState extends State { 'Referer': Uri.parse(url).replace(path: '/').toString(), }, ); - + if (response.statusCode != 200) { throw Exception('下载失败: HTTP ${response.statusCode}'); } @@ -1662,13 +1691,13 @@ class _MovieFormPageState extends State { // 生成文件名 final fileName = 'poster_${DateTime.now().millisecondsSinceEpoch}.jpg'; - + // 如果是编辑模式,使用现有影视ID;如果是新建模式,使用临时ID final movieId = widget.movie?.id ?? DateTime.now().millisecondsSinceEpoch.toString(); - + // 保存到新的路径结构 final targetPath = await ImagePathHelper.instance.getMoviePosterPath( - movieId, + movieId, fileName ); await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath)); @@ -1677,7 +1706,7 @@ class _MovieFormPageState extends State { await File(targetPath).writeAsBytes(response.bodyBytes); setState(() => _posterPath = targetPath); - + if (mounted) { ToastUtil.show(context, '添加成功'); } @@ -1687,7 +1716,7 @@ class _MovieFormPageState extends State { } } } - + /// 选择上映日期 Future _selectReleaseDate() async { final picked = await showDatePicker( @@ -1695,16 +1724,7 @@ class _MovieFormPageState extends State { initialDate: _releaseDate ?? DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime.now().add(const Duration(days: 365 * 5)), - builder: (context, child) { - return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light( - primary: Color(0xFF1A1A1A), - ), - ), - child: child!, - ); - }, + builder: (context, child) => child!, ); if (picked != null) { @@ -1719,23 +1739,14 @@ class _MovieFormPageState extends State { initialDate: _watchDate ?? DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime.now().add(const Duration(days: 365)), - builder: (context, child) { - return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light( - primary: Color(0xFF1A1A1A), - ), - ), - child: child!, - ); - }, + builder: (context, child) => child!, ); if (picked != null) { setState(() => _watchDate = picked); } } - + /// 收集所有多值字段输入框中的未提交内容 void _collectUnsubmittedValues() { // 别名 @@ -1747,7 +1758,7 @@ class _MovieFormPageState extends State { alternateTitlesController.clear(); } } - + // 导演 final directorsController = _tagControllers['directors']; if (directorsController != null) { @@ -1757,7 +1768,7 @@ class _MovieFormPageState extends State { directorsController.clear(); } } - + // 编剧 final writersController = _tagControllers['writers']; if (writersController != null) { @@ -1767,7 +1778,7 @@ class _MovieFormPageState extends State { writersController.clear(); } } - + // 主演 final actorsController = _tagControllers['actors']; if (actorsController != null) { @@ -1777,7 +1788,7 @@ class _MovieFormPageState extends State { actorsController.clear(); } } - + // 类型 final genresController = _tagControllers['genres']; if (genresController != null) { @@ -1794,26 +1805,26 @@ class _MovieFormPageState extends State { if (!_formKey.currentState!.validate()) { return; } - + // 收集所有多值字段输入框中的未提交内容 _collectUnsubmittedValues(); - - final rating = _ratingController.text.isNotEmpty - ? double.tryParse(_ratingController.text) + + final rating = _ratingController.text.isNotEmpty + ? double.tryParse(_ratingController.text) : null; - + final now = DateTime.now(); - + if (widget.movie == null) { // 生成新的影视ID final newMovieId = now.millisecondsSinceEpoch.toString(); - + // 如果有海报,需要移动到正确的ID目录 String? finalPosterPath; if (_posterPath != null && _posterPath!.isNotEmpty) { finalPosterPath = await _movePosterToNewId(_posterPath!, newMovieId); } - + final newMovie = Movie( id: newMovieId, title: _titleController.text.trim(), @@ -1831,7 +1842,7 @@ class _MovieFormPageState extends State { createdAt: now, updatedAt: now, ); - + await context.read().addMovie(newMovie); } else { final updatedMovie = widget.movie!.copyWith( @@ -1849,17 +1860,17 @@ class _MovieFormPageState extends State { watchDate: _watchDate, updatedAt: now, ); - + await context.read().updateMovie(updatedMovie); } - + if (!mounted) return; - + ToastUtil.show(context, widget.movie == null ? '添加成功' : '更新成功'); - + Navigator.pop(context); } - + /// 将海报从临时ID目录移动到新的影视ID目录 Future _movePosterToNewId(String currentPath, String newMovieId) async { // 检查是否已经在正确的目录中(兼容 Windows 路径分隔符) @@ -1867,24 +1878,24 @@ class _MovieFormPageState extends State { if (normalizedPath.contains('/movies/$newMovieId/')) { return currentPath; } - + // 提取文件名 final fileName = p.basename(currentPath); - + // 获取新路径 final newPath = await ImagePathHelper.instance.getMoviePosterPath( - newMovieId, + newMovieId, fileName ); - + // 确保目标目录存在 await ImagePathHelper.instance.ensureDirExists(p.dirname(newPath)); - + // 移动文件 final currentFile = File(currentPath); if (await currentFile.exists()) { await currentFile.rename(newPath); - + // 删除空的临时目录 final tempDir = Directory(p.dirname(currentPath)); if (await tempDir.exists()) { @@ -1894,10 +1905,10 @@ class _MovieFormPageState extends State { // 忽略删除目录失败的情况 } } - + return newPath; } - + return null; } } @@ -1964,13 +1975,14 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; // 未选中的已有标签 final availableTags = widget.existingTags .where((t) => !values.contains(t)) .toList(); return AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Text( widget.title, @@ -1996,9 +2008,9 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { children: [ // 已选择的标签 if (values.isNotEmpty) ...[ - const Text( + Text( '已选择', - style: TextStyle(fontSize: 12, color: Color(0xFFAAAAAA)), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.35)), ), const SizedBox(height: 8), Wrap( @@ -2008,7 +2020,7 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { return Container( padding: const EdgeInsets.only(left: 12, right: 6, top: 7, bottom: 7), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(16), ), child: Row( @@ -2016,10 +2028,10 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { children: [ Text( v, - style: const TextStyle( + style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, - color: Colors.white, + color: colors.onPrimary, ), ), const SizedBox(width: 4), @@ -2031,10 +2043,10 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { width: 18, height: 18, decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.3), + color: colors.onPrimary.withValues(alpha: 0.3), shape: BoxShape.circle, ), - child: const Icon(Icons.close, size: 12, color: Colors.white), + child: Icon(Icons.close, size: 12, color: colors.onPrimary), ), ), ], @@ -2044,15 +2056,15 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { ), if (availableTags.isNotEmpty) ...[ const SizedBox(height: 16), - const Divider(height: 0.5, color: Color(0xFFEEEEEE)), + Divider(height: 0.5, color: colors.outlineVariant), const SizedBox(height: 16), ], ], // 已有类型 if (availableTags.isNotEmpty) ...[ - const Text( + Text( '已有类型', - style: TextStyle(fontSize: 12, color: Color(0xFFAAAAAA)), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.35)), ), const SizedBox(height: 8), Wrap( @@ -2066,24 +2078,24 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), border: Border.all( - color: const Color(0xFFE8E8E8), + color: colors.outline, width: 0.5, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.add, size: 14, color: Color(0xFF999999)), + Icon(Icons.add, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), const SizedBox(width: 4), Text( tag, - style: const TextStyle( + style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, - color: Color(0xFF555555), + color: colors.onSurface.withValues(alpha: 0.7), ), ), ], @@ -2100,7 +2112,7 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { // 固定底部的输入框 if (values.isNotEmpty || availableTags.isNotEmpty) ...[ const SizedBox(height: 12), - const Divider(height: 0.5, color: Color(0xFFEEEEEE)), + Divider(height: 0.5, color: colors.outlineVariant), const SizedBox(height: 12), ], Row( @@ -2109,21 +2121,21 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { child: TextField( controller: controller, autofocus: true, - style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 15, color: colors.onSurface), decoration: InputDecoration( hintText: widget.hint, - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFAAAAAA)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.35)), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE0E0E0)), + borderSide: BorderSide(color: colors.outline), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE0E0E0)), + borderSide: BorderSide(color: colors.outline), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFF1A1A1A)), + borderSide: BorderSide(color: colors.primary, width: 1.5), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), @@ -2136,10 +2148,10 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(8), ), - child: const Icon(Icons.add, size: 20, color: Colors.white), + child: Icon(Icons.add, size: 20, color: colors.onPrimary), ), ), ], @@ -2151,7 +2163,7 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('取消', style: TextStyle(color: Color(0xFF999999))), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4))), ), ElevatedButton( onPressed: () { @@ -2159,8 +2171,8 @@ class _MultiValueDialogState extends State<_MultiValueDialog> { Navigator.pop(context, values); }, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF1A1A1A), - foregroundColor: Colors.white, + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: const Text('确定'), @@ -2205,8 +2217,9 @@ class _TextInputDialogState extends State<_TextInputDialog> { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Text( widget.title, @@ -2216,21 +2229,21 @@ class _TextInputDialogState extends State<_TextInputDialog> { controller: controller, maxLines: widget.maxLines, autofocus: true, - style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 15, color: colors.onSurface), decoration: InputDecoration( hintText: widget.hint, - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFAAAAAA)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.35)), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE0E0E0)), + borderSide: BorderSide(color: colors.outline), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE0E0E0)), + borderSide: BorderSide(color: colors.outline), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFF1A1A1A)), + borderSide: BorderSide(color: colors.primary, width: 1.5), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), @@ -2238,13 +2251,13 @@ class _TextInputDialogState extends State<_TextInputDialog> { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('取消', style: TextStyle(color: Color(0xFF999999))), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4))), ), ElevatedButton( onPressed: () => Navigator.pop(context, controller.text.trim()), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF1A1A1A), - foregroundColor: Colors.white, + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: const Text('确定'), diff --git a/lib/pages/movies/movie_posters_page.dart b/lib/pages/movies/movie_posters_page.dart index 26ebefe..1df2372 100644 --- a/lib/pages/movies/movie_posters_page.dart +++ b/lib/pages/movies/movie_posters_page.dart @@ -45,8 +45,9 @@ class _MoviePostersPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: const Text('海报墙'), actions: [ @@ -66,6 +67,7 @@ class _MoviePostersPageState extends State { } Widget _buildEmptyState() { + final colors = Theme.of(context).colorScheme; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -74,21 +76,21 @@ class _MoviePostersPageState extends State { width: 80, height: 80, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), ), - child: const Icon( + child: Icon( Icons.photo_library_outlined, size: 40, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), ), const SizedBox(height: 20), - const Text( + Text( '暂无海报', style: TextStyle( fontSize: 16, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), const SizedBox(height: 24), @@ -97,15 +99,15 @@ class _MoviePostersPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(8), ), - child: const Text( + child: Text( '添加记录', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: Colors.white, + color: colors.onPrimary, ), ), ), @@ -130,10 +132,11 @@ class _MoviePostersPageState extends State { } Widget _buildPosterItem(MoviePoster poster, int index) { + final colors = Theme.of(context).colorScheme; // 根据索引生成不同的高度,实现瀑布流效果 final heights = [180.0, 220.0, 160.0, 200.0, 240.0, 190.0]; final height = heights[index % heights.length]; - + return GestureDetector( onTap: () => _showPosterDetail(poster), onLongPress: () => _showDeleteDialog(poster), @@ -158,10 +161,10 @@ class _MoviePostersPageState extends State { Image.file( File(poster.posterPath), fit: BoxFit.cover, - errorBuilder: (_, __, ___) => const Center( + errorBuilder: (_, __, ___) => Center( child: Icon( Icons.broken_image, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), ), ), @@ -194,7 +197,7 @@ class _MoviePostersPageState extends State { void _showPosterDetail(MoviePoster poster) { // 找到当前海报的索引 final initialIndex = _posters.indexWhere((p) => p.id == poster.id); - + Navigator.push( context, MaterialPageRoute( @@ -210,61 +213,72 @@ class _MoviePostersPageState extends State { // 显示选择对话框 final result = await showModalBottomSheet( context: context, - backgroundColor: Colors.white, + backgroundColor: Colors.transparent, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - builder: (context) => SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 顶部指示条 - Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: const Color(0xFFE0E0E0), - borderRadius: BorderRadius.circular(2), - ), - ), - const SizedBox(height: 20), - // 标题 - const Padding( - padding: EdgeInsets.symmetric(horizontal: 24), - child: Row( - children: [ - Text( - '添加海报', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), - ), - ), - ], - ), - ), - const SizedBox(height: 16), - // 从相册选择 - _buildAddOption( - icon: Icons.photo_library_outlined, - title: '从相册选择', - subtitle: '选择本地图片', - onTap: () => Navigator.pop(context, 0), - ), - // 网络链接 - _buildAddOption( - icon: Icons.link_outlined, - title: '网络链接', - subtitle: '输入图片URL地址', - onTap: () => Navigator.pop(context, 1), - ), - ], + builder: (context) { + final colors = Theme.of(context).colorScheme; + return Container( + decoration: BoxDecoration( + color: colors.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), ), - ), - ), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 顶部指示条 + Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: colors.outline, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(height: 20), + // 标题 + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + children: [ + Text( + '添加海报', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: colors.onSurface, + ), + ), + ], + ), + ), + const SizedBox(height: 16), + // 从相册选择 + _buildAddOption( + colors: colors, + icon: Icons.photo_library_outlined, + title: '从相册选择', + subtitle: '选择本地图片', + onTap: () => Navigator.pop(context, 0), + ), + // 网络链接 + _buildAddOption( + colors: colors, + icon: Icons.link_outlined, + title: '网络链接', + subtitle: '输入图片URL地址', + onTap: () => Navigator.pop(context, 1), + ), + ], + ), + ), + ), + ); + }, ); if (result == null) return; @@ -289,10 +303,10 @@ class _MoviePostersPageState extends State { if (pickedFile != null) { // 生成文件名 final fileName = 'posterimg_${DateTime.now().millisecondsSinceEpoch}.jpg'; - + // 保存到 posterimgs 子目录: images/movies/{movieId}/posterimgs/{fileName} final targetPath = await ImagePathHelper.instance.getMoviePosterImgPath( - widget.movie.id, + widget.movie.id, fileName ); await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath)); @@ -323,55 +337,58 @@ class _MoviePostersPageState extends State { /// 从网络链接添加 Future _pickFromUrl() async { final urlController = TextEditingController(); - + final confirmed = await showDialog( context: context, - builder: (context) => AlertDialog( - backgroundColor: Colors.white, - elevation: 0, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - title: const Text('添加网络图片'), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - '请输入图片链接地址', - style: TextStyle( - fontSize: 14, - color: Color(0xFF666666), + builder: (context) { + final colors = Theme.of(context).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: const Text('添加网络图片'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '请输入图片链接地址', + style: TextStyle( + fontSize: 14, + color: colors.onSurface.withValues(alpha: 0.6), + ), ), + const SizedBox(height: 12), + TextField( + controller: urlController, + decoration: InputDecoration( + hintText: 'https://example.com/image.jpg', + hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.25)), + border: const UnderlineInputBorder(), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: colors.outline), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: colors.primary), + ), + ), + style: const TextStyle(fontSize: 14), + keyboardType: TextInputType.url, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))), ), - const SizedBox(height: 12), - TextField( - controller: urlController, - decoration: const InputDecoration( - hintText: 'https://example.com/image.jpg', - hintStyle: TextStyle(color: Color(0xFFCCCCCC)), - border: UnderlineInputBorder(), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFFE5E5E5)), - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFF1A1A1A)), - ), - ), - style: const TextStyle(fontSize: 14), - keyboardType: TextInputType.url, + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text('确定', style: TextStyle(color: colors.onSurface)), ), ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('取消', style: TextStyle(color: Color(0xFF666666))), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: const Text('确定', style: TextStyle(color: Color(0xFF1A1A1A))), - ), - ], - ), + ); + }, ); if (confirmed != true) return; @@ -404,7 +421,7 @@ class _MoviePostersPageState extends State { 'Referer': Uri.parse(url).replace(path: '/').toString(), }, ); - + if (response.statusCode != 200) { throw Exception('下载失败: HTTP ${response.statusCode}'); } @@ -422,10 +439,10 @@ class _MoviePostersPageState extends State { // 生成文件名 final fileName = 'posterimg_${DateTime.now().millisecondsSinceEpoch}.jpg'; - + // 保存到 posterimgs 子目录 final targetPath = await ImagePathHelper.instance.getMoviePosterImgPath( - widget.movie.id, + widget.movie.id, fileName ); await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath)); @@ -453,6 +470,7 @@ class _MoviePostersPageState extends State { /// 构建添加选项 Widget _buildAddOption({ + required ColorScheme colors, required IconData icon, required String title, required String subtitle, @@ -468,13 +486,13 @@ class _MoviePostersPageState extends State { width: 44, height: 44, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10), ), child: Icon( icon, size: 22, - color: const Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 16), @@ -484,26 +502,26 @@ class _MoviePostersPageState extends State { children: [ Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), const SizedBox(height: 2), Text( subtitle, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], ), ), - const Icon( + Icon( Icons.chevron_right, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), size: 20, ), ], @@ -515,28 +533,31 @@ class _MoviePostersPageState extends State { void _showDeleteDialog(MoviePoster poster) { 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 { - await context.read().removeMoviePoster(poster.id); - Navigator.pop(context); - _loadPosters(); - ToastUtil.show(context, '已删除'); - }, - child: const Text('删除', style: TextStyle(color: Colors.red)), - ), - ], - ), + builder: (context) { + final colors = Theme.of(context).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: const Text('确认删除'), + content: const Text('确定要删除这张海报吗?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))), + ), + TextButton( + onPressed: () async { + await context.read().removeMoviePoster(poster.id); + Navigator.pop(context); + _loadPosters(); + ToastUtil.show(context, '已删除'); + }, + child: Text('删除', style: TextStyle(color: colors.error)), + ), + ], + ); + }, ); } } diff --git a/lib/pages/movies/movie_review_detail_page.dart b/lib/pages/movies/movie_review_detail_page.dart index 57ae2c2..c0a06bb 100644 --- a/lib/pages/movies/movie_review_detail_page.dart +++ b/lib/pages/movies/movie_review_detail_page.dart @@ -49,8 +49,9 @@ class _MovieReviewDetailPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: const Text('影评详情'), actions: [ @@ -70,9 +71,9 @@ class _MovieReviewDetailPageState extends State { // 影评内容 Text( _review.content, - style: const TextStyle( + style: TextStyle( fontSize: 16, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.8, ), ), @@ -82,7 +83,7 @@ class _MovieReviewDetailPageState extends State { // 分隔线 Container( height: 0.5, - color: const Color(0xFFE5E5E5), + color: colors.outline, ), const SizedBox(height: 24), @@ -92,6 +93,7 @@ class _MovieReviewDetailPageState extends State { icon: Icons.person_outline, label: '影评人:', value: _review.reviewer.isNotEmpty ? _review.reviewer : '匿名', + colors: colors, ), const SizedBox(height: 16), @@ -102,6 +104,7 @@ class _MovieReviewDetailPageState extends State { icon: Icons.source_outlined, label: '来源:', value: _review.source, + colors: colors, ), if (_review.source.isNotEmpty) const SizedBox(height: 16), @@ -111,6 +114,7 @@ class _MovieReviewDetailPageState extends State { icon: Icons.category_outlined, label: '类型:', value: _review.typeText, + colors: colors, ), const SizedBox(height: 16), @@ -120,6 +124,7 @@ class _MovieReviewDetailPageState extends State { icon: Icons.access_time, label: '时间:', value: _formatDate(_review.createdAt), + colors: colors, ), ], ), @@ -132,13 +137,14 @@ class _MovieReviewDetailPageState extends State { required IconData icon, required String label, required String value, + required ColorScheme colors, }) { return Row( children: [ Icon( icon, size: 20, - color: const Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), const SizedBox(width: 12), // 固定宽度容器,以"影评人:"的最大宽度为准 @@ -146,18 +152,18 @@ class _MovieReviewDetailPageState extends State { width: 64, child: Text( label, - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), Expanded( child: Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 15, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ), diff --git a/lib/pages/movies/movie_review_form_page.dart b/lib/pages/movies/movie_review_form_page.dart index 7f32ae0..d995eaf 100644 --- a/lib/pages/movies/movie_review_form_page.dart +++ b/lib/pages/movies/movie_review_form_page.dart @@ -46,10 +46,11 @@ class _MovieReviewFormPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; final isEdit = widget.review != null; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: Text(isEdit ? '编辑影评' : '写影评'), actions: [ @@ -73,24 +74,24 @@ class _MovieReviewFormPageState extends State { // 顶部信息栏 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: const BoxDecoration( + decoration: BoxDecoration( border: Border( - bottom: BorderSide(color: Color(0xFFE5E5E5), width: 0.5), + bottom: BorderSide(color: colors.outline, width: 0.5), ), ), child: Row( children: [ // 类型选择 - _buildTypeSelector(), + _buildTypeSelector(colors), const SizedBox(width: 16), // 评论人 Expanded( child: TextField( controller: _reviewerController, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), - decoration: const InputDecoration( + style: TextStyle(fontSize: 14, color: colors.onSurface), + decoration: InputDecoration( hintText: '评论人', - hintStyle: TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.25)), border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, @@ -103,10 +104,10 @@ class _MovieReviewFormPageState extends State { width: 100, child: TextField( controller: _sourceController, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), - decoration: const InputDecoration( + style: TextStyle(fontSize: 14, color: colors.onSurface), + decoration: InputDecoration( hintText: '来源', - hintStyle: TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.25)), border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, @@ -116,7 +117,7 @@ class _MovieReviewFormPageState extends State { ], ), ), - + // 评论内容区域 Expanded( child: TextFormField( @@ -124,19 +125,19 @@ class _MovieReviewFormPageState extends State { maxLines: null, expands: true, textAlignVertical: TextAlignVertical.top, - style: const TextStyle( + style: TextStyle( fontSize: 16, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.7, ), - decoration: const InputDecoration( + decoration: InputDecoration( hintText: '写下你的影评...', hintStyle: TextStyle( fontSize: 16, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), border: InputBorder.none, - contentPadding: EdgeInsets.all(16), + contentPadding: const EdgeInsets.all(16), ), validator: (value) { if (value == null || value.trim().isEmpty) { @@ -153,29 +154,29 @@ class _MovieReviewFormPageState extends State { } /// 构建类型选择器 - Widget _buildTypeSelector() { + Widget _buildTypeSelector(ColorScheme colors) { return GestureDetector( onTap: () => _showTypeSelector(), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - border: Border.all(color: const Color(0xFFE5E5E5)), + border: Border.all(color: colors.outline), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( _reviewType == 1 ? '短评' : '长评', - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 4), - const Icon( + Icon( Icons.arrow_drop_down, size: 16, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ], ), @@ -187,36 +188,42 @@ class _MovieReviewFormPageState extends State { void _showTypeSelector() { showModalBottomSheet( context: context, - backgroundColor: Colors.white, + backgroundColor: Colors.transparent, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - builder: (context) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: const Text('短评'), - trailing: _reviewType == 1 - ? const Icon(Icons.check, color: Color(0xFF1A1A1A)) - : null, - onTap: () { - setState(() => _reviewType = 1); - Navigator.pop(context); - }, + builder: (context) { + final colors = Theme.of(context).colorScheme; + return Container( + color: colors.surface, + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: const Text('短评'), + trailing: _reviewType == 1 + ? Icon(Icons.check, color: colors.onSurface) + : null, + onTap: () { + setState(() => _reviewType = 1); + Navigator.pop(context); + }, + ), + Divider(height: 0.5, color: colors.outline), + ListTile( + title: const Text('长评'), + trailing: _reviewType == 2 + ? Icon(Icons.check, color: colors.onSurface) + : null, + onTap: () { + setState(() => _reviewType = 2); + Navigator.pop(context); + }, + ), + ], ), - const Divider(height: 0.5), - ListTile( - title: const Text('长评'), - trailing: _reviewType == 2 - ? const Icon(Icons.check, color: Color(0xFF1A1A1A)) - : null, - onTap: () { - setState(() => _reviewType = 2); - Navigator.pop(context); - }, - ), - ], - ), - ), + ), + ); + }, ); } diff --git a/lib/pages/movies/movie_reviews_page.dart b/lib/pages/movies/movie_reviews_page.dart index c6573cb..bf0df69 100644 --- a/lib/pages/movies/movie_reviews_page.dart +++ b/lib/pages/movies/movie_reviews_page.dart @@ -73,19 +73,20 @@ class _MovieReviewsPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: _isSearching ? TextField( controller: _searchController, autofocus: true, - decoration: const InputDecoration( + decoration: InputDecoration( hintText: '搜索影评内容、评论人、来源...', - hintStyle: TextStyle(color: Color(0xFF999999)), + hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.4)), border: InputBorder.none, ), - style: const TextStyle(color: Color(0xFF1A1A1A)), + style: TextStyle(color: colors.onSurface), onChanged: _onSearchChanged, ) : const Text('影评'), @@ -112,6 +113,7 @@ class _MovieReviewsPageState extends State { } Widget _buildEmptyState() { + final colors = Theme.of(context).colorScheme; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -120,21 +122,21 @@ class _MovieReviewsPageState extends State { width: 80, height: 80, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), ), - child: const Icon( + child: Icon( Icons.rate_review_outlined, size: 40, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), ), const SizedBox(height: 20), - const Text( + Text( '暂无影评', style: TextStyle( fontSize: 16, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), const SizedBox(height: 24), @@ -143,15 +145,15 @@ class _MovieReviewsPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(8), ), - child: const Text( + child: Text( '添加记录', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: Colors.white, + color: colors.onPrimary, ), ), ), @@ -176,13 +178,14 @@ class _MovieReviewsPageState extends State { } Widget _buildReviewCard(MovieReview review) { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: () => _navigateToReviewDetail(review), onLongPress: () => _showDeleteDialog(review), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), child: Column( @@ -193,8 +196,8 @@ class _MovieReviewsPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: review.reviewType == 1 - ? Colors.white - : const Color(0xFF1A1A1A), + ? colors.surface + : colors.primary, borderRadius: BorderRadius.circular(4), ), child: Text( @@ -202,8 +205,8 @@ class _MovieReviewsPageState extends State { style: TextStyle( fontSize: 10, color: review.reviewType == 1 - ? const Color(0xFF666666) - : Colors.white, + ? colors.onSurface.withValues(alpha: 0.6) + : colors.onPrimary, ), ), ), @@ -215,9 +218,9 @@ class _MovieReviewsPageState extends State { review.content, maxLines: review.reviewType == 1 ? 4 : 8, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.5, ), ), @@ -232,9 +235,9 @@ class _MovieReviewsPageState extends State { Expanded( child: Text( review.reviewer, - style: const TextStyle( + style: TextStyle( fontSize: 11, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), overflow: TextOverflow.ellipsis, ), @@ -252,9 +255,9 @@ class _MovieReviewsPageState extends State { Expanded( child: Text( review.source, - style: const TextStyle( + style: TextStyle( fontSize: 10, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), overflow: TextOverflow.ellipsis, ), @@ -262,9 +265,9 @@ class _MovieReviewsPageState extends State { // 日期 Text( _formatDate(review.createdAt), - style: const TextStyle( + style: TextStyle( fontSize: 10, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], @@ -315,28 +318,31 @@ class _MovieReviewsPageState extends State { void _showDeleteDialog(MovieReview review) { 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 { - await context.read().removeMovieReview(review.id); - Navigator.pop(context); - _loadReviews(); - ToastUtil.show(context, '已删除'); - }, - child: const Text('删除', style: TextStyle(color: Colors.red)), - ), - ], - ), + builder: (context) { + final colors = Theme.of(context).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: const Text('确认删除'), + content: const Text('确定要删除这条影评吗?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))), + ), + TextButton( + onPressed: () async { + await context.read().removeMovieReview(review.id); + Navigator.pop(context); + _loadReviews(); + ToastUtil.show(context, '已删除'); + }, + child: Text('删除', style: TextStyle(color: colors.error)), + ), + ], + ); + }, ); } -} \ No newline at end of file +} diff --git a/lib/pages/movies/movie_share_page.dart b/lib/pages/movies/movie_share_page.dart index e08d998..59cfe46 100644 --- a/lib/pages/movies/movie_share_page.dart +++ b/lib/pages/movies/movie_share_page.dart @@ -24,21 +24,22 @@ class _MovieSharePageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: const Color(0xFFF5F5F5), + backgroundColor: colors.surfaceContainerHighest, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, leading: IconButton( - icon: const Icon(Icons.close, color: Color(0xFF1A1A1A)), + icon: Icon(Icons.close, color: colors.onSurface), onPressed: () => Navigator.pop(context), ), - title: const Text( + title: Text( '分享海报', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), centerTitle: true, @@ -51,12 +52,12 @@ class _MovieSharePageState extends State { height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) - : const Text( + : Text( '分享', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ), @@ -77,13 +78,14 @@ class _MovieSharePageState extends State { /// 构建海报 Widget Widget _buildPosterWidget() { + final colors = Theme.of(context).colorScheme; final movie = widget.movie; final hasPoster = movie.posterPath != null && movie.posterPath!.isNotEmpty; return Container( width: 320, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( @@ -118,10 +120,10 @@ class _MovieSharePageState extends State { // 标题 Text( movie.title, - style: const TextStyle( + style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), @@ -130,9 +132,9 @@ class _MovieSharePageState extends State { const SizedBox(height: 8), Text( movie.alternateTitles.join(' / '), - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), ], @@ -158,11 +160,11 @@ class _MovieSharePageState extends State { ), ), const SizedBox(width: 4), - const Text( + Text( '/ 10', style: TextStyle( fontSize: 12, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], @@ -172,44 +174,45 @@ class _MovieSharePageState extends State { // 导演 if (movie.directors.isNotEmpty) - _buildInfoRow('导演', movie.directors.join(' / ')), + _buildInfoRow('导演', movie.directors.join(' / '), colors), // 编剧 if (movie.writers.isNotEmpty) - _buildInfoRow('编剧', movie.writers.join(' / ')), + _buildInfoRow('编剧', movie.writers.join(' / '), colors), // 主演 if (movie.actors.isNotEmpty) - _buildInfoRow('主演', movie.actors.take(3).join(' / ')), + _buildInfoRow('主演', movie.actors.take(3).join(' / '), colors), // 类型 if (movie.genres.isNotEmpty) - _buildInfoRow('类型', movie.genres.join(' / ')), + _buildInfoRow('类型', movie.genres.join(' / '), colors), // 上映日期 if (movie.releaseDate != null) _buildInfoRow( '上映', '${movie.releaseDate!.year}.${movie.releaseDate!.month.toString().padLeft(2, '0')}.${movie.releaseDate!.day.toString().padLeft(2, '0')}', + colors, ), const SizedBox(height: 16), // 简介 if (movie.summary != null && movie.summary!.isNotEmpty) ...[ - const Text( + Text( '简介', style: TextStyle( fontSize: 12, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), const SizedBox(height: 8), Text( movie.summary!, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.6, ), maxLines: 5, @@ -220,7 +223,7 @@ class _MovieSharePageState extends State { const SizedBox(height: 20), // 底部标识 - const Divider(height: 1, color: Color(0xFFE8E8E8)), + Divider(height: 1, color: colors.outline), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -228,14 +231,14 @@ class _MovieSharePageState extends State { Icon( Icons.movie_outlined, size: 14, - color: const Color(0xFF1A1A1A).withOpacity(0.5), + color: colors.onSurface.withValues(alpha: 0.5), ), const SizedBox(width: 6), Text( '来自 MookNote', style: TextStyle( fontSize: 12, - color: const Color(0xFF1A1A1A).withOpacity(0.5), + color: colors.onSurface.withValues(alpha: 0.5), ), ), ], @@ -249,7 +252,7 @@ class _MovieSharePageState extends State { } /// 构建信息行 - Widget _buildInfoRow(String label, String value) { + Widget _buildInfoRow(String label, String value, ColorScheme colors) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( @@ -257,17 +260,17 @@ class _MovieSharePageState extends State { children: [ Text( '$label:', - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), Expanded( child: Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF333333), + color: colors.onSurface.withValues(alpha: 0.75), ), ), ), diff --git a/lib/pages/movies/movie_tab_page.dart b/lib/pages/movies/movie_tab_page.dart index b8d8009..ef4089f 100644 --- a/lib/pages/movies/movie_tab_page.dart +++ b/lib/pages/movies/movie_tab_page.dart @@ -17,7 +17,7 @@ class MovieTabPage extends StatefulWidget { } class _MovieTabPageState extends State { - int _layoutStyle = 0; // 0: 海报网格, 1: 列表 + int _layoutStyle = 0; bool _firstLoad = true; @override @@ -31,10 +31,11 @@ class _MovieTabPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Column( children: [ const MovieStatusBar(), - const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)), + Divider(height: 0.5, thickness: 0.5, color: colors.outline), Expanded( child: _buildMovieList(context), ), @@ -43,12 +44,12 @@ class _MovieTabPageState extends State { } Widget _buildMovieList(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Consumer( builder: (context, provider, child) { final statusMap = {0: 'watched', 1: 'watching', 2: 'want_to_watch'}; final currentStatus = statusMap[provider.movieStatusIndex]!; final allMovies = provider.movies.where((m) => !m.isDeleted).toList(); - // 首次加载且数据为空时才显示骨架屏 if (_firstLoad && allMovies.isEmpty) { return _buildSkeleton(); } @@ -59,8 +60,8 @@ class _MovieTabPageState extends State { if (movies.isEmpty) { return RefreshIndicator( onRefresh: () async => await provider.loadMovies(), - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, child: ListView( physics: const AlwaysScrollableScrollPhysics(), children: [_buildEmptyState(context, provider.movieStatusIndex)], @@ -77,10 +78,11 @@ class _MovieTabPageState extends State { } Widget _buildGridView(List movies, AppProvider provider) { + final colors = Theme.of(context).colorScheme; return RefreshIndicator( onRefresh: () async => await provider.loadMovies(), - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, child: GridView.builder( padding: const EdgeInsets.fromLTRB(16, 16, 16, 100), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( @@ -96,10 +98,11 @@ class _MovieTabPageState extends State { } Widget _buildListView(List movies, AppProvider provider) { + final colors = Theme.of(context).colorScheme; return RefreshIndicator( onRefresh: () async => await provider.loadMovies(), - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, child: ListView.builder( padding: const EdgeInsets.fromLTRB(12, 8, 12, 100), itemCount: movies.length, @@ -109,6 +112,7 @@ class _MovieTabPageState extends State { } Widget _buildListCard(movie) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: () => Navigator.pushNamed(context, '/movie-detail', arguments: movie), onLongPress: () => _showDeleteDialog(context, movie), @@ -116,23 +120,22 @@ class _MovieTabPageState extends State { margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ - // 海报缩略图 Container( width: 48, height: 64, decoration: BoxDecoration( - color: const Color(0xFFF0F0F0), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(6), ), clipBehavior: Clip.antiAlias, child: movie.posterPath != null && movie.posterPath!.isNotEmpty ? Image.file(File(movie.posterPath!), fit: BoxFit.cover, - errorBuilder: (_, __, ___) => const Icon(Icons.movie_outlined, size: 22, color: Color(0xFFCCCCCC))) - : const Icon(Icons.movie_outlined, size: 22, color: Color(0xFFCCCCCC)), + errorBuilder: (_, __, ___) => Icon(Icons.movie_outlined, size: 22, color: colors.onSurface.withValues(alpha: 0.25))) + : Icon(Icons.movie_outlined, size: 22, color: colors.onSurface.withValues(alpha: 0.25)), ), const SizedBox(width: 12), Expanded( @@ -140,11 +143,11 @@ class _MovieTabPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(movie.title, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface)), if (movie.alternateTitles.isNotEmpty) ...[ const SizedBox(height: 3), Text(movie.alternateTitles.take(2).join('、'), maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12, color: Color(0xFFAAAAAA))), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.35))), ], const SizedBox(height: 6), if (movie.rating != null) @@ -155,7 +158,7 @@ class _MovieTabPageState extends State { ), ), const SizedBox(width: 8), - const Icon(Icons.chevron_right, color: Color(0xFFD0D0D0), size: 20), + Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.2), size: 20), ], ), ), @@ -163,19 +166,20 @@ class _MovieTabPageState extends State { } void _showDeleteDialog(BuildContext context, movie) { + final colors = Theme.of(context).colorScheme; showDialog( context: context, builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), + title: Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), content: Text('确定要删除《${movie.title}》吗?删除后可在回收站恢复。', - style: const TextStyle(fontSize: 14, color: Color(0xFF666666), height: 1.5)), + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.5)), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text('取消', style: TextStyle(color: Color(0xFF666666))), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))), ), ElevatedButton( onPressed: () async { @@ -183,7 +187,7 @@ class _MovieTabPageState extends State { Navigator.pop(ctx); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, foregroundColor: Colors.white, elevation: 0, + backgroundColor: colors.error, foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), @@ -200,6 +204,7 @@ class _MovieTabPageState extends State { } Widget _buildListSkeleton() { + final colors = Theme.of(context).colorScheme; return ListView.builder( padding: const EdgeInsets.fromLTRB(12, 8, 12, 100), itemCount: 6, @@ -207,7 +212,7 @@ class _MovieTabPageState extends State { margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), ), child: const Row( @@ -235,6 +240,7 @@ class _MovieTabPageState extends State { } Widget _buildEmptyState(BuildContext context, int statusIndex) { + final colors = Theme.of(context).colorScheme; final statusText = ['已看', '在看', '想看'][statusIndex]; return Center( child: Column( @@ -242,11 +248,11 @@ class _MovieTabPageState extends State { children: [ Container( width: 80, height: 80, - decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(20)), - child: const Icon(Icons.movie_outlined, size: 40, color: Color(0xFFCCCCCC)), + decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(20)), + child: Icon(Icons.movie_outlined, size: 40, color: colors.onSurface.withValues(alpha: 0.25)), ), const SizedBox(height: 20), - Text('暂无$statusText的影片', style: const TextStyle(fontSize: 16, color: Color(0xFF999999))), + Text('暂无$statusText的影片', style: TextStyle(fontSize: 16, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(height: 24), InkWell( onTap: () { @@ -255,8 +261,8 @@ class _MovieTabPageState extends State { }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - decoration: BoxDecoration(color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(8)), - child: const Text('添加记录', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)), + decoration: BoxDecoration(color: colors.primary, borderRadius: BorderRadius.circular(8)), + child: Text('添加记录', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onPrimary)), ), ), ], diff --git a/lib/pages/note/note_detail_page.dart b/lib/pages/note/note_detail_page.dart index e96720d..7537ada 100644 --- a/lib/pages/note/note_detail_page.dart +++ b/lib/pages/note/note_detail_page.dart @@ -22,13 +22,14 @@ class _NoteDetailPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; final note = context.watch().notes.firstWhere( (n) => n.id == widget.note.id, orElse: () => widget.note, ); return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: Text( note.title.isNotEmpty @@ -42,22 +43,21 @@ class _NoteDetailPageState extends State { children: [ Column( children: [ - // 日期 + 字数 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), - decoration: const BoxDecoration( + decoration: BoxDecoration( border: Border( - bottom: BorderSide(color: Color(0xFFE8E8E8), width: 0.5), + bottom: BorderSide(color: colors.outlineVariant, width: 0.5), ), ), child: Row( children: [ Text( '${note.createdAt.day}', - style: const TextStyle( + style: TextStyle( fontSize: 30, fontWeight: FontWeight.w200, - color: Color(0xFF333333), + color: colors.onSurface.withValues(alpha: 0.75), height: 1.0, ), ), @@ -68,48 +68,44 @@ class _NoteDetailPageState extends State { children: [ Text( '${note.createdAt.year}/${note.createdAt.month.toString().padLeft(2, '0')} 周${_weekdays[note.createdAt.weekday - 1]}', - style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w500, color: Color(0xFF777777)), + style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.55)), ), const SizedBox(height: 1), Text( '${note.createdAt.hour.toString().padLeft(2, '0')}:${note.createdAt.minute.toString().padLeft(2, '0')}', - style: const TextStyle(fontSize: 11, color: Color(0xFF999999)), + style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4)), ), ], ), const Spacer(), Text( '${note.content.length} 字', - style: const TextStyle(fontSize: 11, color: Color(0xFFAAAAAA)), + style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.35)), ), ], ), ), - // 标签行 if (note.tags.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 6), child: _buildTagRow(note.tags), ), - // 内容 Expanded( child: Markdown( data: note.content, - styleSheet: _buildMarkdownStyleSheet(), + styleSheet: _buildMarkdownStyleSheet(colors), padding: const EdgeInsets.all(16), // ignore: deprecated_member_use imageBuilder: (uri, title, alt) => _buildMarkdownImage(uri, note), ), ), - // 图片行 if (note.images.isNotEmpty) _buildImageRow(note.images), ], ), - // 右下角悬浮按钮组 Positioned( right: 16, bottom: 24, @@ -121,6 +117,7 @@ class _NoteDetailPageState extends State { } Widget _buildTagRow(List tags) { + final colors = Theme.of(context).colorScheme; return Container( height: 28, margin: const EdgeInsets.only(bottom: 2), @@ -133,12 +130,12 @@ class _NoteDetailPageState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(6), ), child: Text( tags[index], - style: const TextStyle(fontSize: 11, color: Color(0xFF666666)), + style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.6)), ), ); }, @@ -147,12 +144,13 @@ class _NoteDetailPageState extends State { } Widget _buildImageRow(List images) { + final colors = Theme.of(context).colorScheme; return Container( height: 80, decoration: BoxDecoration( - color: Colors.white, - border: const Border( - top: BorderSide(color: Color(0xFFE8E8E8), width: 0.5), + color: colors.surface, + border: Border( + top: BorderSide(color: colors.outlineVariant, width: 0.5), ), boxShadow: [ BoxShadow( @@ -175,15 +173,15 @@ class _NoteDetailPageState extends State { height: 64, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), clipBehavior: Clip.antiAlias, child: Image.file( File(images[index]), fit: BoxFit.cover, errorBuilder: (_, __, ___) => Container( - color: const Color(0xFFF5F5F5), - child: const Icon(Icons.broken_image_outlined, size: 20, color: Color(0xFFCCCCCC)), + color: colors.surfaceContainerHighest, + child: Icon(Icons.broken_image_outlined, size: 20, color: colors.onSurface.withValues(alpha: 0.25)), ), ), ), @@ -193,37 +191,37 @@ class _NoteDetailPageState extends State { ); } - MarkdownStyleSheet _buildMarkdownStyleSheet() { + MarkdownStyleSheet _buildMarkdownStyleSheet(ColorScheme colors) { return MarkdownStyleSheet( - h1: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), height: 1.4), - h2: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), height: 1.4), - h3: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), height: 1.4), - h4: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), height: 1.4), - p: const TextStyle(fontSize: 15, color: Color(0xFF333333), height: 1.8), - code: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A), backgroundColor: Color(0xFFF5F5F5), fontFamily: 'monospace'), + h1: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: colors.onSurface, height: 1.4), + h2: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: colors.onSurface, height: 1.4), + h3: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface, height: 1.4), + h4: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface, height: 1.4), + p: TextStyle(fontSize: 15, color: colors.onSurface.withValues(alpha: 0.75), height: 1.8), + code: TextStyle(fontSize: 14, color: colors.onSurface, backgroundColor: colors.surfaceContainerHighest, fontFamily: 'monospace'), codeblockDecoration: BoxDecoration( - color: const Color(0xFFF8F8F8), - border: Border.all(color: const Color(0xFFE5E5E5)), + color: colors.surfaceContainerHigh, + border: Border.all(color: colors.outline), borderRadius: BorderRadius.circular(6), ), codeblockPadding: const EdgeInsets.all(12), - blockquote: const TextStyle(fontSize: 15, color: Color(0xFF666666), fontStyle: FontStyle.italic, height: 1.8), - blockquoteDecoration: const BoxDecoration( - border: Border(left: BorderSide(color: Color(0xFF999999), width: 4)), + blockquote: TextStyle(fontSize: 15, color: colors.onSurface.withValues(alpha: 0.6), fontStyle: FontStyle.italic, height: 1.8), + blockquoteDecoration: BoxDecoration( + border: Border(left: BorderSide(color: colors.onSurface.withValues(alpha: 0.4), width: 4)), ), blockquotePadding: const EdgeInsets.only(left: 12, top: 4, bottom: 4), - listBullet: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A)), + listBullet: TextStyle(fontSize: 15, color: colors.onSurface), listIndent: 24, a: const TextStyle(fontSize: 15, color: Color(0xFF4A90D9), decoration: TextDecoration.underline), - tableHead: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)), - tableBody: const TextStyle(fontSize: 14, color: Color(0xFF333333)), - tableBorder: TableBorder.all(color: const Color(0xFFE5E5E5), width: 0.5), + tableHead: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: colors.onSurface), + tableBody: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.75)), + tableBorder: TableBorder.all(color: colors.outline, width: 0.5), tableColumnWidth: const FlexColumnWidth(), - tableCellsDecoration: const BoxDecoration(color: Colors.white), + tableCellsDecoration: BoxDecoration(color: colors.surface), tablePadding: const EdgeInsets.all(8), - strong: const TextStyle(fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)), - em: const TextStyle(fontStyle: FontStyle.italic, color: Color(0xFF333333)), - del: const TextStyle(decoration: TextDecoration.lineThrough, color: Color(0xFF999999)), + strong: TextStyle(fontWeight: FontWeight.w600, color: colors.onSurface), + em: TextStyle(fontStyle: FontStyle.italic, color: colors.onSurface.withValues(alpha: 0.75)), + del: TextStyle(decoration: TextDecoration.lineThrough, color: colors.onSurface.withValues(alpha: 0.4)), ); } @@ -272,7 +270,6 @@ class _NoteDetailPageState extends State { ); } - /// 截取内容前N个字作为标题 String _truncateContent(String content) { final cleaned = content .replaceAll(RegExp(r'^#+\s+', multiLine: true), '') @@ -298,8 +295,8 @@ class _NoteDetailPageState extends State { }); } - /// 右下角悬浮按钮组 Widget _buildFloatingActionButtons() { + final colors = Theme.of(context).colorScheme; return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -307,13 +304,16 @@ class _NoteDetailPageState extends State { icon: Icons.edit_outlined, onPressed: () => _navigateToEdit(context), tooltip: '编辑', + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, ), const SizedBox(height: 12), _buildFloatingButton( icon: Icons.delete_outline, onPressed: () => _showDeleteDialog(context), tooltip: '删除', - backgroundColor: Colors.red, + backgroundColor: colors.error, + foregroundColor: colors.onError, ), const SizedBox(height: 12), _buildFloatingButton( @@ -321,6 +321,7 @@ class _NoteDetailPageState extends State { onPressed: _shareNote, tooltip: '分享', backgroundColor: const Color(0xFF4CAF50), + foregroundColor: Colors.white, ), ], ); @@ -330,7 +331,8 @@ class _NoteDetailPageState extends State { required IconData icon, required VoidCallback onPressed, required String tooltip, - Color backgroundColor = const Color(0xFF1A1A1A), + required Color backgroundColor, + required Color foregroundColor, }) { return Material( color: Colors.transparent, @@ -349,7 +351,7 @@ class _NoteDetailPageState extends State { ], ), child: IconButton( - icon: Icon(icon, size: 18, color: Colors.white), + icon: Icon(icon, size: 18, color: foregroundColor), onPressed: onPressed, padding: EdgeInsets.zero, tooltip: tooltip, @@ -375,7 +377,7 @@ class _NoteDetailPageState extends State { context.read().removeNote(widget.note.id); Navigator.pop(context); }, - child: const Text('删除', style: TextStyle(color: Colors.red)), + child: Text('删除', style: TextStyle(color: Theme.of(context).colorScheme.error)), ), ], ), diff --git a/lib/pages/note/note_form_page.dart b/lib/pages/note/note_form_page.dart index 39f5927..415c7d8 100644 --- a/lib/pages/note/note_form_page.dart +++ b/lib/pages/note/note_form_page.dart @@ -59,8 +59,9 @@ class _NoteFormPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, resizeToAvoidBottomInset: true, appBar: AppBar( title: GestureDetector( @@ -73,8 +74,8 @@ class _NoteFormPageState extends State { child: TextButton( onPressed: _saveNote, style: TextButton.styleFrom( - backgroundColor: const Color(0xFF1A1A1A), - foregroundColor: Colors.white, + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), @@ -96,9 +97,9 @@ class _NoteFormPageState extends State { // 顶部信息栏 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), - decoration: const BoxDecoration( + decoration: BoxDecoration( border: Border( - bottom: BorderSide(color: Color(0xFFE8E8E8), width: 0.5), + bottom: BorderSide(color: colors.outline, width: 0.5), ), ), child: Column( @@ -110,10 +111,10 @@ class _NoteFormPageState extends State { // 大日号 Text( '${_createdAt.day}', - style: const TextStyle( + style: TextStyle( fontSize: 30, fontWeight: FontWeight.w200, - color: Color(0xFF333333), + color: colors.onSurface.withValues(alpha: 0.75), height: 1.0, ), ), @@ -125,18 +126,18 @@ class _NoteFormPageState extends State { children: [ Text( '${_createdAt.year}/${_createdAt.month.toString().padLeft(2, '0')} 周${_weekdays[_createdAt.weekday - 1]}', - style: const TextStyle( + style: TextStyle( fontSize: 11, fontWeight: FontWeight.w500, - color: Color(0xFF777777), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(height: 1), Text( '${_createdAt.hour.toString().padLeft(2, '0')}:${_createdAt.minute.toString().padLeft(2, '0')}', - style: const TextStyle( + style: TextStyle( fontSize: 11, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], @@ -145,9 +146,9 @@ class _NoteFormPageState extends State { // 字数统计 Text( '$_charCount 字', - style: const TextStyle( + style: TextStyle( fontSize: 11, - color: Color(0xFFAAAAAA), + color: colors.onSurface.withValues(alpha: 0.35), ), ), ], @@ -208,13 +209,14 @@ class _NoteFormPageState extends State { /// 现代极简底部工具栏 Widget _buildFloatingToolbar() { + final colors = Theme.of(context).colorScheme; return Container( margin: const EdgeInsets.fromLTRB(12, 8, 12, 10), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFEAEAEA), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), @@ -251,7 +253,7 @@ class _NoteFormPageState extends State { // 视图模式切换 const SizedBox(width: 8), Container( - width: 1, height: 20, color: const Color(0xFFE5E5E5), + width: 1, height: 20, color: colors.outline, ), const SizedBox(width: 8), _modeSwitch(Icons.edit, 'edit'), @@ -262,6 +264,7 @@ class _NoteFormPageState extends State { } Widget _toolBtn(IconData icon, String tooltip, VoidCallback onTap) { + final colors = Theme.of(context).colorScheme; return Material( color: Colors.transparent, child: InkWell( @@ -271,7 +274,7 @@ class _NoteFormPageState extends State { message: tooltip, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 6), - child: Icon(icon, size: 21, color: const Color(0xFF555555)), + child: Icon(icon, size: 21, color: colors.onSurface.withValues(alpha: 0.7)), ), ), ), @@ -279,16 +282,18 @@ class _NoteFormPageState extends State { } Widget _toolGap() { - return const Padding( - padding: EdgeInsets.symmetric(horizontal: 6), + final colors = Theme.of(context).colorScheme; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), child: SizedBox( height: 18, - child: VerticalDivider(width: 0, thickness: 0.5, color: Color(0xFFE0E0E0)), + child: VerticalDivider(width: 0, thickness: 0.5, color: colors.outline), ), ); } Widget _modeSwitch(IconData icon, String mode) { + final colors = Theme.of(context).colorScheme; final active = _editorMode == mode; return Material( color: Colors.transparent, @@ -298,13 +303,13 @@ class _NoteFormPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 6), decoration: BoxDecoration( - color: active ? const Color(0xFF1A1A1A) : Colors.transparent, + color: active ? colors.primary : Colors.transparent, borderRadius: BorderRadius.circular(8), ), child: Icon( icon, size: 18, - color: active ? Colors.white : const Color(0xFFAAAAAA), + color: active ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.35), ), ), ), @@ -374,6 +379,7 @@ class _NoteFormPageState extends State { /// 编辑器 Widget _buildEditor() { + final colors = Theme.of(context).colorScheme; return TextField( controller: _contentController, maxLines: null, @@ -384,40 +390,41 @@ class _NoteFormPageState extends State { height: 1.6, fontSize: 16, ), - style: const TextStyle( + style: TextStyle( fontSize: 16, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.6, ), - decoration: const InputDecoration( + decoration: InputDecoration( hintText: '使用 Markdown 格式书写...', hintStyle: TextStyle( fontSize: 16, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), height: 1.6, ), border: InputBorder.none, focusedBorder: InputBorder.none, - contentPadding: EdgeInsets.all(16), + contentPadding: const EdgeInsets.all(16), ), ); } /// 实时 Markdown 预览(点击回到编辑) Widget _buildPreview() { + final colors = Theme.of(context).colorScheme; final text = _contentController.text; return GestureDetector( onTap: () => setState(() => _editorMode = 'edit'), behavior: HitTestBehavior.opaque, child: Container( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, child: text.isEmpty - ? const Center( + ? Center( child: Text( '预览区域', style: TextStyle( fontSize: 14, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), ), ), ) @@ -426,45 +433,45 @@ class _NoteFormPageState extends State { selectable: true, padding: const EdgeInsets.all(16), styleSheet: MarkdownStyleSheet( - h1: const TextStyle( + h1: TextStyle( fontSize: 22, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.4, ), - h2: const TextStyle( + h2: TextStyle( fontSize: 19, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.4, ), - h3: const TextStyle( + h3: TextStyle( fontSize: 17, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.4, ), - p: const TextStyle( + p: TextStyle( fontSize: 15, - color: Color(0xFF333333), + color: colors.onSurface.withValues(alpha: 0.75), height: 1.7, ), - code: const TextStyle( + code: TextStyle( fontSize: 14, - color: Color(0xFF1A1A1A), - backgroundColor: Color(0xFFF0F0F0), + color: colors.onSurface, + backgroundColor: colors.outlineVariant, ), codeblockDecoration: BoxDecoration( - color: const Color(0xFFF0F0F0), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(6), ), - blockquote: const TextStyle( + blockquote: TextStyle( fontSize: 15, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), - blockquoteDecoration: const BoxDecoration( + blockquoteDecoration: BoxDecoration( border: Border( - left: BorderSide(color: Color(0xFFCCCCCC), width: 3), + left: BorderSide(color: colors.onSurface.withValues(alpha: 0.25), width: 3), ), ), ), @@ -494,10 +501,11 @@ class _NoteFormPageState extends State { } Widget _buildTagChip(int index) { + final colors = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(6), ), child: Row( @@ -505,12 +513,12 @@ class _NoteFormPageState extends State { children: [ Text( _tags[index], - style: const TextStyle(fontSize: 11, color: Color(0xFF666666)), + style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.6)), ), const SizedBox(width: 3), GestureDetector( onTap: () => setState(() => _tags.removeAt(index)), - child: const Icon(Icons.close, size: 10, color: Color(0xFFBBBBBB)), + child: Icon(Icons.close, size: 10, color: colors.onSurface.withValues(alpha: 0.3)), ), ], ), @@ -518,20 +526,21 @@ class _NoteFormPageState extends State { } Widget _buildAddTagButton() { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: _showAddTagDialog, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), - border: Border.all(color: const Color(0xFFDDDDDD), width: 1), + border: Border.all(color: colors.onSurface.withValues(alpha: 0.25), width: 1), ), - child: const Row( + child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.add, size: 12, color: Color(0xFFAAAAAA)), - SizedBox(width: 2), - Text('标签', style: TextStyle(fontSize: 11, color: Color(0xFFAAAAAA))), + Icon(Icons.add, size: 12, color: colors.onSurface.withValues(alpha: 0.35)), + const SizedBox(width: 2), + Text('标签', style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.35))), ], ), ), @@ -550,18 +559,20 @@ class _NoteFormPageState extends State { showDialog( context: context, - builder: (ctx) => StatefulBuilder( + builder: (ctx) { + final colors = Theme.of(context).colorScheme; + return StatefulBuilder( builder: (ctx, setDialogState) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), - title: const Text( + title: Text( '添加标签', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), contentPadding: const EdgeInsets.fromLTRB(24, 16, 24, 0), @@ -575,13 +586,13 @@ class _NoteFormPageState extends State { TextField( controller: controller, autofocus: true, - style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), - cursorColor: const Color(0xFF1A1A1A), + style: TextStyle(fontSize: 14, color: colors.onSurface), + cursorColor: colors.primary, decoration: InputDecoration( hintText: '输入新标签名称', - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFBBBBBB)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.3)), filled: true, - fillColor: const Color(0xFFF8F8F8), + fillColor: colors.surfaceContainerHigh, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), @@ -593,11 +604,11 @@ class _NoteFormPageState extends State { ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide(color: Color(0xFF1A1A1A), width: 1), + borderSide: BorderSide(color: colors.primary, width: 1), ), suffixIcon: controller.text.isNotEmpty ? IconButton( - icon: const Icon(Icons.clear, size: 16, color: Color(0xFFAAAAAA)), + icon: Icon(Icons.clear, size: 16, color: colors.onSurface.withValues(alpha: 0.35)), onPressed: () => controller.clear(), ) : null, @@ -613,11 +624,11 @@ class _NoteFormPageState extends State { // 已有标签列表 if (availableTags.isNotEmpty) ...[ const SizedBox(height: 20), - const Text( + Text( '或选择已有标签', style: TextStyle( fontSize: 12, - color: Color(0xFFAAAAAA), + color: colors.onSurface.withValues(alpha: 0.35), ), ), const SizedBox(height: 12), @@ -637,15 +648,15 @@ class _NoteFormPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outline, width: 0.5), ), child: Text( tag, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF555555), + color: colors.onSurface.withValues(alpha: 0.7), ), ), ), @@ -673,7 +684,7 @@ class _NoteFormPageState extends State { TextButton( onPressed: () => Navigator.pop(ctx), style: TextButton.styleFrom( - foregroundColor: const Color(0xFF999999), + foregroundColor: colors.onSurface.withValues(alpha: 0.4), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), child: const Text('取消', style: TextStyle(fontSize: 14)), @@ -684,8 +695,8 @@ class _NoteFormPageState extends State { Navigator.pop(ctx); }, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF1A1A1A), - foregroundColor: Colors.white, + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), @@ -695,10 +706,11 @@ class _NoteFormPageState extends State { ], actionsPadding: const EdgeInsets.fromLTRB(16, 8, 16, 16), ), - ), + ); + }, ); } - + /// 获取所有已有标签(从所有笔记中收集) List _getAllExistingTags(AppProvider provider) { final allTags = {}; @@ -717,6 +729,7 @@ class _NoteFormPageState extends State { } Widget _buildAppBarTitle() { + final colors = Theme.of(context).colorScheme; final base = _isEditing ? '编辑笔记' : '新建笔记'; final t = _titleController.text.trim(); if (t.isEmpty) { @@ -725,7 +738,7 @@ class _NoteFormPageState extends State { children: [ Text(base), const SizedBox(width: 6), - const Icon(Icons.edit, size: 14, color: Color(0xFF999999)), + Icon(Icons.edit, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), ], ); } @@ -738,8 +751,10 @@ class _NoteFormPageState extends State { final controller = TextEditingController(text: _titleController.text); showDialog( context: context, - builder: (context) => AlertDialog( - backgroundColor: Colors.white, + builder: (context) { + final colors = Theme.of(context).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: const Text( @@ -749,23 +764,23 @@ class _NoteFormPageState extends State { content: TextField( controller: controller, autofocus: true, - style: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 16, color: colors.onSurface), decoration: InputDecoration( hintText: '输入标题...', - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.25)), filled: true, - fillColor: const Color(0xFFFAFAFA), + fillColor: colors.surfaceContainerHigh, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE8E8E8), width: 0.5), + borderSide: BorderSide(color: colors.outline, width: 0.5), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFFE8E8E8), width: 0.5), + borderSide: BorderSide(color: colors.outline, width: 0.5), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color(0xFF1A1A1A), width: 1), + borderSide: BorderSide(color: colors.primary, width: 1), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), @@ -778,7 +793,7 @@ class _NoteFormPageState extends State { TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( - foregroundColor: const Color(0xFF666666), + foregroundColor: colors.onSurface.withValues(alpha: 0.6), ), child: const Text('取消'), ), @@ -788,8 +803,8 @@ class _NoteFormPageState extends State { Navigator.pop(context); }, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF1A1A1A), - foregroundColor: Colors.white, + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), @@ -797,7 +812,8 @@ class _NoteFormPageState extends State { ), ], actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - ), + ); + }, ); } @@ -826,7 +842,7 @@ class _NoteFormPageState extends State { } else { // 添加新笔记 - 先创建笔记获取ID final noteId = now.millisecondsSinceEpoch.toString(); - + // 如果有图片,需要移动到正确的ID目录 List finalImages = []; if (_images.isNotEmpty) { @@ -835,7 +851,7 @@ class _NoteFormPageState extends State { final newNoteId = noteId; finalImages = await _moveImagesToNewId(oldNoteId, newNoteId); } - + final title = _titleController.text.trim(); final newNote = Note( @@ -856,17 +872,17 @@ class _NoteFormPageState extends State { // 刷新笔记列表 await context.read().loadNotes(); - + if (!mounted) return; Navigator.pop(context); } - + /// 将图片从临时ID目录移动到新ID目录 Future> _moveImagesToNewId(String oldNoteId, String newNoteId) async { final List newPaths = []; - + final newDir = await ImagePathHelper.instance.getNoteImagesDir(newNoteId); - + for (final imagePath in _images) { // 使用路径分隔符检查,兼容 Windows 和 Unix final normalizedPath = imagePath.replaceAll('\\', '/'); @@ -874,9 +890,9 @@ class _NoteFormPageState extends State { // 需要移动的文件 final fileName = p.basename(imagePath); final newPath = p.join(newDir, fileName); - + await ImagePathHelper.instance.ensureDirExists(newDir); - + // 检查源文件是否存在 final sourceFile = File(imagePath); if (await sourceFile.exists()) { @@ -888,14 +904,14 @@ class _NoteFormPageState extends State { newPaths.add(imagePath); } } - + // 删除旧目录 try { await ImagePathHelper.instance.deleteNoteImages(oldNoteId); } catch (e) { // 忽略删除失败 } - + return newPaths; } @@ -908,11 +924,11 @@ class _NoteFormPageState extends State { maxHeight: 1920, imageQuality: 85, ); - + if (image != null) { // 生成唯一的文件名 final fileName = '${DateTime.now().millisecondsSinceEpoch}.jpg'; - + // 如果是编辑模式,使用现有笔记ID;如果是新建模式,使用临时ID(保存时会替换) String noteId; if (_isEditing) { @@ -922,14 +938,14 @@ class _NoteFormPageState extends State { noteId = _tempNoteId ?? DateTime.now().millisecondsSinceEpoch.toString(); _tempNoteId = noteId; } - + // 复制图片到应用目录: images/notes/{noteId}/{fileName} final targetDir = await ImagePathHelper.instance.getNoteImagesDir(noteId); await ImagePathHelper.instance.ensureDirExists(targetDir); final targetPath = p.join(targetDir, fileName); - + await File(image.path).copy(targetPath); - + setState(() => _images.add(targetPath)); } } catch (e) { @@ -939,11 +955,12 @@ class _NoteFormPageState extends State { /// 构建图片横向滚动行 Widget _buildImageRow() { + final colors = Theme.of(context).colorScheme; if (_images.isEmpty) { return Container( height: 80, - decoration: const BoxDecoration( - color: Colors.white, + decoration: BoxDecoration( + color: colors.surface, ), child: ListView( scrollDirection: Axis.horizontal, @@ -958,7 +975,7 @@ class _NoteFormPageState extends State { return Container( height: 88, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.02), @@ -984,6 +1001,7 @@ class _NoteFormPageState extends State { /// 添加图片按钮 Widget _buildAddImageButton() { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: _pickImage, borderRadius: BorderRadius.circular(12), @@ -992,13 +1010,13 @@ class _NoteFormPageState extends State { height: 64, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - color: const Color(0xFFF8F8F8), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + color: colors.surfaceContainerHigh, + border: Border.all(color: colors.outline, width: 0.5), ), - child: const Icon( + child: Icon( Icons.add_photo_alternate_outlined, size: 24, - color: Color(0xFFBBBBBB), + color: colors.onSurface.withValues(alpha: 0.3), ), ), ); @@ -1006,6 +1024,7 @@ class _NoteFormPageState extends State { /// 构建图片项 Widget _buildImageItem(int index) { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: () => _showImagePreview(index), onLongPress: () => _showDeleteImageDialog(index), @@ -1015,7 +1034,7 @@ class _NoteFormPageState extends State { height: 64, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outline, width: 0.5), ), clipBehavior: Clip.antiAlias, child: Image.file( @@ -1056,8 +1075,10 @@ class _NoteFormPageState extends State { void _showDeleteImageDialog(int index) { showDialog( context: context, - builder: (context) => AlertDialog( - backgroundColor: Colors.white, + builder: (context) { + final colors = Theme.of(context).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: const Text( @@ -1067,11 +1088,11 @@ class _NoteFormPageState extends State { fontWeight: FontWeight.w600, ), ), - content: const Text( + content: Text( '确定要删除这张图片吗?此操作不可恢复。', style: TextStyle( fontSize: 14, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.5, ), ), @@ -1079,7 +1100,7 @@ class _NoteFormPageState extends State { TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( - foregroundColor: const Color(0xFF666666), + foregroundColor: colors.onSurface.withValues(alpha: 0.6), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), child: const Text('取消'), @@ -1090,8 +1111,8 @@ class _NoteFormPageState extends State { Navigator.pop(context); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + backgroundColor: colors.error, + foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -1102,7 +1123,8 @@ class _NoteFormPageState extends State { ), ], actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - ), + ); + }, ); } } diff --git a/lib/pages/note/note_share_page.dart b/lib/pages/note/note_share_page.dart index 06deddd..4bb56bc 100644 --- a/lib/pages/note/note_share_page.dart +++ b/lib/pages/note/note_share_page.dart @@ -26,21 +26,22 @@ class _NoteSharePageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: const Color(0xFFF5F5F5), + backgroundColor: colors.surfaceContainerHighest, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, leading: IconButton( - icon: const Icon(Icons.close, color: Color(0xFF1A1A1A)), + icon: Icon(Icons.close, color: colors.onSurface), onPressed: () => Navigator.pop(context), ), - title: const Text( + title: Text( '分享笔记', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), centerTitle: true, @@ -53,12 +54,12 @@ class _NoteSharePageState extends State { height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) - : const Text( + : Text( '分享', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ), @@ -78,6 +79,7 @@ class _NoteSharePageState extends State { } Widget _buildPosterWidget() { + final colors = Theme.of(context).colorScheme; final note = widget.note; final hasImages = note.images.isNotEmpty; final dateStr = @@ -88,7 +90,7 @@ class _NoteSharePageState extends State { return Container( width: 320, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( @@ -124,10 +126,10 @@ class _NoteSharePageState extends State { if (note.title.isNotEmpty) ...[ Text( note.title, - style: const TextStyle( + style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), const SizedBox(height: 4), @@ -136,9 +138,9 @@ class _NoteSharePageState extends State { // 日期 Text( dateStr, - style: const TextStyle( + style: TextStyle( fontSize: 12, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), @@ -152,12 +154,12 @@ class _NoteSharePageState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( - color: const Color(0xFFF0F0F0), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(4), ), child: Text( tag, - style: const TextStyle(fontSize: 11, color: Color(0xFF888888)), + style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.5)), ), ); }).toList(), @@ -165,7 +167,7 @@ class _NoteSharePageState extends State { ], const SizedBox(height: 16), - Container(height: 0.5, color: const Color(0xFFE8E8E8)), + Container(height: 0.5, color: colors.outline), const SizedBox(height: 16), // 正文内容 @@ -173,9 +175,9 @@ class _NoteSharePageState extends State { note.content, maxLines: 12, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF333333), + color: colors.onSurface.withValues(alpha: 0.75), height: 1.8, ), ), @@ -191,11 +193,11 @@ class _NoteSharePageState extends State { ], const Spacer(), // Mooknote 品牌 - const Text( + Text( 'Mooknote', style: TextStyle( fontSize: 11, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), fontWeight: FontWeight.w500, ), ), @@ -210,14 +212,15 @@ class _NoteSharePageState extends State { } Widget _buildMetaChip(IconData icon, String text) { + final colors = Theme.of(context).colorScheme; return Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 13, color: const Color(0xFFAAAAAA)), + Icon(icon, size: 13, color: colors.onSurface.withValues(alpha: 0.35)), const SizedBox(width: 4), Text( text, - style: const TextStyle(fontSize: 11, color: Color(0xFFAAAAAA)), + style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.35)), ), ], ); diff --git a/lib/pages/note/note_tab_page.dart b/lib/pages/note/note_tab_page.dart index 0a616ff..05690bf 100644 --- a/lib/pages/note/note_tab_page.dart +++ b/lib/pages/note/note_tab_page.dart @@ -23,7 +23,7 @@ class _NoteTabPageState extends State { bool _hasMore = true; final ScrollController _scrollController = ScrollController(); - int _layoutStyle = 0; // 0: 列表, 1: 瀑布流, 2: 时间线 + int _layoutStyle = 0; bool _firstLoad = true; @override @@ -110,7 +110,6 @@ class _NoteTabPageState extends State { Widget build(BuildContext context) { return Column( children: [ - // 笔记内容 Expanded( child: Consumer( builder: (context, provider, child) { @@ -122,10 +121,11 @@ class _NoteTabPageState extends State { } if (allNotes.isEmpty && _displayedNotes.isEmpty) { + final colors = Theme.of(context).colorScheme; return RefreshIndicator( onRefresh: _refresh, - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, child: ListView( physics: const AlwaysScrollableScrollPhysics(), children: [_buildEmptyState(context)], @@ -159,6 +159,7 @@ class _NoteTabPageState extends State { } Widget _buildWaterfallSkeleton() { + final colors = Theme.of(context).colorScheme; return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(12, 8, 12, 100), child: Row( @@ -168,7 +169,7 @@ class _NoteTabPageState extends State { children: List.generate(4, (_) => Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow(color: Colors.black.withValues(alpha: 0.04), blurRadius: 6, offset: const Offset(0, 2)), @@ -200,25 +201,24 @@ class _NoteTabPageState extends State { ); } - // ─── 列表视图 ──────────────────────────────────────────────────────── - Widget _buildListView() { + final colors = Theme.of(context).colorScheme; return RefreshIndicator( onRefresh: _refresh, - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, child: ListView.builder( controller: _scrollController, padding: const EdgeInsets.fromLTRB(12, 10, 12, 100), itemCount: _displayedNotes.length + (_hasMore ? 1 : 0), itemBuilder: (context, index) { if (index >= _displayedNotes.length) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 16), + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16), child: Center( child: SizedBox( width: 20, height: 20, - child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF1A1A1A)), + child: CircularProgressIndicator(strokeWidth: 2, color: colors.primary), ), ), ); @@ -229,24 +229,23 @@ class _NoteTabPageState extends State { ); } - // ─── 时间线视图 ────────────────────────────────────────────────────── - Widget _buildTimelineView() { + final colors = Theme.of(context).colorScheme; return RefreshIndicator( onRefresh: _refresh, - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, child: ListView.builder( controller: _scrollController, padding: const EdgeInsets.fromLTRB(12, 8, 12, 100), itemCount: _displayedNotes.length + (_hasMore ? 1 : 0), itemBuilder: (context, index) { if (index >= _displayedNotes.length) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 16), + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16), child: Center( child: SizedBox(width: 20, height: 20, - child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF1A1A1A))), + child: CircularProgressIndicator(strokeWidth: 2, color: colors.primary)), ), ); } @@ -257,6 +256,7 @@ class _NoteTabPageState extends State { } Widget _buildTimelineItem(Note note) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: () { Navigator.pushNamed(context, '/note-detail', arguments: note).then((_) async { @@ -268,72 +268,60 @@ class _NoteTabPageState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - // 左侧时间线 SizedBox( width: 40, child: Column( children: [ - // 圆点 Container( width: 10, height: 10, decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2), + border: Border.all(color: colors.surface, width: 2), ), ), - // 连线 Expanded( child: Container( width: 1, - color: const Color(0xFFE5E5E5), + color: colors.outline, ), ), ], ), ), - - // 右侧内容 Expanded( child: Container( margin: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.all(14), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - // 时间 Text( _formatFullDate(note.updatedAt), - style: const TextStyle(fontSize: 11, color: Color(0xFF999999)), + style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4)), ), - - // 标题 if (note.title.isNotEmpty) ...[ const SizedBox(height: 6), Text( note.title, - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], - - // 内容预览 const SizedBox(height: 6), Text( _getPreviewText(note), maxLines: 2, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 13, color: Color(0xFF888888), height: 1.5), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.5), height: 1.5), ), - - // 标签 if (note.tags.isNotEmpty) ...[ const SizedBox(height: 8), Wrap( @@ -342,10 +330,10 @@ class _NoteTabPageState extends State { children: note.tags.map((tag) => Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(4), ), - child: Text(tag, style: const TextStyle(fontSize: 10, color: Color(0xFF999999))), + child: Text(tag, style: TextStyle(fontSize: 10, color: colors.onSurface.withValues(alpha: 0.4))), )).toList(), ), ], @@ -364,10 +352,8 @@ class _NoteTabPageState extends State { '${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; } - // ─── 瀑布流视图 ────────────────────────────────────────────────────── - Widget _buildWaterfallView() { - // 分为左右两列 + final colors = Theme.of(context).colorScheme; final leftItems = []; final rightItems = []; for (int i = 0; i < _displayedNotes.length; i++) { @@ -380,8 +366,8 @@ class _NoteTabPageState extends State { return RefreshIndicator( onRefresh: _refresh, - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, child: SingleChildScrollView( controller: _scrollController, padding: const EdgeInsets.fromLTRB(12, 8, 12, 100), @@ -398,6 +384,7 @@ class _NoteTabPageState extends State { } Widget _buildWaterfallCard(Note note) { + final colors = Theme.of(context).colorScheme; final contentText = _getPreviewText(note); final images = note.images; final hasImage = images.isNotEmpty; @@ -413,7 +400,7 @@ class _NoteTabPageState extends State { child: Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( @@ -428,7 +415,6 @@ class _NoteTabPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - // 顶部图片 if (hasImage) Stack( children: [ @@ -458,8 +444,6 @@ class _NoteTabPageState extends State { ), ], ), - - // 底部文字区域 Padding( padding: const EdgeInsets.fromLTRB(10, 8, 10, 10), child: Column( @@ -471,47 +455,45 @@ class _NoteTabPageState extends State { note.title, maxLines: 2, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.3, ), ), - if (contentText.isNotEmpty && contentText != '(无内容)') ...[ if (note.title.isNotEmpty) const SizedBox(height: 4), Text( contentText, maxLines: 2, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontSize: 11, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), height: 1.4, ), ), ], - const SizedBox(height: 6), Row( children: [ Expanded( child: Text( _formatTime(note.updatedAt), - style: const TextStyle(fontSize: 10, color: Color(0xFFCCCCCC)), + style: TextStyle(fontSize: 10, color: colors.onSurface.withValues(alpha: 0.25)), ), ), if (note.tags.isNotEmpty) Container( padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(3), ), child: Text( note.tags.first, - style: const TextStyle(fontSize: 10, color: Color(0xFF999999)), + style: TextStyle(fontSize: 10, color: colors.onSurface.withValues(alpha: 0.4)), ), ), ], @@ -546,20 +528,21 @@ class _NoteTabPageState extends State { } void _showDeleteDialog(BuildContext context, Note note) { + final colors = Theme.of(context).colorScheme; showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), - content: const Text('确定要删除这条笔记吗?删除后可在回收站恢复。', - style: TextStyle(fontSize: 14, color: Color(0xFF666666), height: 1.5)), + title: Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), + content: Text('确定要删除这条笔记吗?删除后可在回收站恢复。', + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.5)), actions: [ TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( - foregroundColor: const Color(0xFF666666), + foregroundColor: colors.onSurface.withValues(alpha: 0.6), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), child: const Text('取消'), @@ -570,8 +553,8 @@ class _NoteTabPageState extends State { Navigator.pop(context); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + backgroundColor: colors.error, + foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), @@ -584,8 +567,6 @@ class _NoteTabPageState extends State { ); } - // ─── 数据同步 ──────────────────────────────────────────────────────── - void _syncDisplayedNotes(List allNotes) { final validNoteIds = allNotes.map((n) => n.id).toSet(); final initialLength = _displayedNotes.length; @@ -611,9 +592,8 @@ class _NoteTabPageState extends State { } } - // ─── 空状态 ────────────────────────────────────────────────────────── - Widget _buildEmptyState(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -621,24 +601,24 @@ class _NoteTabPageState extends State { Container( width: 80, height: 80, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), ), - child: const Icon(Icons.note_outlined, size: 40, color: Color(0xFFCCCCCC)), + child: Icon(Icons.note_outlined, size: 40, color: colors.onSurface.withValues(alpha: 0.25)), ), const SizedBox(height: 20), - const Text('暂无笔记', style: TextStyle(fontSize: 16, color: Color(0xFF999999))), + Text('暂无笔记', style: TextStyle(fontSize: 16, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(height: 24), InkWell( onTap: () => Navigator.pushNamed(context, '/note-form'), child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(8), ), - child: const Text('添加记录', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)), + child: Text('添加记录', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onPrimary)), ), ), ], diff --git a/lib/pages/profile_page.dart b/lib/pages/profile_page.dart index 0a03baa..1cf39c8 100644 --- a/lib/pages/profile_page.dart +++ b/lib/pages/profile_page.dart @@ -51,12 +51,13 @@ class _ProfilePageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Column( children: [ AppBar( leading: Builder( builder: (context) => IconButton( - icon: const Icon(Icons.menu, color: Color(0xFF1A1A1A)), + icon: Icon(Icons.menu, color: colors.onSurface), onPressed: () => Scaffold.of(context).openDrawer(), ), ), @@ -64,7 +65,7 @@ class _ProfilePageState extends State { ), Expanded( child: Container( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -98,6 +99,7 @@ class _ProfilePageState extends State { Widget _buildUserCard() { return Consumer( builder: (context, provider, child) { + final colors = Theme.of(context).colorScheme; final movieCount = provider.movies.where((m) => !m.isDeleted).length; final bookCount = provider.books.length; final noteCount = provider.notes.length; @@ -106,7 +108,7 @@ class _ProfilePageState extends State { margin: const EdgeInsets.fromLTRB(16, 12, 16, 0), padding: const EdgeInsets.all(24), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2)), @@ -122,14 +124,14 @@ class _ProfilePageState extends State { width: 64, height: 64, decoration: BoxDecoration( shape: BoxShape.circle, - color: const Color(0xFFF5F5F5), - border: Border.all(color: const Color(0xFFEEEEEE), width: 0.5), + color: colors.surfaceContainerHighest, + border: Border.all(color: colors.outlineVariant, width: 0.5), ), clipBehavior: Clip.antiAlias, child: _avatarPath != null && _avatarPath!.isNotEmpty ? Image.file(File(_avatarPath!), fit: BoxFit.cover, - errorBuilder: (_, __, ___) => const Icon(Icons.person_outline, size: 32, color: Color(0xFFCCCCCC))) - : const Icon(Icons.person_outline, size: 32, color: Color(0xFFCCCCCC)), + errorBuilder: (_, __, ___) => Icon(Icons.person_outline, size: 32, color: colors.onSurface.withValues(alpha: 0.25))) + : Icon(Icons.person_outline, size: 32, color: colors.onSurface.withValues(alpha: 0.25)), ), ), const SizedBox(width: 16), @@ -139,25 +141,25 @@ class _ProfilePageState extends State { children: [ GestureDetector( onTap: () => _editNickname(context), - child: Text(_nickname, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + child: Text(_nickname, style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: colors.onSurface)), ), const SizedBox(height: 4), GestureDetector( onTap: () => _editMotto(context), child: Text(_motto, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 13, color: Color(0xFFAAAAAA))), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.35))), ), ], ), ), IconButton( - icon: const Icon(Icons.settings_outlined, color: Color(0xFF999999)), + icon: Icon(Icons.settings_outlined, color: colors.onSurface.withValues(alpha: 0.4)), onPressed: () => _showSettings(context), ), ], ), const SizedBox(height: 16), - const Divider(height: 1, color: Color(0xFFF0F0F0)), + Divider(height: 1, color: colors.outlineVariant), const SizedBox(height: 12), _buildStatRow(Icons.movie_outlined, _formatCount(movieCount), '观影'), const SizedBox(height: 8), @@ -172,13 +174,14 @@ class _ProfilePageState extends State { } Widget _buildStatRow(IconData icon, String count, String label) { + final colors = Theme.of(context).colorScheme; return Row( children: [ - Icon(icon, size: 14, color: const Color(0xFFBBBBBB)), + Icon(icon, size: 14, color: colors.onSurface.withValues(alpha: 0.3)), const SizedBox(width: 8), - Text(label, style: const TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text(label, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), const Spacer(), - Text(count, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text(count, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: colors.onSurface)), ], ); } @@ -192,11 +195,12 @@ class _ProfilePageState extends State { // ─── 探索行 ────────────────────────────────────────────────────────── Widget _buildExploreRow() { + final colors = Theme.of(context).colorScheme; return Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2)), @@ -204,9 +208,9 @@ class _ProfilePageState extends State { ), child: Row( children: [ - const Icon(Icons.auto_awesome, size: 16, color: Color(0xFFBBBBBB)), + Icon(Icons.auto_awesome, size: 16, color: colors.onSurface.withValues(alpha: 0.3)), const SizedBox(width: 10), - const Text('快捷入口', style: TextStyle(fontSize: 13, color: Color(0xFF888888))), + Text('快捷入口', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.5))), const Spacer(), _buildQuickAction(Icons.explore_outlined, '漫步', () => Navigator.push(context, MaterialPageRoute(builder: (_) => const StrollPage()))), const SizedBox(width: 24), @@ -217,14 +221,15 @@ class _ProfilePageState extends State { } Widget _buildQuickAction(IconData icon, String label, VoidCallback onTap) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: onTap, child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 20, color: const Color(0xFF555555)), + Icon(icon, size: 20, color: colors.onSurface.withValues(alpha: 0.7)), const SizedBox(height: 2), - Text(label, style: const TextStyle(fontSize: 10, color: Color(0xFF999999))), + Text(label, style: TextStyle(fontSize: 10, color: colors.onSurface.withValues(alpha: 0.4))), ], ), ); @@ -233,17 +238,19 @@ class _ProfilePageState extends State { // ─── 菜单 ──────────────────────────────────────────────────────────── Widget _buildSectionHeader(String title) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), - child: Text(title, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFFBBBBBB))), + child: Text(title, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: colors.onSurface.withValues(alpha: 0.3))), ); } Widget _buildMenuGroup(List<_MenuEntry> entries) { + final colors = Theme.of(context).colorScheme; return Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2)), @@ -269,19 +276,19 @@ class _ProfilePageState extends State { Container( width: 36, height: 36, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), - child: Icon(e.value.icon, size: 18, color: const Color(0xFF666666)), + child: Icon(e.value.icon, size: 18, color: colors.onSurface.withValues(alpha: 0.6)), ), const SizedBox(width: 12), - Expanded(child: Text(e.value.title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A)))), - const Icon(Icons.chevron_right, size: 16, color: Color(0xFFD0D0D0)), + Expanded(child: Text(e.value.title, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface))), + Icon(Icons.chevron_right, size: 16, color: colors.onSurface.withValues(alpha: 0.2)), ], ), ), ), - if (!isLast) const Divider(height: 1, indent: 68, endIndent: 20, color: Color(0xFFF0F0F0)), + if (!isLast) Divider(height: 1, indent: 68, endIndent: 20, color: colors.outlineVariant), ], ); }).toList(), @@ -310,16 +317,17 @@ class _ProfilePageState extends State { } void _editNickname(BuildContext context) { + final colors = Theme.of(context).colorScheme; final controller = TextEditingController(text: _nickname); showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Colors.white, elevation: 0, + backgroundColor: colors.surface, 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))))), + title: Text('修改昵称', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), + content: TextField(controller: controller, decoration: InputDecoration(hintText: '输入昵称', border: UnderlineInputBorder(borderSide: BorderSide(color: colors.outline)))), actions: [ - TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消', style: TextStyle(color: Color(0xFF666666)))), + TextButton(onPressed: () => Navigator.pop(context), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))), TextButton(onPressed: () async { final newNickname = controller.text.trim(); if (newNickname.isNotEmpty) { @@ -334,16 +342,17 @@ class _ProfilePageState extends State { } void _editMotto(BuildContext context) { + final colors = Theme.of(context).colorScheme; final controller = TextEditingController(text: _motto); showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Colors.white, elevation: 0, + backgroundColor: colors.surface, 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))))), + title: Text('修改座右铭', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), + content: TextField(controller: controller, maxLines: 2, decoration: InputDecoration(hintText: '输入座右铭', border: UnderlineInputBorder(borderSide: BorderSide(color: colors.outline)))), actions: [ - TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消', style: TextStyle(color: Color(0xFF666666)))), + TextButton(onPressed: () => Navigator.pop(context), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))), TextButton(onPressed: () async { final newMotto = controller.text.trim(); await _userPrefs.setMotto(newMotto); @@ -358,34 +367,35 @@ class _ProfilePageState extends State { // ─── 备份弹窗 ──────────────────────────────────────────────────────── void _showBackupOptions(BuildContext context) { + final colors = Theme.of(context).colorScheme; showModalBottomSheet( context: context, - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), builder: (ctx) => Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: Column( mainAxisSize: MainAxisSize.min, children: [ - Container(width: 36, height: 4, decoration: BoxDecoration(color: const Color(0xFFDDDDDD), borderRadius: BorderRadius.circular(2))), + Container(width: 36, height: 4, decoration: BoxDecoration(color: colors.onSurface.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(2))), const SizedBox(height: 20), - const Align(alignment: Alignment.centerLeft, child: Text('选择备份方式', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)))), + Align(alignment: Alignment.centerLeft, child: Text('选择备份方式', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface))), const SizedBox(height: 16), ListTile( contentPadding: EdgeInsets.zero, - leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), child: const Icon(Icons.folder_outlined, color: Color(0xFF666666))), - title: const Text('本地备份', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A))), - subtitle: const Text('备份到本地文件夹,支持恢复', style: TextStyle(fontSize: 12, color: Color(0xFF999999))), - trailing: const Icon(Icons.chevron_right, color: Color(0xFFCCCCCC)), + leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(Icons.folder_outlined, color: colors.onSurface.withValues(alpha: 0.6))), + title: Text('本地备份', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), + subtitle: Text('备份到本地文件夹,支持恢复', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), + trailing: Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.25)), onTap: () { Navigator.pop(ctx); _push(context, const BackupPage()); }, ), - const Divider(height: 0.5, color: Color(0xFFF0F0F0)), + Divider(height: 0.5, color: colors.outlineVariant), ListTile( contentPadding: EdgeInsets.zero, - leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), child: const Icon(Icons.cloud_outlined, color: Color(0xFF666666))), - title: const Text('云备份', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A))), - subtitle: const Text('通过 WebDAV 同步到云端', style: TextStyle(fontSize: 12, color: Color(0xFF999999))), - trailing: const Icon(Icons.chevron_right, color: Color(0xFFCCCCCC)), + leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(Icons.cloud_outlined, color: colors.onSurface.withValues(alpha: 0.6))), + title: Text('云备份', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), + subtitle: Text('通过 WebDAV 同步到云端', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), + trailing: Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.25)), onTap: () { Navigator.pop(ctx); _push(context, const CloudSyncPage()); }, ), const SizedBox(height: 20), @@ -421,17 +431,20 @@ class SettingsPage extends StatefulWidget { class _SettingsPageState extends State { final UserPrefs _userPrefs = UserPrefs(); bool _hideBottomNavOnScroll = true; + int _themeMode = 0; // 0=系统, 1=浅色, 2=深色 @override void initState() { super.initState(); _hideBottomNavOnScroll = _userPrefs.hideBottomNavOnScroll; + _themeMode = _userPrefs.themeMode; } @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar(title: const Text('设置')), body: ListView( children: [ @@ -442,7 +455,7 @@ class _SettingsPageState extends State { subtitle: '清理未在数据库中引用的图片文件', onTap: () => _showClearCacheDialog(context), ), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildSwitchItem( icon: Icons.swipe_vertical_outlined, title: '底部导航栏滚动隐藏', @@ -450,28 +463,31 @@ class _SettingsPageState extends State { value: _hideBottomNavOnScroll, onChanged: _toggleHideBottomNavOnScroll, ), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildNavigationItem( icon: Icons.apps_outlined, title: '应用图标', subtitle: '更换桌面应用图标', onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AppIconPickerPage())), ), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildNavigationItem( icon: Icons.view_list_outlined, title: '主界面设置', subtitle: '启动标签、模块显示开关', onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const MainContentSettingsPage())), ), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildNavigationItem( icon: Icons.dashboard_outlined, title: '布局设置', subtitle: '笔记、影视、阅读的展示样式', onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const LayoutSettingsPage())), ), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), + _buildSectionHeader('外观'), + _buildThemeModeSelector(), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildSectionHeader('帮助'), _buildLinkItem( context: context, @@ -480,7 +496,7 @@ class _SettingsPageState extends State { subtitle: '查看应用使用指南', url: 'https://mooknote.iletter.top/#/guide', ), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), ], ), ); @@ -491,23 +507,93 @@ class _SettingsPageState extends State { setState(() => _hideBottomNavOnScroll = value); } + static const _themeModeLabels = ['跟随系统', '浅色模式', '深色模式']; + static const _themeModeIcons = [Icons.brightness_auto, Icons.light_mode, Icons.dark_mode]; + + Widget _buildThemeModeSelector() { + final colors = Theme.of(context).colorScheme; + return InkWell( + onTap: () => _showThemeModePicker(), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: Row( + children: [ + Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(_themeModeIcons[_themeMode], color: colors.onSurface.withValues(alpha: 0.6), size: 22)), + const SizedBox(width: 16), + Expanded( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('主题模式', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), + const SizedBox(height: 2), + Text(_themeModeLabels[_themeMode], style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), + ]), + ), + Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.25), size: 20), + ], + ), + ), + ); + } + + void _showThemeModePicker() { + final colors = Theme.of(context).colorScheme; + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + builder: (ctx) => Container( + decoration: BoxDecoration(color: colors.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(16))), + padding: const EdgeInsets.symmetric(vertical: 12), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container(width: 36, height: 4, decoration: BoxDecoration(color: colors.onSurface.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(2))), + const SizedBox(height: 20), + Align(alignment: Alignment.centerLeft, child: Padding(padding: const EdgeInsets.symmetric(horizontal: 24), child: Text('主题模式', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface)))), + const SizedBox(height: 16), + for (int i = 0; i < _themeModeLabels.length; i++) + ListTile( + leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(_themeModeIcons[i], color: colors.onSurface.withValues(alpha: 0.6))), + title: Text(_themeModeLabels[i], style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), + trailing: _themeMode == i ? Icon(Icons.check, color: colors.onSurface, size: 20) : null, + onTap: () async { await _setThemeMode(i); Navigator.pop(ctx); }, + ), + const SizedBox(height: 8), + ], + ), + ), + ); + } + + Future _setThemeMode(int mode) async { + await _userPrefs.setThemeMode(mode); + final themeMode = switch (mode) { + 1 => ThemeMode.light, + 2 => ThemeMode.dark, + _ => ThemeMode.system, + }; + if (mounted) { + context.read().setThemeMode(themeMode); + setState(() => _themeMode = mode); + } + } + Widget _buildSwitchItem({required IconData icon, required String title, required String subtitle, required bool value, required ValueChanged onChanged}) { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: () => onChanged(!value), 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)), + Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(icon, color: colors.onSurface.withValues(alpha: 0.6), 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))), + Text(title, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), const SizedBox(height: 2), - Text(subtitle, style: const TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text(subtitle, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), ]), ), - Switch(value: value, onChanged: onChanged, activeColor: const Color(0xFF1A1A1A), activeTrackColor: const Color(0xFF1A1A1A).withOpacity(0.3), inactiveThumbColor: Colors.white, inactiveTrackColor: const Color(0xFFE5E5E5)), + Switch(value: value, onChanged: onChanged, activeColor: colors.primary, activeTrackColor: colors.primary.withOpacity(0.3), inactiveThumbColor: colors.surface, inactiveTrackColor: colors.outline), ], ), ), @@ -515,29 +601,31 @@ class _SettingsPageState extends State { } Widget _buildSectionHeader(String title) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.fromLTRB(24, 32, 24, 12), - child: Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + child: Text(title, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: colors.onSurface)), ); } Widget _buildNavigationItem({required IconData icon, required String title, required String subtitle, required VoidCallback onTap}) { + final colors = Theme.of(context).colorScheme; 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)), + Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(icon, color: colors.onSurface.withValues(alpha: 0.6), 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))), + Text(title, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), const SizedBox(height: 2), - Text(subtitle, style: const TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text(subtitle, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), ]), ), - const Icon(Icons.chevron_right, color: Color(0xFFCCCCCC), size: 20), + Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.25), size: 20), ], ), ), @@ -545,22 +633,23 @@ class _SettingsPageState extends State { } Widget _buildLinkItem({required BuildContext context, required IconData icon, required String title, required String subtitle, required String url}) { + final colors = Theme.of(context).colorScheme; 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)), + Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(icon, color: colors.onSurface.withValues(alpha: 0.6), 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))), + Text(title, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), const SizedBox(height: 2), - Text(subtitle, style: const TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text(subtitle, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), ]), ), - const Icon(Icons.open_in_new, color: Color(0xFFCCCCCC), size: 18), + Icon(Icons.open_in_new, color: colors.onSurface.withValues(alpha: 0.25), size: 18), ], ), ), @@ -568,19 +657,20 @@ class _SettingsPageState extends State { } Widget _buildActionItem({required IconData icon, required String title, required String subtitle, required VoidCallback onTap}) { + final colors = Theme.of(context).colorScheme; 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)), + Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(icon, color: colors.onSurface.withValues(alpha: 0.6), 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))), + Text(title, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), const SizedBox(height: 2), - Text(subtitle, style: const TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text(subtitle, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), ]), ), ], @@ -590,19 +680,20 @@ class _SettingsPageState extends State { } void _showClearCacheDialog(BuildContext pageContext) { + final colors = Theme.of(context).colorScheme; showDialog( context: pageContext, builder: (dialogContext) => AlertDialog( - backgroundColor: Colors.white, elevation: 0, + backgroundColor: colors.surface, elevation: 0, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), title: const Text('清除缓存数据'), content: const Text('这将删除所有未在数据库中引用的图片文件。确定要继续吗?'), actions: [ - TextButton(onPressed: () => Navigator.pop(dialogContext), child: const Text('取消', style: TextStyle(color: Color(0xFF666666)))), + TextButton(onPressed: () => Navigator.pop(dialogContext), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))), TextButton(onPressed: () async { Navigator.pop(dialogContext); await _clearCacheData(pageContext); - }, child: const Text('确定', style: TextStyle(color: Colors.red))), + }, child: Text('确定', style: TextStyle(color: colors.error))), ], ), ); @@ -712,24 +803,25 @@ class _MainContentSettingsPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar(title: const Text('主界面设置')), body: ListView( children: [ _buildSectionHeader('启动设置'), _buildDefaultTabSelector(), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildSectionHeader('模块开关'), _buildSwitchItem(Icons.movie_outlined, '观影', '记录和管理观影记录', _showMovieTab, _toggleMovieTab), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildSwitchItem(Icons.menu_book_outlined, '阅读', '记录和管理阅读记录', _showBookTab, _toggleBookTab), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildSwitchItem(Icons.note_outlined, '笔记', '记录和管理笔记', _showNoteTab, _toggleNoteTab), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), - child: const Text('至少保留一个模块,关闭后对应标签页将不再显示。', style: TextStyle(fontSize: 12, color: Color(0xFFBBBBBB))), + child: Text('至少保留一个模块,关闭后对应标签页将不再显示。', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.3))), ), ], ), @@ -737,37 +829,40 @@ class _MainContentSettingsPageState extends State { } Widget _buildSectionHeader(String title) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.fromLTRB(24, 28, 24, 12), - child: Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + child: Text(title, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: colors.onSurface)), ); } Widget _buildSwitchItem(IconData icon, String title, String subtitle, bool value, ValueChanged onChanged) { + final colors = Theme.of(context).colorScheme; return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), - leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), child: Icon(icon, color: const Color(0xFF666666), size: 22)), - title: Text(title, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A))), - subtitle: Text(subtitle, style: const TextStyle(fontSize: 12, 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)), + leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(icon, color: colors.onSurface.withValues(alpha: 0.6), size: 22)), + title: Text(title, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), + subtitle: Text(subtitle, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), + trailing: Switch(value: value, onChanged: onChanged, activeColor: colors.primary, activeTrackColor: colors.primary.withOpacity(0.3), inactiveThumbColor: colors.surface, inactiveTrackColor: colors.outline), ); } Widget _buildDefaultTabSelector() { + final colors = Theme.of(context).colorScheme; final labels = ['影视', '阅读', '笔记']; final icons = [Icons.movie_outlined, Icons.menu_book_outlined, Icons.note_outlined]; return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), - leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), child: const Icon(Icons.home_outlined, color: Color(0xFF666666), size: 22)), - title: const Text('默认启动标签', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A))), - subtitle: const Text('打开应用时默认显示的页面', style: TextStyle(fontSize: 12, color: Color(0xFF999999))), + leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(Icons.home_outlined, color: colors.onSurface.withValues(alpha: 0.6), size: 22)), + title: Text('默认启动标签', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), + subtitle: Text('打开应用时默认显示的页面', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ - Text(labels[_defaultTabIndex], style: const TextStyle(fontSize: 14, color: Color(0xFF999999))), + Text(labels[_defaultTabIndex], style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(width: 4), - const Icon(Icons.chevron_right, color: Color(0xFFCCCCCC), size: 20), + Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.25), size: 20), ], ), onTap: () => _showDefaultTabPicker(labels, icons), @@ -775,24 +870,25 @@ class _MainContentSettingsPageState extends State { } void _showDefaultTabPicker(List labels, List icons) { + final colors = Theme.of(context).colorScheme; showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (ctx) => Container( - decoration: const BoxDecoration(color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(16))), + decoration: BoxDecoration(color: colors.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(16))), padding: const EdgeInsets.symmetric(vertical: 12), child: Column( mainAxisSize: MainAxisSize.min, children: [ - Container(width: 36, height: 4, decoration: BoxDecoration(color: const Color(0xFFDDDDDD), borderRadius: BorderRadius.circular(2))), + Container(width: 36, height: 4, decoration: BoxDecoration(color: colors.onSurface.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(2))), const SizedBox(height: 20), - const Align(alignment: Alignment.centerLeft, child: Padding(padding: EdgeInsets.symmetric(horizontal: 24), child: Text('默认启动标签', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))))), + Align(alignment: Alignment.centerLeft, child: Padding(padding: const EdgeInsets.symmetric(horizontal: 24), child: Text('默认启动标签', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface)))), const SizedBox(height: 16), for (int i = 0; i < labels.length; i++) ListTile( - leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), child: Icon(icons[i], color: const Color(0xFF666666))), - title: Text(labels[i], style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A))), - trailing: _defaultTabIndex == i ? const Icon(Icons.check, color: Color(0xFF1A1A1A), size: 20) : null, + leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(icons[i], color: colors.onSurface.withValues(alpha: 0.6))), + title: Text(labels[i], style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), + trailing: _defaultTabIndex == i ? Icon(Icons.check, color: colors.onSurface, size: 20) : null, onTap: () async { await _userPrefs.setDefaultMainTabIndex(i); setState(() => _defaultTabIndex = i); Navigator.pop(ctx); }, ), const SizedBox(height: 8), @@ -828,28 +924,29 @@ class _LayoutSettingsPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar(title: const Text('布局设置')), body: ListView( children: [ _buildSectionHeader('笔记布局'), _buildLayoutOption(icon: Icons.view_list_outlined, title: '列表布局', subtitle: '单列列表,简洁清晰', value: 0, groupValue: _noteLayout, onTap: () => _setLayout('note', 0)), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildLayoutOption(icon: Icons.grid_view_outlined, title: '瀑布流布局', subtitle: '双列卡片,图文并茂', value: 1, groupValue: _noteLayout, onTap: () => _setLayout('note', 1)), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildLayoutOption(icon: Icons.timeline_outlined, title: '时间线布局', subtitle: '按时间排列,纵览全局', value: 2, groupValue: _noteLayout, onTap: () => _setLayout('note', 2)), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildSectionHeader('影视布局'), _buildLayoutOption(icon: Icons.grid_view_outlined, title: '海报网格', subtitle: '三列海报,赏心悦目', value: 0, groupValue: _movieLayout, onTap: () => _setLayout('movie', 0)), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildLayoutOption(icon: Icons.view_list_outlined, title: '列表布局', subtitle: '单列卡片,信息一览', value: 1, groupValue: _movieLayout, onTap: () => _setLayout('movie', 1)), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildSectionHeader('阅读布局'), _buildLayoutOption(icon: Icons.grid_view_outlined, title: '封面网格', subtitle: '三列封面,清新雅致', value: 0, groupValue: _bookLayout, onTap: () => _setLayout('book', 0)), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), _buildLayoutOption(icon: Icons.view_list_outlined, title: '列表布局', subtitle: '单列卡片,信息一览', value: 1, groupValue: _bookLayout, onTap: () => _setLayout('book', 1)), - const Divider(height: 0.5, indent: 24, endIndent: 24), + Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant), ], ), ); @@ -864,20 +961,22 @@ class _LayoutSettingsPageState extends State { } Widget _buildSectionHeader(String title) { + final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.fromLTRB(24, 28, 24, 12), - child: Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + child: Text(title, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: colors.onSurface)), ); } Widget _buildLayoutOption({required IconData icon, required String title, required String subtitle, required int value, required int groupValue, required VoidCallback onTap}) { + final colors = Theme.of(context).colorScheme; final selected = value == groupValue; return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), - leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), child: Icon(icon, color: const Color(0xFF666666), size: 22)), - title: Text(title, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A))), - subtitle: Text(subtitle, style: const TextStyle(fontSize: 12, color: Color(0xFF999999))), - trailing: selected ? const Icon(Icons.check_circle, color: Color(0xFF1A1A1A), size: 20) : const Icon(Icons.circle_outlined, color: Color(0xFFDDDDDD), size: 20), + leading: Container(width: 44, height: 44, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), child: Icon(icon, color: colors.onSurface.withValues(alpha: 0.6), size: 22)), + title: Text(title, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), + subtitle: Text(subtitle, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), + trailing: selected ? Icon(Icons.check_circle, color: colors.onSurface, size: 20) : Icon(Icons.circle_outlined, color: colors.onSurface.withValues(alpha: 0.15), size: 20), onTap: onTap, ); } @@ -911,10 +1010,11 @@ class _WebViewPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, 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)))]), + body: Stack(children: [WebViewWidget(controller: _controller), if (_isLoading) Center(child: CircularProgressIndicator(color: colors.onSurface.withValues(alpha: 0.4)))]), ); } } diff --git a/lib/pages/recycle_bin_page.dart b/lib/pages/recycle_bin_page.dart index 169d98b..ffe2095 100644 --- a/lib/pages/recycle_bin_page.dart +++ b/lib/pages/recycle_bin_page.dart @@ -49,7 +49,7 @@ class _DeletedItem { class _RecycleBinPageState extends State { List<_DeletedItem> _allItems = []; - _ItemType? _filterType; // null = 全部 + _ItemType? _filterType; bool _isLoading = true; List<_DeletedItem> get _filteredItems => @@ -80,8 +80,9 @@ class _RecycleBinPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: const Text('回收站'), actions: [ @@ -91,12 +92,12 @@ class _RecycleBinPageState extends State { child: TextButton( onPressed: _showClearAllDialog, style: TextButton.styleFrom( - backgroundColor: Colors.white, + backgroundColor: colors.surface, foregroundColor: Colors.red, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), - side: const BorderSide(color: Color(0xFFFFDDDD), width: 0.5), + side: BorderSide(color: colors.error.withValues(alpha: 0.15), width: 0.5), ), minimumSize: Size.zero, ), @@ -106,12 +107,10 @@ class _RecycleBinPageState extends State { ], ), body: _isLoading - ? const Center(child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF1A1A1A))) + ? Center(child: CircularProgressIndicator(strokeWidth: 2, color: colors.primary)) : Column( children: [ - // 筛选标签行 if (_allItems.isNotEmpty) _buildFilterRow(), - // 列表 Expanded( child: _filteredItems.isEmpty ? _buildEmptyState() @@ -130,11 +129,12 @@ class _RecycleBinPageState extends State { } Widget _buildFilterRow() { + final colors = Theme.of(context).colorScheme; return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: const BoxDecoration( - border: Border(bottom: BorderSide(color: Color(0xFFEEEEEE), width: 0.5)), + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: colors.outlineVariant, width: 0.5)), ), child: Wrap( spacing: 8, @@ -149,6 +149,7 @@ class _RecycleBinPageState extends State { } Widget _filterChip(String label, _ItemType? type) { + final colors = Theme.of(context).colorScheme; final active = _filterType == type; final count = type == null ? _allItems.length : _allItems.where((i) => i.type == type).length; return GestureDetector( @@ -156,7 +157,7 @@ class _RecycleBinPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), decoration: BoxDecoration( - color: active ? const Color(0xFF1A1A1A) : const Color(0xFFF5F5F5), + color: active ? colors.primary : colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), ), child: Text( @@ -164,7 +165,7 @@ class _RecycleBinPageState extends State { style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, - color: active ? Colors.white : const Color(0xFF888888), + color: active ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.5), ), ), ), @@ -172,6 +173,7 @@ class _RecycleBinPageState extends State { } Widget _buildCard(_DeletedItem item) { + final colors = Theme.of(context).colorScheme; return Dismissible( key: Key('${item.type.name}_${item.id}'), direction: DismissDirection.endToStart, @@ -181,9 +183,9 @@ class _RecycleBinPageState extends State { child: Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), @@ -193,11 +195,11 @@ class _RecycleBinPageState extends State { width: 40, height: 40, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFEEEEEE), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), - child: Icon(item.icon, size: 20, color: const Color(0xFF888888)), + child: Icon(item.icon, size: 20, color: colors.onSurface.withValues(alpha: 0.5)), ), const SizedBox(width: 12), Expanded( @@ -210,7 +212,7 @@ class _RecycleBinPageState extends State { Expanded( child: Text( item.title, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A), height: 1.3), + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface, height: 1.3), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -219,13 +221,13 @@ class _RecycleBinPageState extends State { Container( padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(3), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), child: Text( item.typeLabel, - style: const TextStyle(fontSize: 9, fontWeight: FontWeight.w600, color: Color(0xFF999999)), + style: TextStyle(fontSize: 9, fontWeight: FontWeight.w600, color: colors.onSurface.withValues(alpha: 0.4)), ), ), ], @@ -233,15 +235,15 @@ class _RecycleBinPageState extends State { const SizedBox(height: 3), Text( item.subtitle, - style: const TextStyle(fontSize: 11, color: Color(0xFFAAAAAA)), + style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.35)), ), ], ), ), const SizedBox(width: 8), - _actionBtn(Icons.restore, '恢复', const Color(0xFF1A1A1A), () => _restore(item)), + _actionBtn(Icons.restore, '恢复', colors.primary, () => _restore(item)), const SizedBox(width: 6), - _actionBtn(Icons.delete_outline, '删除', Colors.red, () => _permanentDelete(item)), + _actionBtn(Icons.delete_outline, '删除', colors.error, () => _permanentDelete(item)), ], ), ), @@ -263,6 +265,7 @@ class _RecycleBinPageState extends State { } Widget _actionBtn(IconData icon, String tooltip, Color color, VoidCallback onTap) { + final colors = Theme.of(context).colorScheme; return Material( color: Colors.transparent, child: InkWell( @@ -274,9 +277,9 @@ class _RecycleBinPageState extends State { width: 32, height: 32, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(6), - border: Border.all(color: const Color(0xFFEEEEEE), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), child: Icon(icon, size: 16, color: color), ), @@ -286,6 +289,7 @@ class _RecycleBinPageState extends State { } Widget _buildEmptyState() { + final colors = Theme.of(context).colorScheme; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -294,18 +298,18 @@ class _RecycleBinPageState extends State { width: 72, height: 72, decoration: BoxDecoration( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(18), ), - child: const Icon(Icons.delete_outline, size: 32, color: Color(0xFFD5D5D5)), + child: Icon(Icons.delete_outline, size: 32, color: colors.onSurface.withValues(alpha: 0.15)), ), const SizedBox(height: 16), Text( _filterType == null ? '回收站是空的' : '没有删除的项目', - style: const TextStyle(fontSize: 14, color: Color(0xFFAAAAAA)), + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.35)), ), const SizedBox(height: 4), - const Text('删除的项目会显示在这里', style: TextStyle(fontSize: 12, color: Color(0xFFCCCCCC))), + Text('删除的项目会显示在这里', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.25))), ], ), ); @@ -344,25 +348,26 @@ class _RecycleBinPageState extends State { } Future _showConfirmDialog(String message) async { + final colors = Theme.of(context).colorScheme; final result = await showDialog( context: context, builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), - content: Text(message, style: const TextStyle(fontSize: 14, color: Color(0xFF666666), height: 1.5)), + title: Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), + content: Text(message, style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.5)), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), - style: TextButton.styleFrom(foregroundColor: const Color(0xFF666666)), + style: TextButton.styleFrom(foregroundColor: colors.onSurface.withValues(alpha: 0.6)), child: const Text('取消'), ), ElevatedButton( onPressed: () => Navigator.pop(ctx, true), style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + backgroundColor: colors.error, + foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), @@ -377,27 +382,28 @@ class _RecycleBinPageState extends State { void _showClearAllDialog() { final pageContext = context; + final colors = Theme.of(pageContext).colorScheme; showDialog( context: pageContext, builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Row( + title: Row( children: [ - Icon(Icons.warning_amber_rounded, color: Colors.red, size: 22), - SizedBox(width: 8), - Text('清空回收站', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), + const Icon(Icons.warning_amber_rounded, color: Colors.red, size: 22), + const SizedBox(width: 8), + Text('清空回收站', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), ], ), - content: const Text( + content: Text( '确定要清空回收站吗?所有项目将被彻底删除,此操作不可恢复。', - style: TextStyle(fontSize: 14, color: Color(0xFF666666), height: 1.5), + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.5), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - style: TextButton.styleFrom(foregroundColor: const Color(0xFF666666)), + style: TextButton.styleFrom(foregroundColor: colors.onSurface.withValues(alpha: 0.6)), child: const Text('取消'), ), ElevatedButton( @@ -408,8 +414,8 @@ class _RecycleBinPageState extends State { if (mounted) ToastUtil.show(pageContext, '回收站已清空'); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + backgroundColor: colors.error, + foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), diff --git a/lib/pages/search_page.dart b/lib/pages/search_page.dart index 9c93342..a47786e 100644 --- a/lib/pages/search_page.dart +++ b/lib/pages/search_page.dart @@ -100,8 +100,9 @@ class _SearchPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: const Text('搜索'), elevation: 0, @@ -121,18 +122,19 @@ class _SearchPageState extends State { } Widget _buildSearchBar() { + final colors = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.fromLTRB(20, 12, 20, 4), child: TextField( controller: _searchController, focusNode: _focusNode, - style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 15, color: colors.onSurface), decoration: InputDecoration( hintText: '搜索标题、别名、内容...', - hintStyle: const TextStyle(color: Color(0xFFB0B0B0), fontSize: 15), - prefixIcon: const Padding( - padding: EdgeInsets.only(left: 12, right: 8), - child: Icon(Icons.search, color: Color(0xFF1A1A1A), size: 22), + hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.35), fontSize: 15), + prefixIcon: Padding( + padding: const EdgeInsets.only(left: 12, right: 8), + child: Icon(Icons.search, color: colors.onSurface, size: 22), ), prefixIconConstraints: const BoxConstraints(minWidth: 42, minHeight: 42), suffixIcon: _searchController.text.isNotEmpty @@ -147,15 +149,15 @@ class _SearchPageState extends State { width: 28, height: 28, decoration: BoxDecoration( - color: const Color(0xFFE5E5E5), + color: colors.outline, borderRadius: BorderRadius.circular(14), ), - child: const Icon(Icons.close, color: Color(0xFF666666), size: 16), + child: Icon(Icons.close, color: colors.onSurface.withValues(alpha: 0.6), size: 16), ), ) : null, filled: true, - fillColor: const Color(0xFFF8F8F8), + fillColor: colors.surfaceContainerHigh, border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide.none, @@ -166,7 +168,7 @@ class _SearchPageState extends State { ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), - borderSide: const BorderSide(color: Color(0xFF1A1A1A), width: 1.5), + borderSide: BorderSide(color: colors.primary, width: 1.5), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), @@ -205,26 +207,27 @@ class _SearchPageState extends State { } Widget _buildTypeChip(String label, IconData icon, bool selected, ValueChanged onChanged) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: () => onChanged(!selected), child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7), decoration: BoxDecoration( - color: selected ? const Color(0xFF1A1A1A) : const Color(0xFFF5F5F5), + color: selected ? colors.primary : colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 14, color: selected ? Colors.white : const Color(0xFF888888)), + Icon(icon, size: 14, color: selected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.5)), const SizedBox(width: 5), Text( label, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, - color: selected ? Colors.white : const Color(0xFF888888), + color: selected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.5), ), ), ], @@ -234,6 +237,7 @@ class _SearchPageState extends State { } Widget _buildInitialState() { + final colors = Theme.of(context).colorScheme; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -242,21 +246,22 @@ class _SearchPageState extends State { width: 88, height: 88, decoration: BoxDecoration( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(24), ), - child: const Icon(Icons.search_rounded, size: 44, color: Color(0xFFD0D0D0)), + child: Icon(Icons.search_rounded, size: 44, color: colors.onSurface.withValues(alpha: 0.2)), ), const SizedBox(height: 24), - const Text('输入关键词搜索', style: TextStyle(fontSize: 15, color: Color(0xFF999999))), + Text('输入关键词搜索', style: TextStyle(fontSize: 15, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(height: 6), - const Text('可同时筛选影视、书籍、笔记', style: TextStyle(fontSize: 13, color: Color(0xFFCCCCCC))), + Text('可同时筛选影视、书籍、笔记', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.25))), ], ), ); } Widget _buildEmptyState() { + final colors = Theme.of(context).colorScheme; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -265,15 +270,15 @@ class _SearchPageState extends State { width: 88, height: 88, decoration: BoxDecoration( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(24), ), - child: const Icon(Icons.search_off_rounded, size: 44, color: Color(0xFFD0D0D0)), + child: Icon(Icons.search_off_rounded, size: 44, color: colors.onSurface.withValues(alpha: 0.2)), ), const SizedBox(height: 24), - const Text('未找到相关内容', style: TextStyle(fontSize: 15, color: Color(0xFF999999))), + Text('未找到相关内容', style: TextStyle(fontSize: 15, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(height: 6), - const Text('换个关键词试试', style: TextStyle(fontSize: 13, color: Color(0xFFCCCCCC))), + Text('换个关键词试试', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.25))), ], ), ); @@ -302,13 +307,14 @@ class _SearchPageState extends State { // ─── 影视结果项 ────────────────────────────────────────────────────── Widget _buildMovieItem(Movie movie) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => MovieDetailPage(movie: movie))), child: Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(14), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(14), ), child: Row( @@ -328,17 +334,17 @@ class _SearchPageState extends State { ), const SizedBox(height: 8), Text(movie.title, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface)), if (movie.alternateTitles.isNotEmpty) ...[ const SizedBox(height: 3), Text(movie.alternateTitles.take(2).join('、'), maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12, color: Color(0xFFAAAAAA))), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.35))), ], ], ), ), const SizedBox(width: 8), - const Icon(Icons.chevron_right, color: Color(0xFFD0D0D0), size: 20), + Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.2), size: 20), ], ), ), @@ -348,13 +354,14 @@ class _SearchPageState extends State { // ─── 书籍结果项 ────────────────────────────────────────────────────── Widget _buildBookItem(Book book) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => BookDetailPage(book: book))), child: Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(14), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(14), ), child: Row( @@ -374,17 +381,17 @@ class _SearchPageState extends State { ), const SizedBox(height: 8), Text(book.title, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface)), if (book.authors.isNotEmpty) ...[ const SizedBox(height: 3), Text(book.authors.take(2).join('、'), maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12, color: Color(0xFFAAAAAA))), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.35))), ], ], ), ), const SizedBox(width: 8), - const Icon(Icons.chevron_right, color: Color(0xFFD0D0D0), size: 20), + Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.2), size: 20), ], ), ), @@ -394,13 +401,14 @@ class _SearchPageState extends State { // ─── 笔记结果项 ────────────────────────────────────────────────────── Widget _buildNoteItem(Note note) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => NoteDetailPage(note: note))), child: Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(14), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(14), ), child: Column( @@ -410,7 +418,7 @@ class _SearchPageState extends State { children: [ _typeBadge('笔记', const Color(0xFF66BB6A)), const Spacer(), - const Icon(Icons.chevron_right, color: Color(0xFFD0D0D0), size: 20), + Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.2), size: 20), ], ), const SizedBox(height: 10), @@ -418,7 +426,7 @@ class _SearchPageState extends State { note.summary.trim().isEmpty ? '(无内容)' : note.summary.trim(), maxLines: 3, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 14, color: Color(0xFF333333), height: 1.6), + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.75), height: 1.6), ), if (note.tags.isNotEmpty) ...[ const SizedBox(height: 10), @@ -428,10 +436,10 @@ class _SearchPageState extends State { children: note.tags.map((tag) => Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(6), ), - child: Text(tag, style: const TextStyle(fontSize: 11, color: Color(0xFF888888))), + child: Text(tag, style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.5))), )).toList(), ), ], @@ -444,18 +452,19 @@ class _SearchPageState extends State { // ─── 通用组件 ──────────────────────────────────────────────────────── Widget _buildPosterThumb(String? path, IconData fallback) { + final colors = Theme.of(context).colorScheme; return Container( width: 48, height: 64, decoration: BoxDecoration( - color: const Color(0xFFF0F0F0), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(8), ), clipBehavior: Clip.antiAlias, child: path != null && path.isNotEmpty ? Image.file(File(path), fit: BoxFit.cover, - errorBuilder: (_, __, ___) => Icon(fallback, size: 22, color: const Color(0xFFCCCCCC))) - : Icon(fallback, size: 22, color: const Color(0xFFCCCCCC)), + errorBuilder: (_, __, ___) => Icon(fallback, size: 22, color: colors.onSurface.withValues(alpha: 0.25))) + : Icon(fallback, size: 22, color: colors.onSurface.withValues(alpha: 0.25)), ); } @@ -471,11 +480,12 @@ class _SearchPageState extends State { } Widget _statusBadge(String status) { + final colors = Theme.of(context).colorScheme; final (label, bg, fg) = switch (status) { - 'watched' => ('已看', const Color(0xFF1A1A1A), Colors.white), - 'watching' => ('在看', const Color(0xFFF0F0F0), const Color(0xFF666666)), - 'want_to_watch' => ('想看', const Color(0xFFF5F5F5), const Color(0xFF999999)), - _ => ('', const Color(0xFFF5F5F5), const Color(0xFFBBBBBB)), + 'watched' => ('已看', colors.primary, colors.onPrimary), + 'watching' => ('在看', colors.outlineVariant, colors.onSurface.withValues(alpha: 0.6)), + 'want_to_watch' => ('想看', colors.surfaceContainerHighest, colors.onSurface.withValues(alpha: 0.4)), + _ => ('', colors.surfaceContainerHighest, colors.onSurface.withValues(alpha: 0.3)), }; if (label.isEmpty) return const SizedBox.shrink(); return Container( @@ -486,11 +496,12 @@ class _SearchPageState extends State { } Widget _bookStatusBadge(String status) { + final colors = Theme.of(context).colorScheme; final (label, bg, fg) = switch (status) { - 'read' => ('已读', const Color(0xFF1A1A1A), Colors.white), - 'reading' => ('在读', const Color(0xFFF0F0F0), const Color(0xFF666666)), - 'want_to_read' => ('想读', const Color(0xFFF5F5F5), const Color(0xFF999999)), - _ => ('', const Color(0xFFF5F5F5), const Color(0xFFBBBBBB)), + 'read' => ('已读', colors.primary, colors.onPrimary), + 'reading' => ('在读', colors.outlineVariant, colors.onSurface.withValues(alpha: 0.6)), + 'want_to_read' => ('想读', colors.surfaceContainerHighest, colors.onSurface.withValues(alpha: 0.4)), + _ => ('', colors.surfaceContainerHighest, colors.onSurface.withValues(alpha: 0.3)), }; if (label.isEmpty) return const SizedBox.shrink(); return Container( diff --git a/lib/pages/statistics_page.dart b/lib/pages/statistics_page.dart index cd65ebf..89280f8 100644 --- a/lib/pages/statistics_page.dart +++ b/lib/pages/statistics_page.dart @@ -19,8 +19,9 @@ class _StatisticsPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar(title: const Text('数据统计')), body: Consumer( builder: (context, provider, child) { @@ -63,6 +64,7 @@ class _StatisticsPageState extends State { // ─── 总览卡片 ──────────────────────────────────────────────────────── Widget _buildTotalCards(List movies, List books, List notes) { + final colors = Theme.of(context).colorScheme; final items = <_CardData>[]; if (_showMovies) items.add(_CardData('影视', movies.length, Icons.movie_outlined, const Color(0xFF4A90D9))); if (_showBooks) items.add(_CardData('书籍', books.length, Icons.menu_book_outlined, const Color(0xFF7E57C2))); @@ -83,7 +85,7 @@ class _StatisticsPageState extends State { const SizedBox(height: 10), Text('${d.count}', style: TextStyle(fontSize: 26, fontWeight: FontWeight.w700, color: d.color)), const SizedBox(height: 2), - Text(d.label, style: const TextStyle(fontSize: 12, color: Color(0xFF888888))), + Text(d.label, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.5))), ], ), ), @@ -94,6 +96,7 @@ class _StatisticsPageState extends State { // ─── 状态分布 ──────────────────────────────────────────────────────── Widget _buildStatusSection(String title, List items, String Function(dynamic) getStatus, Map labels) { + final colors = Theme.of(context).colorScheme; final active = items.where((i) => !(i is Movie) || !i.isDeleted).toList(); final total = active.length; @@ -110,11 +113,11 @@ class _StatisticsPageState extends State { children: [ Row( children: [ - Text(e.key, style: const TextStyle(fontSize: 13, color: Color(0xFF666666))), + Text(e.key, style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.6))), const Spacer(), - Text('$count', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text('$count', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: colors.onSurface)), const SizedBox(width: 4), - Text('${(pct * 100).toStringAsFixed(0)}%', style: const TextStyle(fontSize: 12, color: Color(0xFFBBBBBB))), + Text('${(pct * 100).toStringAsFixed(0)}%', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.3))), ], ), const SizedBox(height: 6), @@ -122,8 +125,8 @@ class _StatisticsPageState extends State { borderRadius: BorderRadius.circular(3), child: LinearProgressIndicator( value: pct, - backgroundColor: const Color(0xFFF0F0F0), - color: const Color(0xFF1A1A1A), + backgroundColor: colors.outlineVariant, + color: colors.primary, minHeight: 6, ), ), @@ -138,6 +141,7 @@ class _StatisticsPageState extends State { // ─── 类型/标签分布 ─────────────────────────────────────────────────── Widget _buildGenreDistribution(String title, List items) { + final colors = Theme.of(context).colorScheme; final genreCounts = {}; for (final item in items) { for (final genre in (item.genres as List)) { @@ -161,17 +165,17 @@ class _StatisticsPageState extends State { children: [ SizedBox( width: 60, - child: Text(e.key, style: const TextStyle(fontSize: 12, color: Color(0xFF666666)), overflow: TextOverflow.ellipsis), + child: Text(e.key, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.6)), overflow: TextOverflow.ellipsis), ), const SizedBox(width: 10), Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(2), - child: LinearProgressIndicator(value: pct, backgroundColor: const Color(0xFFF0F0F0), color: const Color(0xFF1A1A1A), minHeight: 4), + child: LinearProgressIndicator(value: pct, backgroundColor: colors.outlineVariant, color: colors.primary, minHeight: 4), ), ), const SizedBox(width: 8), - Text('${e.value}', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text('${e.value}', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: colors.onSurface)), ], ), ); @@ -181,6 +185,7 @@ class _StatisticsPageState extends State { } Widget _buildNoteTagDistribution(String title, List notes) { + final colors = Theme.of(context).colorScheme; final tagCounts = {}; for (final note in notes) { for (final tag in note.tags) { @@ -204,17 +209,17 @@ class _StatisticsPageState extends State { children: [ SizedBox( width: 60, - child: Text(e.key, style: const TextStyle(fontSize: 12, color: Color(0xFF666666)), overflow: TextOverflow.ellipsis), + child: Text(e.key, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.6)), overflow: TextOverflow.ellipsis), ), const SizedBox(width: 10), Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(2), - child: LinearProgressIndicator(value: pct, backgroundColor: const Color(0xFFF0F0F0), color: const Color(0xFF1A1A1A), minHeight: 4), + child: LinearProgressIndicator(value: pct, backgroundColor: colors.outlineVariant, color: colors.primary, minHeight: 4), ), ), const SizedBox(width: 8), - Text('${e.value}', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text('${e.value}', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: colors.onSurface)), ], ), ); @@ -226,6 +231,7 @@ class _StatisticsPageState extends State { // ─── 评分分布 ──────────────────────────────────────────────────────── Widget _buildRatingDistribution(String title, List movies, List books) { + final colors = Theme.of(context).colorScheme; final allRatings = []; for (final m in movies) { if (m.rating != null) allRatings.add(m.rating!); @@ -255,10 +261,10 @@ class _StatisticsPageState extends State { children: [ Row( children: [ - const Text('平均评分', style: TextStyle(fontSize: 13, color: Color(0xFF666666))), + Text('平均评分', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.6))), const Spacer(), - Text(avg.toStringAsFixed(1), style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: Color(0xFF1A1A1A))), - const Text(' / 10', style: TextStyle(fontSize: 13, color: Color(0xFFBBBBBB))), + Text(avg.toStringAsFixed(1), style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: colors.onSurface)), + Text(' / 10', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.3))), ], ), const SizedBox(height: 16), @@ -268,7 +274,7 @@ class _StatisticsPageState extends State { children: [ SizedBox( width: 28, - child: Text(ranges[i], style: const TextStyle(fontSize: 11, color: Color(0xFF999999))), + child: Text(ranges[i], style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4))), ), const SizedBox(width: 8), Expanded( @@ -276,14 +282,14 @@ class _StatisticsPageState extends State { borderRadius: BorderRadius.circular(2), child: LinearProgressIndicator( value: maxCount > 0 ? counts[i] / maxCount : 0.0, - backgroundColor: const Color(0xFFF0F0F0), - color: const Color(0xFF1A1A1A), + backgroundColor: colors.outlineVariant, + color: colors.primary, minHeight: 6, ), ), ), const SizedBox(width: 8), - Text('${counts[i]}', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text('${counts[i]}', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: colors.onSurface)), ], ), )), @@ -295,6 +301,7 @@ class _StatisticsPageState extends State { // ─── 月度趋势 ──────────────────────────────────────────────────────── Widget _buildMonthlyTrend(List movies, List books, List notes) { + final colors = Theme.of(context).colorScheme; final now = DateTime.now(); final months = List.generate(6, (i) { final d = DateTime(now.year, now.month - (5 - i), 1); @@ -323,7 +330,7 @@ class _StatisticsPageState extends State { final maxVal = allValues.isEmpty ? 1 : allValues.reduce((a, b) => a > b ? a : b); final safeMax = maxVal == 0 ? 1 : maxVal; - final colors = { '影视': const Color(0xFF4A90D9), '书籍': const Color(0xFF7E57C2), '笔记': const Color(0xFF66BB6A) }; + final brandColors = { '影视': const Color(0xFF4A90D9), '书籍': const Color(0xFF7E57C2), '笔记': const Color(0xFF66BB6A) }; return _buildCard( title: '近6月趋势', @@ -345,7 +352,7 @@ class _StatisticsPageState extends State { height: h < 2 && e.value[i] > 0 ? 2 : h, margin: const EdgeInsets.only(top: 1), decoration: BoxDecoration( - color: colors[e.key]!.withValues(alpha: 0.7), + color: brandColors[e.key]!.withValues(alpha: 0.7), borderRadius: BorderRadius.circular(2), ), ); @@ -357,12 +364,12 @@ class _StatisticsPageState extends State { ), ), const SizedBox(height: 8), - const Divider(height: 1, color: Color(0xFFF0F0F0)), + Divider(height: 1, color: colors.outlineVariant), const SizedBox(height: 8), // 月份标签 Row( children: months.map((m) => Expanded( - child: Text(m, textAlign: TextAlign.center, style: const TextStyle(fontSize: 11, color: Color(0xFFBBBBBB))), + child: Text(m, textAlign: TextAlign.center, style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.3))), )).toList(), ), const SizedBox(height: 12), @@ -374,9 +381,9 @@ class _StatisticsPageState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Container(width: 8, height: 8, decoration: BoxDecoration(color: colors[k], borderRadius: BorderRadius.circular(2))), + Container(width: 8, height: 8, decoration: BoxDecoration(color: brandColors[k], borderRadius: BorderRadius.circular(2))), const SizedBox(width: 4), - Text(k, style: const TextStyle(fontSize: 12, color: Color(0xFF888888))), + Text(k, style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.5))), ], ), )).toList(), @@ -394,10 +401,11 @@ class _StatisticsPageState extends State { // ─── 通用卡片 ──────────────────────────────────────────────────────── Widget _buildCard({required String title, required Widget child}) { + final colors = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(14), ), child: Column( @@ -405,9 +413,9 @@ class _StatisticsPageState extends State { children: [ Row( children: [ - Container(width: 3, height: 14, decoration: BoxDecoration(color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(2))), + Container(width: 3, height: 14, decoration: BoxDecoration(color: colors.primary, borderRadius: BorderRadius.circular(2))), const SizedBox(width: 8), - Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text(title, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: colors.onSurface)), ], ), const SizedBox(height: 16), diff --git a/lib/pages/stroll_page.dart b/lib/pages/stroll_page.dart index e0a6616..d336544 100644 --- a/lib/pages/stroll_page.dart +++ b/lib/pages/stroll_page.dart @@ -153,8 +153,9 @@ class _StrollPageState extends State with SingleTickerProviderStateM @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: const Text('漫步'), actions: [ @@ -166,15 +167,15 @@ class _StrollPageState extends State with SingleTickerProviderStateM duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(18), ), - child: const Row( + child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.casino_outlined, size: 14, color: Colors.white), - SizedBox(width: 5), - Text('随机', style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.w500)), + Icon(Icons.casino_outlined, size: 14, color: colors.onPrimary), + const SizedBox(width: 5), + Text('随机', style: TextStyle(fontSize: 12, color: colors.onPrimary, fontWeight: FontWeight.w500)), ], ), ), @@ -183,8 +184,10 @@ class _StrollPageState extends State with SingleTickerProviderStateM ], ), body: _currentItem == null - ? const Center(child: Text('还没有任何内容\n去添加一些吧', textAlign: TextAlign.center, - style: TextStyle(fontSize: 15, color: Color(0xFFBBBBBB), height: 1.6))) + ? Center( + child: Text('还没有任何内容\n去添加一些吧', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 15, color: colors.onSurface.withValues(alpha: 0.3), height: 1.6))) : Consumer(builder: (context, provider, _) { final item = _currentItem!; final hasImage = item.imagePath != null && item.imagePath!.isNotEmpty && File(item.imagePath!).existsSync(); @@ -216,7 +219,7 @@ class _StrollPageState extends State with SingleTickerProviderStateM child: Text( item.title, textAlign: TextAlign.center, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w700, color: Color(0xFF1A1A1A), height: 1.3), + style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700, color: colors.onSurface, height: 1.3), ), ), @@ -225,7 +228,7 @@ class _StrollPageState extends State with SingleTickerProviderStateM Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text(item.subtitle, textAlign: TextAlign.center, - style: const TextStyle(fontSize: 13, color: Color(0xFF999999))), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4))), ), ], @@ -242,18 +245,18 @@ class _StrollPageState extends State with SingleTickerProviderStateM margin: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), ), child: Text(item.detail, - style: const TextStyle(fontSize: 13, color: Color(0xFF777777), height: 1.7)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.6), height: 1.7)), ), ], ], // 时间 const SizedBox(height: 12), - Text(_actionText(item), style: const TextStyle(fontSize: 12, color: Color(0xFFCCCCCC))), + Text(_actionText(item), style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.25))), const SizedBox(height: 40), ], @@ -302,11 +305,12 @@ class _StrollPageState extends State with SingleTickerProviderStateM } Widget _buildNoteCard(_StrollItem item, bool hasImage) { + final colors = Theme.of(context).colorScheme; return Container( width: double.infinity, constraints: const BoxConstraints(maxWidth: 360), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow(color: Colors.black.withValues(alpha: 0.06), blurRadius: 16, offset: const Offset(0, 6)), @@ -322,12 +326,12 @@ class _StrollPageState extends State with SingleTickerProviderStateM Padding( padding: const EdgeInsets.all(20), child: Text(item.detail.isEmpty ? '(无内容)' : item.detail, - style: const TextStyle(fontSize: 14, color: Color(0xFF444444), height: 1.8)), + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.73), height: 1.8)), ), - const Divider(height: 1, color: Color(0xFFF0F0F0)), + Divider(height: 1, color: colors.outlineVariant), Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - child: Text('${item.detail.length} 字 · Mooknote', style: const TextStyle(fontSize: 11, color: Color(0xFFBBBBBB))), + child: Text('${item.detail.length} 字 · Mooknote', style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.3))), ), ], ), @@ -335,8 +339,9 @@ class _StrollPageState extends State with SingleTickerProviderStateM } Widget _buildPlaceholder(_StrollItem item) { + final colors = Theme.of(context).colorScheme; return Container( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, child: Center( child: Icon(item.icon, size: 48, color: item.color.withValues(alpha: 0.2)), ), diff --git a/lib/pages/sync/backup_page.dart b/lib/pages/sync/backup_page.dart index bf50e51..801ef47 100644 --- a/lib/pages/sync/backup_page.dart +++ b/lib/pages/sync/backup_page.dart @@ -40,71 +40,74 @@ class _BackupPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar( title: const Text('本地备份'), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : ListView( - padding: const EdgeInsets.all(24), - children: [ - // 自动备份开关 - 紧凑一行 - _buildAutoBackupSection(), + padding: const EdgeInsets.all(24), + children: [ + // 自动备份开关 - 紧凑一行 + _buildAutoBackupSection(colors), - const SizedBox(height: 24), + const SizedBox(height: 24), - // 手动备份 - _buildSectionTitle('手动备份'), - const SizedBox(height: 12), - _buildActionCard( - title: '导出数据', - description: '将所有数据导出为 zip 文件,可用于备份或迁移到其他设备', - icon: Icons.upload_outlined, - buttonText: '导出', - isLoading: _isExporting, - onTap: _exportData, - ), - const SizedBox(height: 12), - _buildActionCard( - title: '导入数据', - description: '从备份文件导入数据,将覆盖当前所有数据', - icon: Icons.download_outlined, - buttonText: '导入', - isLoading: _isImporting, - onTap: _importData, - isDestructive: true, - ), + // 手动备份 + _buildSectionTitle(colors, '手动备份'), + const SizedBox(height: 12), + _buildActionCard( + colors: colors, + title: '导出数据', + description: '将所有数据导出为 zip 文件,可用于备份或迁移到其他设备', + icon: Icons.upload_outlined, + buttonText: '导出', + isLoading: _isExporting, + onTap: _exportData, + ), + const SizedBox(height: 12), + _buildActionCard( + colors: colors, + title: '导入数据', + description: '从备份文件导入数据,将覆盖当前所有数据', + icon: Icons.download_outlined, + buttonText: '导入', + isLoading: _isImporting, + onTap: _importData, + isDestructive: true, + ), - const SizedBox(height: 32), + const SizedBox(height: 32), - // 使用说明 - _buildInfoSection(), - ], - ), + // 使用说明 + _buildInfoSection(colors), + ], + ), ); } /// 构建区块标题 - Widget _buildSectionTitle(String title) { + Widget _buildSectionTitle(ColorScheme colors, String title) { return Row( children: [ Container( width: 4, height: 16, decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ], @@ -113,6 +116,7 @@ class _BackupPageState extends State { /// 构建操作卡片 Widget _buildActionCard({ + required ColorScheme colors, required String title, required String description, required IconData icon, @@ -124,7 +128,7 @@ class _BackupPageState extends State { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), ), child: Column( @@ -136,19 +140,17 @@ class _BackupPageState extends State { width: 44, height: 44, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(10), border: Border.all( - color: isDestructive - ? Colors.red.withOpacity(0.3) - : const Color(0xFFE8E8E8), + color: isDestructive ? Colors.red.withOpacity(0.3) : colors.outline, width: 0.5, ), ), child: Icon( icon, size: 22, - color: isDestructive ? Colors.red : const Color(0xFF666666), + color: isDestructive ? Colors.red : colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 16), @@ -158,18 +160,18 @@ class _BackupPageState extends State { children: [ Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), const SizedBox(height: 4), Text( description, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.4, ), ), @@ -185,25 +187,25 @@ class _BackupPageState extends State { width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( - color: isLoading ? const Color(0xFFCCCCCC) : const Color(0xFF1A1A1A), + color: isLoading ? colors.onSurface.withValues(alpha: 0.25) : colors.primary, borderRadius: BorderRadius.circular(8), ), child: Center( child: isLoading - ? const SizedBox( + ? SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(Colors.white), + valueColor: AlwaysStoppedAnimation(colors.onPrimary), ), ) : Text( buttonText, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, - color: Colors.white, + color: colors.onPrimary, ), ), ), @@ -215,11 +217,11 @@ class _BackupPageState extends State { } /// 构建信息说明区域 - Widget _buildInfoSection() { + Widget _buildInfoSection(ColorScheme colors) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), ), child: Column( @@ -231,42 +233,42 @@ class _BackupPageState extends State { width: 32, height: 32, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outline, width: 0.5), ), - child: const Icon( + child: Icon( Icons.info_outline, size: 18, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 12), - const Text( + Text( '使用说明', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), ], ), const SizedBox(height: 16), - _buildInfoItem('1', '导出数据会生成一个 .zip 文件,包含所有数据和图片'), + _buildInfoItem(colors, '1', '导出数据会生成一个 .zip 文件,包含所有数据和图片'), const SizedBox(height: 12), - _buildInfoItem('2', '选择保存路径后,可以通过微信、邮件等方式发送备份文件'), + _buildInfoItem(colors, '2', '选择保存路径后,可以通过微信、邮件等方式发送备份文件'), const SizedBox(height: 12), - _buildInfoItem('3', '在新设备上选择导入数据,选择备份文件即可恢复'), + _buildInfoItem(colors, '3', '在新设备上选择导入数据,选择备份文件即可恢复'), const SizedBox(height: 12), - _buildInfoItem('4', '导入数据会完全覆盖当前设备的数据,请谨慎操作'), + _buildInfoItem(colors, '4', '导入数据会完全覆盖当前设备的数据,请谨慎操作'), ], ), ); } /// 构建信息项 - Widget _buildInfoItem(String number, String text) { + Widget _buildInfoItem(ColorScheme colors, String number, String text) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -274,16 +276,16 @@ class _BackupPageState extends State { width: 20, height: 20, decoration: BoxDecoration( - color: const Color(0xFFE8E8E8), + color: colors.outline, borderRadius: BorderRadius.circular(10), ), child: Center( child: Text( number, - style: const TextStyle( + style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), ), @@ -292,9 +294,9 @@ class _BackupPageState extends State { Expanded( child: Text( text, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.5, ), ), @@ -311,85 +313,93 @@ class _BackupPageState extends State { }) { showDialog( context: context, - builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, - elevation: 0, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - title: Column( - children: [ - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), - borderRadius: BorderRadius.circular(12), - ), - child: const Icon(Icons.check, color: Color(0xFF1A1A1A), size: 24), - ), - const SizedBox(height: 16), - Text(title, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), - ], - ), - titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 12), - Text( - content, - style: const TextStyle(fontSize: 14, color: Color(0xFF666666), height: 1.6), - textAlign: TextAlign.center, - ), - if (detail != null && detail.isNotEmpty) ...[ - const SizedBox(height: 12), + builder: (ctx) { + final colors = Theme.of(ctx).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: Column( + children: [ Container( - width: double.infinity, - padding: const EdgeInsets.all(12), + width: 48, + height: 48, decoration: BoxDecoration( - color: const Color(0xFFF8F8F8), - borderRadius: BorderRadius.circular(8), - ), - child: Text( - detail, - style: const TextStyle(fontSize: 11, color: Color(0xFF999999)), - maxLines: 3, - overflow: TextOverflow.ellipsis, + color: colors.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), ), + child: Icon(Icons.check, color: colors.primary, size: 24), ), + const SizedBox(height: 16), + Text(title, + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)), ], - ], - ), - contentPadding: const EdgeInsets.fromLTRB(24, 0, 24, 0), - actionsPadding: const EdgeInsets.fromLTRB(16, 20, 16, 16), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - style: TextButton.styleFrom( - minimumSize: const Size(120, 40), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - ), - child: const Text('确定', style: TextStyle(fontSize: 14, color: Color(0xFF1A1A1A))), ), - ], - ), + titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 12), + Text( + content, + style: TextStyle( + fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.6), + textAlign: TextAlign.center, + ), + if (detail != null && detail.isNotEmpty) ...[ + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colors.surfaceContainerHigh, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + detail, + style: + TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4)), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ], + ), + contentPadding: const EdgeInsets.fromLTRB(24, 0, 24, 0), + actionsPadding: const EdgeInsets.fromLTRB(16, 20, 16, 16), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + style: TextButton.styleFrom( + minimumSize: const Size(120, 40), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + child: Text('确定', style: TextStyle(fontSize: 14, color: colors.primary)), + ), + ], + ); + }, ); } /// 导出数据 Future _exportData() async { setState(() => _isExporting = true); - + try { final result = await BackupService.instance.exportDataWithImages(); - + if (!mounted) return; - + if (result.cancelled) { ToastUtil.show(context, '已取消导出'); } else if (result.success) { _showSuccessDialog( title: '导出成功', - content: '备份文件已保存,包含:\n影视 ${result.movieCount} · 书籍 ${result.bookCount} · 笔记 ${result.noteCount} · 图片 ${result.imageCount}', + content: + '备份文件已保存,包含:\n影视 ${result.movieCount} · 书籍 ${result.bookCount} · 笔记 ${result.noteCount} · 图片 ${result.imageCount}', detail: result.filePath ?? '', ); } else { @@ -411,54 +421,63 @@ class _BackupPageState extends State { // 显示确认对话框 final confirmed = await showDialog( context: context, - builder: (context) => AlertDialog( - backgroundColor: Colors.white, - elevation: 0, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - title: Row( - children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: Colors.red.withOpacity(0.08), - borderRadius: BorderRadius.circular(10), + builder: (ctx) { + final colors = Theme.of(ctx).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.08), + borderRadius: BorderRadius.circular(10), + ), + child: const Icon(Icons.warning_amber_rounded, color: Colors.red, size: 22), ), - child: const Icon(Icons.warning_amber_rounded, color: Colors.red, size: 22), + const SizedBox(width: 12), + Text('确认导入', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)), + ], + ), + titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), + content: Padding( + padding: const EdgeInsets.only(top: 16), + child: Text( + '导入数据将覆盖当前所有数据,此操作不可恢复。', + style: TextStyle( + fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.6), + ), + ), + contentPadding: const EdgeInsets.fromLTRB(24, 0, 24, 0), + actionsPadding: const EdgeInsets.fromLTRB(16, 20, 16, 16), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + child: Text('取消', + style: TextStyle( + color: colors.onSurface.withValues(alpha: 0.6), fontSize: 14)), + ), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + child: const Text('确认导入', + style: TextStyle(color: Colors.red, fontSize: 14, fontWeight: FontWeight.w600)), ), - const SizedBox(width: 12), - const Text('确认导入', style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), ], - ), - titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), - content: const Padding( - padding: EdgeInsets.only(top: 16), - child: Text( - '导入数据将覆盖当前所有数据,此操作不可恢复。', - style: TextStyle(fontSize: 14, color: Color(0xFF666666), height: 1.6), - ), - ), - contentPadding: const EdgeInsets.fromLTRB(24, 0, 24, 0), - actionsPadding: const EdgeInsets.fromLTRB(16, 20, 16, 16), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - ), - child: const Text('取消', style: TextStyle(color: Color(0xFF666666), fontSize: 14)), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - ), - child: const Text('确认导入', style: TextStyle(color: Colors.red, fontSize: 14, fontWeight: FontWeight.w600)), - ), - ], - ), + ); + }, ); if (confirmed != true) return; @@ -467,7 +486,7 @@ class _BackupPageState extends State { try { final result = await BackupService.instance.importData(); - + if (!mounted) return; if (result.cancelled) { @@ -499,13 +518,13 @@ class _BackupPageState extends State { } /// 构建自动备份区域 - 紧凑一行 - Widget _buildAutoBackupSection() { + Widget _buildAutoBackupSection(ColorScheme colors) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outline, width: 0.5), ), child: Row( children: [ @@ -513,11 +532,12 @@ class _BackupPageState extends State { width: 40, height: 40, decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(10), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outline, width: 0.5), ), - child: const Icon(Icons.schedule, size: 20, color: Color(0xFF666666)), + child: Icon(Icons.schedule, + size: 20, color: colors.onSurface.withValues(alpha: 0.6)), ), const SizedBox(width: 12), Expanded( @@ -525,22 +545,25 @@ class _BackupPageState extends State { ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( '自动本地备份', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)), + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.w600, color: colors.onSurface), ), const SizedBox(height: 2), Text( _backupDirPath!, - style: const TextStyle(fontSize: 11, color: Color(0xFF999999)), + style: + TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4)), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ) - : const Text( + : Text( '自动本地备份', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)), + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.w600, color: colors.onSurface), ), ), Switch( @@ -555,14 +578,13 @@ class _BackupPageState extends State { } await _loadAutoBackupStatus(); }, - activeColor: const Color(0xFF1A1A1A), - activeTrackColor: const Color(0xFF1A1A1A).withOpacity(0.3), - inactiveThumbColor: Colors.white, - inactiveTrackColor: const Color(0xFFE5E5E5), + activeThumbColor: colors.primary, + activeTrackColor: colors.primary.withValues(alpha: 0.3), + inactiveThumbColor: colors.surface, + inactiveTrackColor: colors.outline, ), ], ), ); } - } diff --git a/lib/pages/sync/cloud_sync_page.dart b/lib/pages/sync/cloud_sync_page.dart index 7a7ae01..8410574 100644 --- a/lib/pages/sync/cloud_sync_page.dart +++ b/lib/pages/sync/cloud_sync_page.dart @@ -8,95 +8,163 @@ class CloudSyncPage extends StatelessWidget { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: const Color(0xFFF8F8F8), + backgroundColor: colors.surfaceContainerHigh, appBar: AppBar(title: const Text('云备份')), body: ListView( padding: const EdgeInsets.all(20), children: [ - _buildSectionTitle('选择备份方式'), + _buildSectionTitle(colors, '选择备份方式'), const SizedBox(height: 12), _buildOption( + colors: colors, icon: Icons.storage_outlined, title: 'WebDAV 备份', subtitle: '通过 WebDAV 协议备份到个人云盘', - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const WebDAVSyncPage())), + onTap: () => + Navigator.push(context, MaterialPageRoute(builder: (_) => const WebDAVSyncPage())), ), const SizedBox(height: 12), _buildOption( + colors: colors, icon: Icons.sync_outlined, title: '服务端实时同步', subtitle: '自建服务端,多设备数据实时同步', enabled: false, - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ServerSyncPage())), + onTap: () => + Navigator.push(context, MaterialPageRoute(builder: (_) => const ServerSyncPage())), ), const SizedBox(height: 28), - _buildInfo(), + _buildInfo(colors), ], ), ); } - Widget _buildSectionTitle(String title) { + Widget _buildSectionTitle(ColorScheme colors, String title) { return Row(children: [ - Container(width: 3, height: 14, decoration: BoxDecoration(color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(2))), + Container( + width: 3, + height: 14, + decoration: + BoxDecoration(color: colors.primary, borderRadius: BorderRadius.circular(2))), const SizedBox(width: 8), - Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text(title, + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: colors.onSurface)), ]); } - Widget _buildOption({required IconData icon, required String title, required String subtitle, required VoidCallback onTap, bool enabled = true}) { + Widget _buildOption({ + required ColorScheme colors, + required IconData icon, + required String title, + required String subtitle, + required VoidCallback onTap, + bool enabled = true, + }) { return GestureDetector( onTap: enabled ? onTap : null, child: Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( - color: enabled ? Colors.white : const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(14), - boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 6, offset: const Offset(0, 2))], + color: enabled ? colors.surface : colors.surfaceContainerHighest, + borderRadius: BorderRadius.circular(14), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.03), + blurRadius: 6, + offset: const Offset(0, 2)), + ], ), child: Row(children: [ - Container(width: 44, height: 44, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), child: Icon(icon, color: enabled ? const Color(0xFF666666) : const Color(0xFFBBBBBB), size: 22)), + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), + child: Icon(icon, + color: enabled + ? colors.onSurface.withValues(alpha: 0.6) + : colors.onSurface.withValues(alpha: 0.3), + size: 22)), const SizedBox(width: 14), - Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(title, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: enabled ? const Color(0xFF1A1A1A) : const Color(0xFFBBBBBB))), + Expanded( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(title, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: enabled ? colors.onSurface : colors.onSurface.withValues(alpha: 0.3))), const SizedBox(height: 3), - Text(subtitle, style: TextStyle(fontSize: 12, color: enabled ? const Color(0xFF999999) : const Color(0xFFCCCCCC))), + Text(subtitle, + style: TextStyle( + fontSize: 12, + color: enabled + ? colors.onSurface.withValues(alpha: 0.4) + : colors.onSurface.withValues(alpha: 0.25))), ])), - Icon(Icons.chevron_right, color: enabled ? const Color(0xFFCCCCCC) : const Color(0xFFE5E5E5)), + Icon(Icons.chevron_right, + color: enabled + ? colors.onSurface.withValues(alpha: 0.25) + : colors.outline), ]), ), ); } - Widget _buildInfo() { + Widget _buildInfo(ColorScheme colors) { return Container( padding: const EdgeInsets.all(18), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(14), - boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 6, offset: const Offset(0, 2))], + decoration: BoxDecoration( + color: colors.surface, + borderRadius: BorderRadius.circular(14), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.03), + blurRadius: 6, + offset: const Offset(0, 2)), + ], ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Container(width: 36, height: 36, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(8)), child: const Icon(Icons.info_outline, size: 18, color: Color(0xFF666666))), + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8)), + child: Icon(Icons.info_outline, + size: 18, color: colors.onSurface.withValues(alpha: 0.6))), const SizedBox(width: 10), - const Text('使用说明', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text('使用说明', + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.w600, color: colors.onSurface)), ]), const SizedBox(height: 14), - _infoItem('WebDAV 备份:将数据备份到支持 WebDAV 的云盘'), + _infoItem(colors, 'WebDAV 备份:将数据备份到支持 WebDAV 的云盘'), const SizedBox(height: 8), - _infoItem('服务端实时同步:通过自建服务端实现多设备实时同步'), + _infoItem(colors, '服务端实时同步:通过自建服务端实现多设备实时同步'), const SizedBox(height: 8), - _infoItem('激活码由服务端管理员在管理后台生成'), + _infoItem(colors, '激活码由服务端管理员在管理后台生成'), const SizedBox(height: 8), - _infoItem('建议定期备份 + 实时同步配合使用'), + _infoItem(colors, '建议定期备份 + 实时同步配合使用'), ]), ); } - Widget _infoItem(String text) { + Widget _infoItem(ColorScheme colors, String text) { return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container(width: 5, height: 5, margin: const EdgeInsets.only(top: 5), decoration: BoxDecoration(color: const Color(0xFFBBBBBB), shape: BoxShape.circle)), + Container( + width: 5, + height: 5, + margin: const EdgeInsets.only(top: 5), + decoration: BoxDecoration( + color: colors.onSurface.withValues(alpha: 0.3), shape: BoxShape.circle)), const SizedBox(width: 10), - Expanded(child: Text(text, style: const TextStyle(fontSize: 12, color: Color(0xFF888888), height: 1.5))), + Expanded( + child: Text(text, + style: TextStyle( + fontSize: 12, color: colors.onSurface.withValues(alpha: 0.5), height: 1.5))), ]); } } diff --git a/lib/pages/sync/server_sync_page.dart b/lib/pages/sync/server_sync_page.dart index fb83e29..cca8971 100644 --- a/lib/pages/sync/server_sync_page.dart +++ b/lib/pages/sync/server_sync_page.dart @@ -157,16 +157,24 @@ class _ServerSyncPageState extends State { Future _disconnect() async { final confirm = await showDialog( context: context, - builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - title: const Text('断开连接', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), - content: const Text('将清除服务器配置和激活信息,确定要断开吗?', style: TextStyle(fontSize: 14, color: Color(0xFF666666))), - actions: [ - TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消', style: TextStyle(color: Color(0xFF999999)))), - TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('确定', style: TextStyle(color: Color(0xFFE53935)))), - ], - ), + builder: (ctx) { + final colors = Theme.of(ctx).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + title: const Text('断开连接', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), + content: Text('将清除服务器配置和激活信息,确定要断开吗?', + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6))), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4)))), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + child: const Text('确定', style: TextStyle(color: Color(0xFFE53935)))), + ], + ); + }, ); if (confirm != true) return; @@ -190,63 +198,107 @@ class _ServerSyncPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: const Color(0xFFF8F8F8), + backgroundColor: colors.surfaceContainerHigh, appBar: AppBar(title: const Text('服务端实时同步')), body: ListView(padding: const EdgeInsets.all(20), children: [ // 状态卡片 Container( padding: const EdgeInsets.all(20), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), - boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2))], + decoration: BoxDecoration( + color: colors.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2)) + ], ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Container(width: 10, height: 10, decoration: BoxDecoration( - color: _isActivated ? const Color(0xFF66BB6A) : const Color(0xFFDDDDDD), shape: BoxShape.circle)), + Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: _isActivated + ? const Color(0xFF66BB6A) + : colors.onSurface.withValues(alpha: 0.15), + shape: BoxShape.circle)), const SizedBox(width: 10), Text(_isActivated ? '已激活' : '未激活', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, - color: _isActivated ? const Color(0xFF66BB6A) : const Color(0xFFBBBBBB))), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: _isActivated + ? const Color(0xFF66BB6A) + : colors.onSurface.withValues(alpha: 0.3))), const Spacer(), if (_isActivated) GestureDetector( onTap: _disconnect, - child: Container(padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(6)), - child: const Text('断开', style: TextStyle(fontSize: 12, color: Color(0xFFE57373)))), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(6)), + child: const Text('断开', + style: TextStyle(fontSize: 12, color: Color(0xFFE57373)))), ), ]), const SizedBox(height: 20), - const Text('服务器地址', style: TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text('服务器地址', + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(height: 6), - TextField(controller: _urlController, style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), - decoration: _inputDeco('例: http://192.168.1.100:5000')), + TextField( + controller: _urlController, + style: TextStyle(fontSize: 14, color: colors.onSurface), + decoration: _inputDeco(colors, '例: http://192.168.1.100:5000'), + ), const SizedBox(height: 14), - const Text('激活码', style: TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text('激活码', + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(height: 6), - TextField(controller: _codeController, style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), - textCapitalization: TextCapitalization.characters, decoration: _inputDeco('例: MK-A1B2C3D4E5F6')), + TextField( + controller: _codeController, + style: TextStyle(fontSize: 14, color: colors.onSurface), + textCapitalization: TextCapitalization.characters, + decoration: _inputDeco(colors, '例: MK-A1B2C3D4E5F6')), const SizedBox(height: 16), - SizedBox(width: double.infinity, + SizedBox( + width: double.infinity, child: ElevatedButton( onPressed: _isChecking ? null : _checkActivation, - style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF1A1A1A), foregroundColor: Colors.white, - disabledBackgroundColor: const Color(0xFFDDDDDD), elevation: 0, + style: ElevatedButton.styleFrom( + backgroundColor: colors.primary, + foregroundColor: colors.onPrimary, + disabledBackgroundColor: colors.onSurface.withValues(alpha: 0.15), + elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(vertical: 13)), child: _isChecking - ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) - : Text(_isActivated ? '重新验证' : '验证激活', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2, color: colors.onPrimary)) + : Text(_isActivated ? '重新验证' : '验证激活', + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), ), ), if (_expiresText.isNotEmpty) ...[ const SizedBox(height: 12), - Center(child: Row(mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.access_time, size: 14, color: _prefs.syncIsPermanent ? const Color(0xFF66BB6A) : const Color(0xFFFF9800)), + Center( + child: Row(mainAxisSize: MainAxisSize.min, children: [ + Icon(Icons.access_time, + size: 14, + color: _prefs.syncIsPermanent + ? const Color(0xFF66BB6A) + : const Color(0xFFFF9800)), const SizedBox(width: 6), - Text(_expiresText, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, - color: _prefs.syncIsPermanent ? const Color(0xFF66BB6A) : const Color(0xFFFF9800))), + Text(_expiresText, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: _prefs.syncIsPermanent + ? const Color(0xFF66BB6A) + : const Color(0xFFFF9800))), ])), ], ]), @@ -255,47 +307,78 @@ class _ServerSyncPageState extends State { // 同步开关 Container( padding: const EdgeInsets.all(20), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), - boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2))]), + decoration: BoxDecoration( + color: colors.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2)) + ]), child: Row(children: [ - Container(width: 44, height: 44, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), - child: Icon(_syncEnabled ? Icons.sync : Icons.sync_disabled, - color: _syncEnabled ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), size: 22)), + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), + child: Icon(_syncEnabled ? Icons.sync : Icons.sync_disabled, + color: _syncEnabled + ? colors.primary + : colors.onSurface.withValues(alpha: 0.25), + size: 22)), const SizedBox(width: 14), - Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('服务端实时同步', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A))), + Expanded( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('服务端实时同步', + style: TextStyle( + fontSize: 15, fontWeight: FontWeight.w500, color: colors.onSurface)), const SizedBox(height: 2), Text(_syncEnabled ? '使用服务端数据,多设备实时共享' : '关闭后下载数据到本地使用', - style: const TextStyle(fontSize: 12, color: Color(0xFFBBBBBB))), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.3))), ])), - Switch(value: _syncEnabled, onChanged: _isActivated ? _toggleSync : null, - activeColor: const Color(0xFF1A1A1A), activeTrackColor: const Color(0xFF1A1A1A).withOpacity(0.3), - inactiveThumbColor: Colors.white, inactiveTrackColor: const Color(0xFFE5E5E5)), + Switch( + value: _syncEnabled, + onChanged: _isActivated ? _toggleSync : null, + activeThumbColor: colors.primary, + activeTrackColor: colors.primary.withValues(alpha: 0.3), + inactiveThumbColor: colors.surface, + inactiveTrackColor: colors.outline), ]), ), const SizedBox(height: 24), // 说明 Container( padding: const EdgeInsets.all(18), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(14), - boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 6, offset: const Offset(0, 2))]), + decoration: BoxDecoration( + color: colors.surface, + borderRadius: BorderRadius.circular(14), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.03), blurRadius: 6, offset: const Offset(0, 2)) + ]), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Container(width: 36, height: 36, decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(8)), - child: const Icon(Icons.info_outline, size: 18, color: Color(0xFF666666))), + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8)), + child: Icon(Icons.info_outline, + size: 18, color: colors.onSurface.withValues(alpha: 0.6))), const SizedBox(width: 10), - const Text('使用说明', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text('使用说明', + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.w600, color: colors.onSurface)), ]), const SizedBox(height: 14), - _infoItem('1. 在服务端管理后台生成激活码'), + _infoItem(colors, '1. 在服务端管理后台生成激活码'), const SizedBox(height: 8), - _infoItem('2. 输入服务器地址和激活码完成验证'), + _infoItem(colors, '2. 输入服务器地址和激活码完成验证'), const SizedBox(height: 8), - _infoItem('3. 验证通过后自动开启实时同步'), + _infoItem(colors, '3. 验证通过后自动开启实时同步'), const SizedBox(height: 8), - _infoItem('4. 开启时所有数据通过服务端接口操作'), + _infoItem(colors, '4. 开启时所有数据通过服务端接口操作'), const SizedBox(height: 8), - _infoItem('5. 关闭时从服务端下载数据到本地使用'), + _infoItem(colors, '5. 关闭时从服务端下载数据到本地使用'), ]), ), const SizedBox(height: 40), @@ -303,21 +386,33 @@ class _ServerSyncPageState extends State { ); } - InputDecoration _inputDeco(String hint) { + InputDecoration _inputDeco(ColorScheme colors, String hint) { return InputDecoration( - hintText: hint, hintStyle: const TextStyle(fontSize: 13, color: Color(0xFFCCCCCC)), - filled: true, fillColor: const Color(0xFFF8F8F8), + hintText: hint, + hintStyle: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.25)), + filled: true, + fillColor: colors.surfaceContainerHigh, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none), - focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: Color(0xFF1A1A1A), width: 1)), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: colors.primary, width: 1)), ); } - Widget _infoItem(String text) { + Widget _infoItem(ColorScheme colors, String text) { return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container(width: 5, height: 5, margin: const EdgeInsets.only(top: 6), decoration: BoxDecoration(color: const Color(0xFFBBBBBB), shape: BoxShape.circle)), + Container( + width: 5, + height: 5, + margin: const EdgeInsets.only(top: 6), + decoration: BoxDecoration( + color: colors.onSurface.withValues(alpha: 0.3), shape: BoxShape.circle)), const SizedBox(width: 10), - Expanded(child: Text(text, style: const TextStyle(fontSize: 13, color: Color(0xFF888888), height: 1.5))), + Expanded( + child: Text(text, + style: TextStyle( + fontSize: 13, color: colors.onSurface.withValues(alpha: 0.5), height: 1.5))), ]); } } diff --git a/lib/pages/sync/webdav_sync_page.dart b/lib/pages/sync/webdav_sync_page.dart index bbd6753..50e1e51 100644 --- a/lib/pages/sync/webdav_sync_page.dart +++ b/lib/pages/sync/webdav_sync_page.dart @@ -66,22 +66,37 @@ class _WebDAVSyncPageState extends State { final password = _passwordController.text; final path = _pathController.text.trim(); - if (url.isEmpty) { ToastUtil.show(context, '请输入服务器地址'); return; } - if (username.isEmpty) { ToastUtil.show(context, '请输入用户名'); return; } - if (password.isEmpty) { ToastUtil.show(context, '请输入密码'); return; } + if (url.isEmpty) { + ToastUtil.show(context, '请输入服务器地址'); + return; + } + if (username.isEmpty) { + ToastUtil.show(context, '请输入用户名'); + return; + } + if (password.isEmpty) { + ToastUtil.show(context, '请输入密码'); + return; + } setState(() => _isLoading = true); try { final result = await WebDAVService.instance.testConnection( - url: url, username: username, password: password, path: path, + url: url, + username: username, + password: password, + path: path, ); if (!mounted) return; if (result['success'] == true) { await WebDAVService.instance.saveConfig( - url: url, username: username, password: password, path: path, + url: url, + username: username, + password: password, + path: path, ); setState(() => _isConfigured = true); ToastUtil.show(context, result['message'] ?? '连接成功,配置已保存'); @@ -104,7 +119,7 @@ class _WebDAVSyncPageState extends State { if (result.success) { final details = '上传: ${result.uploadedFiles} 文件, ${result.uploadedImages} 图片\n' - '下载: ${result.downloadedFiles} 文件, ${result.downloadedImages} 图片'; + '下载: ${result.downloadedFiles} 文件, ${result.downloadedImages} 图片'; if (result.needReload) { ToastUtil.show(context, '数据已更新,正在重新加载...'); @@ -129,39 +144,48 @@ class _WebDAVSyncPageState extends State { void _showResultDialog(String title, String content) { showDialog( context: context, - builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, - elevation: 0, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - title: Column( - children: [ - Container( - width: 48, height: 48, - decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), - borderRadius: BorderRadius.circular(12), + builder: (ctx) { + final colors = Theme.of(ctx).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: Column( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + ), + child: Icon(Icons.check, color: colors.primary, size: 24), ), - child: const Icon(Icons.check, color: Color(0xFF1A1A1A), size: 24), - ), - const SizedBox(height: 16), - Text(title, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), - ], - ), - titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), - content: Padding( - padding: const EdgeInsets.only(top: 12), - child: Text(content, style: const TextStyle(fontSize: 14, color: Color(0xFF666666), height: 1.6), textAlign: TextAlign.center), - ), - contentPadding: const EdgeInsets.fromLTRB(24, 0, 24, 0), - actionsPadding: const EdgeInsets.fromLTRB(16, 20, 16, 16), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - style: TextButton.styleFrom(minimumSize: const Size(120, 40), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), - child: const Text('确定', style: TextStyle(fontSize: 14, color: Color(0xFF1A1A1A))), + const SizedBox(height: 16), + Text(title, + style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)), + ], ), - ], - ), + titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), + content: Padding( + padding: const EdgeInsets.only(top: 12), + child: Text(content, + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.6), + textAlign: TextAlign.center), + ), + contentPadding: const EdgeInsets.fromLTRB(24, 0, 24, 0), + actionsPadding: const EdgeInsets.fromLTRB(16, 20, 16, 16), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + style: TextButton.styleFrom( + minimumSize: const Size(120, 40), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), + child: Text('确定', style: TextStyle(fontSize: 14, color: colors.primary)), + ), + ], + ); + }, ); } @@ -188,25 +212,40 @@ class _WebDAVSyncPageState extends State { final selected = await showModalBottomSheet( context: context, backgroundColor: Colors.white, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), - builder: (ctx) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 8), - Container(width: 36, height: 4, decoration: BoxDecoration(color: const Color(0xFFDDDDDD), borderRadius: BorderRadius.circular(2))), - const SizedBox(height: 12), - const Text('同步间隔', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), - const SizedBox(height: 8), - ...intervals.map((i) => ListTile( - title: Text('$i 分钟', style: TextStyle(fontSize: 15, fontWeight: _autoSyncInterval == i ? FontWeight.w600 : FontWeight.w400, color: const Color(0xFF1A1A1A))), - trailing: _autoSyncInterval == i ? const Icon(Icons.check, color: Color(0xFF1A1A1A), size: 20) : null, - onTap: () => Navigator.pop(ctx, i), - )), - const SizedBox(height: 8), - ], - ), - ), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16))), + builder: (ctx) { + final colors = Theme.of(ctx).colorScheme; + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Container( + width: 36, + height: 4, + decoration: BoxDecoration( + color: colors.onSurface.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(2))), + const SizedBox(height: 12), + Text('同步间隔', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface)), + const SizedBox(height: 8), + ...intervals.map((i) => ListTile( + title: Text('$i 分钟', + style: TextStyle( + fontSize: 15, + fontWeight: _autoSyncInterval == i ? FontWeight.w600 : FontWeight.w400, + color: colors.onSurface)), + trailing: _autoSyncInterval == i + ? Icon(Icons.check, color: colors.primary, size: 20) + : null, + onTap: () => Navigator.pop(ctx, i), + )), + const SizedBox(height: 8), + ], + ), + ); + }, ); if (selected != null && selected != _autoSyncInterval) { @@ -226,41 +265,54 @@ class _WebDAVSyncPageState extends State { Future _clearConfig() async { final confirmed = await showDialog( context: context, - builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, - elevation: 0, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), - title: Row( - children: [ - Container( - width: 40, height: 40, - decoration: BoxDecoration(color: Colors.red.withOpacity(0.08), borderRadius: BorderRadius.circular(10)), - child: const Icon(Icons.warning_amber_rounded, color: Colors.red, size: 22), + builder: (ctx) { + final colors = Theme.of(ctx).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), + title: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.08), borderRadius: BorderRadius.circular(10)), + child: const Icon(Icons.warning_amber_rounded, color: Colors.red, size: 22), + ), + const SizedBox(width: 12), + Text('清除配置', + style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)), + ], + ), + titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), + content: Padding( + padding: const EdgeInsets.only(top: 16), + child: Text('确定要清除 WebDAV 配置吗?', + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.6)), + ), + contentPadding: const EdgeInsets.fromLTRB(24, 0, 24, 0), + actionsPadding: const EdgeInsets.fromLTRB(16, 20, 16, 16), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), + child: Text('取消', + style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6), fontSize: 14)), + ), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), + child: const Text('清除', + style: TextStyle(color: Colors.red, fontSize: 14, fontWeight: FontWeight.w600)), ), - const SizedBox(width: 12), - const Text('清除配置', style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), ], - ), - titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), - content: const Padding( - padding: EdgeInsets.only(top: 16), - child: Text('确定要清除 WebDAV 配置吗?', style: TextStyle(fontSize: 14, color: Color(0xFF666666), height: 1.6)), - ), - contentPadding: const EdgeInsets.fromLTRB(24, 0, 24, 0), - actionsPadding: const EdgeInsets.fromLTRB(16, 20, 16, 16), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - style: TextButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), - child: const Text('取消', style: TextStyle(color: Color(0xFF666666), fontSize: 14)), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - style: TextButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), - child: const Text('清除', style: TextStyle(color: Colors.red, fontSize: 14, fontWeight: FontWeight.w600)), - ), - ], - ), + ); + }, ); if (confirmed == true) { @@ -281,46 +333,54 @@ class _WebDAVSyncPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: colors.surface, appBar: AppBar(title: const Text('WebDAV 备份')), body: _isLoading && !_isConfigured - ? const Center(child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF1A1A1A))) + ? Center(child: CircularProgressIndicator(strokeWidth: 2, color: colors.primary)) : ListView( padding: const EdgeInsets.symmetric(horizontal: 24), children: [ const SizedBox(height: 8), // 已连接提示 - if (_isConfigured) _buildConnectedBanner(), + if (_isConfigured) _buildConnectedBanner(colors), // 服务器配置 - _buildSectionLabel('服务器配置'), + _buildSectionLabel(colors, '服务器配置'), const SizedBox(height: 16), _buildInput( + colors: colors, controller: _urlController, hint: '服务器地址,如 https://dav.example.com', icon: Icons.link, ), const SizedBox(height: 12), _buildInput( + colors: colors, controller: _usernameController, hint: '用户名', icon: Icons.person_outline, ), const SizedBox(height: 12), _buildInput( + colors: colors, controller: _passwordController, hint: '密码', icon: Icons.lock_outline, obscure: _obscurePassword, suffix: GestureDetector( onTap: () => setState(() => _obscurePassword = !_obscurePassword), - child: Icon(_obscurePassword ? Icons.visibility_off : Icons.visibility, size: 20, color: const Color(0xFFBBBBBB)), + child: Icon( + _obscurePassword ? Icons.visibility_off : Icons.visibility, + size: 20, + color: colors.onSurface.withValues(alpha: 0.3)), ), ), const SizedBox(height: 12), _buildInput( + colors: colors, controller: _pathController, hint: '同步路径,如 /mooknote', icon: Icons.folder_outlined, @@ -328,14 +388,15 @@ class _WebDAVSyncPageState extends State { const SizedBox(height: 24), // 测试并保存 - _buildBtn('测试并保存', onTap: _isLoading ? null : _saveConfig), + _buildBtn(colors, '测试并保存', onTap: _isLoading ? null : _saveConfig), const SizedBox(height: 40), if (_isConfigured) ...[ // 自动同步 - _buildSectionLabel('自动同步'), + _buildSectionLabel(colors, '自动同步'), const SizedBox(height: 12), _buildSwitchRow( + colors: colors, icon: Icons.sync, label: '自动同步', sub: _isAutoSyncEnabled ? '每 $_autoSyncInterval 分钟自动同步' : '关闭', @@ -345,6 +406,7 @@ class _WebDAVSyncPageState extends State { if (_isAutoSyncEnabled) ...[ const SizedBox(height: 4), _buildTappableRow( + colors: colors, label: '同步间隔', value: '$_autoSyncInterval 分钟', onTap: _isLoading ? null : _showIntervalPicker, @@ -354,27 +416,31 @@ class _WebDAVSyncPageState extends State { const SizedBox(height: 32), // 手动同步 - _buildSectionLabel('手动同步'), + _buildSectionLabel(colors, '手动同步'), const SizedBox(height: 16), Row( children: [ - Expanded(child: _buildDirectionChip('上传到云端', SyncDirection.upload, Icons.upload)), + Expanded( + child: _buildDirectionChip( + colors, '上传到云端', SyncDirection.upload, Icons.upload)), const SizedBox(width: 12), - Expanded(child: _buildDirectionChip('下载到本地', SyncDirection.download, Icons.download)), + Expanded( + child: _buildDirectionChip( + colors, '下载到本地', SyncDirection.download, Icons.download)), ], ), const SizedBox(height: 20), - _buildBtn('立即同步', onTap: _isLoading ? null : _syncData, loading: _isLoading), + _buildBtn(colors, '立即同步', onTap: _isLoading ? null : _syncData, loading: _isLoading), const SizedBox(height: 32), // 清除配置 - _buildTextBtn('清除配置', onTap: _isLoading ? null : _clearConfig), + _buildTextBtn(colors, '清除配置', onTap: _isLoading ? null : _clearConfig), const SizedBox(height: 8), ], const SizedBox(height: 32), - _buildTips(), + _buildTips(colors), const SizedBox(height: 60), ], ), @@ -383,40 +449,48 @@ class _WebDAVSyncPageState extends State { // ── widgets ───────────────────────────────────────── - Widget _buildConnectedBanner() { + Widget _buildConnectedBanner(ColorScheme colors) { return Container( margin: const EdgeInsets.only(bottom: 24), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Container( - width: 6, height: 6, + width: 6, + height: 6, decoration: const BoxDecoration(color: Color(0xFF4CAF50), shape: BoxShape.circle), ), const SizedBox(width: 10), Expanded( child: Text( _urlController.text, - style: const TextStyle(fontSize: 13, color: Color(0xFF666666)), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.6)), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), - const Text('已连接', style: TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text('已连接', + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4))), ], ), ); } - Widget _buildSectionLabel(String text) { - return Text(text, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF999999), letterSpacing: 0.5)); + Widget _buildSectionLabel(ColorScheme colors, String text) { + return Text(text, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: colors.onSurface.withValues(alpha: 0.4), + letterSpacing: 0.5)); } Widget _buildInput({ + required ColorScheme colors, required TextEditingController controller, required String hint, required IconData icon, @@ -426,35 +500,37 @@ class _WebDAVSyncPageState extends State { return TextField( controller: controller, obscureText: obscure, - style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 15, color: colors.onSurface), decoration: InputDecoration( hintText: hint, - hintStyle: const TextStyle(fontSize: 15, color: Color(0xFFBBBBBB)), + hintStyle: TextStyle(fontSize: 15, color: colors.onSurface.withValues(alpha: 0.3)), prefixIcon: Padding( padding: const EdgeInsets.only(left: 4, right: 8), - child: Icon(icon, size: 20, color: const Color(0xFFBBBBBB)), + child: Icon(icon, size: 20, color: colors.onSurface.withValues(alpha: 0.3)), ), prefixIconConstraints: const BoxConstraints(minWidth: 44), - suffixIcon: suffix != null ? Padding( - padding: const EdgeInsets.only(right: 8), - child: suffix, - ) : null, + suffixIcon: suffix != null + ? Padding( + padding: const EdgeInsets.only(right: 8), + child: suffix, + ) + : null, filled: true, - fillColor: const Color(0xFFF8F8F8), + fillColor: colors.surfaceContainerHigh, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide(color: Color(0xFF1A1A1A), width: 1), + borderSide: BorderSide(color: colors.primary, width: 1), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15), ), ); } - Widget _buildBtn(String text, {VoidCallback? onTap, bool loading = false}) { + Widget _buildBtn(ColorScheme colors, String text, {VoidCallback? onTap, bool loading = false}) { final disabled = onTap == null; return GestureDetector( onTap: onTap, @@ -462,30 +538,44 @@ class _WebDAVSyncPageState extends State { width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 15), decoration: BoxDecoration( - color: disabled ? const Color(0xFFDDDDDD) : const Color(0xFF1A1A1A), + color: disabled ? colors.onSurface.withValues(alpha: 0.15) : colors.primary, borderRadius: BorderRadius.circular(10), ), child: Center( child: loading - ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white))) - : Text(text, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Colors.white)), + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, valueColor: AlwaysStoppedAnimation(colors.onPrimary))) + : Text(text, + style: TextStyle( + fontSize: 15, fontWeight: FontWeight.w600, color: colors.onPrimary)), ), ), ); } - Widget _buildTextBtn(String text, {VoidCallback? onTap}) { + Widget _buildTextBtn(ColorScheme colors, String text, {VoidCallback? onTap}) { return GestureDetector( onTap: onTap, child: Center( child: Padding( padding: const EdgeInsets.symmetric(vertical: 12), - child: Text(text, style: TextStyle(fontSize: 14, color: onTap == null ? const Color(0xFFCCCCCC) : const Color(0xFF999999)))), + child: Text( + text, + style: TextStyle( + fontSize: 14, + color: onTap == null + ? colors.onSurface.withValues(alpha: 0.25) + : colors.onSurface.withValues(alpha: 0.4))), + ), ), ); } Widget _buildSwitchRow({ + required ColorScheme colors, required IconData icon, required String label, required String sub, @@ -495,26 +585,30 @@ class _WebDAVSyncPageState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), ), child: Row( children: [ - Icon(icon, size: 20, color: value ? const Color(0xFF1A1A1A) : const Color(0xFFBBBBBB)), + Icon(icon, + size: 20, + color: value ? colors.primary : colors.onSurface.withValues(alpha: 0.3)), const SizedBox(width: 12), Expanded( child: Text( value ? sub : label, - style: TextStyle(fontSize: 14, color: value ? const Color(0xFF666666) : const Color(0xFF1A1A1A)), + style: TextStyle( + fontSize: 14, + color: value ? colors.onSurface.withValues(alpha: 0.6) : colors.onSurface), ), ), Switch( value: value, onChanged: onChanged, - activeColor: const Color(0xFF1A1A1A), - activeTrackColor: const Color(0xFF1A1A1A).withOpacity(0.3), - inactiveThumbColor: Colors.white, - inactiveTrackColor: const Color(0xFFDDDDDD), + activeThumbColor: colors.primary, + activeTrackColor: colors.primary.withValues(alpha: 0.3), + inactiveThumbColor: colors.surface, + inactiveTrackColor: colors.onSurface.withValues(alpha: 0.15), ), ], ), @@ -522,6 +616,7 @@ class _WebDAVSyncPageState extends State { } Widget _buildTappableRow({ + required ColorScheme colors, required String label, required String value, VoidCallback? onTap, @@ -531,77 +626,99 @@ class _WebDAVSyncPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), ), child: Row( children: [ const SizedBox(width: 32), - Text(label, style: const TextStyle(fontSize: 14, color: Color(0xFF999999))), + Text(label, + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.4))), const Spacer(), - Text(value, style: const TextStyle(fontSize: 14, color: Color(0xFF666666))), + Text(value, + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6))), const SizedBox(width: 4), - const Icon(Icons.chevron_right, size: 16, color: Color(0xFFCCCCCC)), + Icon(Icons.chevron_right, + size: 16, color: colors.onSurface.withValues(alpha: 0.25)), ], ), ), ); } - Widget _buildDirectionChip(String label, SyncDirection dir, IconData icon) { + Widget _buildDirectionChip(ColorScheme colors, String label, SyncDirection dir, IconData icon) { final selected = _syncDirection == dir; return GestureDetector( onTap: () => setState(() => _syncDirection = dir), child: Container( padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( - color: selected ? const Color(0xFF1A1A1A) : const Color(0xFFF8F8F8), + color: selected ? colors.primary : colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(icon, size: 16, color: selected ? Colors.white : const Color(0xFF999999)), + Icon(icon, + size: 16, + color: selected + ? colors.onPrimary + : colors.onSurface.withValues(alpha: 0.4)), const SizedBox(width: 6), - Text(label, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: selected ? Colors.white : const Color(0xFF666666))), + Text(label, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: selected + ? colors.onPrimary + : colors.onSurface.withValues(alpha: 0.6))), ], ), ), ); } - Widget _buildTips() { + Widget _buildTips(ColorScheme colors) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('支持的服务', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF999999), letterSpacing: 0.5)), + Text('支持的服务', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: colors.onSurface.withValues(alpha: 0.4), + letterSpacing: 0.5)), const SizedBox(height: 10), - _tip('坚果云、Nextcloud、AList 等 WebDAV 服务'), - _tip('服务器地址需包含 https://'), - _tip('首次同步可能需要较长时间'), + _tip(colors, '坚果云、Nextcloud、AList 等 WebDAV 服务'), + _tip(colors, '服务器地址需包含 https://'), + _tip(colors, '首次同步可能需要较长时间'), ], ), ); } - Widget _tip(String text) { + Widget _tip(ColorScheme colors, String text) { return Padding( padding: const EdgeInsets.only(bottom: 6), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Padding( - padding: EdgeInsets.only(top: 8), - child: Icon(Icons.circle, size: 4, color: Color(0xFFCCCCCC)), + Padding( + padding: const EdgeInsets.only(top: 8), + child: Icon(Icons.circle, + size: 4, color: colors.onSurface.withValues(alpha: 0.25)), ), const SizedBox(width: 8), - Expanded(child: Text(text, style: const TextStyle(fontSize: 13, color: Color(0xFF888888), height: 1.5))), + Expanded( + child: Text(text, + style: TextStyle( + fontSize: 13, color: colors.onSurface.withValues(alpha: 0.5), height: 1.5))), ], ), ); diff --git a/lib/pages/tag_management_page.dart b/lib/pages/tag_management_page.dart index c6f2215..50b0761 100644 --- a/lib/pages/tag_management_page.dart +++ b/lib/pages/tag_management_page.dart @@ -48,7 +48,6 @@ class _TagManagementPageState extends State { String get _currentType => _tabTypes[_currentIndex]; - /// 计算标签使用次数 Map _getTagUsageCounts(String type) { final provider = context.read(); final counts = {}; @@ -81,16 +80,17 @@ class _TagManagementPageState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: const Color(0xFFF8F8F8), + backgroundColor: colors.surfaceContainerHigh, appBar: AppBar( title: const Text('标签管理'), actions: [ _isSyncing - ? const Padding( - padding: EdgeInsets.all(16), + ? Padding( + padding: const EdgeInsets.all(16), child: SizedBox(width: 20, height: 20, - child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF1A1A1A))), + child: CircularProgressIndicator(strokeWidth: 2, color: colors.primary)), ) : IconButton( icon: const Icon(Icons.sync, size: 20), @@ -112,7 +112,7 @@ class _TagManagementPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow(color: Colors.black.withValues(alpha: 0.12), blurRadius: 12, offset: const Offset(0, 4)), @@ -121,10 +121,10 @@ class _TagManagementPageState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.add, size: 18, color: Colors.white), + Icon(Icons.add, size: 18, color: colors.onPrimary), const SizedBox(width: 6), Text('添加${_typeLabels[_currentIndex]}', - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)), + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onPrimary)), ], ), ), @@ -132,14 +132,13 @@ class _TagManagementPageState extends State { ); } - // ─── Tab 选择器 ────────────────────────────────────────────────────── - Widget _buildTabSelector() { + final colors = Theme.of(context).colorScheme; return Container( margin: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: const Color(0xFFEBEBEB), + color: colors.outlineVariant, borderRadius: BorderRadius.circular(24), ), child: LayoutBuilder( @@ -158,7 +157,7 @@ class _TagManagementPageState extends State { padding: const EdgeInsets.all(3), child: Container( decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(22), boxShadow: [ BoxShadow(color: Colors.black.withValues(alpha: 0.08), blurRadius: 8, offset: const Offset(0, 2)), @@ -183,7 +182,7 @@ class _TagManagementPageState extends State { alignment: Alignment.center, child: Text(_typeLabels[i], style: TextStyle(fontSize: 13, fontWeight: selected ? FontWeight.w600 : FontWeight.w500, - color: selected ? const Color(0xFF1A1A1A) : const Color(0xFF999999))), + color: selected ? colors.onSurface : colors.onSurface.withValues(alpha: 0.4))), ), ), ); @@ -197,9 +196,8 @@ class _TagManagementPageState extends State { ); } - // ─── 标签列表 ──────────────────────────────────────────────────────── - Widget _buildTagList(String type) { + final colors = Theme.of(context).colorScheme; final tags = _tagCache[type] ?? []; final usageCounts = _getTagUsageCounts(type); @@ -207,7 +205,6 @@ class _TagManagementPageState extends State { return _buildEmptyState(type); } - // 按使用次数降序排序 final sorted = List>.from(tags) ..sort((a, b) { final ca = usageCounts[a['name']] ?? 0; @@ -220,18 +217,16 @@ class _TagManagementPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 统计行 Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( children: [ - Text('共 ${tags.length} 个标签', style: const TextStyle(fontSize: 12, color: Color(0xFFBBBBBB))), + Text('共 ${tags.length} 个标签', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.3))), const Spacer(), - Text('已使用 ${usageCounts.length} 个', style: const TextStyle(fontSize: 12, color: Color(0xFFD0D0D0))), + Text('已使用 ${usageCounts.length} 个', style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.2))), ], ), ), - // 标签列表 Expanded( child: SingleChildScrollView( padding: const EdgeInsets.only(bottom: 80), @@ -248,50 +243,50 @@ class _TagManagementPageState extends State { } Widget _buildTagChip(Map tag, Map usageCounts) { + final colors = Theme.of(context).colorScheme; final name = tag['name'] as String; final count = usageCounts[name] ?? 0; - return Container( - padding: const EdgeInsets.only(left: 14, right: 6, top: 8, bottom: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 4, offset: const Offset(0, 2)), - ], - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - // 标签名区域 — 点击改名 - GestureDetector( - onTap: () => _showRenameDialog(tag), - child: Text(name, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A))), - ), - if (count > 0) ...[ - const SizedBox(width: 6), - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration(color: const Color(0xFFF0F0F0), borderRadius: BorderRadius.circular(8)), - child: Text('$count', style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: Color(0xFF999999))), + return GestureDetector( + onLongPress: () => _showRenameDialog(tag), + child: Container( + padding: const EdgeInsets.only(left: 14, right: 6, top: 8, bottom: 8), + decoration: BoxDecoration( + color: colors.surface, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 4, offset: const Offset(0, 2)), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(name, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface)), + if (count > 0) ...[ + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration(color: colors.outlineVariant, borderRadius: BorderRadius.circular(8)), + child: Text('$count', style: TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: colors.onSurface.withValues(alpha: 0.4))), + ), + ], + const SizedBox(width: 4), + GestureDetector( + onTap: () => _showDeleteDialog(tag), + child: Container( + width: 22, height: 22, + decoration: BoxDecoration(color: colors.outlineVariant, borderRadius: BorderRadius.circular(11)), + child: Icon(Icons.close, size: 12, color: colors.onSurface.withValues(alpha: 0.4)), + ), ), ], - const SizedBox(width: 4), - // X 按钮 — 点击删除 - GestureDetector( - onTap: () => _showDeleteDialog(tag), - child: Container( - width: 22, height: 22, - decoration: BoxDecoration(color: const Color(0xFFF0F0F0), borderRadius: BorderRadius.circular(11)), - child: const Icon(Icons.close, size: 12, color: Color(0xFF999999)), - ), - ), - ], + ), ), ); } Widget _buildEmptyState(String type) { + final colors = Theme.of(context).colorScheme; final idx = _tabTypes.indexOf(type); final icon = _typeIcons[idx]; final label = _typeLabels[idx]; @@ -303,57 +298,56 @@ class _TagManagementPageState extends State { children: [ Container( width: 64, height: 64, - decoration: BoxDecoration(color: const Color(0xFFECECEC), borderRadius: BorderRadius.circular(18)), - child: Icon(icon, size: 28, color: const Color(0xFFCCCCCC)), + decoration: BoxDecoration(color: colors.outlineVariant, borderRadius: BorderRadius.circular(18)), + child: Icon(icon, size: 28, color: colors.onSurface.withValues(alpha: 0.25)), ), const SizedBox(height: 16), Text('暂无$label', - style: const TextStyle(fontSize: 14, color: Color(0xFFBBBBBB), fontWeight: FontWeight.w500)), + style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.3), fontWeight: FontWeight.w500)), const SizedBox(height: 6), - Text(hints[idx], style: const TextStyle(fontSize: 12, color: Color(0xFFD5D5D5))), + Text(hints[idx], style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.15))), ], ), ); } - // ─── 添加标签 ──────────────────────────────────────────────────────── - void _showAddDialog() { + final colors = Theme.of(context).colorScheme; final controller = TextEditingController(); final type = _currentType; showDialog( context: context, builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Text('添加${_typeLabels[_currentIndex]}', - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), content: TextField( controller: controller, autofocus: true, - style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 15, color: colors.onSurface), decoration: InputDecoration( hintText: '输入标签名称', - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFAAAAAA)), - filled: true, fillColor: const Color(0xFFFAFAFA), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.35)), + filled: true, fillColor: colors.surfaceContainerHigh, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), - focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Color(0xFF1A1A1A), width: 1)), + focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: colors.primary, width: 1)), ), onSubmitted: (value) => _doAddTag(ctx, controller.text.trim(), type), ), actions: [ - TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('取消', style: TextStyle(color: Color(0xFF999999)))), + TextButton(onPressed: () => Navigator.pop(ctx), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4)))), Container( - decoration: BoxDecoration(color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(20)), + decoration: BoxDecoration(color: colors.primary, borderRadius: BorderRadius.circular(20)), child: Material( color: Colors.transparent, child: InkWell( onTap: () => _doAddTag(ctx, controller.text.trim(), type), borderRadius: BorderRadius.circular(20), - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 8), - child: Text('添加', style: TextStyle(fontSize: 14, color: Colors.white, fontWeight: FontWeight.w500)), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + child: Text('添加', style: TextStyle(fontSize: 14, color: colors.onPrimary, fontWeight: FontWeight.w500)), ), ), ), @@ -378,9 +372,8 @@ class _TagManagementPageState extends State { await _loadTags(type); } - // ─── 重命名 ────────────────────────────────────────────────────────── - void _showRenameDialog(Map tag) { + final colors = Theme.of(context).colorScheme; final controller = TextEditingController(text: tag['name'] as String); final tagId = tag['id'] as String; final type = tag['type'] as String; @@ -389,34 +382,34 @@ class _TagManagementPageState extends State { showDialog( context: context, builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - title: const Text('重命名标签', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + title: Text('重命名标签', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), content: TextField( controller: controller, autofocus: true, - style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A)), + style: TextStyle(fontSize: 15, color: colors.onSurface), decoration: InputDecoration( hintText: '输入新名称', - hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFAAAAAA)), - filled: true, fillColor: const Color(0xFFFAFAFA), + hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.35)), + filled: true, fillColor: colors.surfaceContainerHigh, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), - focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Color(0xFF1A1A1A), width: 1)), + focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: colors.primary, width: 1)), ), onSubmitted: (value) => _doRenameTag(ctx, tagId, value.trim(), type, oldName), ), actions: [ - TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('取消', style: TextStyle(color: Color(0xFF999999)))), + TextButton(onPressed: () => Navigator.pop(ctx), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4)))), Container( - decoration: BoxDecoration(color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(20)), + decoration: BoxDecoration(color: colors.primary, borderRadius: BorderRadius.circular(20)), child: Material( color: Colors.transparent, child: InkWell( onTap: () => _doRenameTag(ctx, tagId, controller.text.trim(), type, oldName), borderRadius: BorderRadius.circular(20), - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 8), - child: Text('确定', style: TextStyle(fontSize: 14, color: Colors.white, fontWeight: FontWeight.w500)), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + child: Text('确定', style: TextStyle(fontSize: 14, color: colors.onPrimary, fontWeight: FontWeight.w500)), ), ), ), @@ -439,9 +432,8 @@ class _TagManagementPageState extends State { if (success) await _loadTags(type); } - // ─── 删除(长按触发)────────────────────────────────────────────────── - void _showDeleteDialog(Map tag) { + final colors = Theme.of(context).colorScheme; final tagId = tag['id'] as String; final type = tag['type'] as String; final name = tag['name'] as String; @@ -457,17 +449,17 @@ class _TagManagementPageState extends State { context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setDialogState) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - decoration: BoxDecoration(color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(12)), - child: Text(name, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: Color(0xFF666666))), + decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(12)), + child: Text(name, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.6))), ), const SizedBox(width: 10), - const Text('删除标签', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text('删除标签', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), ], ), titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0), @@ -481,7 +473,7 @@ class _TagManagementPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), - const Text('删除后对已有条目的影响:', style: TextStyle(fontSize: 13, color: Color(0xFF999999))), + Text('删除后对已有条目的影响:', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4))), const SizedBox(height: 12), _buildDeleteOption(value: 'remove', groupValue: selectedAction, onChanged: (v) => setDialogState(() => selectedAction = v), title: '从所有条目中移除该标签', subtitle: '标签将从影视/书籍/笔记中清除'), const SizedBox(height: 4), @@ -504,19 +496,19 @@ class _TagManagementPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7), decoration: BoxDecoration( - color: isSelected ? const Color(0xFF1A1A1A) : const Color(0xFFF5F5F5), + color: isSelected ? colors.primary : colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), - border: Border.all(color: isSelected ? const Color(0xFF1A1A1A) : const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: isSelected ? colors.primary : colors.outlineVariant, width: 0.5), ), - child: Text(t, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: isSelected ? Colors.white : const Color(0xFF555555))), + child: Text(t, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.7))), ), ); }).toList(), ) : Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), - decoration: BoxDecoration(color: const Color(0xFFFAFAFA), borderRadius: BorderRadius.circular(12)), - child: const Text('无其他标签可替换', style: TextStyle(fontSize: 13, color: Color(0xFFAAAAAA))), + decoration: BoxDecoration(color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12)), + child: Text('无其他标签可替换', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.35))), ), ), ], @@ -526,7 +518,7 @@ class _TagManagementPageState extends State { ), contentPadding: const EdgeInsets.fromLTRB(24, 16, 24, 0), actions: [ - TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('取消', style: TextStyle(color: Color(0xFF999999)))), + TextButton(onPressed: () => Navigator.pop(ctx), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.4)))), Container( decoration: BoxDecoration(color: const Color(0xFFE53935), borderRadius: BorderRadius.circular(20)), child: Material( @@ -573,15 +565,16 @@ class _TagManagementPageState extends State { required String title, String? subtitle, }) { + final colors = Theme.of(context).colorScheme; final selected = value == groupValue; return GestureDetector( onTap: () => onChanged(value), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), decoration: BoxDecoration( - color: selected ? const Color(0xFFFAFAFA) : Colors.white, + color: selected ? colors.surfaceContainerHigh : colors.surface, borderRadius: BorderRadius.circular(12), - border: Border.all(color: selected ? const Color(0xFF1A1A1A) : const Color(0xFFEEEEEE), width: selected ? 1 : 0.5), + border: Border.all(color: selected ? colors.primary : colors.outlineVariant, width: selected ? 1 : 0.5), ), child: Row( children: [ @@ -589,14 +582,14 @@ class _TagManagementPageState extends State { width: 18, height: 18, decoration: BoxDecoration( shape: BoxShape.circle, - border: Border.all(color: selected ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC), width: selected ? 5 : 1.5), + border: Border.all(color: selected ? colors.primary : colors.onSurface.withValues(alpha: 0.25), width: selected ? 5 : 1.5), ), ), const SizedBox(width: 12), Expanded( child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text(title, style: TextStyle(fontSize: 14, fontWeight: selected ? FontWeight.w500 : FontWeight.normal, color: selected ? const Color(0xFF1A1A1A) : const Color(0xFF666666))), - if (subtitle != null) Padding(padding: const EdgeInsets.only(top: 2), child: Text(subtitle, style: const TextStyle(fontSize: 11, color: Color(0xFFAAAAAA)))), + Text(title, style: TextStyle(fontSize: 14, fontWeight: selected ? FontWeight.w500 : FontWeight.normal, color: selected ? colors.onSurface : colors.onSurface.withValues(alpha: 0.6))), + if (subtitle != null) Padding(padding: const EdgeInsets.only(top: 2), child: Text(subtitle, style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.35)))), ]), ), ], diff --git a/lib/providers/app_provider.dart b/lib/providers/app_provider.dart index ca2b4be..3df95c0 100644 --- a/lib/providers/app_provider.dart +++ b/lib/providers/app_provider.dart @@ -32,13 +32,16 @@ class AppProvider extends ChangeNotifier { // 当前主界面选中的标签 (0: 观影,1: 阅读,2: 笔记) int _mainTabIndex = 0; - + // 当前底部导航选中的索引 (0: 主页,1: 新增,2: 我的) int _bottomNavIndex = 0; // 底部导航栏是否可见 bool _bottomNavVisible = true; + // 主题模式 + ThemeMode _themeMode = ThemeMode.system; + /// 是否使用远程服务端(同步开关 + 已激活) bool get _useRemote { final prefs = UserPrefs(); @@ -134,6 +137,7 @@ class AppProvider extends ChangeNotifier { int get bookStatusIndex => _bookStatusIndex; bool get drawerOpen => _drawerOpen; bool get bottomNavVisible => _bottomNavVisible; + ThemeMode get themeMode => _themeMode; List get movies => _movies; List get books => _books; List get notes => _notes; @@ -167,6 +171,26 @@ class AppProvider extends ChangeNotifier { } } + void setThemeMode(ThemeMode mode) { + if (_themeMode != mode) { + _themeMode = mode; + notifyListeners(); + } + } + + void loadThemeMode() { + final prefs = UserPrefs(); + switch (prefs.themeMode) { + case 1: + _themeMode = ThemeMode.light; + case 2: + _themeMode = ThemeMode.dark; + default: + _themeMode = ThemeMode.system; + } + notifyListeners(); + } + void setMovieStatusIndex(int index) { _movieStatusIndex = index; notifyListeners(); diff --git a/lib/utils/theme/app_theme.dart b/lib/utils/theme/app_theme.dart index ad4ca3e..c740a88 100644 --- a/lib/utils/theme/app_theme.dart +++ b/lib/utils/theme/app_theme.dart @@ -51,6 +51,10 @@ class AppTheme { onSurface: _black, error: error, onError: _white, + surfaceContainerHighest: _offWhite, + surfaceContainerHigh: Color(0xFFFAFAFA), + outline: _lighterGray, + outlineVariant: Color(0xFFF0F0F0), ), // AppBar - 极简无边框 @@ -262,6 +266,10 @@ class AppTheme { onSurface: _white, error: Color(0xFFEF4444), onError: _black, + surfaceContainerHighest: _darkGray, + surfaceContainerHigh: Color(0xFF2A2A2A), + outline: _gray, + outlineVariant: _darkGray, ), appBarTheme: const AppBarTheme( @@ -286,6 +294,12 @@ class AppTheme { shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), margin: EdgeInsets.zero, ), + + listTileTheme: const ListTileThemeData( + contentPadding: EdgeInsets.symmetric(horizontal: 24, vertical: 16), + minLeadingWidth: 0, + dense: true, + ), dividerTheme: DividerThemeData( color: _darkGray, @@ -304,6 +318,9 @@ class AppTheme { focusedBorder: UnderlineInputBorder( borderSide: BorderSide(color: _white, width: 1), ), + errorBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color(0xFFEF4444), width: 0.5), + ), contentPadding: EdgeInsets.symmetric(vertical: 12), hintStyle: TextStyle( fontFamily: _fontFamily, @@ -311,6 +328,12 @@ class AppTheme { fontWeight: _regular, color: _gray, ), + labelStyle: TextStyle( + fontFamily: _fontFamily, + fontSize: 13, + fontWeight: _medium, + color: _lightGray, + ), ), elevatedButtonTheme: ElevatedButtonThemeData( @@ -327,6 +350,11 @@ class AppTheme { style: TextButton.styleFrom( foregroundColor: _white, padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + textStyle: TextStyle( + fontFamily: _fontFamily, + fontSize: 14, + fontWeight: _medium, + ), ), ), @@ -335,6 +363,84 @@ class AppTheme { selectedItemColor: _white, unselectedItemColor: _gray, elevation: 0, + type: BottomNavigationBarType.fixed, + selectedLabelStyle: TextStyle( + fontFamily: _fontFamily, + fontSize: 11, + fontWeight: _medium, + ), + unselectedLabelStyle: TextStyle( + fontFamily: _fontFamily, + fontSize: 11, + fontWeight: _regular, + ), + ), + + textTheme: const TextTheme( + headlineLarge: TextStyle( + fontFamily: _fontFamily, + fontSize: 32, + fontWeight: _semibold, + color: _white, + letterSpacing: -0.5, + height: 1.2, + ), + headlineMedium: TextStyle( + fontFamily: _fontFamily, + fontSize: 24, + fontWeight: _semibold, + color: _white, + letterSpacing: -0.3, + height: 1.3, + ), + headlineSmall: TextStyle( + fontFamily: _fontFamily, + fontSize: 20, + fontWeight: _semibold, + color: _white, + letterSpacing: -0.2, + height: 1.4, + ), + bodyLarge: TextStyle( + fontFamily: _fontFamily, + fontSize: 16, + fontWeight: _regular, + color: _offWhite, + height: 1.6, + ), + bodyMedium: TextStyle( + fontFamily: _fontFamily, + fontSize: 15, + fontWeight: _regular, + color: _offWhite, + height: 1.5, + ), + bodySmall: TextStyle( + fontFamily: _fontFamily, + fontSize: 13, + fontWeight: _regular, + color: _lightGray, + height: 1.5, + ), + labelLarge: TextStyle( + fontFamily: _fontFamily, + fontSize: 14, + fontWeight: _medium, + color: _white, + ), + labelMedium: TextStyle( + fontFamily: _fontFamily, + fontSize: 12, + fontWeight: _medium, + color: _lightGray, + ), + labelSmall: TextStyle( + fontFamily: _fontFamily, + fontSize: 11, + fontWeight: _medium, + color: _gray, + letterSpacing: 0.3, + ), ), ); } diff --git a/lib/utils/usage_stats_service.dart b/lib/utils/usage_stats_service.dart index 95f1968..24b0845 100644 --- a/lib/utils/usage_stats_service.dart +++ b/lib/utils/usage_stats_service.dart @@ -17,8 +17,8 @@ class UsageStatsService with WidgetsBindingObserver { final UserPrefs _prefs = UserPrefs(); /// 统计服务器地址,发布前替换为实际地址,置空则禁用 - static String serverUrl = 'http://api.mooknote.iletter.top/'; - + // static String serverUrl = 'http://api.mooknote.iletter.top/'; + static String serverUrl = 'http://192.168.31.48:27050/'; Timer? _heartbeatTimer; bool _started = false; diff --git a/lib/utils/user_prefs.dart b/lib/utils/user_prefs.dart index 9928b1b..8c3c1c4 100644 --- a/lib/utils/user_prefs.dart +++ b/lib/utils/user_prefs.dart @@ -11,6 +11,11 @@ class UserPrefs { /// 初始化 static Future init() async { _prefs = await SharedPreferences.getInstance(); + // 迁移旧版 isDarkMode 布尔值到新版 themeMode 三态值 + if (_prefs!.containsKey('isDarkMode') && !_prefs!.containsKey('themeMode')) { + final oldValue = _prefs!.getBool('isDarkMode') ?? false; + await _prefs!.setInt('themeMode', oldValue ? 2 : 0); + } } /// 获取实例 @@ -38,9 +43,9 @@ class UserPrefs { // ========== 应用设置 ========== - /// 是否暗黑模式 - bool get isDarkMode => prefs.getBool('isDarkMode') ?? false; - Future setDarkMode(bool value) => prefs.setBool('isDarkMode', value); + /// 主题模式: 0=跟随系统, 1=浅色, 2=深色 + int get themeMode => prefs.getInt('themeMode') ?? 0; + Future setThemeMode(int value) => prefs.setInt('themeMode', value); /// 是否首次启动 bool get isFirstLaunch => prefs.getBool('isFirstLaunch') ?? true; diff --git a/lib/widgets/animated_star_rating.dart b/lib/widgets/animated_star_rating.dart index 1823d3d..86a3c80 100644 --- a/lib/widgets/animated_star_rating.dart +++ b/lib/widgets/animated_star_rating.dart @@ -55,6 +55,7 @@ class _AnimatedStarRatingState extends State @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; final starValue = widget.rating / 2; return Row( mainAxisSize: MainAxisSize.min, @@ -84,7 +85,7 @@ class _AnimatedStarRatingState extends State style: TextStyle( fontSize: widget.starSize, fontWeight: FontWeight.w600, - color: const Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ); }, diff --git a/lib/widgets/app_refresh_indicator.dart b/lib/widgets/app_refresh_indicator.dart index 0eee924..90e4ac5 100644 --- a/lib/widgets/app_refresh_indicator.dart +++ b/lib/widgets/app_refresh_indicator.dart @@ -15,10 +15,11 @@ class AppRefreshIndicator extends StatelessWidget { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return RefreshIndicator( onRefresh: onRefresh, - color: const Color(0xFF1A1A1A), - backgroundColor: Colors.white, + color: colors.primary, + backgroundColor: colors.surface, strokeWidth: 2.5, displacement: 60, semanticsLabel: semanticsLabel, diff --git a/lib/widgets/book_list_item.dart b/lib/widgets/book_list_item.dart index e506e9d..e785892 100644 --- a/lib/widgets/book_list_item.dart +++ b/lib/widgets/book_list_item.dart @@ -15,6 +15,7 @@ class BookListItem extends StatelessWidget { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: () { Navigator.pushNamed(context, '/book-detail', arguments: book); @@ -23,28 +24,21 @@ class BookListItem extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 封面 Expanded( - child: _buildCover(), + child: _buildCover(colors), ), - const SizedBox(height: 8), - - // 书名 Text( book.title, - style: const TextStyle( + style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 4), - - // 评分 if (book.rating != null) AnimatedStarRating(rating: book.rating!, starSize: 12, showNumber: true) else @@ -53,45 +47,45 @@ class BookListItem extends StatelessWidget { ), ); } - - /// 构建封面 - Widget _buildCover() { + + Widget _buildCover(ColorScheme colors) { return Container( width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), - border: Border.all(color: const Color(0xFFE5E5E5), width: 0.5), + border: Border.all(color: colors.outline, width: 0.5), ), clipBehavior: Clip.antiAlias, child: FadeInLocalImage( path: book.coverPath, fit: BoxFit.cover, - placeholder: const Center(child: Icon(Icons.menu_book_outlined, size: 32, color: Color(0xFFCCCCCC))), - errorWidget: const Center(child: Icon(Icons.menu_book_outlined, size: 32, color: Color(0xFFCCCCCC))), + placeholder: Center(child: Icon(Icons.menu_book_outlined, size: 32, color: colors.onSurface.withValues(alpha: 0.25))), + errorWidget: Center(child: Icon(Icons.menu_book_outlined, size: 32, color: colors.onSurface.withValues(alpha: 0.25))), ), ); } - /// 显示删除确认对话框 void _showDeleteDialog(BuildContext context) { + final colors = Theme.of(context).colorScheme; showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text( + title: Text( '确认删除', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, + color: colors.onSurface, ), ), content: Text( '确定要删除《${book.title}》吗?删除后可在回收站恢复。', - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.5, ), ), @@ -99,7 +93,7 @@ class BookListItem extends StatelessWidget { TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( - foregroundColor: const Color(0xFF666666), + foregroundColor: colors.onSurface.withValues(alpha: 0.6), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), child: const Text('取消'), @@ -111,8 +105,8 @@ class BookListItem extends StatelessWidget { ToastUtil.show(context, '已删除'); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + backgroundColor: colors.error, + foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), diff --git a/lib/widgets/book_status_bar.dart b/lib/widgets/book_status_bar.dart index c469e08..a8749a8 100644 --- a/lib/widgets/book_status_bar.dart +++ b/lib/widgets/book_status_bar.dart @@ -8,20 +8,21 @@ class BookStatusBar extends StatelessWidget { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Consumer( builder: (context, provider, child) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: const BoxDecoration( - color: Colors.white, + decoration: BoxDecoration( + color: colors.surface, border: Border( - bottom: BorderSide(color: Color(0xFFE5E5E5), width: 0.5), + bottom: BorderSide(color: colors.outline, width: 0.5), ), ), child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(24), ), child: LayoutBuilder( @@ -42,7 +43,7 @@ class BookStatusBar extends StatelessWidget { padding: const EdgeInsets.all(3), child: Container( decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( @@ -58,18 +59,21 @@ class BookStatusBar extends StatelessWidget { Row( children: [ _buildTab( + colors: colors, label: '已读', icon: Icons.check_circle_outline, isSelected: provider.bookStatusIndex == 0, onTap: () => provider.setBookStatusIndex(0), ), _buildTab( + colors: colors, label: '在读', icon: Icons.menu_book_outlined, isSelected: provider.bookStatusIndex == 1, onTap: () => provider.setBookStatusIndex(1), ), _buildTab( + colors: colors, label: '想读', icon: Icons.bookmark_outlined, isSelected: provider.bookStatusIndex == 2, @@ -89,6 +93,7 @@ class BookStatusBar extends StatelessWidget { } Widget _buildTab({ + required ColorScheme colors, required String label, required IconData icon, required bool isSelected, @@ -108,7 +113,7 @@ class BookStatusBar extends StatelessWidget { Icon( icon, size: 16, - color: isSelected ? Colors.white : const Color(0xFF666666), + color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6), ), const SizedBox(width: 6), Text( @@ -116,7 +121,7 @@ class BookStatusBar extends StatelessWidget { style: TextStyle( fontSize: 13, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, - color: isSelected ? Colors.white : const Color(0xFF666666), + color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6), ), ), ], diff --git a/lib/widgets/bottom_nav_bar.dart b/lib/widgets/bottom_nav_bar.dart index 947d17a..400a12e 100644 --- a/lib/widgets/bottom_nav_bar.dart +++ b/lib/widgets/bottom_nav_bar.dart @@ -8,34 +8,33 @@ class CustomBottomNavBar extends StatelessWidget { @override Widget build(BuildContext context) { - // 获取底部安全区域高度 final bottomPadding = MediaQuery.of(context).padding.bottom; - + final colors = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + return Consumer( builder: (context, provider, child) { return Container( - // 高度:导航栏本身高度 + 底部安全距离 + 上下边距 height: 64 + bottomPadding + 16, color: Colors.transparent, child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Dock栏主体 - 悬浮效果 Container( height: 56, margin: const EdgeInsets.symmetric(horizontal: 40), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(28), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.08), + color: Colors.black.withOpacity(isDark ? 0.3 : 0.08), blurRadius: 20, offset: const Offset(0, 4), spreadRadius: 0, ), BoxShadow( - color: Colors.black.withOpacity(0.04), + color: Colors.black.withOpacity(isDark ? 0.15 : 0.04), blurRadius: 8, offset: const Offset(0, 2), spreadRadius: -2, @@ -45,19 +44,16 @@ class CustomBottomNavBar extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - // 主页按钮 _buildNavItem( + colors: colors, icon: Icons.home_outlined, activeIcon: Icons.home, isActive: provider.bottomNavIndex == 0, onTap: () => provider.setBottomNavIndex(0), ), - - // 中间新增按钮 _buildAddButton(context, provider), - - // 我的按钮 _buildNavItem( + colors: colors, icon: Icons.person_outline, activeIcon: Icons.person, isActive: provider.bottomNavIndex == 2, @@ -66,7 +62,6 @@ class CustomBottomNavBar extends StatelessWidget { ], ), ), - // 底部安全距离占位 SizedBox(height: bottomPadding + 8), ], ), @@ -74,9 +69,9 @@ class CustomBottomNavBar extends StatelessWidget { }, ); } - - /// 构建导航项 + Widget _buildNavItem({ + required ColorScheme colors, required IconData icon, required IconData activeIcon, required bool isActive, @@ -92,16 +87,16 @@ class CustomBottomNavBar extends StatelessWidget { child: Center( child: Icon( isActive ? activeIcon : icon, - color: isActive ? const Color(0xFF1A1A1A) : const Color(0xFF999999), + color: isActive ? colors.primary : colors.onSurface.withValues(alpha: 0.4), size: 26, ), ), ), ); } - - /// 构建中间新增按钮 + Widget _buildAddButton(BuildContext context, AppProvider provider) { + final colors = Theme.of(context).colorScheme; return GestureDetector( onTap: () => _showAddDialog(context, provider), onLongPress: () => _showQuickAddDialog(context, provider), @@ -109,25 +104,23 @@ class CustomBottomNavBar extends StatelessWidget { width: 44, height: 44, decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(12), ), - child: const Icon( + child: Icon( Icons.add, - color: Colors.white, + color: colors.onPrimary, size: 24, ), ), ); } - /// 长按快速添加 - 根据当前界面直接跳转到对应添加界面 void _showQuickAddDialog(BuildContext context, AppProvider provider) { - // 根据当前主标签页决定跳转到哪个添加界面 final currentTab = provider.mainTabIndex; - + switch (currentTab) { - case 0: // 观影标签页 + case 0: final statusMap = { 0: 'watched', 1: 'watching', @@ -140,7 +133,7 @@ class CustomBottomNavBar extends StatelessWidget { arguments: {'initialStatus': currentStatus}, ); break; - case 1: // 阅读标签页 + case 1: final statusMap = { 0: 'read', 1: 'reading', @@ -153,7 +146,7 @@ class CustomBottomNavBar extends StatelessWidget { arguments: {'initialStatus': currentStatus}, ); break; - case 2: // 笔记标签页 + case 2: Navigator.pushNamed(context, '/note-form'); break; default: @@ -161,34 +154,33 @@ class CustomBottomNavBar extends StatelessWidget { } } - /// 显示新增对话框 void _showAddDialog(BuildContext context, AppProvider provider) { + final colors = Theme.of(context).colorScheme; showModalBottomSheet( context: context, - backgroundColor: Colors.white, + backgroundColor: colors.surface, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (BuildContext context) { + final bottomColors = Theme.of(context).colorScheme; return SafeArea( child: Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: Column( mainAxisSize: MainAxisSize.min, children: [ - // 顶部指示条 Container( width: 40, height: 4, decoration: BoxDecoration( - color: const Color(0xFFE0E0E0), + color: bottomColors.onSurface.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 20), - // 标题 - const Padding( - padding: EdgeInsets.symmetric(horizontal: 24), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), child: Row( children: [ Text( @@ -196,15 +188,15 @@ class CustomBottomNavBar extends StatelessWidget { style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: bottomColors.onSurface, ), ), ], ), ), const SizedBox(height: 16), - // 选项列表 _buildAddOption( + colors: bottomColors, icon: Icons.movie_outlined, title: '添加观影', subtitle: '记录你看过的电影', @@ -224,6 +216,7 @@ class CustomBottomNavBar extends StatelessWidget { }, ), _buildAddOption( + colors: bottomColors, icon: Icons.menu_book_outlined, title: '添加阅读', subtitle: '记录你读过的书', @@ -243,6 +236,7 @@ class CustomBottomNavBar extends StatelessWidget { }, ), _buildAddOption( + colors: bottomColors, icon: Icons.note_outlined, title: '添加笔记', subtitle: '记录你的想法和笔记', @@ -259,8 +253,8 @@ class CustomBottomNavBar extends StatelessWidget { ); } - /// 构建新增选项 Widget _buildAddOption({ + required ColorScheme colors, required IconData icon, required String title, required String subtitle, @@ -276,13 +270,13 @@ class CustomBottomNavBar extends StatelessWidget { width: 44, height: 44, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10), ), child: Icon( icon, size: 22, - color: const Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), const SizedBox(width: 16), @@ -292,26 +286,26 @@ class CustomBottomNavBar extends StatelessWidget { children: [ Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, - color: Color(0xFF1A1A1A), + color: colors.onSurface, ), ), const SizedBox(height: 2), Text( subtitle, - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ], ), ), - const Icon( + Icon( Icons.chevron_right, - color: Color(0xFFCCCCCC), + color: colors.onSurface.withValues(alpha: 0.25), size: 20, ), ], diff --git a/lib/widgets/custom_drawer.dart b/lib/widgets/custom_drawer.dart index 7e8fb0c..4e83b28 100644 --- a/lib/widgets/custom_drawer.dart +++ b/lib/widgets/custom_drawer.dart @@ -33,36 +33,25 @@ class _CustomDrawerState extends State { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Drawer( - backgroundColor: const Color(0xFFF8F8F8), + backgroundColor: colors.surfaceContainerHigh, child: SafeArea( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 头像 + 统计(独立卡片区域) _buildProfileCard(context), - const SizedBox(height: 16), - - // 热力图 _buildCalendarSection(context), - const SizedBox(height: 16), - - // 最近添加 _buildRecentSection(context), - const SizedBox(height: 16), - - // 功能入口 _buildToolsCard(context), - - // 底部版本号 Padding( padding: const EdgeInsets.fromLTRB(20, 20, 20, 32), child: Center( - child: Text('v$_version', style: const TextStyle(fontSize: 11, color: Color(0xFFD0D0D0))), + child: Text('v$_version', style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.2))), ), ), ], @@ -77,6 +66,7 @@ class _CustomDrawerState extends State { Widget _buildProfileCard(BuildContext context) { return Consumer( builder: (context, provider, child) { + final colors = Theme.of(context).colorScheme; final userPrefs = UserPrefs(); final nickname = userPrefs.nickname; final motto = userPrefs.motto; @@ -89,77 +79,69 @@ class _CustomDrawerState extends State { margin: const EdgeInsets.fromLTRB(16, 16, 16, 0), padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 头像 + 名称/座右铭 + 统计 Row( children: [ - // 头像 Container( width: 52, height: 52, decoration: BoxDecoration( shape: BoxShape.circle, - color: const Color(0xFFF5F5F5), - border: Border.all(color: const Color(0xFFEEEEEE), width: 0.5), + color: colors.surfaceContainerHighest, + border: Border.all(color: colors.outlineVariant, width: 0.5), ), clipBehavior: Clip.antiAlias, child: avatarPath != null && avatarPath.isNotEmpty ? Image.file(File(avatarPath), fit: BoxFit.cover, errorBuilder: (_, __, ___) => - const Icon(Icons.person_outline, size: 26, color: Color(0xFFBBBBBB))) - : const Icon(Icons.person_outline, size: 26, color: Color(0xFFBBBBBB)), + Icon(Icons.person_outline, size: 26, color: colors.onSurface.withValues(alpha: 0.3))) + : Icon(Icons.person_outline, size: 26, color: colors.onSurface.withValues(alpha: 0.3)), ), const SizedBox(width: 14), - // 名称 + 座右铭 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text(nickname, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text(nickname, style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)), const SizedBox(height: 2), Text(motto, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12, color: Color(0xFFAAAAAA))), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.35))), ], ), ), ], ), - const SizedBox(height: 16), - const Divider(height: 1, color: Color(0xFFF0F0F0)), + Divider(height: 1, color: colors.outlineVariant), const SizedBox(height: 14), - - // 统计数字 _buildProfileStatRow(Icons.movie_outlined, movieCount, '观影'), const SizedBox(height: 12), _buildProfileStatRow(Icons.menu_book_outlined, bookCount, '阅读'), const SizedBox(height: 12), _buildProfileStatRow(Icons.note_outlined, noteCount, '笔记'), - - // 设置入口 const SizedBox(height: 14), - const Divider(height: 1, color: Color(0xFFF0F0F0)), + Divider(height: 1, color: colors.outlineVariant), InkWell( onTap: () { Navigator.pop(context); Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsPage())); }, borderRadius: BorderRadius.circular(8), - child: const Padding( - padding: EdgeInsets.only(top: 14), + child: Padding( + padding: const EdgeInsets.only(top: 14), child: Row( children: [ - Icon(Icons.settings_outlined, size: 16, color: Color(0xFF999999)), - SizedBox(width: 8), - Text('设置', style: TextStyle(fontSize: 13, color: Color(0xFF888888))), - Spacer(), - Icon(Icons.chevron_right, size: 14, color: Color(0xFFD0D0D0)), + Icon(Icons.settings_outlined, size: 16, color: colors.onSurface.withValues(alpha: 0.4)), + const SizedBox(width: 8), + Text('设置', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.5))), + const Spacer(), + Icon(Icons.chevron_right, size: 14, color: colors.onSurface.withValues(alpha: 0.2)), ], ), ), @@ -172,13 +154,14 @@ class _CustomDrawerState extends State { } Widget _buildProfileStatRow(IconData icon, int count, String label) { + final colors = Theme.of(context).colorScheme; return Row( children: [ - Icon(icon, size: 16, color: const Color(0xFF888888)), + Icon(icon, size: 16, color: colors.onSurface.withValues(alpha: 0.5)), const SizedBox(width: 10), - Text(label, style: const TextStyle(fontSize: 13, color: Color(0xFF888888))), + Text(label, style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.5))), const Spacer(), - Text(_formatCount(count), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + Text(_formatCount(count), style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface)), ], ); } @@ -192,10 +175,11 @@ class _CustomDrawerState extends State { // ─── 功能入口卡片 ──────────────────────────────────────────────────── Widget _buildToolsCard(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(16), ), child: Column( @@ -204,12 +188,12 @@ class _CustomDrawerState extends State { Navigator.pop(context); Navigator.push(context, MaterialPageRoute(builder: (_) => const StrollPage())); }, topRounded: true), - const Divider(height: 1, indent: 52, endIndent: 20, color: Color(0xFFF0F0F0)), + Divider(height: 1, indent: 52, endIndent: 20, color: colors.outlineVariant), _buildToolItem(Icons.label_outline, '标签管理', () { Navigator.pop(context); Navigator.push(context, MaterialPageRoute(builder: (_) => const TagManagementPage())); }), - const Divider(height: 1, indent: 52, endIndent: 20, color: Color(0xFFF0F0F0)), + Divider(height: 1, indent: 52, endIndent: 20, color: colors.outlineVariant), _buildToolItem(Icons.description_outlined, 'MD阅读', () { Navigator.pop(context); Navigator.push(context, MaterialPageRoute(builder: (_) => const MdReaderTabPage())); @@ -220,6 +204,7 @@ class _CustomDrawerState extends State { } Widget _buildToolItem(IconData icon, String title, VoidCallback onTap, {bool topRounded = false, bool bottomRounded = false}) { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: onTap, borderRadius: BorderRadius.only( @@ -232,21 +217,22 @@ class _CustomDrawerState extends State { padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 20), child: Row( children: [ - Icon(icon, size: 20, color: const Color(0xFF555555)), + Icon(icon, size: 20, color: colors.onSurface.withValues(alpha: 0.7)), const SizedBox(width: 12), - Expanded(child: Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A)))), - const Icon(Icons.chevron_right, size: 16, color: Color(0xFFD0D0D0)), + Expanded(child: Text(title, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface))), + Icon(Icons.chevron_right, size: 16, color: colors.onSurface.withValues(alpha: 0.2)), ], ), ), ); } - // ─── 热力图(保持现状) ────────────────────────────────────────────── + // ─── 热力图 ────────────────────────────────────────────────────────── Widget _buildCalendarSection(BuildContext context) { return Consumer( builder: (context, provider, child) { + final colors = Theme.of(context).colorScheme; final Map dailyCounts = {}; for (final movie in provider.movies.where((m) => !m.isDeleted)) { final date = DateTime(movie.createdAt.year, movie.createdAt.month, movie.createdAt.day); @@ -299,15 +285,15 @@ class _CustomDrawerState extends State { return Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(18), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), + decoration: BoxDecoration(color: colors.surface, borderRadius: BorderRadius.circular(16)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Row( + Row( children: [ - Icon(Icons.calendar_today, size: 14, color: Color(0xFF999999)), - SizedBox(width: 8), - Text('热力图', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: Color(0xFF666666))), + Icon(Icons.calendar_today, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), + const SizedBox(width: 8), + Text('热力图', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.6))), ], ), const SizedBox(height: 14), @@ -324,7 +310,7 @@ class _CustomDrawerState extends State { final label = keepWeeks.contains(week) ? monthLabels[week] : null; return SizedBox( width: cellSize + cellGap, - child: label != null ? Text(label, style: const TextStyle(fontSize: 9, color: Color(0xFFBBBBBB))) : null, + child: label != null ? Text(label, style: TextStyle(fontSize: 9, color: colors.onSurface.withValues(alpha: 0.3))) : null, ); }), ), @@ -347,7 +333,7 @@ class _CustomDrawerState extends State { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - const Text('少', style: TextStyle(fontSize: 9, color: Color(0xFFBBBBBB))), + Text('少', style: TextStyle(fontSize: 9, color: colors.onSurface.withValues(alpha: 0.3))), const SizedBox(width: 3), _legendCell(const Color(0xFFF0F0F0)), _legendCell(const Color(0xFFC8E6C9)), @@ -355,7 +341,7 @@ class _CustomDrawerState extends State { _legendCell(const Color(0xFF2E7D32)), _legendCell(const Color(0xFF1B5E20)), const SizedBox(width: 3), - const Text('多', style: TextStyle(fontSize: 9, color: Color(0xFFBBBBBB))), + Text('多', style: TextStyle(fontSize: 9, color: colors.onSurface.withValues(alpha: 0.3))), ], ), ], @@ -384,21 +370,22 @@ class _CustomDrawerState extends State { Widget _buildRecentSection(BuildContext context) { return Consumer( builder: (context, provider, child) { + final colors = Theme.of(context).colorScheme; final recent = _getRecentItems(provider); if (recent.isEmpty) return const SizedBox.shrink(); return Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(18), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), + decoration: BoxDecoration(color: colors.surface, borderRadius: BorderRadius.circular(16)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Row( + Row( children: [ - Icon(Icons.schedule, size: 14, color: Color(0xFF999999)), - SizedBox(width: 8), - Text('最近添加', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: Color(0xFF666666))), + Icon(Icons.schedule, size: 14, color: colors.onSurface.withValues(alpha: 0.4)), + const SizedBox(width: 8), + Text('最近添加', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.6))), ], ), const SizedBox(height: 14), @@ -408,15 +395,15 @@ class _CustomDrawerState extends State { children: [ Icon( item.type == 'movie' ? Icons.movie_outlined : item.type == 'book' ? Icons.menu_book_outlined : Icons.note_outlined, - size: 14, color: const Color(0xFFBBBBBB), + size: 14, color: colors.onSurface.withValues(alpha: 0.3), ), const SizedBox(width: 10), Expanded( child: Text(item.title, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 13, color: Color(0xFF444444))), + style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.75))), ), const SizedBox(width: 8), - Text(_recentTimeAgo(item.date), style: const TextStyle(fontSize: 10, color: Color(0xFFCCCCCC))), + Text(_recentTimeAgo(item.date), style: TextStyle(fontSize: 10, color: colors.onSurface.withValues(alpha: 0.25))), ], ), )), diff --git a/lib/widgets/fade_in_local_image.dart b/lib/widgets/fade_in_local_image.dart index c28a33d..b6f8c1b 100644 --- a/lib/widgets/fade_in_local_image.dart +++ b/lib/widgets/fade_in_local_image.dart @@ -51,7 +51,6 @@ class _FadeInLocalImageState extends State return; } - // 如果是 http 开头,直接当网络图片 if (widget.path!.startsWith('http')) { _useNetwork = true; _imageUrl = widget.path; @@ -60,7 +59,6 @@ class _FadeInLocalImageState extends State return; } - // 本地文件存在就直接显示 final file = File(widget.path!); if (file.existsSync()) { setState(() => _loaded = true); @@ -68,7 +66,6 @@ class _FadeInLocalImageState extends State return; } - // 本地不存在,尝试服务端 URL if (ServerDataService.isActive) { try { final url = await ServerDataService.toImageUrl(widget.path!); @@ -105,13 +102,14 @@ class _FadeInLocalImageState extends State @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; if (_error) { return widget.errorWidget ?? Container( width: widget.width, height: widget.height, - color: const Color(0xFFF5F5F5), - child: const Icon(Icons.broken_image_outlined, size: 24, color: Color(0xFFCCCCCC)), + color: colors.surfaceContainerHighest, + child: Icon(Icons.broken_image_outlined, size: 24, color: colors.onSurface.withValues(alpha: 0.25)), ); } if (!_loaded) { @@ -119,7 +117,7 @@ class _FadeInLocalImageState extends State Container( width: widget.width, height: widget.height, - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, ); } return FadeTransition( @@ -136,8 +134,8 @@ class _FadeInLocalImageState extends State Container( width: widget.width, height: widget.height, - color: const Color(0xFFF5F5F5), - child: const Icon(Icons.broken_image_outlined, size: 24, color: Color(0xFFCCCCCC)), + color: colors.surfaceContainerHighest, + child: Icon(Icons.broken_image_outlined, size: 24, color: colors.onSurface.withValues(alpha: 0.25)), ); }, ) @@ -150,8 +148,8 @@ class _FadeInLocalImageState extends State Container( width: widget.width, height: widget.height, - color: const Color(0xFFF5F5F5), - child: const Icon(Icons.broken_image_outlined, size: 24, color: Color(0xFFCCCCCC)), + color: colors.surfaceContainerHighest, + child: Icon(Icons.broken_image_outlined, size: 24, color: colors.onSurface.withValues(alpha: 0.25)), ), ), ); diff --git a/lib/widgets/movie_list_item.dart b/lib/widgets/movie_list_item.dart index f42591e..ab4963d 100644 --- a/lib/widgets/movie_list_item.dart +++ b/lib/widgets/movie_list_item.dart @@ -15,6 +15,7 @@ class MovieListItem extends StatelessWidget { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: () { Navigator.pushNamed(context, '/movie-detail', arguments: movie); @@ -23,28 +24,21 @@ class MovieListItem extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 海报 Expanded( - child: _buildPoster(), + child: _buildPoster(colors), ), - const SizedBox(height: 8), - - // 影视名称 Text( movie.title, - style: const TextStyle( + style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, - color: Color(0xFF1A1A1A), + color: Theme.of(context).colorScheme.onSurface, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 4), - - // 评分 if (movie.rating != null) AnimatedStarRating(rating: movie.rating!, starSize: 12, showNumber: true) else @@ -53,45 +47,45 @@ class MovieListItem extends StatelessWidget { ), ); } - - /// 构建海报 - Widget _buildPoster() { + + Widget _buildPoster(ColorScheme colors) { return Container( width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFE5E5E5), width: 0.5), + border: Border.all(color: colors.outline, width: 0.5), ), clipBehavior: Clip.antiAlias, child: FadeInLocalImage( path: movie.posterPath, fit: BoxFit.cover, - placeholder: const Center(child: Icon(Icons.movie_outlined, size: 24, color: Color(0xFFCCCCCC))), - errorWidget: const Center(child: Icon(Icons.movie_outlined, size: 24, color: Color(0xFFCCCCCC))), + placeholder: Center(child: Icon(Icons.movie_outlined, size: 24, color: colors.onSurface.withValues(alpha: 0.25))), + errorWidget: Center(child: Icon(Icons.movie_outlined, size: 24, color: colors.onSurface.withValues(alpha: 0.25))), ), ); } - - /// 显示删除确认对话框 + void _showDeleteDialog(BuildContext context) { + final colors = Theme.of(context).colorScheme; showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text( + title: Text( '确认删除', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, + color: colors.onSurface, ), ), content: Text( '确定要删除《${movie.title}》吗?删除后可在回收站恢复。', - style: const TextStyle( + style: TextStyle( fontSize: 14, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.5, ), ), @@ -99,7 +93,7 @@ class MovieListItem extends StatelessWidget { TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( - foregroundColor: const Color(0xFF666666), + foregroundColor: colors.onSurface.withValues(alpha: 0.6), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), child: const Text('取消'), @@ -111,8 +105,8 @@ class MovieListItem extends StatelessWidget { ToastUtil.show(context, '已删除'); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + backgroundColor: colors.error, + foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), diff --git a/lib/widgets/movie_status_bar.dart b/lib/widgets/movie_status_bar.dart index 7b7b72b..55c9573 100644 --- a/lib/widgets/movie_status_bar.dart +++ b/lib/widgets/movie_status_bar.dart @@ -8,20 +8,21 @@ class MovieStatusBar extends StatelessWidget { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return Consumer( builder: (context, provider, child) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: const BoxDecoration( - color: Colors.white, + decoration: BoxDecoration( + color: colors.surface, border: Border( - bottom: BorderSide(color: Color(0xFFE5E5E5), width: 0.5), + bottom: BorderSide(color: colors.outline, width: 0.5), ), ), child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(24), ), child: LayoutBuilder( @@ -42,7 +43,7 @@ class MovieStatusBar extends StatelessWidget { padding: const EdgeInsets.all(3), child: Container( decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: colors.primary, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( @@ -58,18 +59,21 @@ class MovieStatusBar extends StatelessWidget { Row( children: [ _buildTab( + colors: colors, label: '已看', icon: Icons.check_circle_outline, isSelected: provider.movieStatusIndex == 0, onTap: () => provider.setMovieStatusIndex(0), ), _buildTab( + colors: colors, label: '在看', icon: Icons.play_circle_outline, isSelected: provider.movieStatusIndex == 1, onTap: () => provider.setMovieStatusIndex(1), ), _buildTab( + colors: colors, label: '想看', icon: Icons.bookmark_outline, isSelected: provider.movieStatusIndex == 2, @@ -89,6 +93,7 @@ class MovieStatusBar extends StatelessWidget { } Widget _buildTab({ + required ColorScheme colors, required String label, required IconData icon, required bool isSelected, @@ -108,7 +113,7 @@ class MovieStatusBar extends StatelessWidget { Icon( icon, size: 16, - color: isSelected ? Colors.white : const Color(0xFF666666), + color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6), ), const SizedBox(width: 6), Text( @@ -116,7 +121,7 @@ class MovieStatusBar extends StatelessWidget { style: TextStyle( fontSize: 13, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, - color: isSelected ? Colors.white : const Color(0xFF666666), + color: isSelected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6), ), ), ], diff --git a/lib/widgets/note_list_item.dart b/lib/widgets/note_list_item.dart index 2954bdb..6e2677b 100644 --- a/lib/widgets/note_list_item.dart +++ b/lib/widgets/note_list_item.dart @@ -14,14 +14,12 @@ class NoteListItem extends StatelessWidget { @override Widget build(BuildContext context) { - // 使用 RepaintBoundary 减少重绘 return RepaintBoundary( child: _NoteListItemContent(note: note), ); } } -/// 笔记列表项内容 - 分离出来便于优化 class _NoteListItemContent extends StatelessWidget { final Note note; @@ -29,10 +27,10 @@ class _NoteListItemContent extends StatelessWidget { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return InkWell( onTap: () { Navigator.pushNamed(context, '/note-detail', arguments: note).then((_) async { - // 返回时刷新笔记列表 await context.read().loadNotes(); }); }, @@ -41,40 +39,37 @@ class _NoteListItemContent extends StatelessWidget { margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.fromLTRB(12, 10, 12, 10), decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - // 顶部:时间 + MD标记 Row( children: [ - // 时间 Text( _formatDateCached(note.updatedAt), - style: const TextStyle( + style: TextStyle( fontSize: 11, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), const SizedBox(width: 8), - // MD标记 Container( padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(3), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), - child: const Text( + child: Text( 'MD', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w600, - color: Color(0xFF999999), + color: colors.onSurface.withValues(alpha: 0.4), ), ), ), @@ -83,14 +78,13 @@ class _NoteListItemContent extends StatelessWidget { const SizedBox(height: 6), - // 标题 if (note.title.isNotEmpty) ...[ Text( note.title, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: Color(0xFF1A1A1A), + color: colors.onSurface, height: 1.4, ), maxLines: 1, @@ -99,20 +93,18 @@ class _NoteListItemContent extends StatelessWidget { const SizedBox(height: 4), ], - // 内容摘要(去除Markdown标记),内容为空则不显示 if (_collapseBlankLines(_cleanMarkdown(note.content).trim()).isNotEmpty) Text( _collapseBlankLines(_cleanMarkdown(note.content).trim()), - style: const TextStyle( + style: TextStyle( fontSize: 13, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.5, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), - // 底部标签 if (note.tags.isNotEmpty) ...[ const SizedBox(height: 6), Wrap( @@ -122,15 +114,15 @@ class _NoteListItemContent extends StatelessWidget { return Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( - color: Colors.white, + color: colors.surface, borderRadius: BorderRadius.circular(4), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), child: Text( tag, - style: const TextStyle( + style: TextStyle( fontSize: 11, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), ), ), ); @@ -138,10 +130,9 @@ class _NoteListItemContent extends StatelessWidget { ), ], - // 图片预览 if (note.images.isNotEmpty) ...[ const SizedBox(height: 8), - _buildImagePreviewRow(), + _buildImagePreviewRow(colors), ], ], ), @@ -149,8 +140,7 @@ class _NoteListItemContent extends StatelessWidget { ); } - /// 图片预览行 - Widget _buildImagePreviewRow() { + Widget _buildImagePreviewRow(ColorScheme colors) { final images = note.images; final count = images.length.clamp(0, 3); return Row( @@ -162,14 +152,14 @@ class _NoteListItemContent extends StatelessWidget { margin: const EdgeInsets.only(right: 6), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), - border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), + border: Border.all(color: colors.outlineVariant, width: 0.5), ), clipBehavior: Clip.antiAlias, child: FadeInLocalImage( path: images[i], fit: BoxFit.cover, errorWidget: Container( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, ), ), ), @@ -178,13 +168,13 @@ class _NoteListItemContent extends StatelessWidget { width: 48, height: 48, decoration: BoxDecoration( - color: const Color(0xFFF5F5F5), + color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(6), ), child: Center( child: Text( '+${images.length - 3}', - style: const TextStyle(fontSize: 12, color: Color(0xFF999999), fontWeight: FontWeight.w500), + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.4), fontWeight: FontWeight.w500), ), ), ), @@ -192,45 +182,44 @@ class _NoteListItemContent extends StatelessWidget { ); } - /// 清理 Markdown 标记,提取纯文本 String _cleanMarkdown(String text) { return text - .replaceAll(RegExp(r'^#+\s+', multiLine: true), '') // 标题 - .replaceAll(RegExp(r'\*\*(.+?)\*\*'), r'$1') // 粗体 - .replaceAll(RegExp(r'\*(.+?)\*'), r'$1') // 斜体 - .replaceAll(RegExp(r'`(.+?)`'), r'$1') // 行内代码 - .replaceAll(RegExp(r'^\s*[-*+]\s', multiLine: true), '') // 列表 - .replaceAll(RegExp(r'^\s*>\s', multiLine: true), '') // 引用 - .replaceAll(RegExp(r'\[([^\]]+)\]\([^)]+\)'), r'$1') // 链接 - .replaceAll(RegExp(r'!\[([^\]]*)\]\([^)]+\)'), '') // 图片 + .replaceAll(RegExp(r'^#+\s+', multiLine: true), '') + .replaceAll(RegExp(r'\*\*(.+?)\*\*'), r'$1') + .replaceAll(RegExp(r'\*(.+?)\*'), r'$1') + .replaceAll(RegExp(r'`(.+?)`'), r'$1') + .replaceAll(RegExp(r'^\s*[-*+]\s', multiLine: true), '') + .replaceAll(RegExp(r'^\s*>\s', multiLine: true), '') + .replaceAll(RegExp(r'\[([^\]]+)\]\([^)]+\)'), r'$1') + .replaceAll(RegExp(r'!\[([^\]]*)\]\([^)]+\)'), '') .trim(); } - /// 合并连续空行为单行 String _collapseBlankLines(String text) { return text.replaceAll(RegExp(r'\n\s*\n+'), '\n'); } - /// 显示删除确认对话框 void _showDeleteDialog(BuildContext context) { + final colors = Theme.of(context).colorScheme; showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Colors.white, + backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Text( + title: Text( '确认删除', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, + color: colors.onSurface, ), ), - content: const Text( + content: Text( '确定要删除这条笔记吗?删除后可在回收站恢复。', style: TextStyle( fontSize: 14, - color: Color(0xFF666666), + color: colors.onSurface.withValues(alpha: 0.6), height: 1.5, ), ), @@ -238,7 +227,7 @@ class _NoteListItemContent extends StatelessWidget { TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( - foregroundColor: const Color(0xFF666666), + foregroundColor: colors.onSurface.withValues(alpha: 0.6), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), child: const Text('取消'), @@ -250,8 +239,8 @@ class _NoteListItemContent extends StatelessWidget { ToastUtil.show(context, '已删除'); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + backgroundColor: colors.error, + foregroundColor: colors.onError, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -267,28 +256,24 @@ class _NoteListItemContent extends StatelessWidget { } } -// 日期格式化缓存 final Map _dateFormatCache = {}; -/// 格式化日期(带缓存) String _formatDateCached(DateTime date) { - // 使用日期部分作为缓存键(忽略时分秒) final cacheKey = DateTime(date.year, date.month, date.day); - + if (_dateFormatCache.containsKey(cacheKey)) { return _dateFormatCache[cacheKey]!; } - + final result = _formatDate(date); _dateFormatCache[cacheKey] = result; return result; } -/// 格式化日期 String _formatDate(DateTime date) { final now = DateTime.now(); final difference = now.difference(date); - + if (difference.inDays == 0) { if (difference.inHours == 0) { if (difference.inMinutes == 0) { diff --git a/lib/widgets/shimmer_skeleton.dart b/lib/widgets/shimmer_skeleton.dart index 59aff11..91f28ed 100644 --- a/lib/widgets/shimmer_skeleton.dart +++ b/lib/widgets/shimmer_skeleton.dart @@ -42,6 +42,7 @@ class _ShimmerSkeletonState extends State @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return AnimatedBuilder( animation: _animation, builder: (context, child) { @@ -49,7 +50,7 @@ class _ShimmerSkeletonState extends State width: widget.width, height: widget.height, decoration: BoxDecoration( - color: const Color(0xFFE0E0E0).withValues(alpha: _animation.value), + color: colors.surfaceContainerHighest.withValues(alpha: _animation.value), borderRadius: BorderRadius.circular(widget.borderRadius), ), ); @@ -134,6 +135,7 @@ class NoteSkeletonList extends StatelessWidget { @override Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; return ListView.builder( padding: const EdgeInsets.fromLTRB(12, 10, 12, 100), itemCount: 6, @@ -141,7 +143,7 @@ class NoteSkeletonList extends StatelessWidget { margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFFF8F8F8), + color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(8), ), child: const Column(