diff --git a/lib/main.dart b/lib/main.dart index 211f117..044b144 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,6 +22,8 @@ import 'services/usage_stats_service.dart'; import 'services/sync/backup_service.dart'; import 'providers/app_provider.dart'; import 'widgets/app_shell.dart'; +import 'widgets/frosted_background.dart'; +import './widgets/app_overlay.dart'; final RouteObserver> routeObserver = RouteObserver>(); @@ -234,7 +236,7 @@ class _MyAppState extends State with WidgetsBindingObserver { void _showUpdateDialog(BuildContext context, String version, List features, String localVersion) { if (!context.mounted) return; final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -381,12 +383,15 @@ class _MyAppState extends State with WidgetsBindingObserver { return Consumer( builder: (context, provider, _) { AppTheme.setFontFamily(provider.fontFamily); + final isFrosted = provider.frostedActive; final light = AppTheme.getLightTheme( provider.colorSchemeIndex, monetColor: monetColor, ); ThemeData dark; - if (provider.colorSchemeIndex == -1 && monetColor != null) { + if (isFrosted) { + dark = AppTheme.frostedTheme; + } else if (provider.colorSchemeIndex == -1 && monetColor != null) { final scheme = ColorScheme.fromSeed( seedColor: monetColor, brightness: Brightness.dark, @@ -419,9 +424,19 @@ class _MyAppState extends State with WidgetsBindingObserver { debugShowCheckedModeBanner: false, theme: light, darkTheme: dark, - themeMode: provider.themeMode, + themeMode: isFrosted ? ThemeMode.dark : provider.themeMode, builder: (ctx, nav) { Widget shell = AppShell(child: nav!); + // 毛玻璃主题:全局背景层(模糊封面 + 深色遮罩)垫底 + if (isFrosted) { + shell = Stack( + fit: StackFit.expand, + children: [ + FrostedBackground(coverPath: provider.frostedCoverPath), + shell, + ], + ); + } // Android: 点击空白区域收起键盘 if (Platform.isAndroid) { shell = GestureDetector( diff --git a/lib/pages/book/book_add_page.dart b/lib/pages/book/book_add_page.dart index 39cbdba..7cc08c6 100644 --- a/lib/pages/book/book_add_page.dart +++ b/lib/pages/book/book_add_page.dart @@ -13,6 +13,7 @@ import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; import '../../utils/image_path_helper.dart'; import '../../widgets/genre_selector_page.dart'; +import '../../widgets/app_overlay.dart'; class BookAddPage extends StatefulWidget { final VoidCallback? onCancel; @@ -271,7 +272,7 @@ class _BookAddPageState extends State { void _showCoverOptions() { final c = Theme.of(context).colorScheme; - showModalBottomSheet(context: context, backgroundColor: c.surface, + appModalBottomSheet(context: context, backgroundColor: c.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), builder: (ctx) => SafeArea(child: Padding(padding: const EdgeInsets.symmetric(vertical: 16), child: Column(mainAxisSize: MainAxisSize.min, children: [ @@ -301,7 +302,7 @@ class _BookAddPageState extends State { Future _pickCoverFromUrl() async { final ctrl = TextEditingController(); - final ok = await showDialog(context: context, builder: (ctx) { + final ok = await appDialog(context: context, builder: (ctx) { final c = Theme.of(ctx).colorScheme; return AlertDialog(backgroundColor: c.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Text('添加网络图片', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: c.onSurface)), diff --git a/lib/pages/book/book_detail_page.dart b/lib/pages/book/book_detail_page.dart index 8999a2c..7788d54 100644 --- a/lib/pages/book/book_detail_page.dart +++ b/lib/pages/book/book_detail_page.dart @@ -25,6 +25,7 @@ import '../character/character_list_page.dart'; import '../../data/epub/reader_dao.dart'; import '../epub_reader/epub_highlights_page.dart'; import '../epub_reader/reader_screen.dart'; +import '../../widgets/app_overlay.dart'; /// 书籍详情页 - 极简主义设计 class BookDetailPage extends StatefulWidget { @@ -627,7 +628,7 @@ class _BookDetailPageState extends State { void _showEditCoverOptions() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet(context: context, backgroundColor: colors.surface, + appModalBottomSheet(context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), builder: (ctx) => SafeArea(child: Padding(padding: const EdgeInsets.symmetric(vertical: 16), child: Column(mainAxisSize: MainAxisSize.min, children: [ @@ -661,7 +662,7 @@ class _BookDetailPageState extends State { Future _pickEditCoverFromUrl() async { final urlCtrl = TextEditingController(); - final confirmed = await showDialog(context: context, builder: (ctx) { + final confirmed = await appDialog(context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; return AlertDialog(backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), @@ -1312,7 +1313,7 @@ class _BookDetailPageState extends State { const names = ['默认样式', '毛玻璃层叠']; const icons = [Icons.article_outlined, Icons.blur_on_outlined]; const subtitles = ['标准封面顶部布局', '封面背景 + 毛玻璃卡片']; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -2173,7 +2174,7 @@ class _BookDetailPageState extends State { void _showDeleteDialog(BuildContext context) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (context) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/book/book_excerpts_page.dart b/lib/pages/book/book_excerpts_page.dart index 361c915..3a65703 100644 --- a/lib/pages/book/book_excerpts_page.dart +++ b/lib/pages/book/book_excerpts_page.dart @@ -7,6 +7,7 @@ import '../../utils/toast_util.dart'; import '../../data/epub/reader_dao.dart'; import '../../widgets/fade_in_local_image.dart'; import 'book_excerpt_form_page.dart'; +import '../../widgets/app_overlay.dart'; /// 书籍摘抄列表页面 class BookExcerptsPage extends StatefulWidget { @@ -363,7 +364,7 @@ class _BookExcerptsPageState extends State { Future _showDeleteDialog(BookExcerpt excerpt) { final colors = Theme.of(context).colorScheme; - return showDialog( + return appDialog( context: context, builder: (context) { return AlertDialog( @@ -407,7 +408,7 @@ class _BookExcerptsPageState extends State { void _showDeleteAllDialog() { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (context) { return AlertDialog( @@ -449,7 +450,7 @@ class _BookExcerptsPageState extends State { void _showImportSheet() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, @@ -532,7 +533,7 @@ class _BookExcerptsPageState extends State { void _showWechatReadImport() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, diff --git a/lib/pages/book/book_form_page.dart b/lib/pages/book/book_form_page.dart index cfa58f4..8cda961 100644 --- a/lib/pages/book/book_form_page.dart +++ b/lib/pages/book/book_form_page.dart @@ -15,6 +15,7 @@ import '../../utils/image_path_helper.dart'; import '../../widgets/genre_selector_page.dart'; import '../../widgets/text_input_panel.dart'; import '../../widgets/alternate_titles_dialog.dart'; +import '../../widgets/app_overlay.dart'; /// 从多值字段列表中提取去重排序的唯一值(供 compute 使用) List _collectUnique(List> lists) { @@ -394,7 +395,7 @@ class _BookFormPageState extends State { Future _pickCover() async { final colors = Theme.of(context).colorScheme; - await showModalBottomSheet( + await appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -447,7 +448,7 @@ class _BookFormPageState extends State { Future _pickCoverFromUrl() async { final urlController = TextEditingController(); - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) { final c = Theme.of(ctx).colorScheme; @@ -567,7 +568,7 @@ class _BookFormPageState extends State { /// 编辑书名(弹窗) Future _editTitle() async { final controller = TextEditingController(text: _titleController.text); - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -615,7 +616,7 @@ class _BookFormPageState extends State { /// 编辑别名(弹窗 + 标签式输入) Future _editAlternateTitles() async { - final result = await showDialog>( + final result = await appDialog>( context: context, builder: (ctx) => AlternateTitlesDialog(initial: _alternateTitles), ); @@ -643,7 +644,7 @@ class _BookFormPageState extends State { Future _editReadCount() async { final controller = TextEditingController(text: _readCount > 0 ? '$_readCount' : ''); - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('阅读次数'), @@ -689,7 +690,7 @@ class _BookFormPageState extends State { Future _confirmLeave() async { if (!_hasContent()) return true; final colors = Theme.of(context).colorScheme; - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), diff --git a/lib/pages/book/book_review_detail_page.dart b/lib/pages/book/book_review_detail_page.dart index 9935b14..9e1a301 100644 --- a/lib/pages/book/book_review_detail_page.dart +++ b/lib/pages/book/book_review_detail_page.dart @@ -4,6 +4,7 @@ import '../../models/data_models.dart'; import '../../providers/app_provider.dart'; import '../../utils/toast_util.dart'; import 'book_review_form_page.dart'; +import '../../widgets/app_overlay.dart'; /// 书评详情页 class BookReviewDetailPage extends StatefulWidget { @@ -174,7 +175,7 @@ class _BookReviewDetailPageState extends State { Future _deleteReview() async { final colors = Theme.of(context).colorScheme; - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/book/book_reviews_page.dart b/lib/pages/book/book_reviews_page.dart index 413780d..e46cb48 100644 --- a/lib/pages/book/book_reviews_page.dart +++ b/lib/pages/book/book_reviews_page.dart @@ -6,6 +6,7 @@ import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; import 'book_review_form_page.dart'; import 'book_review_detail_page.dart'; +import '../../widgets/app_overlay.dart'; /// 书籍书评列表页面 class BookReviewsPage extends StatefulWidget { @@ -286,7 +287,7 @@ class _BookReviewsPageState extends State { } void _showDeleteDialog(BookReview review) { - showDialog( + appDialog( context: context, builder: (context) { final colors = Theme.of(context).colorScheme; diff --git a/lib/pages/book/book_tab_page.dart b/lib/pages/book/book_tab_page.dart index e675d91..d9aa31a 100644 --- a/lib/pages/book/book_tab_page.dart +++ b/lib/pages/book/book_tab_page.dart @@ -14,6 +14,7 @@ import '../../widgets/master_detail_scaffold.dart'; import '../../widgets/detail_placeholder.dart'; import 'book_detail_page.dart'; import 'book_add_page.dart'; +import '../../widgets/app_overlay.dart'; /// 状态索引 → 状态值 const _bookStatusMap = {0: 'read', 1: 'reading', 2: 'want_to_read', 3: 'abandoned'}; @@ -412,7 +413,7 @@ class _BookTabViewState extends State<_BookTabView> void _showDeleteDialog(BuildContext context, Book book) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, diff --git a/lib/pages/character/character_form_page.dart b/lib/pages/character/character_form_page.dart index a92a0ce..1a79920 100644 --- a/lib/pages/character/character_form_page.dart +++ b/lib/pages/character/character_form_page.dart @@ -11,6 +11,7 @@ import '../../utils/image_path_helper.dart'; import '../../utils/toast_util.dart'; import '../../widgets/fade_in_local_image.dart'; import '../../widgets/genre_selector_page.dart'; +import '../../widgets/app_overlay.dart'; /// 角色编辑/添加页面 /// @@ -220,7 +221,7 @@ class _CharacterFormPageState extends State { void _showImageOptions() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -277,7 +278,7 @@ class _CharacterFormPageState extends State { Future _pickImageFromUrl() async { String? url; - final confirmed = await showDialog(context: context, builder: (ctx) { + final confirmed = await appDialog(context: context, builder: (ctx) { final urlCtrl = TextEditingController(); final colors = Theme.of(ctx).colorScheme; return AlertDialog( @@ -424,7 +425,7 @@ class _CharacterFormPageState extends State { Future _confirmLeave() async { if (!_hasContent()) return true; final colors = Theme.of(context).colorScheme; - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, diff --git a/lib/pages/character/character_list_page.dart b/lib/pages/character/character_list_page.dart index 8b5a7f1..c0ce7ac 100644 --- a/lib/pages/character/character_list_page.dart +++ b/lib/pages/character/character_list_page.dart @@ -5,6 +5,7 @@ import '../../providers/app_provider.dart'; import '../../utils/toast_util.dart'; import '../../widgets/fade_in_local_image.dart'; import 'character_form_page.dart'; +import '../../widgets/app_overlay.dart'; /// 影视角色列表页 class MovieCharactersPage extends StatefulWidget { @@ -358,7 +359,7 @@ class _CharacterTile extends StatelessWidget { void _showDeleteDialog(BuildContext context) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/epub_reader/control_panel.dart b/lib/pages/epub_reader/control_panel.dart index 6397c7f..05be79d 100644 --- a/lib/pages/epub_reader/control_panel.dart +++ b/lib/pages/epub_reader/control_panel.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'reader_style_sheet.dart'; +import '../../widgets/app_overlay.dart'; class ControlPanel extends StatefulWidget { final bool showControls; @@ -217,7 +218,7 @@ class _ControlPanelState extends State { void _openStyleSheet() { widget.onToggleStyleDrawer(); - showModalBottomSheet( + appModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, diff --git a/lib/pages/epub_reader/epub_detail_page.dart b/lib/pages/epub_reader/epub_detail_page.dart index 8205aa1..f0dc1a7 100644 --- a/lib/pages/epub_reader/epub_detail_page.dart +++ b/lib/pages/epub_reader/epub_detail_page.dart @@ -17,6 +17,7 @@ import 'epub_edit_page.dart'; import 'epub_highlights_page.dart'; import 'highlight_detail_sheet.dart'; import 'reader_screen.dart'; +import '../../widgets/app_overlay.dart'; /// EPUB 书籍详情页 class EpubDetailPage extends StatefulWidget { @@ -730,7 +731,7 @@ class _EpubDetailPageState extends State { /// 删除摘抄(同步删除蓝色高亮) Future _deleteExcerpt(BookExcerpt excerpt, ColorScheme colors) async { - final confirmed = showDialog( + final confirmed = appDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('删除摘抄', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), @@ -774,7 +775,7 @@ class _EpubDetailPageState extends State { } Future _deleteHighlight(int id, ColorScheme colors) async { - final confirmed = showDialog( + final confirmed = appDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('删除句读', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), @@ -873,7 +874,7 @@ class _EpubDetailPageState extends State { } void _showLinkedBookActions(ColorScheme colors, String title) { - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -914,7 +915,7 @@ class _EpubDetailPageState extends State { title: Text('取消关联', style: TextStyle(fontSize: 13, color: colors.error)), onTap: () { Navigator.pop(ctx); - showDialog( + appDialog( context: context, builder: (confirmCtx) => AlertDialog( title: const Text('取消关联', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), diff --git a/lib/pages/epub_reader/epub_highlights_page.dart b/lib/pages/epub_reader/epub_highlights_page.dart index d738e79..74e9d4d 100644 --- a/lib/pages/epub_reader/epub_highlights_page.dart +++ b/lib/pages/epub_reader/epub_highlights_page.dart @@ -6,6 +6,7 @@ import '../../utils/toast_util.dart'; import '../../utils/user_prefs.dart'; import 'highlight_detail_sheet.dart'; import 'reader_screen.dart'; +import '../../widgets/app_overlay.dart'; /// EPUB 句读(高亮)管理页面 —— 支持瀑布流/列表模式切换 class EpubHighlightsPage extends StatefulWidget { @@ -317,7 +318,7 @@ class _EpubHighlightsPageState extends State { } void _showDeleteConfirm(int id, ColorScheme colors) { - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/epub_reader/epub_library_page.dart b/lib/pages/epub_reader/epub_library_page.dart index 5b99d53..b0489a5 100644 --- a/lib/pages/epub_reader/epub_library_page.dart +++ b/lib/pages/epub_reader/epub_library_page.dart @@ -8,6 +8,7 @@ import '../../utils/toast_util.dart'; import '../../widgets/fade_in_local_image.dart'; import '../../widgets/shimmer_skeleton.dart'; import 'epub_detail_page.dart'; +import '../../widgets/app_overlay.dart'; /// EPUB 书架页面 class EpubLibraryPage extends StatefulWidget { @@ -88,7 +89,7 @@ class _EpubLibraryPageState extends State { } if (!mounted) return; - showDialog( + appDialog( context: context, barrierDismissible: false, builder: (_) => const Center(child: CircularProgressIndicator()), @@ -107,7 +108,7 @@ class _EpubLibraryPageState extends State { Future _deleteBook(Map book) async { final colors = Theme.of(context).colorScheme; - final confirm = await showDialog( + final confirm = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -166,7 +167,7 @@ class _EpubLibraryPageState extends State { (2, '按阅读进度排序', Icons.auto_stories_outlined), (3, '按书名排序', Icons.sort_by_alpha), ]; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), diff --git a/lib/pages/epub_reader/highlight_detail_sheet.dart b/lib/pages/epub_reader/highlight_detail_sheet.dart index b492061..c903f4c 100644 --- a/lib/pages/epub_reader/highlight_detail_sheet.dart +++ b/lib/pages/epub_reader/highlight_detail_sheet.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../utils/toast_util.dart'; import 'highlight_share_page.dart'; +import '../../widgets/app_overlay.dart'; /// 句读详情弹窗 —— 类似分享卡片的样式 /// 从 epub_detail_page 和 epub_highlights_page 共用 @@ -19,7 +20,7 @@ void showHighlightDetailSheet( final createdAt = highlight['created_at'] as String? ?? ''; final bookTitle = book['title'] as String? ?? ''; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, @@ -246,7 +247,7 @@ void showExcerptDetailSheet( }) { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, diff --git a/lib/pages/epub_reader/mixins/link_handling_mixin.dart b/lib/pages/epub_reader/mixins/link_handling_mixin.dart index 7384aa5..e0eea1e 100644 --- a/lib/pages/epub_reader/mixins/link_handling_mixin.dart +++ b/lib/pages/epub_reader/mixins/link_handling_mixin.dart @@ -45,7 +45,7 @@ mixin _LinkHandlingMixin on State { } else if (linkHandling == ReaderLinkHandling.ask) { if (mounted && context.mounted) { final shouldOpen = - await showDialog( + await appDialog( context: context, builder: (context) => AlertDialog( title: const Text('打开外部链接'), diff --git a/lib/pages/epub_reader/reader_screen.dart b/lib/pages/epub_reader/reader_screen.dart index 7f6a2be..a005ea8 100644 --- a/lib/pages/epub_reader/reader_screen.dart +++ b/lib/pages/epub_reader/reader_screen.dart @@ -23,6 +23,7 @@ import 'footnote_popup.dart'; import 'search_sheet.dart'; import 'epub_selection_toolbar.dart'; import 'selection_handles.dart'; +import '../../widgets/app_overlay.dart'; part 'mixins/spine_navigation_mixin.dart'; part 'mixins/page_navigation_mixin.dart'; @@ -439,7 +440,7 @@ class _ReaderScreenState extends State } void _openSearch() { - showModalBottomSheet( + appModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, diff --git a/lib/pages/epub_reader/reader_style_sheet.dart b/lib/pages/epub_reader/reader_style_sheet.dart index 141c997..5d28771 100644 --- a/lib/pages/epub_reader/reader_style_sheet.dart +++ b/lib/pages/epub_reader/reader_style_sheet.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import '../../services/epub/epub_theme.dart'; import 'widgets/integer_stepper.dart'; import 'widgets/reader_scale_slider.dart'; +import '../../widgets/app_overlay.dart'; /// Simplified reader style configuration bottom sheet. /// @@ -382,7 +383,7 @@ class _ReaderStyleSheetState extends State { Color bgColor = Color(_customBgColor); Color textColor = Color(_customTextColor); - showDialog( + appDialog( context: context, builder: (ctx) { return StatefulBuilder( diff --git a/lib/pages/explore/media_calendar_page.dart b/lib/pages/explore/media_calendar_page.dart index b5ccda9..557df9c 100644 --- a/lib/pages/explore/media_calendar_page.dart +++ b/lib/pages/explore/media_calendar_page.dart @@ -8,6 +8,7 @@ import '../movies/movie_detail_page.dart'; import '../movies/movie_form_page.dart'; import '../book/book_detail_page.dart'; import '../book/book_form_page.dart'; +import '../../widgets/app_overlay.dart'; /// 书影日历 - 按月展示影视/书籍添加记录 class MediaCalendarPage extends StatefulWidget { @@ -457,7 +458,7 @@ class _MediaCalendarPageState extends State { void _showAddMenu(BuildContext context) { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), diff --git a/lib/pages/game/game_add_page.dart b/lib/pages/game/game_add_page.dart index bb9f0f9..491585a 100644 --- a/lib/pages/game/game_add_page.dart +++ b/lib/pages/game/game_add_page.dart @@ -13,6 +13,7 @@ import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; import '../../utils/image_path_helper.dart'; import '../../widgets/genre_selector_page.dart'; +import '../../widgets/app_overlay.dart'; class GameAddPage extends StatefulWidget { final VoidCallback? onCancel; @@ -307,7 +308,7 @@ class _GameAddPageState extends State { void _showCoverOptions() { final c = Theme.of(context).colorScheme; - showModalBottomSheet(context: context, backgroundColor: c.surface, + appModalBottomSheet(context: context, backgroundColor: c.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), builder: (ctx) => SafeArea(child: Padding(padding: const EdgeInsets.symmetric(vertical: 16), child: Column(mainAxisSize: MainAxisSize.min, children: [ @@ -337,7 +338,7 @@ class _GameAddPageState extends State { Future _pickCoverFromUrl() async { final ctrl = TextEditingController(); - final ok = await showDialog(context: context, builder: (ctx) { + final ok = await appDialog(context: context, builder: (ctx) { final c = Theme.of(ctx).colorScheme; return AlertDialog(backgroundColor: c.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Text('添加网络图片', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: c.onSurface)), diff --git a/lib/pages/game/game_detail_page.dart b/lib/pages/game/game_detail_page.dart index 50f1d0c..8e3dbdb 100644 --- a/lib/pages/game/game_detail_page.dart +++ b/lib/pages/game/game_detail_page.dart @@ -22,6 +22,7 @@ import 'game_reviews_page.dart'; import 'game_screenshots_page.dart'; import 'game_share_page.dart'; import '../character/character_list_page.dart'; +import '../../widgets/app_overlay.dart'; /// 游戏详情页 - 极简主义设计 class GameDetailPage extends StatefulWidget { @@ -835,7 +836,7 @@ class _GameDetailPageState extends State { void _showEditCoverOptions() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), builder: (ctx) => SafeArea( @@ -875,7 +876,7 @@ class _GameDetailPageState extends State { Future _pickEditCoverFromUrl() async { final urlCtrl = TextEditingController(); - final confirmed = await showDialog(context: context, builder: (ctx) { + final confirmed = await appDialog(context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; return AlertDialog( backgroundColor: colors.surface, elevation: 0, @@ -1872,7 +1873,7 @@ class _GameDetailPageState extends State { const names = ['默认样式', '毛玻璃层叠']; const icons = [Icons.article_outlined, Icons.blur_on_outlined]; const subtitles = ['标准封面顶部布局', '封面背景 + 毛玻璃卡片']; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -1917,7 +1918,7 @@ class _GameDetailPageState extends State { void _showDeleteDialog(BuildContext context) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (context) => AlertDialog( backgroundColor: colors.surface, elevation: 0, diff --git a/lib/pages/game/game_form_page.dart b/lib/pages/game/game_form_page.dart index 136e8f3..2f19a4c 100644 --- a/lib/pages/game/game_form_page.dart +++ b/lib/pages/game/game_form_page.dart @@ -14,6 +14,7 @@ import '../../utils/toast_util.dart'; import '../../utils/image_path_helper.dart'; import '../../widgets/genre_selector_page.dart'; import '../../widgets/text_input_panel.dart'; +import '../../widgets/app_overlay.dart'; /// 从多值字段列表中提取去重排序的唯一值(供 compute 使用) List _collectUnique(List> lists) { @@ -833,7 +834,7 @@ class _GameFormPageState extends State { /// 显示封面选择选项 void _showCoverOptions() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( @@ -904,7 +905,7 @@ class _GameFormPageState extends State { /// 从网络链接选择封面 Future _pickCoverFromUrl() async { final urlController = TextEditingController(); - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -1001,7 +1002,7 @@ class _GameFormPageState extends State { /// 编辑游戏名称(弹窗) Future _editTitle() async { final controller = TextEditingController(text: _titleController.text); - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -1049,7 +1050,7 @@ class _GameFormPageState extends State { Future _editPlayCount() async { final controller = TextEditingController(text: _playCount > 0 ? '$_playCount' : ''); - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('游玩次数'), @@ -1076,7 +1077,7 @@ class _GameFormPageState extends State { final hoursController = TextEditingController(text: _playTimeHoursController.text); final minutesController = TextEditingController(text: _playTimeMinutesController.text); - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -1185,7 +1186,7 @@ class _GameFormPageState extends State { Future _confirmLeave() async { if (!_hasContent()) return true; final colors = Theme.of(context).colorScheme; - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, diff --git a/lib/pages/game/game_review_detail_page.dart b/lib/pages/game/game_review_detail_page.dart index bc84ddb..bfd3afb 100644 --- a/lib/pages/game/game_review_detail_page.dart +++ b/lib/pages/game/game_review_detail_page.dart @@ -5,6 +5,7 @@ import '../../providers/app_provider.dart'; import '../../utils/toast_util.dart'; import '../../widgets/fade_in_local_image.dart'; import 'game_review_form_page.dart'; +import '../../widgets/app_overlay.dart'; /// 游戏评价详情页 class GameReviewDetailPage extends StatefulWidget { @@ -41,7 +42,7 @@ class _GameReviewDetailPageState extends State { Future _deleteReview() async { final colors = Theme.of(context).colorScheme; - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, diff --git a/lib/pages/game/game_reviews_page.dart b/lib/pages/game/game_reviews_page.dart index 8aeabd7..72163f4 100644 --- a/lib/pages/game/game_reviews_page.dart +++ b/lib/pages/game/game_reviews_page.dart @@ -6,6 +6,7 @@ import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; import 'game_review_form_page.dart'; import 'game_review_detail_page.dart'; +import '../../widgets/app_overlay.dart'; /// 游戏评价列表页面 class GameReviewsPage extends StatefulWidget { @@ -224,7 +225,7 @@ class _GameReviewsPageState extends State { } void _showDeleteDialog(GameReview review) { - showDialog( + appDialog( context: context, builder: (context) { final colors = Theme.of(context).colorScheme; diff --git a/lib/pages/game/game_screenshots_page.dart b/lib/pages/game/game_screenshots_page.dart index d1a1314..ddacc67 100644 --- a/lib/pages/game/game_screenshots_page.dart +++ b/lib/pages/game/game_screenshots_page.dart @@ -12,6 +12,7 @@ import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; import '../../utils/image_path_helper.dart'; import 'screenshot_gallery_page.dart'; +import '../../widgets/app_overlay.dart'; /// 游戏截图页面 class GameScreenshotsPage extends StatefulWidget { @@ -147,7 +148,7 @@ class _GameScreenshotsPageState extends State { } Future _pickScreenshot() async { - final result = await showModalBottomSheet( + final result = await appModalBottomSheet( context: context, backgroundColor: Colors.transparent, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -247,7 +248,7 @@ class _GameScreenshotsPageState extends State { Future _pickFromUrl() async { final urlController = TextEditingController(); - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (context) { final colors = Theme.of(context).colorScheme; @@ -332,7 +333,7 @@ class _GameScreenshotsPageState extends State { } void _showDeleteDialog(GameScreenshot screenshot) { - showDialog( + appDialog( context: context, builder: (context) { final colors = Theme.of(context).colorScheme; diff --git a/lib/pages/game/game_share_page.dart b/lib/pages/game/game_share_page.dart index 66586b1..c53bae5 100644 --- a/lib/pages/game/game_share_page.dart +++ b/lib/pages/game/game_share_page.dart @@ -7,6 +7,7 @@ import 'package:share_plus/share_plus.dart'; import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; import '../../widgets/fade_in_local_image.dart'; +import '../../widgets/app_overlay.dart'; class GameSharePage extends StatefulWidget { final Game game; @@ -61,7 +62,7 @@ class _GameSharePageState extends State { final colors = Theme.of(context).colorScheme; const icons = [Icons.image_outlined, Icons.sports_esports_outlined]; const subtitles = ['简约海报风格', '游戏信息卡风格']; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), diff --git a/lib/pages/game/game_tab_page.dart b/lib/pages/game/game_tab_page.dart index 84e4f57..93abc84 100644 --- a/lib/pages/game/game_tab_page.dart +++ b/lib/pages/game/game_tab_page.dart @@ -14,6 +14,7 @@ import '../../widgets/master_detail_scaffold.dart'; import '../../widgets/detail_placeholder.dart'; import 'game_detail_page.dart'; import 'game_add_page.dart'; +import '../../widgets/app_overlay.dart'; /// 状态索引 → 状态值 const _gameStatusMap = {0: 'completed', 1: 'playing', 2: 'want_to_play', 3: 'abandoned'}; @@ -519,7 +520,7 @@ class _GameTabViewState extends State<_GameTabView> void _showDeleteDialog(BuildContext context, Game game) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index 0d50b5c..2611dc7 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -26,6 +26,7 @@ import 'main_content_page.dart'; import '../online_search/search_page.dart'; import '../online_search/online_search_page.dart'; import '../profile/profile_page.dart'; +import '../../widgets/app_overlay.dart'; /// 主页 - 包含底部导航,可切换主页/我的 class HomePage extends StatefulWidget { @@ -451,7 +452,7 @@ class _DesktopIconRail extends StatelessWidget { void _showSearchDialog(BuildContext context) { final colors = Theme.of(context).colorScheme; final dialogHeight = (MediaQuery.of(context).size.height * 0.82).clamp(560.0, 820.0); - showDialog( + appDialog( context: context, builder: (dialogCtx) => Dialog( backgroundColor: colors.surface, @@ -473,7 +474,7 @@ class _DesktopIconRail extends StatelessWidget { final colors = Theme.of(context).colorScheme; final email = 'dellevin99@gmail.com'; final qqGroup = '1087203310'; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -536,40 +537,40 @@ class _DesktopIconRail extends StatelessWidget { ); } void _showBackupDialog(BuildContext context) { - showDialog( + appDialog( context: context, builder: (_) => const _BackupChoiceDialog(), ); } void _showEncounterDialog(BuildContext context) { - showDialog(context: context, builder: (_) => const _EncounterDialog()); + appDialog(context: context, builder: (_) => const _EncounterDialog()); } void _showStrollDialog(BuildContext context) { - showDialog(context: context, builder: (_) => const _StrollDialog()); + appDialog(context: context, builder: (_) => const _StrollDialog()); } void _showCalendarDialog(BuildContext context) { - showDialog(context: context, builder: (_) => const _CalendarDialog()); + appDialog(context: context, builder: (_) => const _CalendarDialog()); } void _showPersonDialog(BuildContext context) { - showDialog( + appDialog( context: context, builder: (_) => const _PersonListDialog(), ); } void _showTagDialog(BuildContext context) { - showDialog( + appDialog( context: context, builder: (_) => const _TagManagementDialog(), ); } void _showRecycleBinDialog(BuildContext context) { - showDialog( + appDialog( context: context, builder: (_) => const _RecycleBinDialog(), ); @@ -1165,7 +1166,7 @@ class _StrollDialogState extends State<_StrollDialog> { void _showDeleteConfirm(_StrollItem item) { final colors = Theme.of(context).colorScheme; - showDialog(context: context, builder: (ctx) => AlertDialog( + appDialog(context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)), @@ -2058,7 +2059,7 @@ class _TagManagementDialogState extends State<_TagManagementDialog> { final name = tag['name'] as String; final isHidden = (tag['is_hidden'] as int?) == 1; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))), @@ -2127,7 +2128,7 @@ class _TagManagementDialogState extends State<_TagManagementDialog> { } else { for (final n in provider.notes.where((n) => !n.isDeleted && n.tags.contains(tagName))) items.add((title: n.title.isNotEmpty ? n.title : '随手记', subtitle: null, type: '笔记')); } - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -2161,7 +2162,7 @@ class _TagManagementDialogState extends State<_TagManagementDialog> { final colors = Theme.of(context).colorScheme; final controller = TextEditingController(); final type = _currentType; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), @@ -2208,7 +2209,7 @@ class _TagManagementDialogState extends State<_TagManagementDialog> { final tagId = tag['id'] as String; final type = tag['type'] as String; final oldName = tag['name'] as String; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), @@ -2250,7 +2251,7 @@ class _TagManagementDialogState extends State<_TagManagementDialog> { bool showAdvanced = false; final otherTags = (_tagCache[type] ?? []).where((t) => t['id'] != tagId).map((t) => t['name'] as String).toList(); - showDialog( + appDialog( context: context, builder: (ctx) => StatefulBuilder(builder: (ctx, setDialogState) { final bc = Theme.of(ctx).colorScheme; @@ -2625,7 +2626,7 @@ class _LocalBackupContentState extends State<_LocalBackupContent> { } Future _importData() async { - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -2820,7 +2821,7 @@ class _WebDAVBackupContentState extends State<_WebDAVBackupContent> { Future _confirmSync(SyncDirection direction) async { final isUpload = direction == SyncDirection.upload; - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -2844,7 +2845,7 @@ class _WebDAVBackupContentState extends State<_WebDAVBackupContent> { } Future _clearConfig() async { - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -3343,7 +3344,7 @@ class _RecycleBinDialogState extends State<_RecycleBinDialog> { Future _permanentDelete(_BinItem item) async { final colors = Theme.of(context).colorScheme; - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, @@ -3382,7 +3383,7 @@ class _RecycleBinDialogState extends State<_RecycleBinDialog> { void _showClearAllDialog() { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, @@ -4007,7 +4008,7 @@ class _DesktopListPanelState extends State<_DesktopListPanel> { Future _deleteItem(BuildContext context, String type, String id, String title) async { final provider = context.read(); - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: Theme.of(ctx).colorScheme.surface, @@ -4463,7 +4464,7 @@ class _DesktopNoteItem extends StatelessWidget { // 进入编辑模式由 NoteDetailPage 处理 break; case 'delete': - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: Theme.of(ctx).colorScheme.surface, diff --git a/lib/pages/home/main_content_page.dart b/lib/pages/home/main_content_page.dart index 0cdd0f2..1d9e726 100644 --- a/lib/pages/home/main_content_page.dart +++ b/lib/pages/home/main_content_page.dart @@ -10,6 +10,7 @@ import '../note/note_tab_page.dart'; import '../game/game_tab_page.dart'; import '../online_search/search_hub_page.dart'; import '../sync/webdav_sync_page.dart'; +import '../../widgets/app_overlay.dart'; /// 主内容页 - 观影/阅读/笔记标签页(PageView 滑动切换) class MainContentPage extends StatefulWidget { @@ -149,7 +150,7 @@ class _MainContentPageState extends State { void _showModulePicker(BuildContext context, List<_TabItem> tabs, int currentIndex) { final colors = Theme.of(context).colorScheme; final provider = context.read(); - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -211,7 +212,7 @@ class _MainContentPageState extends State { final colors = Theme.of(context).colorScheme; final hasConfig = (await WebDAVService.instance.getConfig()) != null; if (!mounted) return; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))), @@ -331,7 +332,7 @@ class _MainContentPageState extends State { void _showSortMenu(BuildContext context, String title, int current, List<(int, String, IconData)> options, ValueChanged onSelected) { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -663,7 +664,7 @@ class _CloudSheetContentState extends State<_CloudSheetContent> { // 使用保存的 navigator context,而非 state context(bottom sheet 已 pop) final overlayCtx = navigator.context; final colors = Theme.of(overlayCtx).colorScheme; - showDialog( + appDialog( context: overlayCtx, builder: (dialogCtx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, diff --git a/lib/pages/markdown_reader/md_reader_tab_page.dart b/lib/pages/markdown_reader/md_reader_tab_page.dart index a57b829..2880977 100644 --- a/lib/pages/markdown_reader/md_reader_tab_page.dart +++ b/lib/pages/markdown_reader/md_reader_tab_page.dart @@ -5,6 +5,7 @@ import 'package:path/path.dart' as p; import 'package:permission_handler/permission_handler.dart'; import '../../utils/user_prefs.dart'; import 'md_viewer_page.dart'; +import '../../widgets/app_overlay.dart'; /// Markdown 阅读器 - 文件浏览器 class MdReaderTabPage extends StatefulWidget { @@ -88,7 +89,7 @@ class _MdReaderTabPageState extends State { } void _showPermissionDeniedDialog() { - showDialog( + appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -276,7 +277,7 @@ class _MdReaderTabPageState extends State { } void _showSettingsSheet() { - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: Theme.of(context).colorScheme.surface, shape: const RoundedRectangleBorder( diff --git a/lib/pages/movies/douban_webview_page.dart b/lib/pages/movies/douban_webview_page.dart index ea9c765..ecacf9c 100644 --- a/lib/pages/movies/douban_webview_page.dart +++ b/lib/pages/movies/douban_webview_page.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import '../../widgets/app_overlay.dart'; /// 豆瓣影视WebView页面 - 用于抓取影视信息 class DoubanWebViewPage extends StatefulWidget { @@ -139,7 +140,7 @@ class _DoubanWebViewPageState extends State { // 显示提取的信息 if (mounted) { - showDialog( + appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -242,7 +243,7 @@ class _DoubanWebViewPageState extends State { _isExtracting = true; // 显示加载提示 - showDialog( + appDialog( context: context, barrierDismissible: false, builder: (dialogContext) => const Center( diff --git a/lib/pages/movies/movie_add_page.dart b/lib/pages/movies/movie_add_page.dart index ba6d957..5d673af 100644 --- a/lib/pages/movies/movie_add_page.dart +++ b/lib/pages/movies/movie_add_page.dart @@ -13,6 +13,7 @@ import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; import '../../utils/image_path_helper.dart'; import '../../widgets/genre_selector_page.dart'; +import '../../widgets/app_overlay.dart'; class MovieAddPage extends StatefulWidget { final VoidCallback? onCancel; @@ -281,7 +282,7 @@ class _MovieAddPageState extends State { void _showCoverOptions() { final c = Theme.of(context).colorScheme; - showModalBottomSheet(context: context, backgroundColor: c.surface, + appModalBottomSheet(context: context, backgroundColor: c.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), builder: (ctx) => SafeArea(child: Padding(padding: const EdgeInsets.symmetric(vertical: 16), child: Column(mainAxisSize: MainAxisSize.min, children: [ @@ -311,7 +312,7 @@ class _MovieAddPageState extends State { Future _pickCoverFromUrl() async { final ctrl = TextEditingController(); - final ok = await showDialog(context: context, builder: (ctx) { + final ok = await appDialog(context: context, builder: (ctx) { final c = Theme.of(ctx).colorScheme; return AlertDialog(backgroundColor: c.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Text('添加网络图片', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: c.onSurface)), diff --git a/lib/pages/movies/movie_detail_page.dart b/lib/pages/movies/movie_detail_page.dart index 235e3f3..0d1ed26 100644 --- a/lib/pages/movies/movie_detail_page.dart +++ b/lib/pages/movies/movie_detail_page.dart @@ -22,6 +22,7 @@ import 'movie_reviews_page.dart'; import 'movie_posters_page.dart'; import 'movie_share_page.dart'; import '../character/character_list_page.dart'; +import '../../widgets/app_overlay.dart'; /// 影视详情页 - 极简主义设计 class MovieDetailPage extends StatefulWidget { @@ -786,7 +787,7 @@ class _MovieDetailPageState extends State { void _showEditCoverOptions() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), builder: (ctx) => SafeArea( @@ -826,7 +827,7 @@ class _MovieDetailPageState extends State { Future _pickEditCoverFromUrl() async { final urlCtrl = TextEditingController(); - final confirmed = await showDialog(context: context, builder: (ctx) { + final confirmed = await appDialog(context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; return AlertDialog( backgroundColor: colors.surface, elevation: 0, @@ -1197,7 +1198,7 @@ class _MovieDetailPageState extends State { const names = ['默认样式', '毛玻璃层叠']; const icons = [Icons.article_outlined, Icons.blur_on_outlined]; const subtitles = ['标准封面顶部布局', '封面背景 + 毛玻璃卡片']; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -2103,7 +2104,7 @@ class _MovieDetailPageState extends State { void _showDeleteDialog(BuildContext context) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (context) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/movies/movie_form_page.dart b/lib/pages/movies/movie_form_page.dart index fff6b1b..7cad7ed 100644 --- a/lib/pages/movies/movie_form_page.dart +++ b/lib/pages/movies/movie_form_page.dart @@ -16,6 +16,7 @@ import '../../widgets/genre_selector_page.dart'; import '../../widgets/text_input_panel.dart'; import '../../widgets/duration_picker.dart'; import '../../widgets/alternate_titles_dialog.dart'; +import '../../widgets/app_overlay.dart'; /// 从多值字段列表中提取去重排序的唯一值(供 compute 使用) List _collectUnique(List> lists) { @@ -147,7 +148,7 @@ class _MovieFormPageState extends State { void _showQuickAddDialog() { final textController = TextEditingController(); - showDialog( + appDialog( context: context, builder: (dialogContext) { final colors = Theme.of(dialogContext).colorScheme; @@ -748,7 +749,7 @@ class _MovieFormPageState extends State { /// 编辑名称(弹窗) Future _editTitle() async { final controller = TextEditingController(text: _titleController.text); - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -796,7 +797,7 @@ class _MovieFormPageState extends State { /// 编辑别名(弹窗 + 标签式输入) Future _editAlternateTitles() async { - final result = await showDialog>( + final result = await appDialog>( context: context, builder: (ctx) => AlternateTitlesDialog(initial: _alternateTitles), ); @@ -807,7 +808,7 @@ class _MovieFormPageState extends State { /// 编辑观看次数 Future _editWatchCount() async { final controller = TextEditingController(text: _watchCount > 0 ? '$_watchCount' : ''); - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('观看次数'), @@ -1118,7 +1119,7 @@ class _MovieFormPageState extends State { /// 显示封面选择选项 void _showCoverOptions() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( @@ -1292,7 +1293,7 @@ class _MovieFormPageState extends State { Future _pickCoverFromUrl() async { final urlController = TextEditingController(); - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -1400,7 +1401,7 @@ class _MovieFormPageState extends State { Future _confirmLeave() async { if (!_hasContent()) return true; final colors = Theme.of(context).colorScheme; - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/movies/movie_posters_page.dart b/lib/pages/movies/movie_posters_page.dart index 20ee35d..1b16a13 100644 --- a/lib/pages/movies/movie_posters_page.dart +++ b/lib/pages/movies/movie_posters_page.dart @@ -12,6 +12,7 @@ import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; import '../../utils/image_path_helper.dart'; import 'poster_gallery_page.dart'; +import '../../widgets/app_overlay.dart'; /// 影视海报墙页面 class MoviePostersPage extends StatefulWidget { @@ -184,7 +185,7 @@ class _MoviePostersPageState extends State { Future _pickPoster() async { // 显示选择对话框 - final result = await showModalBottomSheet( + final result = await appModalBottomSheet( context: context, backgroundColor: Colors.transparent, shape: const RoundedRectangleBorder( @@ -311,7 +312,7 @@ class _MoviePostersPageState extends State { Future _pickFromUrl() async { final urlController = TextEditingController(); - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (context) { final colors = Theme.of(context).colorScheme; @@ -504,7 +505,7 @@ class _MoviePostersPageState extends State { } void _showDeleteDialog(MoviePoster poster) { - showDialog( + appDialog( context: context, builder: (context) { final colors = Theme.of(context).colorScheme; diff --git a/lib/pages/movies/movie_review_detail_page.dart b/lib/pages/movies/movie_review_detail_page.dart index 750849c..ed6427e 100644 --- a/lib/pages/movies/movie_review_detail_page.dart +++ b/lib/pages/movies/movie_review_detail_page.dart @@ -5,6 +5,7 @@ import '../../providers/app_provider.dart'; import '../../utils/toast_util.dart'; import '../../widgets/fade_in_local_image.dart'; import 'movie_review_form_page.dart'; +import '../../widgets/app_overlay.dart'; /// 影评详情页 class MovieReviewDetailPage extends StatefulWidget { @@ -45,7 +46,7 @@ class _MovieReviewDetailPageState extends State { Future _deleteReview() async { final colors = Theme.of(context).colorScheme; - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/movies/movie_reviews_page.dart b/lib/pages/movies/movie_reviews_page.dart index bc7513e..7e62c2d 100644 --- a/lib/pages/movies/movie_reviews_page.dart +++ b/lib/pages/movies/movie_reviews_page.dart @@ -6,6 +6,7 @@ import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; import 'movie_review_form_page.dart'; import 'movie_review_detail_page.dart'; +import '../../widgets/app_overlay.dart'; /// 影视影评列表页面 class MovieReviewsPage extends StatefulWidget { @@ -286,7 +287,7 @@ class _MovieReviewsPageState extends State { } void _showDeleteDialog(MovieReview review) { - showDialog( + appDialog( context: context, builder: (context) { final colors = Theme.of(context).colorScheme; diff --git a/lib/pages/movies/movie_share_page.dart b/lib/pages/movies/movie_share_page.dart index 2b49057..9c58a73 100644 --- a/lib/pages/movies/movie_share_page.dart +++ b/lib/pages/movies/movie_share_page.dart @@ -7,6 +7,7 @@ import 'package:share_plus/share_plus.dart'; import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; import '../../widgets/fade_in_local_image.dart'; +import '../../widgets/app_overlay.dart'; class MovieSharePage extends StatefulWidget { final Movie movie; @@ -128,7 +129,7 @@ class _MovieSharePageState extends State { final colors = Theme.of(context).colorScheme; const icons = [Icons.image_outlined, Icons.confirmation_num_outlined, Icons.local_movies_outlined, Icons.card_giftcard_outlined]; const subtitles = ['简约海报风格', '经典复古票根', '电影票票根样式', '高端收藏版票根']; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -185,7 +186,7 @@ class _MovieSharePageState extends State { void _showEditSheet() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: colors.surface, diff --git a/lib/pages/movies/movie_tab_page.dart b/lib/pages/movies/movie_tab_page.dart index 67e4ee4..8928760 100644 --- a/lib/pages/movies/movie_tab_page.dart +++ b/lib/pages/movies/movie_tab_page.dart @@ -15,6 +15,7 @@ import '../../widgets/master_detail_scaffold.dart'; import '../../widgets/detail_placeholder.dart'; import 'movie_detail_page.dart'; import 'movie_add_page.dart'; +import '../../widgets/app_overlay.dart'; /// 状态索引 → 状态值 const _statusMap = {0: 'watched', 1: 'watching', 2: 'want_to_watch'}; @@ -530,7 +531,7 @@ class _MovieTabViewState extends State<_MovieTabView> void _showDeleteDialog(BuildContext context, Movie movie) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, diff --git a/lib/pages/note/note_detail_page.dart b/lib/pages/note/note_detail_page.dart index c5d00af..8721671 100644 --- a/lib/pages/note/note_detail_page.dart +++ b/lib/pages/note/note_detail_page.dart @@ -15,6 +15,7 @@ import '../../utils/responsive.dart'; import '../../widgets/vditor_editor.dart'; import '../../widgets/tag_side_panel.dart'; import 'note_share_page.dart'; +import '../../widgets/app_overlay.dart'; /// 笔记详情页 class NoteDetailPage extends StatefulWidget { @@ -804,7 +805,7 @@ class _NoteDetailPageState extends State { } void _showImagePreview(List images, int initialIndex) { - showDialog( + appDialog( context: context, barrierDismissible: true, builder: (context) => GestureDetector( @@ -920,7 +921,7 @@ class _NoteDetailPageState extends State { void _showDeleteDialog(BuildContext context) { final errorColor = Theme.of(context).colorScheme.error; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('删除笔记'), diff --git a/lib/pages/note/note_form_page.dart b/lib/pages/note/note_form_page.dart index 3459033..b1674c6 100644 --- a/lib/pages/note/note_form_page.dart +++ b/lib/pages/note/note_form_page.dart @@ -13,6 +13,7 @@ import '../../utils/image_saver.dart'; import '../../widgets/fade_in_local_image.dart'; import '../../widgets/tag_side_panel.dart'; import '../../widgets/vditor_editor.dart'; +import '../../widgets/app_overlay.dart'; /// 添加/编辑笔记页面 - 极简书写界面 class NoteFormPage extends StatefulWidget { @@ -625,7 +626,7 @@ class _NoteFormPageState extends State { _autoSaveTimer?.cancel(); if (!_hasContent()) return true; - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -899,7 +900,7 @@ class _NoteFormPageState extends State { /// 显示图片预览 void _showImagePreview(int index) { - showDialog( + appDialog( context: context, barrierDismissible: true, builder: (context) => GestureDetector( @@ -926,7 +927,7 @@ class _NoteFormPageState extends State { /// 显示删除图片确认对话框 void _showDeleteImageDialog(int index) { - showDialog( + appDialog( context: context, builder: (context) { final colors = Theme.of(context).colorScheme; diff --git a/lib/pages/note/note_tab_page.dart b/lib/pages/note/note_tab_page.dart index 3ac4ba0..764aaf0 100644 --- a/lib/pages/note/note_tab_page.dart +++ b/lib/pages/note/note_tab_page.dart @@ -11,6 +11,7 @@ import '../../widgets/master_detail_scaffold.dart'; import '../../widgets/detail_placeholder.dart'; import 'note_detail_page.dart'; import 'note_add_page.dart'; +import '../../widgets/app_overlay.dart'; /// 笔记标签页(分页 + 触底加载) class NoteTabPage extends StatefulWidget { @@ -355,7 +356,7 @@ class _NoteTabPageState extends State { void _showNoteActions(Note note) { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -398,7 +399,7 @@ class _NoteTabPageState extends State { void _showDeleteDialog(Note note) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, diff --git a/lib/pages/online_search/book_detail_page.dart b/lib/pages/online_search/book_detail_page.dart index 8a90e40..a48e14e 100644 --- a/lib/pages/online_search/book_detail_page.dart +++ b/lib/pages/online_search/book_detail_page.dart @@ -12,6 +12,7 @@ import '../../models/data_models.dart'; import '../../providers/app_provider.dart'; import '../../utils/image_path_helper.dart'; import '../../utils/toast_util.dart'; +import '../../widgets/app_overlay.dart'; /// 书籍详情页 - 在线版 class BookDetailPage extends StatefulWidget { @@ -227,7 +228,7 @@ class _BookDetailPageState extends State { void _showAddSheet() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( diff --git a/lib/pages/online_search/movie_detail_page.dart b/lib/pages/online_search/movie_detail_page.dart index 90de718..0a109d5 100644 --- a/lib/pages/online_search/movie_detail_page.dart +++ b/lib/pages/online_search/movie_detail_page.dart @@ -15,6 +15,7 @@ import '../../models/data_models.dart'; import '../../providers/app_provider.dart'; import '../../utils/image_path_helper.dart'; import '../../utils/toast_util.dart'; +import '../../widgets/app_overlay.dart'; /// 影视详情页 - 在线版 class MovieDetailPage extends StatefulWidget { @@ -318,7 +319,7 @@ class _MovieDetailPageState extends State Future _showRateSheet() async { final colors = Theme.of(context).colorScheme; final rates = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0]; - final selected = await showModalBottomSheet( + final selected = await appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( @@ -547,7 +548,7 @@ class _MovieDetailPageState extends State void _showAddSheet() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( diff --git a/lib/pages/online_search/online_search_page.dart b/lib/pages/online_search/online_search_page.dart index ffefc7a..ce95e57 100644 --- a/lib/pages/online_search/online_search_page.dart +++ b/lib/pages/online_search/online_search_page.dart @@ -6,6 +6,7 @@ import '../../utils/user_prefs.dart'; import '../../utils/toast_util.dart'; import 'movie_detail_page.dart'; import 'book_detail_page.dart'; +import '../../widgets/app_overlay.dart'; /// 在线搜索影视/书籍(带 Scaffold + AppBar) class OnlineSearchPage extends StatelessWidget { @@ -143,7 +144,7 @@ class _OnlineSearchPageBodyState extends State { void _showUnlockDialog() { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -499,7 +500,7 @@ class _OnlineSearchPageBodyState extends State { } void _showTokenExpiredDialog() { - showDialog( + appDialog( context: context, builder: (ctx) { final c = Theme.of(ctx).colorScheme; @@ -982,7 +983,7 @@ class _OnlineSearchPageBodyState extends State { const Spacer(), GestureDetector( onTap: () { - showDialog( + appDialog( context: context, builder: (ctx) { final c = Theme.of(ctx).colorScheme; @@ -1057,7 +1058,7 @@ class _OnlineSearchPageBodyState extends State { _doSearch(); }, onLongPress: () { - showDialog( + appDialog( context: context, builder: (ctx) { final c = Theme.of(ctx).colorScheme; diff --git a/lib/pages/people/person_detail_page.dart b/lib/pages/people/person_detail_page.dart index 2708d00..0a86bea 100644 --- a/lib/pages/people/person_detail_page.dart +++ b/lib/pages/people/person_detail_page.dart @@ -10,6 +10,7 @@ import '../movies/movie_detail_page.dart'; import '../book/book_detail_page.dart'; import '../game/game_detail_page.dart'; import 'person_form_page.dart'; +import '../../widgets/app_overlay.dart'; /// 人物档案详情页 class PersonDetailPage extends StatefulWidget { @@ -464,7 +465,7 @@ class _PersonDetailPageState extends State { void _showDeleteDialog(Person person) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (context) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/people/person_form_page.dart b/lib/pages/people/person_form_page.dart index f83adf1..f7e624f 100644 --- a/lib/pages/people/person_form_page.dart +++ b/lib/pages/people/person_form_page.dart @@ -11,6 +11,7 @@ import '../../utils/image_path_helper.dart'; import '../../utils/toast_util.dart'; import '../../widgets/fade_in_local_image.dart'; import '../../widgets/genre_selector_page.dart'; +import '../../widgets/app_overlay.dart'; /// 人物编辑/添加页面 class PersonFormPage extends StatefulWidget { @@ -259,7 +260,7 @@ class _PersonFormPageState extends State { void _showPhotoOptions() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -311,7 +312,7 @@ class _PersonFormPageState extends State { Future _pickPhotoFromUrl() async { String? url; - final confirmed = await showDialog(context: context, builder: (ctx) { + final confirmed = await appDialog(context: context, builder: (ctx) { final urlCtrl = TextEditingController(); final colors = Theme.of(ctx).colorScheme; return AlertDialog( @@ -503,7 +504,7 @@ class _PersonFormPageState extends State { Future _confirmLeave() async { if (!_hasContent()) return true; final colors = Theme.of(context).colorScheme; - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, elevation: 0, diff --git a/lib/pages/people/person_list_page.dart b/lib/pages/people/person_list_page.dart index 3c304c1..5619f3c 100644 --- a/lib/pages/people/person_list_page.dart +++ b/lib/pages/people/person_list_page.dart @@ -8,6 +8,7 @@ import '../../utils/toast_util.dart'; import '../../widgets/person_avatar.dart'; import 'person_detail_page.dart'; import 'person_form_page.dart'; +import '../../widgets/app_overlay.dart'; /// 人物列表页 class PersonListPage extends StatefulWidget { @@ -526,7 +527,7 @@ class _PersonListPageState extends State { Future _refreshRelations() async { final provider = context.read(); // 显示加载弹窗 - showDialog( + appDialog( context: context, barrierDismissible: false, builder: (ctx) => PopScope( diff --git a/lib/pages/playlist/playlist_list_page.dart b/lib/pages/playlist/playlist_list_page.dart index a7c67e6..231dab1 100644 --- a/lib/pages/playlist/playlist_list_page.dart +++ b/lib/pages/playlist/playlist_list_page.dart @@ -6,6 +6,7 @@ import '../../utils/user_prefs.dart'; import '../../widgets/fade_in_local_image.dart'; import 'playlist_create_page.dart'; import 'playlist_detail_page.dart'; +import '../../widgets/app_overlay.dart'; class PlaylistListPage extends StatefulWidget { const PlaylistListPage({super.key}); @@ -561,7 +562,7 @@ class _PlaylistListPageState extends State { void _showActionSheet(BuildContext context, Playlist playlist, AppProvider provider) { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -642,7 +643,7 @@ class _PlaylistListPageState extends State { Future _showDeleteDialog(BuildContext context, Playlist playlist, AppProvider provider) { final colors = Theme.of(context).colorScheme; - return showDialog( + return appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/profile/changelog_page.dart b/lib/pages/profile/changelog_page.dart index 0c9602e..5e92e3d 100644 --- a/lib/pages/profile/changelog_page.dart +++ b/lib/pages/profile/changelog_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../services/changelog_service.dart'; +import '../../widgets/app_overlay.dart'; /// 更新日志页面 class ChangelogPage extends StatefulWidget { @@ -55,7 +56,7 @@ class _ChangelogPageState extends State { void _showNoUpdateDialog(String localVersion) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -77,7 +78,7 @@ class _ChangelogPageState extends State { void _showUpdateDialog({required String version, String? localVersion}) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/profile/feature_settings_page.dart b/lib/pages/profile/feature_settings_page.dart index 851190a..bf6e3da 100644 --- a/lib/pages/profile/feature_settings_page.dart +++ b/lib/pages/profile/feature_settings_page.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import '../../utils/user_prefs.dart'; import '../../utils/toast_util.dart'; +import '../../widgets/app_overlay.dart'; class FeatureSettingsPage extends StatefulWidget { const FeatureSettingsPage({super.key}); @@ -442,7 +443,7 @@ class _FeatureSettingsPageState extends State { void _showDefaultTabPicker() { final colors = Theme.of(context).colorScheme; final enabled = _enabledTabs; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (ctx) => Container( diff --git a/lib/pages/profile/font_picker_page.dart b/lib/pages/profile/font_picker_page.dart index 1cfb251..bc69534 100644 --- a/lib/pages/profile/font_picker_page.dart +++ b/lib/pages/profile/font_picker_page.dart @@ -6,6 +6,7 @@ import 'package:permission_handler/permission_handler.dart'; import '../../services/font_download_manager.dart'; import '../../utils/toast_util.dart'; import '../../utils/user_prefs.dart'; +import '../../widgets/app_overlay.dart'; /// 字体选择页面 /// @@ -59,7 +60,7 @@ class _FontPickerPageState extends State { /// 显示权限提示弹窗 void _showPermissionDialog() { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/profile/layout_settings_page.dart b/lib/pages/profile/layout_settings_page.dart index db35fbc..bac4bff 100644 --- a/lib/pages/profile/layout_settings_page.dart +++ b/lib/pages/profile/layout_settings_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../providers/app_provider.dart'; import '../../utils/user_prefs.dart'; +import '../../widgets/app_overlay.dart'; class LayoutSettingsPage extends StatefulWidget { const LayoutSettingsPage({super.key}); @@ -337,7 +338,7 @@ class _LayoutSettingsPageState extends State { void _showHomeModuleSheet() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -380,7 +381,7 @@ class _LayoutSettingsPageState extends State { void _showMovieSheet() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -445,7 +446,7 @@ class _LayoutSettingsPageState extends State { void _showBookSheet() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -502,7 +503,7 @@ class _LayoutSettingsPageState extends State { void _showNoteSheet() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -538,7 +539,7 @@ class _LayoutSettingsPageState extends State { void _showGameSheet() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), diff --git a/lib/pages/profile/profile_page.dart b/lib/pages/profile/profile_page.dart index c145ced..7517807 100644 --- a/lib/pages/profile/profile_page.dart +++ b/lib/pages/profile/profile_page.dart @@ -21,6 +21,7 @@ import '../explore/stroll_page.dart'; import '../sync/cloud_sync_page.dart'; import 'settings_page.dart'; import 'watchlist_page.dart'; +import '../../widgets/app_overlay.dart'; /// 个人中心页面 class ProfilePage extends StatefulWidget { @@ -1053,7 +1054,7 @@ class _ProfilePageState extends State with RouteAware { void _showFeedbackDialog(BuildContext context) { final colors = Theme.of(context).colorScheme; final email = 'dellevin99@gmail.com'; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( @@ -1210,7 +1211,7 @@ class _ProfilePageState extends State with RouteAware { Future _pickAvatar() async { final colors = Theme.of(context).colorScheme; - await showModalBottomSheet( + await appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( @@ -1312,7 +1313,7 @@ class _ProfilePageState extends State with RouteAware { void _showBackupOptions(BuildContext context) { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( @@ -1414,7 +1415,7 @@ class _ProfilePageState extends State with RouteAware { } options.add(('笔记', Icons.sticky_note_2_outlined, provider.notes.where((n) => !n.isDeleted).length)); - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( @@ -1515,7 +1516,7 @@ class _ProfilePageState extends State with RouteAware { void _showExportResult(BuildContext context, bool success, String message) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( icon: Icon( diff --git a/lib/pages/profile/settings_page.dart b/lib/pages/profile/settings_page.dart index 9a2e7e0..e85a9b8 100644 --- a/lib/pages/profile/settings_page.dart +++ b/lib/pages/profile/settings_page.dart @@ -20,6 +20,7 @@ import 'feature_settings_page.dart'; import 'layout_settings_page.dart'; import 'changelog_page.dart'; import 'font_picker_page.dart'; +import '../../widgets/app_overlay.dart'; /// 设置页面 class SettingsPage extends StatefulWidget { @@ -263,7 +264,7 @@ class _SettingsPageState extends State { final colors = Theme.of(context).colorScheme; final nicknameController = TextEditingController(text: _userPrefs.nickname); final mottoController = TextEditingController(text: _userPrefs.motto); - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -351,11 +352,12 @@ class _SettingsPageState extends State { ); } - static const _themeModeLabels = ['跟随系统', '浅色模式', '深色模式']; + static const _themeModeLabels = ['跟随系统', '浅色模式', '深色模式', '毛玻璃']; static const _themeModeIcons = [ Icons.brightness_auto, Icons.light_mode, - Icons.dark_mode + Icons.dark_mode, + Icons.blur_on ]; Widget _buildThemeModeSelector() { @@ -401,7 +403,7 @@ class _SettingsPageState extends State { void _showThemeModePicker() { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (ctx) => Container( @@ -464,6 +466,13 @@ class _SettingsPageState extends State { Future _setThemeMode(int mode) async { await _userPrefs.setThemeMode(mode); + if (mode == 3) { + if (mounted) { + context.read().enableFrosted(); + setState(() => _themeMode = mode); + } + return; + } final themeMode = switch (mode) { 1 => ThemeMode.light, 2 => ThemeMode.dark, @@ -527,7 +536,7 @@ class _SettingsPageState extends State { void _showColorSchemePicker() { final colors = Theme.of(context).colorScheme; final provider = context.read(); - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (ctx) => Container( @@ -904,7 +913,7 @@ class _SettingsPageState extends State { void _showStoragePermissionDialog() { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -945,7 +954,7 @@ class _SettingsPageState extends State { void _showClearCacheDialog(BuildContext pageContext) async { final colors = Theme.of(context).colorScheme; // 先扫描分析 - showDialog( + appDialog( context: pageContext, barrierDismissible: false, builder: (_) => Center(child: CircularProgressIndicator(color: colors.primary)), @@ -968,7 +977,7 @@ class _SettingsPageState extends State { return; } - showDialog( + appDialog( context: pageContext, builder: (dialogContext) => AlertDialog( backgroundColor: colors.surface, @@ -1038,7 +1047,7 @@ class _SettingsPageState extends State { Future _clearCacheData(BuildContext context) async { try { - showDialog( + appDialog( context: context, barrierDismissible: false, builder: (_) => const Center(child: CircularProgressIndicator())); @@ -1058,7 +1067,7 @@ class _SettingsPageState extends State { void _showCacheResult( BuildContext context, bool success, String message, bool cleaned) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( icon: Icon( diff --git a/lib/pages/settings/recycle_bin_page.dart b/lib/pages/settings/recycle_bin_page.dart index 653cba5..4ec07c2 100644 --- a/lib/pages/settings/recycle_bin_page.dart +++ b/lib/pages/settings/recycle_bin_page.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import '../../providers/app_provider.dart'; import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; +import '../../widgets/app_overlay.dart'; /// 回收站页面 class RecycleBinPage extends StatefulWidget { @@ -441,7 +442,7 @@ class _RecycleBinPageState extends State { } Future _restore(_DeletedItem item) async { - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: Theme.of(context).colorScheme.surface, @@ -548,7 +549,7 @@ class _RecycleBinPageState extends State { Future _showConfirmDialog(String message) async { final colors = Theme.of(context).colorScheme; - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -582,7 +583,7 @@ class _RecycleBinPageState extends State { void _showClearAllDialog() { final pageContext = context; final colors = Theme.of(pageContext).colorScheme; - showDialog( + appDialog( context: pageContext, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/pages/settings/tag_management_page.dart b/lib/pages/settings/tag_management_page.dart index fe9584d..6dd6ba5 100644 --- a/lib/pages/settings/tag_management_page.dart +++ b/lib/pages/settings/tag_management_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../providers/app_provider.dart'; import '../../utils/toast_util.dart'; +import '../../widgets/app_overlay.dart'; class TagManagementPage extends StatefulWidget { const TagManagementPage({super.key}); @@ -561,7 +562,7 @@ class _TagManagementPageState extends State { final name = tag['name'] as String; final isHidden = (tag['is_hidden'] as int?) == 1; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))), @@ -680,7 +681,7 @@ class _TagManagementPageState extends State { } } - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), @@ -762,7 +763,7 @@ class _TagManagementPageState extends State { .where((t) => (t['id'] as String) != tagId && !descendants.contains(t['id'] as String)) .toList(); - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))), @@ -857,7 +858,7 @@ class _TagManagementPageState extends State { final controller = TextEditingController(); final type = _currentType; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -927,7 +928,7 @@ class _TagManagementPageState extends State { final type = tag['type'] as String; final oldName = tag['name'] as String; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, @@ -995,7 +996,7 @@ class _TagManagementPageState extends State { .map((t) => t['name'] as String) .toList(); - showDialog( + appDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setDialogState) { diff --git a/lib/pages/sync/backup_page.dart b/lib/pages/sync/backup_page.dart index ebcb91e..fed6d88 100644 --- a/lib/pages/sync/backup_page.dart +++ b/lib/pages/sync/backup_page.dart @@ -5,6 +5,7 @@ import '../../providers/app_provider.dart'; import '../../services/sync/backup_service.dart'; import '../../utils/toast_util.dart'; import '../../utils/user_prefs.dart'; +import '../../widgets/app_overlay.dart'; /// 本地备份页面 class BackupPage extends StatefulWidget { @@ -500,7 +501,7 @@ class _BackupPageState extends State { required String content, String? detail, }) { - showDialog( + appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -600,7 +601,7 @@ class _BackupPageState extends State { /// 导入数据 Future _importData() async { // 显示确认对话框 - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; diff --git a/lib/pages/sync/webdav_sync_page.dart b/lib/pages/sync/webdav_sync_page.dart index 5796eee..289bd92 100644 --- a/lib/pages/sync/webdav_sync_page.dart +++ b/lib/pages/sync/webdav_sync_page.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import '../../utils/toast_util.dart'; import '../../services/sync/webdav_service.dart'; import '../../providers/app_provider.dart'; +import '../../widgets/app_overlay.dart'; /// WebDAV 备份页面 class WebDAVSyncPage extends StatefulWidget { @@ -183,7 +184,7 @@ class _WebDAVSyncPageState extends State { } void _showResultDialog(String title, String content) { - showDialog( + appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -231,7 +232,7 @@ class _WebDAVSyncPageState extends State { } Future _showUploadConfirm() async { - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -269,7 +270,7 @@ class _WebDAVSyncPageState extends State { } Future _showDownloadConfirm() async { - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; @@ -307,7 +308,7 @@ class _WebDAVSyncPageState extends State { } Future _clearConfig() async { - final confirmed = await showDialog( + final confirmed = await appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; diff --git a/lib/providers/app_provider.dart b/lib/providers/app_provider.dart index 146e247..331c669 100644 --- a/lib/providers/app_provider.dart +++ b/lib/providers/app_provider.dart @@ -1,5 +1,6 @@ import 'dart:collection'; import 'dart:io'; +import 'dart:math'; import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; import '../models/data_models.dart'; @@ -81,6 +82,10 @@ class AppProvider extends ChangeNotifier { // 主题模式 ThemeMode _themeMode = ThemeMode.system; + // 毛玻璃主题(themeMode=3):标志 + 随机封面 + bool _frostedActive = false; + String? _frostedCoverPath; + // 配色方案索引 int _colorSchemeIndex = 0; @@ -250,12 +255,14 @@ class AppProvider extends ChangeNotifier { // 加载影视数据 Future loadMovies() async { _movies = await _movieDao.getAllMovies(sortMode: UserPrefs().movieSortMode); + if (_frostedActive && _frostedCoverPath == null) refreshFrostedCover(); notifyListeners(); } // 加载书籍数据 Future loadBooks() async { _books = await _bookDao.getAllBooks(sortMode: UserPrefs().bookSortMode); + if (_frostedActive && _frostedCoverPath == null) refreshFrostedCover(); notifyListeners(); } @@ -268,6 +275,7 @@ class AppProvider extends ChangeNotifier { // 加载游戏数据 Future loadGames() async { _games = await _gameDao.getAllGames(sortMode: UserPrefs().gameSortMode); + if (_frostedActive && _frostedCoverPath == null) refreshFrostedCover(); notifyListeners(); } @@ -336,6 +344,10 @@ class AppProvider extends ChangeNotifier { bool get bottomNavVisible => _bottomNavVisible; ThemeMode get themeMode => _themeMode; int get colorSchemeIndex => _colorSchemeIndex; + + // 毛玻璃状态 + bool get frostedActive => _frostedActive; + String? get frostedCoverPath => _frostedCoverPath; String get fontFamily => _fontFamily; List get movies => UnmodifiableListView(_movies); List get books => UnmodifiableListView(_books); @@ -443,13 +455,35 @@ class AppProvider extends ChangeNotifier { } void setThemeMode(ThemeMode mode) { - if (_themeMode != mode) { + final wasFrosted = _frostedActive; + _frostedActive = false; + UserPrefs().setThemeMode(mode.index); // 0=system, 1=light, 2=dark, 3=frosted + if (_themeMode != mode || wasFrosted) { _themeMode = mode; - UserPrefs().setThemeMode(mode.index); // 0=system, 1=light, 2=dark notifyListeners(); } } + /// 启用毛玻璃主题(themeMode=3),随机取一张封面作为背景 + void enableFrosted() { + _frostedActive = true; + _themeMode = ThemeMode.dark; + UserPrefs().setThemeMode(3); + refreshFrostedCover(); + } + + /// 从影视/书籍/游戏封面中随机取一张作为毛玻璃背景 + void refreshFrostedCover() { + if (!_frostedActive) return; + final paths = [ + ..._movies.where((m) => m.posterPath != null && m.posterPath!.isNotEmpty).map((m) => m.posterPath!), + ..._books.where((b) => b.coverPath != null && b.coverPath!.isNotEmpty).map((b) => b.coverPath!), + ..._games.where((g) => g.coverPath != null && g.coverPath!.isNotEmpty).map((g) => g.coverPath!), + ]; + _frostedCoverPath = paths.isEmpty ? null : paths[Random().nextInt(paths.length)]; + notifyListeners(); + } + void loadThemeMode() { final prefs = UserPrefs(); switch (prefs.themeMode) { @@ -457,10 +491,13 @@ class AppProvider extends ChangeNotifier { _themeMode = ThemeMode.light; case 2: _themeMode = ThemeMode.dark; + case 3: + _frostedActive = true; + _themeMode = ThemeMode.dark; default: _themeMode = ThemeMode.system; } - _colorSchemeIndex = prefs.colorSchemeIndex; + _colorSchemeIndex = prefs.colorSchemeIndex.clamp(-1, AppTheme.seedColors.length - 1); _fontFamily = prefs.fontFamily; AppTheme.setFontFamily(_fontFamily); // 异步预加载已缓存的字体(不阻塞 UI) diff --git a/lib/utils/image_saver.dart b/lib/utils/image_saver.dart index 73ab964..ce1d7a9 100644 --- a/lib/utils/image_saver.dart +++ b/lib/utils/image_saver.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; +import '../widgets/app_overlay.dart'; /// 图片保存工具 —— 将图片复制/写入到 /sdcard/Pictures/mooknote/ class ImageSaver { @@ -82,7 +83,7 @@ class ImageSaver { required Future Function() onConfirm, }) { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( diff --git a/lib/utils/theme/app_theme.dart b/lib/utils/theme/app_theme.dart index 173e6b1..7df81cf 100644 --- a/lib/utils/theme/app_theme.dart +++ b/lib/utils/theme/app_theme.dart @@ -181,6 +181,139 @@ class AppTheme { ); } + // 毛玻璃主题 - 半透明面板透出背景的暗色玻璃 + static ThemeData get frostedTheme { + const bg = Color(0xFF121418); // 深冷玻璃底(兜底) + const surface = Color(0x52121418); // 内容区 32% + const surfaceHigh = Color(0x8C121418); // 面板 55% + const surfaceHighest = Color(0xA6121418); // 卡片/输入 65% + const border = Color(0x1FFFFFFF); // 白 12% + const borderStrong = Color(0x2EFFFFFF); // 白 18% + const onSurface = Colors.white; + const onSurfaceWeak = Color(0x99FFFFFF); // 白 60% + const onSurfaceFaint = Color(0x66FFFFFF); // 白 40% + const accent = Color(0xFF8AB4FF); // 柔和玻璃蓝 + + return ThemeData( + useMaterial3: true, + brightness: Brightness.dark, + scaffoldBackgroundColor: Colors.transparent, + colorScheme: const ColorScheme.dark( + primary: accent, + onPrimary: Color(0xFF0B0D11), + secondary: Color(0xFF9AA5B5), + onSecondary: bg, + surface: surface, + onSurface: onSurface, + error: Color(0xFFEF6A6A), + onError: Color(0xFF2A0A0A), + surfaceContainerHigh: surfaceHigh, + surfaceContainerHighest: surfaceHighest, + outline: borderStrong, + outlineVariant: border, + ), + appBarTheme: AppBarTheme( + backgroundColor: surfaceHigh, + foregroundColor: onSurface, + elevation: 0, + scrolledUnderElevation: 0, + surfaceTintColor: Colors.transparent, + centerTitle: false, + titleSpacing: 24, + titleTextStyle: TextStyle( + fontFamily: _fontFamily, fontSize: 18, fontWeight: _semibold, + color: onSurface, letterSpacing: 0, + ), + ), + cardTheme: CardThemeData( + color: surfaceHighest, elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: const BorderSide(color: borderStrong), + ), + margin: EdgeInsets.zero, + ), + dialogTheme: DialogThemeData( + backgroundColor: const Color(0x99121418), // 60% 深色,透出外层磨砂 + surfaceTintColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide(color: borderStrong), + ), + ), + bottomSheetTheme: BottomSheetThemeData( + backgroundColor: const Color(0xCC121418), // 80% 深色(封装 sheet 用 transparent + FrostedPanel) + surfaceTintColor: Colors.transparent, + showDragHandle: true, + dragHandleColor: borderStrong, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + side: const BorderSide(color: borderStrong), + ), + ), + listTileTheme: const ListTileThemeData( + contentPadding: EdgeInsets.symmetric(horizontal: 24, vertical: 16), + minLeadingWidth: 0, dense: true, + ), + dividerTheme: DividerThemeData(color: border, thickness: 0.5, space: 0), + scrollbarTheme: ScrollbarThemeData( + thumbColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.hovered)) return onSurface.withValues(alpha: 0.3); + return onSurface.withValues(alpha: 0.15); + }), + thickness: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.hovered)) return 6.0; + return 4.0; + }), + radius: const Radius.circular(3), + ), + inputDecorationTheme: InputDecorationTheme( + filled: false, + border: UnderlineInputBorder(borderSide: const BorderSide(color: borderStrong, width: 0.5)), + enabledBorder: UnderlineInputBorder(borderSide: const BorderSide(color: borderStrong, width: 0.5)), + focusedBorder: UnderlineInputBorder(borderSide: const BorderSide(color: accent, width: 1)), + errorBorder: UnderlineInputBorder(borderSide: const BorderSide(color: Color(0xFFEF6A6A), width: 0.5)), + contentPadding: const EdgeInsets.symmetric(vertical: 12), + hintStyle: TextStyle(fontFamily: _fontFamily, fontSize: 15, fontWeight: _regular, color: onSurfaceFaint), + labelStyle: TextStyle(fontFamily: _fontFamily, fontSize: 13, fontWeight: _medium, color: onSurfaceWeak), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: accent, foregroundColor: const Color(0xFF0B0D11), + elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + textStyle: TextStyle(fontFamily: _fontFamily, fontSize: 14, fontWeight: _medium, letterSpacing: 0.3), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: accent, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + textStyle: TextStyle(fontFamily: _fontFamily, fontSize: 14, fontWeight: _medium), + ), + ), + bottomNavigationBarTheme: BottomNavigationBarThemeData( + backgroundColor: const Color(0xCC14161A), + selectedItemColor: accent, + unselectedItemColor: onSurfaceFaint, + elevation: 0, type: BottomNavigationBarType.fixed, + selectedLabelStyle: TextStyle(fontFamily: _fontFamily, fontSize: 11, fontWeight: _medium), + unselectedLabelStyle: TextStyle(fontFamily: _fontFamily, fontSize: 11, fontWeight: _regular, color: onSurfaceFaint), + ), + textTheme: TextTheme( + headlineLarge: TextStyle(fontFamily: _fontFamily, fontSize: 32, fontWeight: _semibold, color: onSurface, letterSpacing: 0, height: 1.2), + headlineMedium: TextStyle(fontFamily: _fontFamily, fontSize: 24, fontWeight: _semibold, color: onSurface, letterSpacing: 0, height: 1.3), + headlineSmall: TextStyle(fontFamily: _fontFamily, fontSize: 20, fontWeight: _semibold, color: onSurface, letterSpacing: 0, height: 1.4), + bodyLarge: TextStyle(fontFamily: _fontFamily, fontSize: 16, fontWeight: _regular, color: onSurfaceWeak, height: 1.6), + bodyMedium: TextStyle(fontFamily: _fontFamily, fontSize: 15, fontWeight: _regular, color: onSurfaceWeak, height: 1.5), + bodySmall: TextStyle(fontFamily: _fontFamily, fontSize: 13, fontWeight: _regular, color: onSurfaceFaint, height: 1.5), + labelLarge: TextStyle(fontFamily: _fontFamily, fontSize: 14, fontWeight: _medium, color: onSurface), + labelMedium: TextStyle(fontFamily: _fontFamily, fontSize: 12, fontWeight: _medium, color: onSurfaceWeak), + labelSmall: TextStyle(fontFamily: _fontFamily, fontSize: 11, fontWeight: _medium, color: onSurfaceFaint, letterSpacing: 0.3), + ), + ); + } + // 亮色主题 - 极简主义 static ThemeData get lightTheme { return ThemeData( diff --git a/lib/utils/user_prefs.dart b/lib/utils/user_prefs.dart index 230d95e..cb38e50 100644 --- a/lib/utils/user_prefs.dart +++ b/lib/utils/user_prefs.dart @@ -54,7 +54,7 @@ class UserPrefs { // ========== 应用设置 ========== - /// 主题模式: 0=跟随系统, 1=浅色, 2=深色 + /// 主题模式: 0=跟随系统, 1=浅色, 2=深色, 3=毛玻璃 int get themeMode => prefs.getInt('themeMode') ?? 0; Future setThemeMode(int value) => prefs.setInt('themeMode', value); diff --git a/lib/widgets/add_sheet.dart b/lib/widgets/add_sheet.dart index 082a05c..b1e58cc 100644 --- a/lib/widgets/add_sheet.dart +++ b/lib/widgets/add_sheet.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'dart:io' show Platform; import '../providers/app_provider.dart'; import '../utils/user_prefs.dart'; +import '../widgets/app_overlay.dart'; /// 新增记录弹窗 — 供底部导航栏和 NavigationRail 共用 @@ -98,7 +99,7 @@ void showAddSheet(BuildContext context, AppProvider provider) { } if (Platform.isWindows) { - showDialog( + appDialog( context: context, builder: (ctx) { final bc = Theme.of(ctx).colorScheme; @@ -121,7 +122,7 @@ void showAddSheet(BuildContext context, AppProvider provider) { }, ); } else { - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( diff --git a/lib/widgets/add_type_selector.dart b/lib/widgets/add_type_selector.dart index c82de58..78fd381 100644 --- a/lib/widgets/add_type_selector.dart +++ b/lib/widgets/add_type_selector.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; import '../utils/user_prefs.dart'; +import '../widgets/app_overlay.dart'; /// 添加类型选择弹窗 Future showAddTypeDialog(BuildContext context) async { - final result = await showDialog( + final result = await appDialog( context: context, builder: (ctx) => const _AddTypeDialog(), ); @@ -15,7 +16,7 @@ Future showAddTypeDialog(BuildContext context) async { /// 返回选中的类型索引 (0=影视, 1=阅读, 2=笔记, 3=游戏),取消返回 null Future showAddTypeSelector(BuildContext context) { - return showDialog( + return appDialog( context: context, builder: (ctx) => const _AddTypeDialog(), ); diff --git a/lib/widgets/app_overlay.dart b/lib/widgets/app_overlay.dart new file mode 100644 index 0000000..f34e3d9 --- /dev/null +++ b/lib/widgets/app_overlay.dart @@ -0,0 +1,102 @@ +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../providers/app_provider.dart'; + +/// 毛玻璃主题下的通用磨砂面板:真实背景模糊 + 半透明深色 +/// 仅在毛玻璃模式下生效,其余主题直接透传 child,不影响原样式 +class FrostedPanel extends StatelessWidget { + final Widget child; + final BorderRadiusGeometry borderRadius; + final Color color; + + const FrostedPanel({ + super.key, + required this.child, + this.borderRadius = BorderRadius.zero, + this.color = const Color(0xCC121418), + }); + + @override + Widget build(BuildContext context) { + final frosted = context.watch().frostedActive; + if (!frosted) return child; + return ClipRRect( + borderRadius: borderRadius, + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 22, sigmaY: 22), + child: ColoredBox(color: color, child: child), + ), + ); + } +} + +/// 毛玻璃版底部弹层:毛玻璃模式下自动加磨砂,其余主题与原 showModalBottomSheet 一致 +Future appModalBottomSheet({ + required BuildContext context, + Color? backgroundColor, + Color? barrierColor, + double? elevation, + ShapeBorder? shape, + bool isScrollControlled = false, + bool isDismissible = true, + bool enableDrag = true, + bool useSafeArea = false, + bool showDragHandle = false, + BoxConstraints? constraints, + bool useRootNavigator = false, + required WidgetBuilder builder, +}) { + final frosted = context.read().frostedActive; + return showModalBottomSheet( + context: context, + backgroundColor: frosted ? Colors.transparent : backgroundColor, + barrierColor: barrierColor, + elevation: elevation, + shape: shape, + isScrollControlled: isScrollControlled, + isDismissible: isDismissible, + enableDrag: enableDrag, + useSafeArea: useSafeArea, + showDragHandle: showDragHandle, + constraints: constraints, + useRootNavigator: useRootNavigator, + builder: (ctx) { + final content = builder(ctx); + if (!frosted) return content; + return FrostedPanel( + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), + child: content, + ); + }, + ); +} + +/// 毛玻璃版对话框:毛玻璃模式下自动加磨砂 +Future appDialog({ + required BuildContext context, + bool barrierDismissible = true, + Color? barrierColor, + bool useSafeArea = true, + RouteSettings? routeSettings, + bool useRootNavigator = true, + required WidgetBuilder builder, +}) { + final frosted = context.read().frostedActive; + return showDialog( + context: context, + barrierDismissible: barrierDismissible, + barrierColor: barrierColor, + useSafeArea: useSafeArea, + routeSettings: routeSettings, + useRootNavigator: useRootNavigator, + builder: (ctx) { + final content = builder(ctx); + if (!frosted) return content; + return FrostedPanel( + borderRadius: BorderRadius.circular(20), + child: content, + ); + }, + ); +} \ No newline at end of file diff --git a/lib/widgets/book_list_item.dart b/lib/widgets/book_list_item.dart index 374bed2..e804575 100644 --- a/lib/widgets/book_list_item.dart +++ b/lib/widgets/book_list_item.dart @@ -5,6 +5,7 @@ import '../providers/app_provider.dart'; import '../widgets/fade_in_local_image.dart'; import '../widgets/animated_star_rating.dart'; import '../utils/toast_util.dart'; +import '../widgets/app_overlay.dart'; /// 书籍列表项组件 - 网格布局设计 class BookListItem extends StatelessWidget { @@ -73,7 +74,7 @@ class BookListItem extends StatelessWidget { void _showDeleteDialog(BuildContext context) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (context) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/widgets/bottom_nav_bar.dart b/lib/widgets/bottom_nav_bar.dart index 4179cf4..8339e84 100644 --- a/lib/widgets/bottom_nav_bar.dart +++ b/lib/widgets/bottom_nav_bar.dart @@ -1,3 +1,4 @@ +import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/app_provider.dart'; @@ -10,9 +11,6 @@ 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( @@ -21,48 +19,7 @@ class CustomBottomNavBar extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Container( - height: 56, - margin: const EdgeInsets.symmetric(horizontal: 40), - decoration: BoxDecoration( - color: colors.surface, - borderRadius: BorderRadius.circular(28), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: isDark ? 0.3 : 0.08), - blurRadius: 20, - offset: const Offset(0, 4), - spreadRadius: 0, - ), - BoxShadow( - color: Colors.black.withValues(alpha: isDark ? 0.15 : 0.04), - blurRadius: 8, - offset: const Offset(0, 2), - spreadRadius: -2, - ), - ], - ), - 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, - onTap: () => provider.setBottomNavIndex(2), - ), - ], - ), - ), + _buildDock(context, provider: provider), SizedBox(height: bottomPadding + 8), ], ), @@ -71,6 +28,71 @@ class CustomBottomNavBar extends StatelessWidget { ); } + /// Dock 悬浮导航条;毛玻璃模式下加真实模糊,保证可读 + Widget _buildDock(BuildContext context, {required AppProvider provider}) { + final colors = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + final isFrosted = provider.frostedActive; + + final dock = Container( + height: 56, + decoration: BoxDecoration( + color: colors.surface, + borderRadius: BorderRadius.circular(28), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: isDark ? 0.3 : 0.08), + blurRadius: 20, + offset: const Offset(0, 4), + spreadRadius: 0, + ), + BoxShadow( + color: Colors.black.withValues(alpha: isDark ? 0.15 : 0.04), + blurRadius: 8, + offset: const Offset(0, 2), + spreadRadius: -2, + ), + ], + ), + 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, + onTap: () => provider.setBottomNavIndex(2), + ), + ], + ), + ); + + // 毛玻璃模式下:裁剪 + 模糊区域与胶囊同宽(外围留白),避免整行全宽模糊 + Widget result = dock; + if (isFrosted) { + result = ClipRRect( + borderRadius: BorderRadius.circular(28), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 25, sigmaY: 25), + child: dock, + ), + ); + } + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: result, + ); + } + Widget _buildNavItem({ required ColorScheme colors, required IconData icon, @@ -116,4 +138,4 @@ class CustomBottomNavBar extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/widgets/character_info_sheet.dart b/lib/widgets/character_info_sheet.dart index c23f60b..f95d04c 100644 --- a/lib/widgets/character_info_sheet.dart +++ b/lib/widgets/character_info_sheet.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../pages/character/character_form_page.dart'; import 'fade_in_local_image.dart'; +import '../widgets/app_overlay.dart'; /// 角色信息底部弹窗 /// @@ -26,7 +27,7 @@ class CharacterInfoSheet extends StatefulWidget { required String entityId, required dynamic character, }) { - return showModalBottomSheet( + return appModalBottomSheet( context: context, backgroundColor: Theme.of(context).colorScheme.surface, isScrollControlled: true, diff --git a/lib/widgets/custom_drawer.dart b/lib/widgets/custom_drawer.dart index f29cb85..2ef6abc 100644 --- a/lib/widgets/custom_drawer.dart +++ b/lib/widgets/custom_drawer.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -27,6 +28,7 @@ import '../pages/profile/settings_page.dart'; import '../models/data_models.dart'; import 'fade_in_local_image.dart'; import 'shimmer_skeleton.dart'; +import '../widgets/app_overlay.dart'; /// 自定义侧边栏 class CustomDrawer extends StatefulWidget { @@ -194,6 +196,18 @@ class _CustomDrawerState extends State { ); if (widget.embedded) return content; + final isFrosted = context.read().frostedActive; + if (isFrosted) { + return Drawer( + backgroundColor: Colors.transparent, + child: ClipRect( + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 25, sigmaY: 25), + child: Container(color: colors.surfaceContainerHigh, child: content), + ), + ), + ); + } return Drawer( backgroundColor: colors.surfaceContainerHigh, child: content, @@ -689,7 +703,7 @@ class _CustomDrawerState extends State { final dayNotes = notes.where((n) => !n.isDeleted && DateTime(n.createdAt.year, n.createdAt.month, n.createdAt.day) == date).toList(); final dayGames = games.where((g) => !g.isDeleted && DateTime(g.createdAt.year, g.createdAt.month, g.createdAt.day) == date).toList(); - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), diff --git a/lib/widgets/duration_picker.dart b/lib/widgets/duration_picker.dart index c7abb0a..b372d27 100644 --- a/lib/widgets/duration_picker.dart +++ b/lib/widgets/duration_picker.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import '../widgets/app_overlay.dart'; /// 时长选择器(时:分两个滚轮的底部弹窗) /// 返回总分钟数,取消返回 null @@ -11,7 +12,7 @@ class DurationPicker { int initialMinutes = 0, String title = '影视总时长', }) { - return showModalBottomSheet( + return appModalBottomSheet( context: context, backgroundColor: Theme.of(context).colorScheme.surface, isScrollControlled: true, diff --git a/lib/widgets/frosted_background.dart b/lib/widgets/frosted_background.dart new file mode 100644 index 0000000..801ab67 --- /dev/null +++ b/lib/widgets/frosted_background.dart @@ -0,0 +1,35 @@ +import 'dart:io'; +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; + +/// 毛玻璃主题背景层:封面图 + 模糊 + 深色遮罩 +/// 用 ImageFiltered 模糊静态图片(开销低于 BackdropFilter 实时模糊) +class FrostedBackground extends StatelessWidget { + final String? coverPath; + + const FrostedBackground({super.key, this.coverPath}); + + @override + Widget build(BuildContext context) { + if (coverPath == null || coverPath!.isEmpty) { + // 无封面兜底:纯色玻璃底 + return const ColoredBox(color: Color(0xFF121418)); + } + return Stack( + fit: StackFit.expand, + children: [ + ImageFiltered( + imageFilter: ui.ImageFilter.blur(sigmaX: 25, sigmaY: 25), + child: Image.file( + File(coverPath!), + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => + const ColoredBox(color: Color(0xFF121418)), + ), + ), + // 深色遮罩,保证内容可读 + const ColoredBox(color: Color(0x73000000)), // 黑 ~45% + ], + ); + } +} \ No newline at end of file diff --git a/lib/widgets/game_list_item.dart b/lib/widgets/game_list_item.dart index f36503d..95a5e51 100644 --- a/lib/widgets/game_list_item.dart +++ b/lib/widgets/game_list_item.dart @@ -5,6 +5,7 @@ import '../providers/app_provider.dart'; import '../widgets/fade_in_local_image.dart'; import '../widgets/animated_star_rating.dart'; import '../utils/toast_util.dart'; +import '../widgets/app_overlay.dart'; /// 游戏列表项组件 - 网格布局设计 class GameListItem extends StatelessWidget { @@ -73,7 +74,7 @@ class GameListItem extends StatelessWidget { void _showDeleteDialog(BuildContext context) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (context) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/widgets/genre_selector_page.dart b/lib/widgets/genre_selector_page.dart index 51b394a..be375bb 100644 --- a/lib/widgets/genre_selector_page.dart +++ b/lib/widgets/genre_selector_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../widgets/app_overlay.dart'; /// 类型/标签选择右侧弹窗(影视类型、导演、编剧、主演、书籍类型通用) class GenreSelectorPage extends StatefulWidget { @@ -109,7 +110,7 @@ class _GenreSelectorPageState extends State { void _editItem(int index, String oldValue) { final editController = TextEditingController(text: oldValue); - showDialog( + appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme; diff --git a/lib/widgets/movie_list_item.dart b/lib/widgets/movie_list_item.dart index 9f736e1..1c0af5f 100644 --- a/lib/widgets/movie_list_item.dart +++ b/lib/widgets/movie_list_item.dart @@ -5,6 +5,7 @@ import '../providers/app_provider.dart'; import '../widgets/fade_in_local_image.dart'; import '../widgets/animated_star_rating.dart'; import '../utils/toast_util.dart'; +import '../widgets/app_overlay.dart'; /// 观影列表项组件 - 网格布局设计 class MovieListItem extends StatelessWidget { @@ -73,7 +74,7 @@ class MovieListItem extends StatelessWidget { void _showDeleteDialog(BuildContext context) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (context) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/widgets/note_list_item.dart b/lib/widgets/note_list_item.dart index 5e55d7a..0761d04 100644 --- a/lib/widgets/note_list_item.dart +++ b/lib/widgets/note_list_item.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import '../providers/app_provider.dart'; import '../models/data_models.dart'; import 'fade_in_local_image.dart'; +import '../widgets/app_overlay.dart'; /// 笔记列表项组件 - 卡片式设计,内容展示在卡片内 class NoteListItem extends StatelessWidget { @@ -189,7 +190,7 @@ class _NoteListItemContent extends StatelessWidget { void _showActions(BuildContext context) { final colors = Theme.of(context).colorScheme; - showModalBottomSheet( + appModalBottomSheet( context: context, backgroundColor: colors.surface, shape: const RoundedRectangleBorder( @@ -272,7 +273,7 @@ class _NoteListItemContent extends StatelessWidget { void _showDeleteConfirm(BuildContext context) { final colors = Theme.of(context).colorScheme; - showDialog( + appDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surface, diff --git a/lib/widgets/person_info_sheet.dart b/lib/widgets/person_info_sheet.dart index 17939e4..776a564 100644 --- a/lib/widgets/person_info_sheet.dart +++ b/lib/widgets/person_info_sheet.dart @@ -5,6 +5,7 @@ import '../pages/people/person_detail_page.dart'; import '../providers/app_provider.dart'; import 'fade_in_local_image.dart'; import 'person_avatar.dart'; +import '../widgets/app_overlay.dart'; /// 人物信息浮动面板(底部 ModalBottomSheet) /// 展示人物基本信息 + 关联作品,点击「查看全部」跳转到原详情页 @@ -14,7 +15,7 @@ class PersonInfoSheet extends StatefulWidget { const PersonInfoSheet({super.key, required this.person}); static Future show(BuildContext context, Person person) { - return showModalBottomSheet( + return appModalBottomSheet( context: context, backgroundColor: Theme.of(context).colorScheme.surface, isScrollControlled: true, diff --git a/lib/widgets/work_selector_page.dart b/lib/widgets/work_selector_page.dart index 5cf4932..7416653 100644 --- a/lib/widgets/work_selector_page.dart +++ b/lib/widgets/work_selector_page.dart @@ -4,6 +4,7 @@ import 'package:uuid/uuid.dart'; import '../models/data_models.dart'; import '../providers/app_provider.dart'; import 'fade_in_local_image.dart'; +import '../widgets/app_overlay.dart'; /// 人物详情页使用的作品关联结果(按媒体类型分组) class WorkSelectionResult { @@ -382,7 +383,7 @@ class _WorkSelectorPageState extends State { final title = isVoiceActor ? '配音角色' : '饰演角色'; final hint = isVoiceActor ? '如:米老鼠' : '如:关羽'; final ctrl = TextEditingController(text: current ?? ''); - showDialog( + appDialog( context: context, builder: (ctx) { final colors = Theme.of(ctx).colorScheme;