From 4bc04fd8ae08aad764198158c4302b6cfd4d8c05 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Thu, 22 Oct 2026 13:49:20 +0800 Subject: [PATCH] =?UTF-8?q?style(theme):=20=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E6=B7=B1=E8=89=B2=E8=A7=86=E8=A7=89=E9=A3=8E?= =?UTF-8?q?=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/2026-10-22-dark-theme-client.md | 28 +++ docs/plans/PROGRESS.md | 55 ++++++ lib/app.dart | 18 +- lib/core/theme/app_colors.dart | 120 ++++++++----- lib/core/theme/app_dimens.dart | 26 +-- lib/core/theme/app_theme.dart | 164 +++++++++++++++--- lib/core/widgets/app_bottom_nav.dart | 12 +- lib/core/widgets/auth_gate.dart | 4 +- lib/core/widgets/catalog_endpoint_editor.dart | 3 +- lib/core/widgets/centered_toast.dart | 10 +- lib/core/widgets/empty_state.dart | 3 +- lib/core/widgets/glass_panel.dart | 4 +- lib/core/widgets/remote_image.dart | 2 +- lib/core/widgets/subpage_header.dart | 13 +- .../account/account_deactivate_page.dart | 2 +- lib/pages/account/account_devices_page.dart | 2 +- lib/pages/account/account_export_page.dart | 2 +- lib/pages/account/account_profile_page.dart | 3 +- lib/pages/auth/auth_form_widgets.dart | 2 +- lib/pages/auth/auth_view.dart | 2 +- lib/pages/auth/auth_widgets.dart | 2 +- lib/pages/auth/phone_auth_sheet.dart | 4 +- lib/pages/create/create_view.dart | 10 +- lib/pages/discover/discover_view.dart | 14 +- .../widgets/discover_search_field.dart | 2 +- lib/pages/discover/widgets/drama_grid.dart | 8 +- .../widgets/feed_banner_carousel.dart | 6 +- lib/pages/downloads/downloads_page.dart | 18 +- lib/pages/drama_detail/drama_detail_view.dart | 10 +- .../widgets/drama_detail_sections.dart | 2 +- lib/pages/favorites/favorites_page.dart | 4 +- lib/pages/filter/filter_page.dart | 2 +- lib/pages/home/home_view.dart | 18 +- lib/pages/home/widgets/home_drama_grid.dart | 2 +- lib/pages/mine/mine_view.dart | 4 +- lib/pages/mine/widgets/mine_header.dart | 2 +- lib/pages/mine/widgets/mine_menu.dart | 6 +- lib/pages/new_releases/new_releases_page.dart | 17 +- lib/pages/notices/notices_page.dart | 2 +- lib/pages/player/player_logic.dart | 15 +- lib/pages/player/player_view.dart | 24 +-- lib/pages/player/player_view_actions.dart | 6 +- .../player/widgets/drama_comments_sheet.dart | 7 +- .../player/widgets/episode_picker_sheet.dart | 8 +- .../widgets/exit_clear_screen_button.dart | 6 +- .../player/widgets/home_feed_header.dart | 22 ++- .../player/widgets/home_feed_overlay.dart | 26 +-- .../widgets/home_feed_overlay_parts.dart | 9 +- .../player/widgets/interaction_rail.dart | 6 +- .../widgets/loading_progress_track.dart | 6 +- .../player/widgets/player_center_toggle.dart | 4 +- .../player/widgets/player_more_sheet.dart | 8 +- lib/pages/player/widgets/player_overlay.dart | 6 +- .../player/widgets/player_overlay_parts.dart | 4 +- .../player/widgets/player_video_surface.dart | 6 +- .../player/widgets/scrub_preview_card.dart | 6 +- .../player/widgets/seek_progress_bar.dart | 2 +- lib/pages/ranking/ranking_page.dart | 12 +- lib/pages/reserve/reserve_page.dart | 10 +- lib/pages/settings/settings_page.dart | 2 +- lib/pages/store/store_page.dart | 2 +- lib/pages/wallet/wallet_page.dart | 2 +- .../watch_records/watch_records_page.dart | 2 +- .../widgets/watch_record_card.dart | 2 +- test/auth/auth_visual_test.dart | 6 +- test/core/subpage_header_test.dart | 12 +- test/empty_state/empty_state_test.dart | 8 +- test/home/app_bottom_nav_test.dart | 2 +- test/home/home_nav_ui_test.dart | 16 +- test/home/k28_a_navigation_test.dart | 4 +- test/player/loading_progress_track_test.dart | 5 +- test/player/player_fullscreen_test.dart | 5 +- test/theme/app_dimens_test.dart | 12 +- test/theme/app_theme_test.dart | 64 ++++--- test/theme/dark_theme_smoke_test.dart | 117 +++++++++++++ 75 files changed, 732 insertions(+), 330 deletions(-) create mode 100644 docs/plans/2026-10-22-dark-theme-client.md create mode 100644 test/theme/dark_theme_smoke_test.dart diff --git a/docs/plans/2026-10-22-dark-theme-client.md b/docs/plans/2026-10-22-dark-theme-client.md new file mode 100644 index 0000000..6f2e2fd --- /dev/null +++ b/docs/plans/2026-10-22-dark-theme-client.md @@ -0,0 +1,28 @@ +# ReelShort 客户端深色视觉改造 + +## 目标 + +- 深色设为默认且唯一主题,页面、卡片、浮层、播放器和底部导航保持一致。 +- 色板收敛到 `AppColors`,主题组件配置收敛到 `AppTheme`。 +- 保持现有布局、信息结构、路由、业务逻辑和文案不变。 + +## 结果 + +- 背景 `#0B1020`、卡片 `#151B2E`、浮层 `#1C2436`、分割线白色 6%。 +- 主文字白色 92%、次文字 64%、禁用 38%。 +- 主色 `#FF6B2C`,辅助链接色 `#22D3EE`。 +- 卡片 12、按钮/输入框 10、标签 6,阴影关闭,组件使用分割线分层。 +- 页面级透明、遮罩、阴影、媒体底、玻璃面和渐变全部映射到 AppColors 语义 token。 +- 系统栏统一浅色图标,AppBar 与底部导航使用深色表面。 + +## 验证 + +- 主题 token、对比度、系统栏、圆角与关键页 smoke 测试。 +- `dart analyze lib test` +- `flutter test --no-pub` +- `flutter build apk --debug` + +## 约束 + +- 不提交 `pubspec.lock` 漂移。 +- 不启停服务、不操作真机。 diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index eee3f2f..0213a52 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -730,3 +730,58 @@ $ flutter test --no-pub 解读:有简介时文本最多 2 行并省略,空简介不渲染且卡片高度与正常卡片一致; 全量静态分析零告警,631 项通过、1 项运行中旧后端能力探测跳过。 `pubspec.lock` 既有改动未提交。 + +## 2026-10-22 ReelShort 客户端深色视觉改造 + +- 分支:`feature/dark-theme-client` +- 基线:开发前已有约 43 个未提交视觉文件,本次在原改动上继续收敛,未推倒重来。 +- 计划:`docs/plans/2026-10-22-dark-theme-client.md` + +### 颜色与主题 + +- `AppTheme.dark()` 成为默认且唯一主题,系统状态栏/导航栏图标统一浅色。 +- 色板统一为背景 `#0B1020`、卡片 `#151B2E`、浮层 `#1C2436`、 + divider `#0FFFFFFF`、主色 `#FF6B2C`、辅助链接色 `#22D3EE`。 +- 主/次/禁用文字分别使用白色 92% / 64% / 38%。 +- 卡片/按钮/输入框/标签圆角统一为 12/10/10/6,卡片阴影关闭。 +- 页面和组件内的 `Colors.*`、`Color(0x...)` 已全部映射到 `AppColors` + 语义 token;剩余原始色值只在 `app_colors.dart` 中定义。 +- 导航、玻璃面板、播放器遮罩、加载轨道、渐变、边框和阴影均改为深色语义。 +- 修正全局输入装饰 padding 导致账户性别下拉溢出,保持原布局宽度。 + +### 验证命令与原始结果 + +```text +$ git checkout -- pubspec.lock +$ git status --short --branch +## feature/dark-theme-client + +$ rg -n -P "Color\(0x|(? { localeResolutionCallback: (deviceLocale, _) => resolveAppLocale(deviceLocale), debugShowCheckedModeBanner: false, - themeMode: ThemeMode.light, - theme: AppTheme.light(), - darkTheme: AppTheme.light(), + themeMode: ThemeMode.dark, + theme: AppTheme.dark(), + darkTheme: AppTheme.dark(), builder: (context, child) => AnnotatedRegion( value: appSystemOverlayStyle, child: child ?? const SizedBox.shrink(), diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index e8961a9..585fc68 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -2,107 +2,143 @@ import 'package:flutter/material.dart'; -/// K28-S 浅色暖橙主题色板。 +/// 深色视觉色板。 /// /// 保留既有语义名,避免页面大范围改名;实际颜色统一由本文件控制。 abstract final class AppColors { - /// 主操作、选中态、播放进度。 - static const blue = Color(0xFFFF7A1A); + /// 透明与媒体黑底。 + static const transparent = Colors.transparent; + static const mediaBlack = Color(0xFF000000); + + /// 阴影与遮罩层级。 + static const shadowSubtle = Color(0x14000000); + static const shadowSoft = Color(0x33000000); + static const shadow = Color(0x8A000000); + static const shadowStrong = Color(0xE6000000); + static const scrim = Color(0xD1000000); + static const controlScrim = Color(0x80000000); + static const controlSoftScrim = Color(0x42000000); + static const scrubPreviewScrim = Color(0xDE000000); + static const overlaySoft = Color(0x66000000); + static const overlayBar = Color(0x4D000000); + static const overlayMedium = Color(0x99000000); + static const headerScrim = Color(0x59000000); + + /// 主操作、选中态、播放进度(品牌橙)。 + static const blue = Color(0xFFFF6B2C); static const primary = blue; /// 主操作按下态。 - static const bluePressed = Color(0xFFFF6A00); + static const bluePressed = Color(0xFFE55A1F); - /// 粉色辅助强调色。 - static const blueSoft = Color(0xFFFF6A8A); + /// 链接与次要高亮(禁止大面积铺色)。 + static const blueSoft = Color(0xFF22D3EE); static const secondary = blueSoft; static const lavender = blueSoft; /// 金币与奖励。 static const gold = Color(0xFFF3B34C); + static const goldDeep = Color(0xFFD9A23A); + static const bronze = Color(0xFFCD7F32); + static const bronzeDeep = Color(0xFFA9713A); /// 成功状态。 static const success = Color(0xFF2FA873); - /// 收藏和喜欢使用粉色强调。 - static const favorite = secondary; + /// 错误与危险操作。 + static const error = Color(0xFFFF6B6B); + + /// 收藏与喜欢(非主色铺色,独立强调)。 + static const favorite = Color(0xFFFF6A8A); /// 金币底色上的文字。 - static const onGold = Color(0xFF5A3511); + static const onGold = Color(0xFF3D2408); /// 主色上的文字与图标。 static const onPrimary = Color(0xFFFFFFFF); /// 页面背景。 - static const ink = Color(0xFFFFF8F0); + static const ink = Color(0xFF0B1020); /// 原生开屏与冷启动过渡底色。 - static const splashInk = Color(0xFF1F1B16); + static const splashInk = ink; - /// 页面顶部暖色渐变起点。 - static const surfaceTop = Color(0xFFFFE9D6); + /// 顶部与页面底同色,靠容器层次区分。 + static const surfaceTop = ink; /// 底部导航底色。 - static const bar = Color(0xFFFFFDFB); + static const bar = ink; - /// 卡片轻阴影。 - static const cardShadow = Color(0x14FF8A3D); + /// 卡片阴影(深色几乎不用阴影)。 + static const cardShadow = Color(0x00000000); - /// 卡片与弹层。 - static const card = Color(0xFFFFFFFF); + /// 卡片与列表容器。 + static const card = Color(0xFF151B2E); /// 次要卡片表面。 - static const cardAlt = Color(0xFFFFF4E9); + static const cardAlt = card; - /// 浮层、输入框和嵌套区域。 - static const raised = Color(0xFFFFF1E5); - static const quick = Color(0xFFFFF0E1); - static const input = Color(0xFFFFF4EA); + /// 浮层、弹窗、输入与嵌套区域。 + static const raised = Color(0xFF1C2436); + static const quick = raised; + static const input = raised; /// 评论输入框底色。 - static const commentInput = Color(0xFFF5EFE8); - static const goldCard = Color(0xFFFFF4DF); + static const commentInput = raised; + static const goldCard = Color(0xFF2A2418); - /// 正文颜色。 - static const cream = Color(0xFF1F1B16); + /// 正文颜色(与主文字同级)。 + static const cream = Color(0xEBFFFFFF); - /// 主文字。 - static const onSurface = Color(0xFF1F1B16); + /// 主文字 rgba(255,255,255,0.92)。 + static const onSurface = Color(0xEBFFFFFF); - /// 次要文字。 - static const muted = Color(0xFF8C837B); + /// 次要文字 rgba(255,255,255,0.64)。 + static const muted = Color(0xA3FFFFFF); - /// 弱提示与时间。 - static const subtle = Color(0xFFB3AAA1); + /// 弱提示与禁用 rgba(255,255,255,0.38)。 + static const subtle = Color(0x61FFFFFF); /// 橙色强调文案。 static const accent = blue; - /// 卡片描边。 - static const border = Color(0xFFF0E4D6); + /// 分割线 rgba(255,255,255,0.06)。 + static const border = Color(0x0FFFFFFF); /// 玻璃面板描边。 - static const glassBorder = Color(0x66FFFFFF); + static const glassBorder = Color(0x33FFFFFF); + static const glassSurfaceStart = Color(0xEB1C2436); + static const glassSurfaceEnd = Color(0xD1151B2E); - /// 粉色角标。 - static const badgePink = Color(0xFFFFE3EC); - static const badgePinkText = Color(0xFFFF6A8A); + /// 角标底与文字。 + static const badgePink = Color(0x33FF6A8A); + static const badgePinkText = favorite; /// 封面锁定数量与视频遮罩。 - static const overlayDark = Color(0x33000000); + static const overlayDark = Color(0x66000000); /// 封面集数胶囊。 - static const badgeBeige = Color(0xFFFFF0DF); + static const badgeBeige = raised; /// 导航未选中。 static const navUnselected = muted; /// 播放器次要文字。 - static const playerMuted = Color(0xFFBBBBBB); + static const playerMuted = Color(0xA3FFFFFF); /// 播放器控件胶囊与圆形底色。 static const playerOverlayPill = Color(0xB3000000); + /// 进度条未激活轨道。 + static const inactiveTrack = Color(0x24FFFFFF); + + /// 横幅/封面底部渐变。 + static const bannerScrimMid = Color(0x8A0B1020); + static const bannerScrimEnd = Color(0xE6000000); + + /// 票券虚线等主色描边。 + static const primaryBorder = Color(0x66FF6B2C); + static const outlineDark = border; static const outlineVariantDark = border; static const outlineLight = muted; diff --git a/lib/core/theme/app_dimens.dart b/lib/core/theme/app_dimens.dart index 828b371..5de3484 100644 --- a/lib/core/theme/app_dimens.dart +++ b/lib/core/theme/app_dimens.dart @@ -9,18 +9,20 @@ abstract final class AppDimens { static const double spaceXl = 24; static const double pageHorizontal = 16; - static const double cardRadius = 16; - static const double authCardRadius = 20; + static const double cardRadius = 12; + static const double authCardRadius = 12; static const double cardPadding = 16; - static const double innerRadius = 12; + static const double innerRadius = 10; static const double innerPadding = 12; static const double pillRadius = 999; - static const double glassSmallRadius = 16; - static const double glassRadius = 20; - static const double glassCardRadius = 20; - static const double buttonRadius = 999; - static const double coverRadius = 16; - static const double sheetRadius = 20; + static const double glassSmallRadius = 12; + static const double glassRadius = 12; + static const double glassCardRadius = 12; + static const double buttonRadius = 10; + static const double inputRadius = 10; + static const double chipRadius = 6; + static const double coverRadius = 12; + static const double sheetRadius = 12; static const double caption = 12; static const double emptyStateSubtitle = 12.5; @@ -61,13 +63,13 @@ abstract final class AppDimens { static const double watchPlaceholderHeight = 114; static const double heroBannerHeight = 240; - static const double heroBannerRadius = 20; + static const double heroBannerRadius = 12; static const double heroTitle = 28; static const double heroSubtitle = 14; static const double cardMeta = 12; static const double cardViews = 11; static const double cardTitle = 15; - static const double cardShadowBlur = 18; - static const double cardShadowY = 6; + static const double cardShadowBlur = 0; + static const double cardShadowY = 0; } diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index 9683d2a..dc0003d 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -6,9 +6,9 @@ import 'package:flutter/services.dart'; import 'app_colors.dart'; import 'app_dimens.dart'; -/// K28-S 浅色暖橙主题。 +/// 深色视觉主题(默认且唯一)。 abstract final class AppTheme { - /// 页面顶部暖色到米白背景的渐变。 + /// 页面背景:深色扁平,靠容器层次区分。 static const LinearGradient pageGradient = LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, @@ -16,36 +16,50 @@ abstract final class AppTheme { stops: [0, 0.24], ); - /// 卡片轻阴影。 + /// 卡片阴影关闭,改用底色与分割线。 static const BoxShadow cardShadow = BoxShadow( color: AppColors.cardShadow, blurRadius: AppDimens.cardShadowBlur, offset: Offset(0, AppDimens.cardShadowY), ); - /// 全局浅色暖橙主题。 - static ThemeData light() { - const scheme = ColorScheme.light( + static final BorderRadius _buttonRadius = BorderRadius.circular( + AppDimens.buttonRadius, + ); + static final BorderRadius _cardRadius = BorderRadius.circular( + AppDimens.cardRadius, + ); + static final BorderRadius _inputRadius = BorderRadius.circular( + AppDimens.inputRadius, + ); + + /// 全局深色主题。 + static ThemeData dark() { + const scheme = ColorScheme.dark( primary: AppColors.blue, onPrimary: AppColors.onPrimary, secondary: AppColors.secondary, - onSecondary: AppColors.onPrimary, - primaryContainer: AppColors.surfaceTop, + onSecondary: AppColors.ink, + primaryContainer: AppColors.raised, onPrimaryContainer: AppColors.onSurface, secondaryContainer: AppColors.badgePink, onSecondaryContainer: AppColors.badgePinkText, surface: AppColors.ink, onSurface: AppColors.onSurface, + surfaceContainerHighest: AppColors.raised, outline: AppColors.border, outlineVariant: AppColors.border, - surfaceTint: AppColors.blue, + surfaceTint: AppColors.transparent, inversePrimary: AppColors.bluePressed, + error: AppColors.error, + onError: AppColors.onPrimary, ); final base = ThemeData( useMaterial3: true, - brightness: Brightness.light, + brightness: Brightness.dark, colorScheme: scheme, scaffoldBackgroundColor: AppColors.ink, + canvasColor: AppColors.ink, fontFamily: 'PlusJakartaSans', progressIndicatorTheme: const ProgressIndicatorThemeData( color: AppColors.blue, @@ -61,7 +75,23 @@ abstract final class AppTheme { unselectedLabelColor: AppColors.muted, ), textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom(foregroundColor: AppColors.blue), + style: TextButton.styleFrom( + foregroundColor: AppColors.secondary, + shape: RoundedRectangleBorder(borderRadius: _buttonRadius), + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.resolveWith(_buttonColor), + foregroundColor: const WidgetStatePropertyAll(AppColors.onPrimary), + elevation: const WidgetStatePropertyAll(0), + minimumSize: const WidgetStatePropertyAll( + Size(0, AppDimens.buttonHeight), + ), + shape: WidgetStatePropertyAll( + RoundedRectangleBorder(borderRadius: _buttonRadius), + ), + ), ), filledButtonTheme: FilledButtonThemeData( style: ButtonStyle( @@ -70,7 +100,9 @@ abstract final class AppTheme { minimumSize: const WidgetStatePropertyAll( Size(0, AppDimens.buttonHeight), ), - shape: const WidgetStatePropertyAll(StadiumBorder()), + shape: WidgetStatePropertyAll( + RoundedRectangleBorder(borderRadius: _buttonRadius), + ), ), ), outlinedButtonTheme: OutlinedButtonThemeData( @@ -82,14 +114,46 @@ abstract final class AppTheme { side: const WidgetStatePropertyAll( BorderSide(color: AppColors.border), ), - shape: const WidgetStatePropertyAll(StadiumBorder()), + shape: WidgetStatePropertyAll( + RoundedRectangleBorder(borderRadius: _buttonRadius), + ), + ), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: AppColors.input, + hintStyle: const TextStyle(color: AppColors.subtle), + labelStyle: const TextStyle(color: AppColors.muted), + border: OutlineInputBorder( + borderRadius: _inputRadius, + borderSide: const BorderSide(color: AppColors.border), + ), + enabledBorder: OutlineInputBorder( + borderRadius: _inputRadius, + borderSide: const BorderSide(color: AppColors.border), + ), + focusedBorder: OutlineInputBorder( + borderRadius: _inputRadius, + borderSide: const BorderSide(color: AppColors.blue), + ), + ), + chipTheme: ChipThemeData( + backgroundColor: AppColors.raised, + selectedColor: AppColors.blue.withValues(alpha: 0.22), + labelStyle: const TextStyle(color: AppColors.onSurface), + secondaryLabelStyle: const TextStyle(color: AppColors.muted), + side: const BorderSide(color: AppColors.border), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppDimens.chipRadius), ), ), appBarTheme: const AppBarTheme( - backgroundColor: AppColors.ink, + backgroundColor: AppColors.card, foregroundColor: AppColors.onSurface, elevation: 0, - systemOverlayStyle: SystemUiOverlayStyle.dark, + scrolledUnderElevation: 0, + surfaceTintColor: AppColors.transparent, + systemOverlayStyle: SystemUiOverlayStyle.light, titleTextStyle: TextStyle( color: AppColors.onSurface, fontFamily: 'Outfit', @@ -98,19 +162,47 @@ abstract final class AppTheme { ), ), iconTheme: const IconThemeData(color: AppColors.onSurface), - dividerTheme: const DividerThemeData(color: AppColors.border), - cardTheme: const CardThemeData( + dividerTheme: const DividerThemeData( + color: AppColors.border, + thickness: 1, + space: 1, + ), + cardTheme: CardThemeData( color: AppColors.card, elevation: 0, + shadowColor: AppColors.transparent, + surfaceTintColor: AppColors.transparent, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(AppDimens.cardRadius)), - side: BorderSide(color: AppColors.border), + borderRadius: _cardRadius, + side: const BorderSide(color: AppColors.border), ), ), + dialogTheme: DialogThemeData( + backgroundColor: AppColors.raised, + surfaceTintColor: AppColors.transparent, + shape: RoundedRectangleBorder(borderRadius: _cardRadius), + titleTextStyle: const TextStyle( + color: AppColors.onSurface, + fontFamily: 'Outfit', + fontSize: AppDimens.sectionTitle, + fontWeight: FontWeight.w700, + ), + contentTextStyle: const TextStyle( + color: AppColors.muted, + fontSize: AppDimens.body, + ), + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: AppColors.bar, + selectedItemColor: AppColors.blue, + unselectedItemColor: AppColors.navUnselected, + elevation: 0, + type: BottomNavigationBarType.fixed, + ), bottomSheetTheme: const BottomSheetThemeData( - backgroundColor: Colors.transparent, - modalBackgroundColor: Colors.transparent, - surfaceTintColor: Colors.transparent, + backgroundColor: AppColors.transparent, + modalBackgroundColor: AppColors.transparent, + surfaceTintColor: AppColors.transparent, ), switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith( @@ -124,8 +216,13 @@ abstract final class AppTheme { ), ), snackBarTheme: const SnackBarThemeData( - backgroundColor: AppColors.onSurface, - contentTextStyle: TextStyle(color: AppColors.onPrimary), + backgroundColor: AppColors.raised, + contentTextStyle: TextStyle(color: AppColors.onSurface), + actionTextColor: AppColors.secondary, + ), + listTileTheme: const ListTileThemeData( + iconColor: AppColors.onSurface, + textColor: AppColors.onSurface, ), ); final textTheme = base.textTheme @@ -142,10 +239,29 @@ abstract final class AppTheme { headlineMedium: _display(base.textTheme.headlineMedium), headlineSmall: _display(base.textTheme.headlineSmall), titleLarge: _display(base.textTheme.titleLarge), + bodyLarge: base.textTheme.bodyLarge?.copyWith( + color: AppColors.onSurface, + ), + bodyMedium: base.textTheme.bodyMedium?.copyWith( + color: AppColors.onSurface, + ), + bodySmall: base.textTheme.bodySmall?.copyWith(color: AppColors.muted), + labelLarge: base.textTheme.labelLarge?.copyWith( + color: AppColors.onSurface, + ), + labelMedium: base.textTheme.labelMedium?.copyWith( + color: AppColors.muted, + ), + labelSmall: base.textTheme.labelSmall?.copyWith( + color: AppColors.subtle, + ), ); return base.copyWith(textTheme: textTheme); } + /// 兼容旧调用点,统一落到深色主题。 + static ThemeData light() => dark(); + static TextStyle? _display(TextStyle? style) => style?.copyWith(fontFamily: 'Outfit', color: AppColors.onSurface); diff --git a/lib/core/widgets/app_bottom_nav.dart b/lib/core/widgets/app_bottom_nav.dart index 102feb7..9b29d9d 100644 --- a/lib/core/widgets/app_bottom_nav.dart +++ b/lib/core/widgets/app_bottom_nav.dart @@ -26,7 +26,7 @@ class AppBottomNavItem { final bool selected; } -/// 四槽底部导航:首页 feed 用深色样式,其它页面保持浅色暖橙风格。 +/// 四槽底部导航:所有页面统一深色底与浅色图标。 class AppBottomNav extends StatelessWidget { const AppBottomNav({ super.key, @@ -47,24 +47,22 @@ class AppBottomNav extends StatelessWidget { key: barKey, height: AppDimens.navHeight, decoration: BoxDecoration( - color: dark ? Colors.black : backgroundColor.withValues(alpha: 0.95), - border: Border( - top: BorderSide(color: dark ? Colors.white12 : AppColors.border), - ), + color: dark ? AppColors.bar : backgroundColor.withValues(alpha: 0.96), + border: const Border(top: BorderSide(color: AppColors.border)), ), child: Row( children: [ for (final item in items) _NavDestination( item: item, - unselectedColor: dark ? Colors.white70 : AppColors.navUnselected, + unselectedColor: AppColors.navUnselected, ), ], ), ); if (dark) { return ColoredBox( - color: Colors.black, + color: AppColors.bar, child: SafeArea(top: false, child: bar), ); } diff --git a/lib/core/widgets/auth_gate.dart b/lib/core/widgets/auth_gate.dart index 6d5e079..9c78b84 100644 --- a/lib/core/widgets/auth_gate.dart +++ b/lib/core/widgets/auth_gate.dart @@ -37,7 +37,7 @@ Future ensureSignedIn(BuildContext context) async { Text( l10n.authGateTitle, style: const TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 16, fontWeight: FontWeight.w800, ), @@ -98,7 +98,7 @@ class AuthGatePanel extends StatelessWidget { l10n.authGateTitle, textAlign: TextAlign.center, style: const TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 18, fontWeight: FontWeight.w800, ), diff --git a/lib/core/widgets/catalog_endpoint_editor.dart b/lib/core/widgets/catalog_endpoint_editor.dart index e5922ba..1016672 100644 --- a/lib/core/widgets/catalog_endpoint_editor.dart +++ b/lib/core/widgets/catalog_endpoint_editor.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import '../config/app_config.dart'; import '../config/server_endpoint.dart'; +import '../theme/app_colors.dart'; const String _title = '设置服务地址'; const String _save = '保存'; @@ -94,7 +95,7 @@ class _CatalogEndpointDialogState extends State<_CatalogEndpointDialog> { const Text( _localNetworkHint, key: Key('catalog-debug-endpoint-hint'), - style: TextStyle(fontSize: 12, color: Colors.grey), + style: TextStyle(fontSize: 12, color: AppColors.muted), ), if (_error != null) ...[ const SizedBox(height: 8), diff --git a/lib/core/widgets/centered_toast.dart b/lib/core/widgets/centered_toast.dart index 709be8a..23f9e10 100644 --- a/lib/core/widgets/centered_toast.dart +++ b/lib/core/widgets/centered_toast.dart @@ -4,6 +4,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import '../theme/app_colors.dart'; + const Key _toastKey = Key('centered-toast'); const Duration _fadeDuration = Duration(milliseconds: 200); const Duration _visibleDuration = Duration(seconds: 2); @@ -11,9 +13,8 @@ const double _toastRadius = 12; const double _toastMaxWidth = 320; const double _toastHorizontalInset = 20; const double _toastVerticalInset = 12; -const double _toastBackgroundOpacity = 0.78; const TextStyle _toastTextStyle = TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 14, height: 1.4, fontWeight: FontWeight.w500, @@ -108,10 +109,9 @@ class _CenteredToastState extends State<_CenteredToast> constraints: const BoxConstraints(maxWidth: _toastMaxWidth), padding: _toastPadding, decoration: BoxDecoration( - color: Colors.black.withValues( - alpha: _toastBackgroundOpacity, - ), + color: AppColors.raised, borderRadius: BorderRadius.circular(_toastRadius), + border: Border.all(color: AppColors.border), ), child: Text( widget.message, diff --git a/lib/core/widgets/empty_state.dart b/lib/core/widgets/empty_state.dart index 75b3ca3..08eac68 100644 --- a/lib/core/widgets/empty_state.dart +++ b/lib/core/widgets/empty_state.dart @@ -87,12 +87,11 @@ class _EmptyMark extends StatelessWidget { @override Widget build(BuildContext context) { - final dark = Theme.of(context).brightness == Brightness.dark; return Icon( icon, key: const Key('empty-state-icon'), size: compact ? _compactIconSize : _regularIconSize, - color: dark ? AppColors.gold : AppColors.blue, + color: AppColors.blueSoft, ); } } diff --git a/lib/core/widgets/glass_panel.dart b/lib/core/widgets/glass_panel.dart index 1edd1fb..c790938 100644 --- a/lib/core/widgets/glass_panel.dart +++ b/lib/core/widgets/glass_panel.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import '../theme/app_colors.dart'; import '../theme/app_dimens.dart'; -/// 首页信息卡与浮层控件使用的浅色玻璃面板。 +/// 首页信息卡与浮层控件使用的深色玻璃面板。 class GlassPanel extends StatelessWidget { const GlassPanel({ super.key, @@ -38,7 +38,7 @@ class GlassPanel extends StatelessWidget { const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [Color(0xE6FFFFFF), Color(0xB3FFFDFB)], + colors: [AppColors.glassSurfaceStart, AppColors.glassSurfaceEnd], ), ), child: Padding(padding: padding ?? EdgeInsets.zero, child: child), diff --git a/lib/core/widgets/remote_image.dart b/lib/core/widgets/remote_image.dart index 60d10be..9711f2b 100644 --- a/lib/core/widgets/remote_image.dart +++ b/lib/core/widgets/remote_image.dart @@ -16,7 +16,7 @@ class RemoteImage extends StatelessWidget { this.showFallbackIcon = false, this.fallbackIconKey, this.fallbackAspectRatio, - this.fallbackColor = AppColors.bar, + this.fallbackColor = AppColors.raised, }); final String path; diff --git a/lib/core/widgets/subpage_header.dart b/lib/core/widgets/subpage_header.dart index 6924433..ed8131f 100644 --- a/lib/core/widgets/subpage_header.dart +++ b/lib/core/widgets/subpage_header.dart @@ -16,9 +16,6 @@ const double _iconSize = 22; const double _iconWeight = 600; const double _borderWidth = 1; const double _shadowBlur = 8; -const Color _lightShadowColor = Color(0x14000000); -const Color _darkBackground = Color(0x59000000); -const Color _darkShadowColor = Color(0x33000000); const Offset _lightShadowOffset = Offset(0, 2); /// 二级页面统一圆形操作按钮,返回、分享和更多共用同一尺寸与表面样式。 @@ -52,14 +49,14 @@ class AppSubpageCircleButton extends StatelessWidget { shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: dark ? _darkShadowColor : _lightShadowColor, + color: dark ? AppColors.shadowSoft : AppColors.shadowSubtle, blurRadius: _shadowBlur, offset: dark ? Offset.zero : _lightShadowOffset, ), ], ), child: Material( - color: dark ? _darkBackground : AppColors.card, + color: dark ? AppColors.headerScrim : AppColors.card, shape: CircleBorder( side: dark ? BorderSide.none @@ -75,7 +72,7 @@ class AppSubpageCircleButton extends StatelessWidget { child: Center( child: Icon( icon, - color: dark ? Colors.white : AppColors.onSurface, + color: AppColors.onSurface, size: _iconSize, weight: _iconWeight, ), @@ -134,8 +131,8 @@ class AppSubpageHeader extends StatelessWidget { @override Widget build(BuildContext context) { - final foreground = dark ? Colors.white : AppColors.onSurface; - final secondary = dark ? Colors.white70 : AppColors.muted; + final foreground = AppColors.onSurface; + final secondary = AppColors.muted; return SizedBox( key: const Key('subpage-header'), height: subtitle == null ? _height : _heightWithSubtitle, diff --git a/lib/pages/account/account_deactivate_page.dart b/lib/pages/account/account_deactivate_page.dart index f5a123b..73d4711 100644 --- a/lib/pages/account/account_deactivate_page.dart +++ b/lib/pages/account/account_deactivate_page.dart @@ -51,7 +51,7 @@ class _AccountDeactivatePageState extends State { final signedIn = context.watch().isSignedIn; final canShow = signedIn || _logic.scheduledAt != null; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: PlainPageScaffold( pageKey: const Key('account-deactivate-page'), backKey: const Key('account-deactivate-back'), diff --git a/lib/pages/account/account_devices_page.dart b/lib/pages/account/account_devices_page.dart index 99f3101..e35ccb6 100644 --- a/lib/pages/account/account_devices_page.dart +++ b/lib/pages/account/account_devices_page.dart @@ -51,7 +51,7 @@ class _AccountDevicesPageState extends State { final l10n = AppLocalizations.of(context)!; final signedIn = context.watch().isSignedIn; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: PlainPageScaffold( pageKey: const Key('account-devices-page'), backKey: const Key('account-devices-back'), diff --git a/lib/pages/account/account_export_page.dart b/lib/pages/account/account_export_page.dart index f451b27..4d134cd 100644 --- a/lib/pages/account/account_export_page.dart +++ b/lib/pages/account/account_export_page.dart @@ -52,7 +52,7 @@ class _AccountExportPageState extends State { final l10n = AppLocalizations.of(context)!; final signedIn = context.watch().isSignedIn; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: PlainPageScaffold( pageKey: const Key('account-export-page'), backKey: const Key('account-export-back'), diff --git a/lib/pages/account/account_profile_page.dart b/lib/pages/account/account_profile_page.dart index 46ebb99..87b15d4 100644 --- a/lib/pages/account/account_profile_page.dart +++ b/lib/pages/account/account_profile_page.dart @@ -70,7 +70,7 @@ class _AccountProfilePageState extends State { final l10n = AppLocalizations.of(context)!; final signedIn = context.watch().isSignedIn; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: PlainPageScaffold( pageKey: const Key('account-profile-page'), backKey: const Key('account-profile-back'), @@ -181,6 +181,7 @@ class _AccountProfilePageState extends State { Widget _genderField(AppLocalizations l10n) => DropdownButtonFormField( key: const Key('account-gender'), + isExpanded: true, initialValue: _gender, decoration: InputDecoration(labelText: l10n.accountGender), items: [ diff --git a/lib/pages/auth/auth_form_widgets.dart b/lib/pages/auth/auth_form_widgets.dart index 81a58a7..b46fd1a 100644 --- a/lib/pages/auth/auth_form_widgets.dart +++ b/lib/pages/auth/auth_form_widgets.dart @@ -268,7 +268,7 @@ class _ModeSegment extends StatelessWidget { duration: _segmentDuration, alignment: Alignment.center, decoration: BoxDecoration( - color: selected ? AppColors.blue : Colors.transparent, + color: selected ? AppColors.blue : AppColors.transparent, borderRadius: BorderRadius.circular(18), ), child: Stack( diff --git a/lib/pages/auth/auth_view.dart b/lib/pages/auth/auth_view.dart index 9d5106a..5cf37b5 100644 --- a/lib/pages/auth/auth_view.dart +++ b/lib/pages/auth/auth_view.dart @@ -258,7 +258,7 @@ class _AuthPageState extends State { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: Scaffold( resizeToAvoidBottomInset: false, backgroundColor: AppColors.ink, diff --git a/lib/pages/auth/auth_widgets.dart b/lib/pages/auth/auth_widgets.dart index 9355158..55b160e 100644 --- a/lib/pages/auth/auth_widgets.dart +++ b/lib/pages/auth/auth_widgets.dart @@ -141,7 +141,7 @@ class _AuthModeSwitch extends StatelessWidget { style: TextButton.styleFrom( minimumSize: const Size(0, _minTapTarget), padding: EdgeInsets.zero, - overlayColor: Colors.transparent, + overlayColor: AppColors.transparent, splashFactory: NoSplash.splashFactory, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(22)), ), diff --git a/lib/pages/auth/phone_auth_sheet.dart b/lib/pages/auth/phone_auth_sheet.dart index 0355821..9fe651b 100644 --- a/lib/pages/auth/phone_auth_sheet.dart +++ b/lib/pages/auth/phone_auth_sheet.dart @@ -28,8 +28,8 @@ Future showPhoneAuthSheet(BuildContext context, {bool signUp = false}) { context: context, isScrollControlled: true, useSafeArea: true, - backgroundColor: Colors.transparent, - barrierColor: Colors.black.withValues(alpha: 0.62), + backgroundColor: AppColors.transparent, + barrierColor: AppColors.mediaBlack.withValues(alpha: 0.62), builder: (_) => PhoneAuthSheet(initialSignUp: signUp), ); } diff --git a/lib/pages/create/create_view.dart b/lib/pages/create/create_view.dart index bf6e729..d354834 100644 --- a/lib/pages/create/create_view.dart +++ b/lib/pages/create/create_view.dart @@ -13,8 +13,8 @@ const double _arrowSize = 36; const double _illustrationWidth = 156; const double _illustrationHeight = 88; const double _mottoLineWidth = 96; -const List _aiGradientStart = [Color(0xFFFFE3D0), Color(0xFFFFC9AC)]; -const List _uploadGradient = [Color(0xFFFFEAD9), Color(0xFFFFD3B3)]; +const List _aiGradientStart = [AppColors.raised, AppColors.goldCard]; +const List _uploadGradient = [AppColors.card, AppColors.raised]; /// 创作页:AI 制作与上传发布两张主卡,底部品牌标语。 class CreatePage extends StatelessWidget { @@ -24,7 +24,7 @@ class CreatePage extends StatelessWidget { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: SafeArea( bottom: false, child: ListView( @@ -131,7 +131,7 @@ class _CreateCard extends StatelessWidget { boxShadow: const [AppTheme.cardShadow], ), child: Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(_cardRadius), clipBehavior: Clip.antiAlias, child: InkWell( @@ -296,7 +296,7 @@ class _UploadIllustration extends StatelessWidget { width: 30, height: 20, decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.55), + color: AppColors.onSurface.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(4), ), child: const Icon( diff --git a/lib/pages/discover/discover_view.dart b/lib/pages/discover/discover_view.dart index ef3771a..961f09b 100644 --- a/lib/pages/discover/discover_view.dart +++ b/lib/pages/discover/discover_view.dart @@ -35,8 +35,8 @@ import 'widgets/discover_header.dart'; import 'widgets/discover_search_results.dart'; import 'widgets/drama_grid.dart'; -const Color _filterIconColor = Color(0xFF7B61FF); -const Color _newIconColor = Color(0xFF14B8A6); +const Color _filterIconColor = AppColors.blueSoft; +const Color _newIconColor = AppColors.blueSoft; const double _toolbarHeight = 36; const double _toolbarGap = 10; const double _toolbarRadius = 10; @@ -165,9 +165,9 @@ class _DiscoverPageState extends State { ), ); return AnnotatedRegion( - value: SystemUiOverlayStyle.dark, + value: SystemUiOverlayStyle.light, child: Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: embedded ? content : Scaffold(backgroundColor: AppColors.ink, body: content), @@ -228,13 +228,13 @@ class _DiscoverPageState extends State { color: AppColors.ink, border: Border( bottom: BorderSide( - color: pinned ? AppColors.border : Colors.transparent, + color: pinned ? AppColors.border : AppColors.transparent, ), ), boxShadow: pinned ? const [ BoxShadow( - color: Color(0x14000000), + color: AppColors.shadowSubtle, blurRadius: 8, offset: Offset(0, 2), ), @@ -419,7 +419,7 @@ class _DiscoverPageState extends State { _categoryUnderlineHeight, ), painter: CategoryUnderlinePainter( - color: selected ? AppColors.blue : Colors.transparent, + color: selected ? AppColors.blue : AppColors.transparent, ), ), ], diff --git a/lib/pages/discover/widgets/discover_search_field.dart b/lib/pages/discover/widgets/discover_search_field.dart index 345d408..0ddcc2f 100644 --- a/lib/pages/discover/widgets/discover_search_field.dart +++ b/lib/pages/discover/widgets/discover_search_field.dart @@ -97,7 +97,7 @@ class _DiscoverSearchFieldState extends State { color: AppColors.card, borderRadius: BorderRadius.circular(_searchRadius), border: Border.all( - color: _focused ? AppColors.blue : Colors.transparent, + color: _focused ? AppColors.blue : AppColors.transparent, ), ), child: Row( diff --git a/lib/pages/discover/widgets/drama_grid.dart b/lib/pages/discover/widgets/drama_grid.dart index b58d8cf..a76d7eb 100644 --- a/lib/pages/discover/widgets/drama_grid.dart +++ b/lib/pages/discover/widgets/drama_grid.dart @@ -23,8 +23,8 @@ const double _tagChipFontSize = 11; const double _heatFontSize = 9; const double _heatIconSize = 11; const int _maxTags = 3; -const Color _heatBackground = Color(0x99000000); -const Color _tagChipBackground = Color(0xFFF4F1EE); +const Color _heatBackground = AppColors.overlayMedium; +const Color _tagChipBackground = AppColors.raised; final RegExp _tagSplitter = RegExp(r'[,/、·]'); /// 剧场/分类页共用的双列剧目网格。 @@ -231,11 +231,11 @@ class _DiscoverCoverImage extends StatelessWidget { Widget _placeholder() { return const ColoredBox( - color: AppColors.card, + color: AppColors.raised, child: Center( child: Icon( Icons.image_not_supported_outlined, - color: AppColors.muted, + color: AppColors.subtle, size: 32, ), ), diff --git a/lib/pages/discover/widgets/feed_banner_carousel.dart b/lib/pages/discover/widgets/feed_banner_carousel.dart index b48cc83..839071c 100644 --- a/lib/pages/discover/widgets/feed_banner_carousel.dart +++ b/lib/pages/discover/widgets/feed_banner_carousel.dart @@ -183,8 +183,8 @@ class _FeedBannerCarouselState extends State { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: drama == null - ? const [Color(0x00000000), Color(0x8A0B0C10)] - : const [Color(0x00000000), Color(0xE6000000)], + ? const [AppColors.transparent, AppColors.bannerScrimMid] + : const [AppColors.transparent, AppColors.bannerScrimEnd], ), ), ), @@ -232,7 +232,7 @@ class _FeedBannerCarouselState extends State { child: dramaId == null ? card : Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: borderRadius, clipBehavior: Clip.antiAlias, child: InkWell( diff --git a/lib/pages/downloads/downloads_page.dart b/lib/pages/downloads/downloads_page.dart index 359c2a7..4be91fc 100644 --- a/lib/pages/downloads/downloads_page.dart +++ b/lib/pages/downloads/downloads_page.dart @@ -56,7 +56,7 @@ class _DownloadsPageState extends State { final l10n = AppLocalizations.of(context)!; final signedIn = context.watch().isSignedIn; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: PlainPageScaffold( pageKey: const Key('downloads-page'), backKey: const Key('downloads-back'), @@ -105,7 +105,7 @@ class _DownloadsBody extends StatelessWidget { child: Text( l10n.downloadedItems, style: const TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 16, fontWeight: FontWeight.w700, ), @@ -236,7 +236,7 @@ class _PolicyPanel extends StatelessWidget { Text( l10n.downloadQuota(policy.remaining, policy.maxPerDevice), style: const TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 17, fontWeight: FontWeight.w800, ), @@ -261,7 +261,7 @@ class _PolicyPanel extends StatelessWidget { child: Text( l10n.downloadWifiOnly, style: const TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: AppDimens.body, ), ), @@ -279,7 +279,7 @@ class _PolicyPanel extends StatelessWidget { child: Text( l10n.downloadQuality, style: const TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: AppDimens.body, ), ), @@ -375,7 +375,7 @@ class _LicenseTile extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 15, fontWeight: FontWeight.w700, ), @@ -418,12 +418,12 @@ class _LicenseTile extends StatelessWidget { ), Text( l10n.downloadProgress(license.progressPercent), - style: const TextStyle(color: Colors.white70, fontSize: 12), + style: const TextStyle(color: AppColors.muted, fontSize: 12), ), if (license.quality.isNotEmpty) Text( license.quality, - style: const TextStyle(color: Colors.white70, fontSize: 12), + style: const TextStyle(color: AppColors.muted, fontSize: 12), ), ], ), @@ -433,7 +433,7 @@ class _LicenseTile extends StatelessWidget { child: LinearProgressIndicator( minHeight: AppDimens.progressHeight, value: progress.clamp(0.0, 1.0), - backgroundColor: Colors.white24, + backgroundColor: AppColors.border, valueColor: const AlwaysStoppedAnimation(AppColors.blueSoft), ), ), diff --git a/lib/pages/drama_detail/drama_detail_view.dart b/lib/pages/drama_detail/drama_detail_view.dart index f3bcb48..eaedc8e 100644 --- a/lib/pages/drama_detail/drama_detail_view.dart +++ b/lib/pages/drama_detail/drama_detail_view.dart @@ -88,15 +88,15 @@ class _DramaDetailPageState extends State { Widget build(BuildContext context) { return AnnotatedRegion( key: const Key('detail-system-overlay'), - value: SystemUiOverlayStyle.dark.copyWith( - statusBarColor: Colors.transparent, + value: SystemUiOverlayStyle.light.copyWith( + statusBarColor: AppColors.transparent, systemNavigationBarColor: AppColors.ink, - systemNavigationBarDividerColor: Colors.transparent, - systemNavigationBarIconBrightness: Brightness.dark, + systemNavigationBarDividerColor: AppColors.transparent, + systemNavigationBarIconBrightness: Brightness.light, systemNavigationBarContrastEnforced: false, ), child: Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: ListenableBuilder( listenable: logic, builder: (context, _) => _page(context), diff --git a/lib/pages/drama_detail/widgets/drama_detail_sections.dart b/lib/pages/drama_detail/widgets/drama_detail_sections.dart index 9b2e559..1db22b8 100644 --- a/lib/pages/drama_detail/widgets/drama_detail_sections.dart +++ b/lib/pages/drama_detail/widgets/drama_detail_sections.dart @@ -330,7 +330,7 @@ class DramaEpisodeGrid extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(_chipRadius), side: BorderSide( - color: selected || locked ? Colors.transparent : AppColors.border, + color: selected || locked ? AppColors.transparent : AppColors.border, ), ), clipBehavior: Clip.antiAlias, diff --git a/lib/pages/favorites/favorites_page.dart b/lib/pages/favorites/favorites_page.dart index 11512c3..5333467 100644 --- a/lib/pages/favorites/favorites_page.dart +++ b/lib/pages/favorites/favorites_page.dart @@ -75,7 +75,7 @@ class _FavoritesPageState extends State { final l10n = AppLocalizations.of(context)!; final signedIn = context.watch().isSignedIn; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: Scaffold( key: const Key('favorites-page'), backgroundColor: AppColors.ink, @@ -264,7 +264,7 @@ class _FavoriteCard extends StatelessWidget { boxShadow: const [AppTheme.cardShadow], ), child: Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(_cardRadius), clipBehavior: Clip.antiAlias, child: InkWell( diff --git a/lib/pages/filter/filter_page.dart b/lib/pages/filter/filter_page.dart index 2def7eb..43dc6dc 100644 --- a/lib/pages/filter/filter_page.dart +++ b/lib/pages/filter/filter_page.dart @@ -58,7 +58,7 @@ class _FilterPageState extends State { ); final results = _filtered(catalog.dramas); return AnnotatedRegion( - value: SystemUiOverlayStyle.dark, + value: SystemUiOverlayStyle.light, child: Scaffold( key: const Key('filter-page'), backgroundColor: AppColors.ink, diff --git a/lib/pages/home/home_view.dart b/lib/pages/home/home_view.dart index dcd4075..6a3ab4c 100644 --- a/lib/pages/home/home_view.dart +++ b/lib/pages/home/home_view.dart @@ -100,7 +100,7 @@ class _HomePageState extends State with WidgetsBindingObserver { final catalog = context.watch(); final l10n = AppLocalizations.of(context)!; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: _buildHome(context, catalog, l10n), ); } @@ -115,7 +115,7 @@ class _HomePageState extends State with WidgetsBindingObserver { } if (catalog.error != null) { return Scaffold( - backgroundColor: Colors.transparent, + backgroundColor: AppColors.transparent, body: HomeFailureView( error: catalog.debugError ?? catalog.error, onRetry: () => unawaited(_retryHome(catalog)), @@ -125,7 +125,7 @@ class _HomePageState extends State with WidgetsBindingObserver { } if (catalog.dramas.isEmpty) { return Scaffold( - backgroundColor: Colors.transparent, + backgroundColor: AppColors.transparent, body: EmptyState( key: const Key('home-empty'), icon: Icons.movie_outlined, @@ -271,14 +271,14 @@ class _HomeTabsState extends State<_HomeTabs> { }, child: Scaffold( key: logic.scaffoldKey, - backgroundColor: Colors.transparent, + backgroundColor: AppColors.transparent, resizeToAvoidBottomInset: false, body: _body(context), bottomNavigationBar: logic.videoImmersive ? null : _nav(context, l10n), ), ); return AnnotatedRegion( - value: feed ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, + value: SystemUiOverlayStyle.light, child: feed ? content : DecoratedBox( @@ -352,13 +352,13 @@ class _HomeTabsState extends State<_HomeTabs> { child: IgnorePointer( key: const Key('home-feed-channel-loading'), child: ColoredBox( - color: Colors.black26, + color: AppColors.controlSoftScrim, child: Center( child: SizedBox.square( dimension: 24, child: CircularProgressIndicator( strokeWidth: 2.4, - color: Colors.white70, + color: AppColors.muted, ), ), ), @@ -374,7 +374,7 @@ class _HomeTabsState extends State<_HomeTabs> { return Stack( fit: StackFit.expand, children: [ - const ColoredBox(color: Colors.black), + const ColoredBox(color: AppColors.mediaBlack), SafeArea( bottom: false, child: Column( @@ -395,7 +395,7 @@ class _HomeTabsState extends State<_HomeTabs> { child: CircularProgressIndicator( key: Key('home-feed-channel-loading'), strokeWidth: 2.4, - color: Colors.white70, + color: AppColors.muted, ), ), ) diff --git a/lib/pages/home/widgets/home_drama_grid.dart b/lib/pages/home/widgets/home_drama_grid.dart index a5b82ce..c923237 100644 --- a/lib/pages/home/widgets/home_drama_grid.dart +++ b/lib/pages/home/widgets/home_drama_grid.dart @@ -86,7 +86,7 @@ class HomeDramaCard extends StatelessWidget { boxShadow: const [AppTheme.cardShadow], ), child: Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(AppDimens.cardRadius), clipBehavior: Clip.antiAlias, child: InkWell( diff --git a/lib/pages/mine/mine_view.dart b/lib/pages/mine/mine_view.dart index 3b777a5..cb3ac65 100644 --- a/lib/pages/mine/mine_view.dart +++ b/lib/pages/mine/mine_view.dart @@ -91,7 +91,7 @@ class _MyPageState extends State { ), ); return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: embedded ? content : Scaffold(backgroundColor: AppColors.ink, body: content), @@ -223,7 +223,7 @@ class _MyPageState extends State { Future _openLanguage() async { final selected = await showModalBottomSheet( context: context, - backgroundColor: Colors.transparent, + backgroundColor: AppColors.transparent, showDragHandle: false, builder: (_) => _LanguageSheet(selected: context.read().locale), diff --git a/lib/pages/mine/widgets/mine_header.dart b/lib/pages/mine/widgets/mine_header.dart index cce6ebc..9b80dbf 100644 --- a/lib/pages/mine/widgets/mine_header.dart +++ b/lib/pages/mine/widgets/mine_header.dart @@ -36,7 +36,7 @@ class MineHeader extends StatelessWidget { boxShadow: const [AppTheme.cardShadow], ), child: Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(AppDimens.cardRadius), clipBehavior: Clip.antiAlias, child: InkWell( diff --git a/lib/pages/mine/widgets/mine_menu.dart b/lib/pages/mine/widgets/mine_menu.dart index 8fc6270..7eab07a 100644 --- a/lib/pages/mine/widgets/mine_menu.dart +++ b/lib/pages/mine/widgets/mine_menu.dart @@ -71,7 +71,7 @@ class _ShortcutTile extends StatelessWidget { boxShadow: const [AppTheme.cardShadow], ), child: Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(AppDimens.cardRadius), clipBehavior: Clip.antiAlias, child: InkWell( @@ -198,7 +198,7 @@ class MineVipCard extends StatelessWidget { boxShadow: const [AppTheme.cardShadow], ), child: Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(AppDimens.cardRadius), clipBehavior: Clip.antiAlias, child: InkWell( @@ -287,7 +287,7 @@ class MineMenuList extends StatelessWidget { boxShadow: const [AppTheme.cardShadow], ), child: Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(AppDimens.cardRadius), clipBehavior: Clip.antiAlias, child: Column( diff --git a/lib/pages/new_releases/new_releases_page.dart b/lib/pages/new_releases/new_releases_page.dart index 3076aab..12351e3 100644 --- a/lib/pages/new_releases/new_releases_page.dart +++ b/lib/pages/new_releases/new_releases_page.dart @@ -102,7 +102,7 @@ class _NewReleasesPageState extends State { final hero = visible.isEmpty ? null : visible.first; final more = pager.items.skip(1).take(_moreLimit).toList(); return AnnotatedRegion( - value: SystemUiOverlayStyle.dark, + value: SystemUiOverlayStyle.light, child: Scaffold( key: const Key('new-releases-page'), backgroundColor: AppColors.ink, @@ -259,7 +259,10 @@ class _NewReleasesPageState extends State { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [Color(0x00000000), Color(0xB3000000)], + colors: [ + AppColors.transparent, + AppColors.playerOverlayPill, + ], ), ), ), @@ -282,7 +285,7 @@ class _NewReleasesPageState extends State { Text( l10n.premiereToday, style: const TextStyle( - color: Colors.white70, + color: AppColors.muted, fontSize: 10, fontWeight: FontWeight.w600, ), @@ -293,7 +296,7 @@ class _NewReleasesPageState extends State { maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: Colors.white, + color: AppColors.onPrimary, fontSize: 20, fontWeight: FontWeight.w800, ), @@ -315,7 +318,7 @@ class _NewReleasesPageState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: Colors.white70, + color: AppColors.muted, fontSize: 11, ), ), @@ -338,7 +341,7 @@ class _NewReleasesPageState extends State { ), child: const Icon( Icons.play_arrow_rounded, - color: Colors.white, + color: AppColors.onPrimary, size: 22, ), ), @@ -490,7 +493,7 @@ class _NewReleasesPageState extends State { child: Text( l10n.newBadge, style: TextStyle( - color: Colors.white, + color: AppColors.onPrimary, fontSize: compact ? 9 : 10, fontWeight: FontWeight.w800, ), diff --git a/lib/pages/notices/notices_page.dart b/lib/pages/notices/notices_page.dart index ef59a4d..4ed3619 100644 --- a/lib/pages/notices/notices_page.dart +++ b/lib/pages/notices/notices_page.dart @@ -46,7 +46,7 @@ class _NoticesPageState extends State { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: PlainPageScaffold( pageKey: const Key('notices-page'), backKey: const Key('notices-back'), diff --git a/lib/pages/player/player_logic.dart b/lib/pages/player/player_logic.dart index 15c1260..97b90a3 100644 --- a/lib/pages/player/player_logic.dart +++ b/lib/pages/player/player_logic.dart @@ -23,6 +23,7 @@ import '../../core/data/services/playback_session.dart'; import '../../core/data/services/video_player_port.dart'; import '../../core/data/services/wallet_controller.dart'; import '../../core/formatting/localized_content.dart'; +import '../../core/theme/app_colors.dart'; import '../../core/navigation/fade_page_route.dart'; import '../../l10n/app_localizations.dart'; import '../discover/discover_types.dart'; @@ -937,8 +938,9 @@ class PlayerLogic extends ChangeNotifier { } catch (_) { if (context.mounted && session.active == player) { final l10n = AppLocalizations.of(context)!; - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(l10n.seekFailed))); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(l10n.seekFailed))); } } finally { if (scrubbingPlayer == player) cancelScrub(); @@ -1017,7 +1019,7 @@ class PlayerLogic extends ChangeNotifier { try { await showModalBottomSheet( context: context, - backgroundColor: Colors.transparent, + backgroundColor: AppColors.transparent, isScrollControlled: true, builder: (_) => EpisodePickerSheet( drama: drama, @@ -1079,8 +1081,9 @@ class PlayerLogic extends ChangeNotifier { await session.playbackSettled; await saveCurrentProgress(); if (!context.mounted) return; - final action = await Navigator.of(context) - .push(MaterialPageRoute(builder: (_) => const MyPage())); + final action = await Navigator.of( + context, + ).push(MaterialPageRoute(builder: (_) => const MyPage())); if (!context.mounted) return; if (action == MyPageAction.discover) { await showDiscover(context); @@ -1154,7 +1157,7 @@ class PlayerLogic extends ChangeNotifier { await showModalBottomSheet( context: context, isScrollControlled: true, - backgroundColor: Colors.transparent, + backgroundColor: AppColors.transparent, showDragHandle: false, isDismissible: false, enableDrag: false, diff --git a/lib/pages/player/player_view.dart b/lib/pages/player/player_view.dart index db69db6..42fe56a 100644 --- a/lib/pages/player/player_view.dart +++ b/lib/pages/player/player_view.dart @@ -393,19 +393,19 @@ class _PlayerPageState extends State return AnnotatedRegion( key: const Key('player-system-overlay'), value: SystemUiOverlayStyle.light.copyWith( - statusBarColor: Colors.transparent, - systemNavigationBarColor: Colors.black, - systemNavigationBarDividerColor: Colors.transparent, + statusBarColor: AppColors.transparent, + systemNavigationBarColor: AppColors.mediaBlack, + systemNavigationBarDividerColor: AppColors.transparent, systemNavigationBarIconBrightness: Brightness.light, systemNavigationBarContrastEnforced: false, ), child: Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: widget.embedded ? body : Scaffold( key: const Key('player-scaffold'), - backgroundColor: Colors.transparent, + backgroundColor: AppColors.transparent, resizeToAvoidBottomInset: false, body: body, ), @@ -457,7 +457,7 @@ class _PlayerPageState extends State if (widget.homeMode) const ColoredBox( key: Key('home-feed-black-background'), - color: Colors.black, + color: AppColors.mediaBlack, ), PlayerVideoSurface( controller: videoPort?.controller, @@ -497,14 +497,14 @@ class _PlayerPageState extends State children: [ const Icon( Icons.wifi_off_rounded, - color: Colors.white70, + color: AppColors.muted, size: 36, ), const SizedBox(height: 12), Text( errorText(l10n, logic.ticketError, l10n.playbackError), textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white70), + style: const TextStyle(color: AppColors.muted), ), const SizedBox(height: 12), FilledButton( @@ -524,14 +524,14 @@ class _PlayerPageState extends State children: [ const Icon( Icons.error_outline_rounded, - color: Colors.white70, + color: AppColors.muted, size: 36, ), const SizedBox(height: 12), Text( l10n.playbackError, textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white70), + style: const TextStyle(color: AppColors.muted), ), ], ), @@ -551,7 +551,7 @@ class _PlayerPageState extends State children: [ const Icon( Icons.movie_filter_outlined, - color: Colors.white70, + color: AppColors.muted, size: 36, ), const SizedBox(height: 12), @@ -560,7 +560,7 @@ class _PlayerPageState extends State ? l10n.lockedTitle(episode.number) : l10n.noMediaNotice, textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white70), + style: const TextStyle(color: AppColors.muted), ), ], ), diff --git a/lib/pages/player/player_view_actions.dart b/lib/pages/player/player_view_actions.dart index ead3fe0..448b7c2 100644 --- a/lib/pages/player/player_view_actions.dart +++ b/lib/pages/player/player_view_actions.dart @@ -6,7 +6,7 @@ extension _PlayerViewActions on _PlayerPageState { Future _openSpeedSheet(BuildContext context) async { final selected = await showModalBottomSheet( context: context, - backgroundColor: Colors.transparent, + backgroundColor: AppColors.transparent, showDragHandle: false, builder: (_) => PlayerSpeedSheet(selected: _playbackSpeed), ); @@ -19,7 +19,7 @@ extension _PlayerViewActions on _PlayerPageState { final l10n = AppLocalizations.of(context)!; await showModalBottomSheet( context: context, - backgroundColor: Colors.transparent, + backgroundColor: AppColors.transparent, showDragHandle: false, isScrollControlled: true, builder: (_) => PlayerMoreSheet( @@ -52,7 +52,7 @@ extension _PlayerViewActions on _PlayerPageState { if (wallet == null) return; await showModalBottomSheet( context: context, - backgroundColor: Colors.transparent, + backgroundColor: AppColors.transparent, showDragHandle: false, isScrollControlled: true, builder: (_) => PlayerUnlockSheet( diff --git a/lib/pages/player/widgets/drama_comments_sheet.dart b/lib/pages/player/widgets/drama_comments_sheet.dart index 794a7fe..5f000f7 100644 --- a/lib/pages/player/widgets/drama_comments_sheet.dart +++ b/lib/pages/player/widgets/drama_comments_sheet.dart @@ -413,13 +413,16 @@ class _CommentPostSuccessOverlay extends StatelessWidget { vertical: 10, ), decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.82), + color: AppColors.mediaBlack.withValues(alpha: 0.82), borderRadius: BorderRadius.circular(8), ), child: Text( message, textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white, fontSize: 14), + style: const TextStyle( + color: AppColors.onPrimary, + fontSize: 14, + ), ), ), ), diff --git a/lib/pages/player/widgets/episode_picker_sheet.dart b/lib/pages/player/widgets/episode_picker_sheet.dart index a59d148..df4b9b6 100644 --- a/lib/pages/player/widgets/episode_picker_sheet.dart +++ b/lib/pages/player/widgets/episode_picker_sheet.dart @@ -40,11 +40,11 @@ class EpisodePickerSheet extends StatelessWidget { final episodes = drama.episodes; return AnnotatedRegion( key: const Key('player-episodes-system-overlay'), - value: SystemUiOverlayStyle.dark.copyWith( - statusBarColor: Colors.transparent, + value: SystemUiOverlayStyle.light.copyWith( + statusBarColor: AppColors.transparent, systemNavigationBarColor: AppColors.card, - systemNavigationBarDividerColor: Colors.transparent, - systemNavigationBarIconBrightness: Brightness.dark, + systemNavigationBarDividerColor: AppColors.transparent, + systemNavigationBarIconBrightness: Brightness.light, systemNavigationBarContrastEnforced: false, ), child: Container( diff --git a/lib/pages/player/widgets/exit_clear_screen_button.dart b/lib/pages/player/widgets/exit_clear_screen_button.dart index a2b1c51..a514aaa 100644 --- a/lib/pages/player/widgets/exit_clear_screen_button.dart +++ b/lib/pages/player/widgets/exit_clear_screen_button.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; +import '../../../core/theme/app_colors.dart'; + /// Shared floating action used to leave clear-screen mode. class ExitClearScreenButton extends StatelessWidget { const ExitClearScreenButton({ @@ -23,8 +25,8 @@ class ExitClearScreenButton extends StatelessWidget { onPressed: onPressed, padding: EdgeInsets.zero, style: IconButton.styleFrom( - backgroundColor: const Color(0x80000000), - foregroundColor: Colors.white, + backgroundColor: AppColors.controlScrim, + foregroundColor: AppColors.onPrimary, shape: const CircleBorder(), ), icon: const Icon(Icons.fullscreen_exit_rounded, size: 18), diff --git a/lib/pages/player/widgets/home_feed_header.dart b/lib/pages/player/widgets/home_feed_header.dart index 1723d5d..fb192f5 100644 --- a/lib/pages/player/widgets/home_feed_header.dart +++ b/lib/pages/player/widgets/home_feed_header.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; +import '../../../core/theme/app_colors.dart'; + import '../../home/home_feed_channel.dart'; import '../../../l10n/app_localizations.dart'; @@ -140,8 +142,8 @@ class _TopIconButton extends StatelessWidget { icon: Icon( icon, size: glyphSize, - color: Colors.white, - shadows: const [Shadow(blurRadius: 6, color: Colors.black)], + color: AppColors.onPrimary, + shadows: const [Shadow(blurRadius: 6, color: AppColors.mediaBlack)], ), ), ); @@ -184,15 +186,17 @@ class _RefreshButton extends StatelessWidget { child: CircularProgressIndicator( key: Key('home-feed-refreshing'), strokeWidth: 2, - color: Colors.white, + color: AppColors.onPrimary, ), ), ) : Icon( Icons.refresh_rounded, size: glyphSize, - color: Colors.white, - shadows: const [Shadow(blurRadius: 6, color: Colors.black)], + color: AppColors.onPrimary, + shadows: const [ + Shadow(blurRadius: 6, color: AppColors.mediaBlack), + ], ), ), ), @@ -232,10 +236,12 @@ class _FeedTabItem extends StatelessWidget { label, maxLines: 1, style: TextStyle( - color: selected ? Colors.white : Colors.white70, + color: selected ? AppColors.onPrimary : AppColors.muted, fontSize: fontSize, fontWeight: selected ? FontWeight.w800 : FontWeight.w600, - shadows: const [Shadow(blurRadius: 6, color: Colors.black)], + shadows: const [ + Shadow(blurRadius: 6, color: AppColors.mediaBlack), + ], ), ), const SizedBox(height: 4), @@ -243,7 +249,7 @@ class _FeedTabItem extends StatelessWidget { width: underlineWidth, height: underlineHeight, decoration: BoxDecoration( - color: selected ? Colors.white : Colors.transparent, + color: selected ? AppColors.onPrimary : AppColors.transparent, borderRadius: BorderRadius.circular(1), ), ), diff --git a/lib/pages/player/widgets/home_feed_overlay.dart b/lib/pages/player/widgets/home_feed_overlay.dart index 5dbf276..26a7d53 100644 --- a/lib/pages/player/widgets/home_feed_overlay.dart +++ b/lib/pages/player/widgets/home_feed_overlay.dart @@ -20,8 +20,8 @@ import 'seek_progress_bar.dart'; part 'home_feed_overlay_parts.dart'; -const Color _overlayPill = Color(0x66000000); -const Color _fullBarBackground = Color(0x4D000000); +const Color _overlayPill = AppColors.overlaySoft; +const Color _fullBarBackground = AppColors.overlayBar; const double _titleFontSize = 17; const double _metaFontSize = 12; const double _tagFontSize = 11; @@ -29,7 +29,7 @@ const double _fullBarHeight = 44; const double _progressHeight = 2; const double _progressHitHeight = 24; const List _boostTextShadows = [ - Shadow(color: Colors.black54, blurRadius: 5, offset: Offset(0, 1)), + Shadow(color: AppColors.shadow, blurRadius: 5, offset: Offset(0, 1)), ]; const int _maxTags = 4; final RegExp _tagSplitter = RegExp(r'[,/、·]'); @@ -236,7 +236,7 @@ class _HomeFeedOverlayState extends State { key: const Key('home-feed-author-note'), maxLines: 2, overflow: TextOverflow.ellipsis, - style: const TextStyle(color: Colors.white70, fontSize: 11), + style: const TextStyle(color: AppColors.muted, fontSize: 11), ), ], ], @@ -260,14 +260,14 @@ class _HomeFeedOverlayState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: Colors.white70, + color: AppColors.muted, fontSize: _metaFontSize, ), ), ), const Icon( Icons.chevron_right_rounded, - color: Colors.white70, + color: AppColors.muted, size: 16, ), ], @@ -288,16 +288,16 @@ class _HomeFeedOverlayState extends State { overflow: TextOverflow.ellipsis, style: const TextStyle( fontFamily: 'Outfit', - color: Colors.white, + color: AppColors.onPrimary, fontSize: _titleFontSize, fontWeight: FontWeight.w800, - shadows: [Shadow(blurRadius: 8, color: Colors.black)], + shadows: [Shadow(blurRadius: 8, color: AppColors.mediaBlack)], ), ), ), const Icon( Icons.chevron_right_rounded, - color: Colors.white, + color: AppColors.onPrimary, size: 20, ), ], @@ -326,7 +326,7 @@ class _HomeFeedOverlayState extends State { tag, maxLines: 1, style: const TextStyle( - color: Colors.white, + color: AppColors.onPrimary, fontSize: _tagFontSize, ), ), @@ -369,7 +369,7 @@ class _HomeFeedOverlayState extends State { children: [ const Icon( Icons.play_arrow_rounded, - color: Colors.white, + color: AppColors.onPrimary, size: 14, ), const SizedBox(width: 6), @@ -381,7 +381,7 @@ class _HomeFeedOverlayState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: Colors.white, + color: AppColors.onPrimary, fontSize: 13, fontWeight: FontWeight.w600, ), @@ -389,7 +389,7 @@ class _HomeFeedOverlayState extends State { ), const Icon( Icons.chevron_right_rounded, - color: Colors.white, + color: AppColors.onPrimary, size: 18, ), ], diff --git a/lib/pages/player/widgets/home_feed_overlay_parts.dart b/lib/pages/player/widgets/home_feed_overlay_parts.dart index bbf57a2..522b535 100644 --- a/lib/pages/player/widgets/home_feed_overlay_parts.dart +++ b/lib/pages/player/widgets/home_feed_overlay_parts.dart @@ -251,7 +251,10 @@ class _FeedProgressBarState extends State<_FeedProgressBar> { bottom: bottom, child: SizedBox( height: widget.height, - child: ColoredBox(key: _inactiveTrackKey, color: Colors.white24), + child: ColoredBox( + key: _inactiveTrackKey, + color: AppColors.border, + ), ), ), ], @@ -301,7 +304,7 @@ class _FeedProgressBarState extends State<_FeedProgressBar> { child: Container( key: const Key('home-feed-progress-track'), height: trackHeight, - color: Colors.white24, + color: AppColors.border, ), ), Positioned( @@ -321,7 +324,7 @@ class _FeedProgressBarState extends State<_FeedProgressBar> { width: size, height: size, decoration: const BoxDecoration( - color: Colors.white, + color: AppColors.onPrimary, shape: BoxShape.circle, ), ), diff --git a/lib/pages/player/widgets/interaction_rail.dart b/lib/pages/player/widgets/interaction_rail.dart index f3ff832..3ee085d 100644 --- a/lib/pages/player/widgets/interaction_rail.dart +++ b/lib/pages/player/widgets/interaction_rail.dart @@ -71,7 +71,7 @@ class _RailButton extends StatelessWidget { width: _iconSize, height: _iconSize, fit: BoxFit.contain, - color: item.selected ? AppColors.blue : Colors.white, + color: item.selected ? AppColors.blue : AppColors.onPrimary, colorBlendMode: BlendMode.srcIn, ), ), @@ -83,11 +83,11 @@ class _RailButton extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: Colors.white, + color: AppColors.onPrimary, height: 1, fontSize: 12, fontWeight: FontWeight.w600, - shadows: [Shadow(blurRadius: 6, color: Colors.black)], + shadows: [Shadow(blurRadius: 6, color: AppColors.mediaBlack)], ), ), ], diff --git a/lib/pages/player/widgets/loading_progress_track.dart b/lib/pages/player/widgets/loading_progress_track.dart index d54cb91..8b78abb 100644 --- a/lib/pages/player/widgets/loading_progress_track.dart +++ b/lib/pages/player/widgets/loading_progress_track.dart @@ -2,13 +2,15 @@ import 'package:flutter/material.dart'; +import '../../../core/theme/app_colors.dart'; + /// 播放未就绪时沿进度轨道循环移动的加载高光。 class LoadingProgressTrack extends StatefulWidget { const LoadingProgressTrack({ super.key = const Key('progress-loading'), required this.height, - this.trackColor = Colors.white24, - this.highlightColor = Colors.white, + this.trackColor = AppColors.inactiveTrack, + this.highlightColor = AppColors.onPrimary, }); final double height; diff --git a/lib/pages/player/widgets/player_center_toggle.dart b/lib/pages/player/widgets/player_center_toggle.dart index cfb03c9..78ba374 100644 --- a/lib/pages/player/widgets/player_center_toggle.dart +++ b/lib/pages/player/widgets/player_center_toggle.dart @@ -6,9 +6,9 @@ import '../../../core/theme/app_colors.dart'; const double _toggleSize = 68; const double _iconSize = 36; -const Color _toggleBackground = Colors.black26; +const Color _toggleBackground = AppColors.controlSoftScrim; const List _iconShadows = [ - Shadow(color: Colors.black54, blurRadius: 12), + Shadow(color: AppColors.shadow, blurRadius: 12), ]; /// 首页沉浸流与全屏播放器共用的播放/暂停按钮。 diff --git a/lib/pages/player/widgets/player_more_sheet.dart b/lib/pages/player/widgets/player_more_sheet.dart index 18e9ad1..c59d828 100644 --- a/lib/pages/player/widgets/player_more_sheet.dart +++ b/lib/pages/player/widgets/player_more_sheet.dart @@ -271,7 +271,7 @@ class _PlayerMoreSheetState extends State { }) { return Material( key: key, - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(8), child: InkWell( borderRadius: BorderRadius.circular(8), @@ -347,7 +347,7 @@ class _SegmentButton extends StatelessWidget { ? AppColors.muted : AppColors.subtle; return Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(_segmentRadius), child: InkWell( onTap: onTap, @@ -357,12 +357,12 @@ class _SegmentButton extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: _segmentPadding), alignment: Alignment.center, decoration: BoxDecoration( - color: selected ? AppColors.card : Colors.transparent, + color: selected ? AppColors.card : AppColors.transparent, borderRadius: BorderRadius.circular(_segmentRadius), boxShadow: selected ? const [ BoxShadow( - color: Color(0x14000000), + color: AppColors.shadowSubtle, blurRadius: 4, offset: Offset(0, 1), ), diff --git a/lib/pages/player/widgets/player_overlay.dart b/lib/pages/player/widgets/player_overlay.dart index a401acf..9781bf3 100644 --- a/lib/pages/player/widgets/player_overlay.dart +++ b/lib/pages/player/widgets/player_overlay.dart @@ -18,7 +18,7 @@ const double _topBarHeight = 58; const double _boostHintIconSize = 16; const double _boostHintFontSize = 13; const List _playerTextShadows = [ - Shadow(color: Colors.black54, blurRadius: 5, offset: Offset(0, 1)), + Shadow(color: AppColors.shadow, blurRadius: 5, offset: Offset(0, 1)), ]; /// K29 竖屏播放页浮层。 @@ -270,7 +270,7 @@ class PlayerPlaybackOverlay extends StatelessWidget { thumbnailsUrl: data.episode.thumbnailsUrl, episodeId: data.episode.id, posterUrl: data.drama.posterUrl, - inactiveTrackColor: Colors.white24, + inactiveTrackColor: AppColors.inactiveTrack, onScrubStart: actions.onScrubStart, onSeek: actions.onSeek, ), @@ -279,7 +279,7 @@ class PlayerPlaybackOverlay extends StatelessWidget { Widget _detailRow(String series) { return Material( - color: Colors.transparent, + color: AppColors.transparent, child: InkWell( key: const Key('player-open-detail'), onTap: actions.onDetail, diff --git a/lib/pages/player/widgets/player_overlay_parts.dart b/lib/pages/player/widgets/player_overlay_parts.dart index 383365d..39a1710 100644 --- a/lib/pages/player/widgets/player_overlay_parts.dart +++ b/lib/pages/player/widgets/player_overlay_parts.dart @@ -22,7 +22,7 @@ class _TopButton extends StatelessWidget { tooltip: tooltip, padding: EdgeInsets.zero, style: IconButton.styleFrom( - backgroundColor: const Color(0x80000000), + backgroundColor: AppColors.controlScrim, foregroundColor: AppColors.onPrimary, ), onPressed: onPressed, @@ -41,7 +41,7 @@ class _SpeedEntry extends StatelessWidget { @override Widget build(BuildContext context) { return Material( - color: const Color(0x80000000), + color: AppColors.controlScrim, borderRadius: BorderRadius.circular(999), child: InkWell( onTap: onTap, diff --git a/lib/pages/player/widgets/player_video_surface.dart b/lib/pages/player/widgets/player_video_surface.dart index 1226e3f..1636f31 100644 --- a/lib/pages/player/widgets/player_video_surface.dart +++ b/lib/pages/player/widgets/player_video_surface.dart @@ -4,6 +4,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; +import '../../../core/theme/app_colors.dart'; + const Duration _videoFadeDuration = Duration(milliseconds: 200); const double _errorHorizontal = 32; const int _errorMaxLines = 3; @@ -55,7 +57,7 @@ class _PlayerVideoSurfaceState extends State { children: [ const ColoredBox( key: Key('player-fallback-background'), - color: Colors.black, + color: AppColors.mediaBlack, ), AnimatedOpacity( key: const Key('player-video-opacity'), @@ -81,7 +83,7 @@ class _PlayerVideoSurfaceState extends State { maxLines: _errorMaxLines, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white70, fontSize: 13), + style: const TextStyle(color: AppColors.muted, fontSize: 13), ), ), ), diff --git a/lib/pages/player/widgets/scrub_preview_card.dart b/lib/pages/player/widgets/scrub_preview_card.dart index 89472a1..6bb199d 100644 --- a/lib/pages/player/widgets/scrub_preview_card.dart +++ b/lib/pages/player/widgets/scrub_preview_card.dart @@ -164,7 +164,7 @@ class _ScrubPreviewCardState extends State { decoration: BoxDecoration( color: AppColors.card, borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.white, width: 1), + border: Border.all(color: AppColors.onPrimary, width: 1), ), child: ClipRRect( borderRadius: BorderRadius.circular(7), @@ -174,7 +174,7 @@ class _ScrubPreviewCardState extends State { Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), - color: Colors.black87, + color: AppColors.scrubPreviewScrim, child: Text( '${_format(widget.position)} / ${_format(widget.duration)}', key: const Key('scrub-preview-time'), @@ -182,7 +182,7 @@ class _ScrubPreviewCardState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( - color: Colors.white, + color: AppColors.onPrimary, fontSize: 11, fontWeight: FontWeight.w600, height: 1.2, diff --git a/lib/pages/player/widgets/seek_progress_bar.dart b/lib/pages/player/widgets/seek_progress_bar.dart index ed0f53d..9723933 100644 --- a/lib/pages/player/widgets/seek_progress_bar.dart +++ b/lib/pages/player/widgets/seek_progress_bar.dart @@ -233,7 +233,7 @@ class _SeekProgressBarState extends State { '${_format(previewPosition)} / ${_format(video.duration)}', textAlign: TextAlign.center, style: const TextStyle( - color: Colors.white, + color: AppColors.onPrimary, fontSize: 14, fontWeight: FontWeight.w600, height: 1.2, diff --git a/lib/pages/ranking/ranking_page.dart b/lib/pages/ranking/ranking_page.dart index e28923d..24c34a4 100644 --- a/lib/pages/ranking/ranking_page.dart +++ b/lib/pages/ranking/ranking_page.dart @@ -27,10 +27,10 @@ const double _rankColumnWidth = 56; const double _listCoverWidth = 72; const double _listCoverHeight = 96; const double _loadMoreThreshold = 200; -const Color _goldStart = Color(0xFFF3B34C); -const Color _goldEnd = Color(0xFFD9A23A); -const Color _bronzeStart = Color(0xFFCD7F32); -const Color _bronzeEnd = Color(0xFFA9713A); +const Color _goldStart = AppColors.gold; +const Color _goldEnd = AppColors.goldDeep; +const Color _bronzeStart = AppColors.bronze; +const Color _bronzeEnd = AppColors.bronzeDeep; /// 剧场榜单页:前三名横排领奖台,第 4 名起纵向列表。 class RankingPage extends StatefulWidget { @@ -265,7 +265,7 @@ class _PodiumCard extends StatelessWidget { l10n.topLabel(rank), maxLines: 1, style: const TextStyle( - color: Colors.white, + color: AppColors.onPrimary, fontSize: 11, fontWeight: FontWeight.w800, ), @@ -335,7 +335,7 @@ class _RankingRow extends StatelessWidget { boxShadow: const [AppTheme.cardShadow], ), child: Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(14), clipBehavior: Clip.antiAlias, child: InkWell( diff --git a/lib/pages/reserve/reserve_page.dart b/lib/pages/reserve/reserve_page.dart index 551449d..b6bd7c7 100644 --- a/lib/pages/reserve/reserve_page.dart +++ b/lib/pages/reserve/reserve_page.dart @@ -12,7 +12,7 @@ import '../new_releases/new_releases_page.dart'; const double _ticketRadius = 20; const double _notchSize = 24; -const Color _ticketDash = Color(0x66FF7A1A); +const Color _ticketDash = AppColors.primaryBorder; /// 预约页:档期周历 + 电影票式空态 + 预约权益说明。 class ReservePage extends StatefulWidget { @@ -131,7 +131,7 @@ class _ReservePageState extends State { Text( weekdays[date.weekday - 1], style: TextStyle( - color: selected ? Colors.white : AppColors.muted, + color: selected ? AppColors.onPrimary : AppColors.muted, fontSize: 11, fontWeight: FontWeight.w600, ), @@ -140,7 +140,7 @@ class _ReservePageState extends State { Text( '${date.day}', style: TextStyle( - color: selected ? Colors.white : AppColors.onSurface, + color: selected ? AppColors.onPrimary : AppColors.onSurface, fontSize: 15, fontWeight: FontWeight.w800, ), @@ -207,7 +207,7 @@ class _ReservePageState extends State { ), style: FilledButton.styleFrom( backgroundColor: AppColors.blue, - foregroundColor: Colors.white, + foregroundColor: AppColors.onPrimary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -285,7 +285,7 @@ class _ReservePageState extends State { child: Text( '${index + 1}', style: const TextStyle( - color: Colors.white, + color: AppColors.onPrimary, fontSize: 12, fontWeight: FontWeight.w800, ), diff --git a/lib/pages/settings/settings_page.dart b/lib/pages/settings/settings_page.dart index bd8cc4e..180ca52 100644 --- a/lib/pages/settings/settings_page.dart +++ b/lib/pages/settings/settings_page.dart @@ -23,7 +23,7 @@ class SettingsPage extends StatelessWidget { final l10n = AppLocalizations.of(context)!; final auth = context.watch(); return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: PlainPageScaffold( pageKey: const Key('settings-page'), backKey: const Key('settings-back'), diff --git a/lib/pages/store/store_page.dart b/lib/pages/store/store_page.dart index beed81a..63bb10a 100644 --- a/lib/pages/store/store_page.dart +++ b/lib/pages/store/store_page.dart @@ -57,7 +57,7 @@ class _StorePageState extends State { final l10n = AppLocalizations.of(context)!; final isVip = widget.type == StoreController.vipType; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: PlainPageScaffold( pageKey: const Key('store-page'), backKey: const Key('store-back'), diff --git a/lib/pages/wallet/wallet_page.dart b/lib/pages/wallet/wallet_page.dart index 72a5948..86b9827 100644 --- a/lib/pages/wallet/wallet_page.dart +++ b/lib/pages/wallet/wallet_page.dart @@ -46,7 +46,7 @@ class _WalletPageState extends State { final l10n = AppLocalizations.of(context)!; final wallet = context.watch(); return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: PlainPageScaffold( pageKey: const Key('wallet-page'), backKey: const Key('wallet-back'), diff --git a/lib/pages/watch_records/watch_records_page.dart b/lib/pages/watch_records/watch_records_page.dart index c8c326a..b244c63 100644 --- a/lib/pages/watch_records/watch_records_page.dart +++ b/lib/pages/watch_records/watch_records_page.dart @@ -38,7 +38,7 @@ class WatchRecordsPage extends StatelessWidget { final signedIn = context.watch().isSignedIn; final l10n = AppLocalizations.of(context)!; return Theme( - data: AppTheme.light(), + data: AppTheme.dark(), child: Scaffold( key: const Key('watch-records-page'), backgroundColor: AppColors.ink, diff --git a/lib/pages/watch_records/widgets/watch_record_card.dart b/lib/pages/watch_records/widgets/watch_record_card.dart index bdc6d28..edb59ef 100644 --- a/lib/pages/watch_records/widgets/watch_record_card.dart +++ b/lib/pages/watch_records/widgets/watch_record_card.dart @@ -170,7 +170,7 @@ class WatchRecordCard extends StatelessWidget { key: ValueKey('watch-record-${record.dramaId}'), decoration: _cardDecoration(), child: Material( - color: Colors.transparent, + color: AppColors.transparent, borderRadius: BorderRadius.circular(_cardRadius), clipBehavior: Clip.antiAlias, child: InkWell( diff --git a/test/auth/auth_visual_test.dart b/test/auth/auth_visual_test.dart index 192ed41..92481fd 100644 --- a/test/auth/auth_visual_test.dart +++ b/test/auth/auth_visual_test.dart @@ -13,6 +13,7 @@ import 'package:fruitshorts/core/data/services/auth_controller.dart'; import 'package:fruitshorts/core/data/services/auth_session_store.dart'; import 'package:fruitshorts/core/platform/haptics.dart'; import 'package:fruitshorts/core/theme/app_colors.dart'; +import 'package:fruitshorts/core/theme/app_dimens.dart'; import 'package:fruitshorts/core/theme/app_theme.dart'; import 'package:fruitshorts/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/auth/auth_view.dart'; @@ -131,7 +132,10 @@ void main() { final card = tester.widget(find.byKey(const Key('auth-card'))); final cardDecoration = card.decoration! as BoxDecoration; expect(cardDecoration.color, AppColors.card); - expect(cardDecoration.borderRadius, BorderRadius.circular(20)); + expect( + cardDecoration.borderRadius, + BorderRadius.circular(AppDimens.authCardRadius), + ); expect(cardDecoration.boxShadow, contains(AppTheme.cardShadow)); expect(card.padding, const EdgeInsets.all(20)); diff --git a/test/core/subpage_header_test.dart b/test/core/subpage_header_test.dart index 353c4e6..cf8d3ea 100644 --- a/test/core/subpage_header_test.dart +++ b/test/core/subpage_header_test.dart @@ -7,7 +7,7 @@ import 'package:fruitshorts/core/theme/app_theme.dart'; import 'package:fruitshorts/core/widgets/subpage_header.dart'; void main() { - testWidgets('should_render_unified_light_header_dimensions', (tester) async { + testWidgets('should_render_unified_dark_header_dimensions', (tester) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.light(), @@ -47,7 +47,7 @@ void main() { ) .decoration as BoxDecoration; - expect(shadow.boxShadow!.single.color, const Color(0x14000000)); + expect(shadow.boxShadow!.single.color, AppColors.shadowSubtle); expect(shadow.boxShadow!.single.blurRadius, 8); expect(shadow.boxShadow!.single.offset, const Offset(0, 2)); }); @@ -75,7 +75,7 @@ void main() { MaterialApp( theme: AppTheme.light(), home: const Scaffold( - backgroundColor: Colors.black, + backgroundColor: AppColors.mediaBlack, body: AppSubpageHeader( title: 'Title', backKey: Key('sample-back'), @@ -97,8 +97,8 @@ void main() { matching: find.byIcon(Icons.chevron_left_rounded), ), ); - expect(material.color, const Color(0x59000000)); - expect(icon.color, Colors.white); + expect(material.color, AppColors.headerScrim); + expect(icon.color, AppColors.onSurface); expect(icon.size, 22); expect(icon.weight, 600); final shadow = @@ -108,7 +108,7 @@ void main() { ) .decoration as BoxDecoration; - expect(shadow.boxShadow!.single.color, const Color(0x33000000)); + expect(shadow.boxShadow!.single.color, AppColors.shadowSoft); expect(shadow.boxShadow!.single.blurRadius, 8); }); } diff --git a/test/empty_state/empty_state_test.dart b/test/empty_state/empty_state_test.dart index cfac0bd..8c56f39 100644 --- a/test/empty_state/empty_state_test.dart +++ b/test/empty_state/empty_state_test.dart @@ -33,7 +33,7 @@ void main() { ); expect( tester.widget(find.byKey(const Key('empty-state-icon'))).color, - AppColors.blue, + AppColors.blueSoft, ); expect( tester @@ -103,7 +103,7 @@ void main() { expect(find.byKey(const Key('empty-state-action')), findsNothing); }); - testWidgets('should_tint_the_bare_icon_with_the_light_theme', (tester) async { + testWidgets('should_tint_the_bare_icon_with_the_dark_theme', (tester) async { await _pump( tester, theme: AppTheme.light(), @@ -111,7 +111,7 @@ void main() { ); expect( tester.widget(find.byKey(const Key('empty-state-icon'))).color, - AppColors.blue, + AppColors.blueSoft, ); await _pump( @@ -121,7 +121,7 @@ void main() { ); expect( tester.widget(find.byKey(const Key('empty-state-icon'))).color, - AppColors.blue, + AppColors.blueSoft, ); }); diff --git a/test/home/app_bottom_nav_test.dart b/test/home/app_bottom_nav_test.dart index f019a17..2ddf85a 100644 --- a/test/home/app_bottom_nav_test.dart +++ b/test/home/app_bottom_nav_test.dart @@ -144,6 +144,6 @@ void main() { matching: find.byType(ColoredBox), ), ); - expect(coloredBox.color, Colors.black); + expect(coloredBox.color, AppColors.bar); }); } diff --git a/test/home/home_nav_ui_test.dart b/test/home/home_nav_ui_test.dart index 1b61ce0..03bd987 100644 --- a/test/home/home_nav_ui_test.dart +++ b/test/home/home_nav_ui_test.dart @@ -52,9 +52,9 @@ void main() { final bar = tester.widget(barFinder); final decoration = bar.decoration as BoxDecoration; final border = decoration.border as Border; - // 首页 feed 用深色导航。 - expect(decoration.color, Colors.black); - expect(border.top.color, Colors.white12); + // 所有页面统一深色导航。 + expect(decoration.color, AppColors.bar); + expect(border.top.color, AppColors.border); expect( tester .widget( @@ -65,7 +65,7 @@ void main() { ) .style ?.color, - Colors.white70, + AppColors.navUnselected, ); expect( tester @@ -100,10 +100,10 @@ void main() { await tester.tap(find.byKey(const Key('tab-theater'))); await tester.pumpAndSettle(); expect(haptics.selectionClicks, 1); - final lightBar = tester.widget(barFinder); - final lightDecoration = lightBar.decoration as BoxDecoration; - expect(lightDecoration.color, AppColors.bar.withValues(alpha: 0.95)); - expect((lightDecoration.border as Border).top.color, AppColors.border); + final theaterBar = tester.widget(barFinder); + final theaterDecoration = theaterBar.decoration as BoxDecoration; + expect(theaterDecoration.color, AppColors.bar.withValues(alpha: 0.96)); + expect((theaterDecoration.border as Border).top.color, AppColors.border); await tester.tap(find.byKey(const Key('tab-theater'))); await tester.pumpAndSettle(); expect(haptics.selectionClicks, 1); diff --git a/test/home/k28_a_navigation_test.dart b/test/home/k28_a_navigation_test.dart index 0998a3e..ab70046 100644 --- a/test/home/k28_a_navigation_test.dart +++ b/test/home/k28_a_navigation_test.dart @@ -328,7 +328,7 @@ void main() { expect(find.byKey(const Key('home-feed-pip')), findsNothing); }); - testWidgets('should_restore_light_theme_after_leaving_feed_tab', ( + testWidgets('should_keep_light_status_bar_after_leaving_feed_tab', ( tester, ) async { await _pumpApp(tester); @@ -338,7 +338,7 @@ void main() { await tester.pumpAndSettle(); expect(find.byKey(const Key('home-page-gradient')), findsOneWidget); - expect(_hasLightStatusBar(tester), isFalse); + expect(_hasLightStatusBar(tester), isTrue); }); testWidgets('should_switch_feed_drama_when_swiped_up', (tester) async { diff --git a/test/player/loading_progress_track_test.dart b/test/player/loading_progress_track_test.dart index b461a8b..090ead8 100644 --- a/test/player/loading_progress_track_test.dart +++ b/test/player/loading_progress_track_test.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:fruitshorts/core/theme/app_colors.dart'; import 'package:fruitshorts/pages/player/widgets/loading_progress_track.dart'; void main() { @@ -28,7 +29,7 @@ void main() { tester .widget(find.byKey(const Key('progress-loading-track'))) .color, - Colors.white24, + AppColors.inactiveTrack, ); expect( tester @@ -36,7 +37,7 @@ void main() { find.byKey(const Key('progress-loading-highlight')), ) .color, - Colors.white, + AppColors.onPrimary, ); final initialLeft = tester diff --git a/test/player/player_fullscreen_test.dart b/test/player/player_fullscreen_test.dart index 9bfd278..bc1bd8c 100644 --- a/test/player/player_fullscreen_test.dart +++ b/test/player/player_fullscreen_test.dart @@ -825,7 +825,10 @@ void main() { for (final style in [label, synopsis, separator]) { expect(style.fontSize, 12); expect(style.fontWeight, FontWeight.w500); - expect(style.color?.withValues(alpha: 1), AppColors.playerMuted); + expect( + style.color?.withValues(alpha: 1), + AppColors.playerMuted.withValues(alpha: 1), + ); } expect(separator.color, isNot(Colors.white)); }); diff --git a/test/theme/app_dimens_test.dart b/test/theme/app_dimens_test.dart index 4051eb1..d48b70d 100644 --- a/test/theme/app_dimens_test.dart +++ b/test/theme/app_dimens_test.dart @@ -4,13 +4,15 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/theme/app_dimens.dart'; void main() { - test('should_expose_the_k20_spacing_and_type_scale', () { + test('should_expose_dark_theme_radius_and_spacing_scale', () { expect(AppDimens.pageHorizontal, 16); - expect(AppDimens.cardRadius, 16); - expect(AppDimens.coverRadius, 16); - expect(AppDimens.authCardRadius, 20); + expect(AppDimens.cardRadius, 12); + expect(AppDimens.coverRadius, 12); + expect(AppDimens.authCardRadius, 12); + expect(AppDimens.buttonRadius, 10); + expect(AppDimens.inputRadius, 10); + expect(AppDimens.chipRadius, 6); expect(AppDimens.pillRadius, 999); - expect(AppDimens.buttonRadius, 999); expect(AppDimens.caption, 12); expect(AppDimens.body, 14); expect(AppDimens.listTitle, 15); diff --git a/test/theme/app_theme_test.dart b/test/theme/app_theme_test.dart index e7bc83c..1cd52c2 100644 --- a/test/theme/app_theme_test.dart +++ b/test/theme/app_theme_test.dart @@ -7,6 +7,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/app.dart'; import 'package:fruitshorts/core/theme/app_colors.dart'; +import 'package:fruitshorts/core/theme/app_dimens.dart'; import 'package:fruitshorts/core/theme/app_theme.dart'; double _contrast(Color foreground, Color background) { @@ -16,44 +17,59 @@ double _contrast(Color foreground, Color background) { } void main() { - test('should_expose_light_warm_orange_tokens', () { - expect(AppColors.blue, const Color(0xFFFF7A1A)); - expect(AppColors.bluePressed, const Color(0xFFFF6A00)); - expect(AppColors.secondary, const Color(0xFFFF6A8A)); - expect(AppColors.ink, const Color(0xFFFFF8F0)); - expect(AppColors.surfaceTop, const Color(0xFFFFE9D6)); - expect(AppColors.card, const Color(0xFFFFFFFF)); - expect(AppColors.onSurface, const Color(0xFF1F1B16)); - expect(AppColors.muted, const Color(0xFF8C837B)); - expect(AppColors.subtle, const Color(0xFFB3AAA1)); - expect(AppColors.border, const Color(0xFFF0E4D6)); - expect(AppColors.badgePink, const Color(0xFFFFE3EC)); - expect(AppColors.badgePinkText, const Color(0xFFFF6A8A)); + test('should_expose_dark_visual_tokens', () { + expect(AppColors.blue, const Color(0xFFFF6B2C)); + expect(AppColors.bluePressed, const Color(0xFFE55A1F)); + expect(AppColors.secondary, const Color(0xFF22D3EE)); + expect(AppColors.ink, const Color(0xFF0B1020)); + expect(AppColors.card, const Color(0xFF151B2E)); + expect(AppColors.raised, const Color(0xFF1C2436)); + expect(AppColors.border, const Color(0x0FFFFFFF)); + expect(AppColors.onSurface, const Color(0xEBFFFFFF)); + expect(AppColors.muted, const Color(0xA3FFFFFF)); + expect(AppColors.subtle, const Color(0x61FFFFFF)); + expect(AppDimens.cardRadius, 12); + expect(AppDimens.buttonRadius, 10); + expect(AppDimens.inputRadius, 10); + expect(AppDimens.chipRadius, 6); }); - test('should_keep_primary_text_readable_on_warm_background', () { + test('should_keep_primary_and_secondary_text_readable_on_dark', () { expect( _contrast(AppColors.onSurface, AppColors.ink), greaterThanOrEqualTo(4.5), ); + expect( + _contrast(AppColors.onSurface, AppColors.card), + greaterThanOrEqualTo(4.5), + ); expect(_contrast(AppColors.muted, AppColors.card), greaterThanOrEqualTo(3)); + expect(_contrast(AppColors.gold, AppColors.ink), greaterThanOrEqualTo(3)); + expect(_contrast(AppColors.blue, AppColors.ink), greaterThanOrEqualTo(3)); }); - test('should_use_light_color_scheme_and_dark_system_icons', () { - final theme = AppTheme.light(); + test('should_use_dark_color_scheme_and_light_system_icons', () { + final theme = AppTheme.dark(); - expect(theme.brightness, Brightness.light); + expect(theme.brightness, Brightness.dark); expect(theme.colorScheme.primary, AppColors.blue); expect(theme.colorScheme.onPrimary, AppColors.onPrimary); + expect(theme.colorScheme.secondary, AppColors.secondary); expect(theme.colorScheme.surface, AppColors.ink); expect(theme.colorScheme.onSurface, AppColors.onSurface); expect(theme.colorScheme.outline, AppColors.border); expect(theme.scaffoldBackgroundColor, AppColors.ink); - expect(theme.appBarTheme.systemOverlayStyle, SystemUiOverlayStyle.dark); + expect(theme.appBarTheme.backgroundColor, AppColors.card); + expect(theme.appBarTheme.systemOverlayStyle, SystemUiOverlayStyle.light); + expect(theme.cardTheme.elevation, 0); + expect(theme.dialogTheme.backgroundColor, AppColors.raised); + expect(theme.bottomNavigationBarTheme.backgroundColor, AppColors.bar); + expect(theme.inputDecorationTheme.fillColor, AppColors.input); }); - test('should_use_ink_navigation_bar_without_contrast_scrim', () { + test('should_use_ink_navigation_bar_with_light_icons', () { expect(appSystemOverlayStyle.statusBarColor, Colors.transparent); + expect(appSystemOverlayStyle.statusBarIconBrightness, Brightness.light); expect(appSystemOverlayStyle.systemNavigationBarColor, AppColors.ink); expect( appSystemOverlayStyle.systemNavigationBarDividerColor, @@ -61,20 +77,20 @@ void main() { ); expect( appSystemOverlayStyle.systemNavigationBarIconBrightness, - Brightness.dark, + Brightness.light, ); expect(appSystemOverlayStyle.systemNavigationBarContrastEnforced, isFalse); - final sheetTheme = AppTheme.light().bottomSheetTheme; + final sheetTheme = AppTheme.dark().bottomSheetTheme; expect(sheetTheme.backgroundColor, Colors.transparent); expect(sheetTheme.modalBackgroundColor, Colors.transparent); expect(sheetTheme.surfaceTintColor, Colors.transparent); }); - test('should_expose_page_gradient_and_soft_card_shadow', () { + test('should_expose_flat_page_gradient_without_card_shadow', () { expect(AppTheme.pageGradient.colors, [AppColors.surfaceTop, AppColors.ink]); expect(AppTheme.cardShadow.color, AppColors.cardShadow); - expect(AppTheme.cardShadow.blurRadius, 18); - expect(AppTheme.cardShadow.offset, const Offset(0, 6)); + expect(AppTheme.cardShadow.blurRadius, 0); + expect(AppTheme.cardShadow.offset, Offset.zero); }); } diff --git a/test/theme/dark_theme_smoke_test.dart b/test/theme/dark_theme_smoke_test.dart new file mode 100644 index 0000000..2fea453 --- /dev/null +++ b/test/theme/dark_theme_smoke_test.dart @@ -0,0 +1,117 @@ +// Author: fruit.com.cn + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fruitshorts/core/data/remote/api_client.dart'; +import 'package:fruitshorts/core/data/remote/auth_api.dart'; +import 'package:fruitshorts/core/data/services/auth_controller.dart'; +import 'package:fruitshorts/core/data/services/auth_session_store.dart'; +import 'package:fruitshorts/core/theme/app_colors.dart'; +import 'package:fruitshorts/core/theme/app_theme.dart'; +import 'package:fruitshorts/l10n/app_localizations.dart'; +import 'package:fruitshorts/pages/watch_records/watch_records_page.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; + +import '../support/fake_http_adapter.dart'; + +void main() { + setUp(() { + SharedPreferencesAsyncPlatform.instance = + InMemorySharedPreferencesAsync.empty(); + }); + + testWidgets('should_render_material_app_with_dark_default_theme', ( + tester, + ) async { + await tester.pumpWidget( + MaterialApp( + theme: AppTheme.dark(), + darkTheme: AppTheme.dark(), + themeMode: ThemeMode.dark, + locale: const Locale('en'), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold( + key: const Key('dark-smoke-scaffold'), + appBar: AppBar(title: const Text('Dark')), + body: Card( + key: const Key('dark-smoke-card'), + child: ListTile( + title: Text( + 'Primary', + style: TextStyle(color: AppColors.onSurface), + ), + subtitle: Text('Muted', style: TextStyle(color: AppColors.muted)), + trailing: Text('128', style: TextStyle(color: AppColors.gold)), + ), + ), + bottomNavigationBar: NavigationBar( + selectedIndex: 0, + destinations: const [ + NavigationDestination(icon: Icon(Icons.home), label: 'Home'), + NavigationDestination(icon: Icon(Icons.person), label: 'Mine'), + ], + ), + ), + ), + ); + await tester.pumpAndSettle(); + + final context = tester.element( + find.byKey(const Key('dark-smoke-scaffold')), + ); + final theme = Theme.of(context); + expect(theme.brightness, Brightness.dark); + expect(theme.scaffoldBackgroundColor, AppColors.ink); + expect(theme.appBarTheme.backgroundColor, AppColors.card); + expect(theme.cardTheme.color, AppColors.card); + + final card = tester.widget(find.byKey(const Key('dark-smoke-card'))); + expect(card.color ?? theme.cardTheme.color, AppColors.card); + expect(find.text('Primary'), findsOneWidget); + expect(find.text('Muted'), findsOneWidget); + expect(find.text('128'), findsOneWidget); + }); + + testWidgets('should_render_watch_records_page_on_dark_surface', ( + tester, + ) async { + final auth = AuthController( + AuthApi( + ApiClient( + baseUrl: 'http://127.0.0.1:1', + adapter: FakeHttpAdapter( + (_) async => + FakeHttpResponse('{"code":0,"message":"ok","data":null}'), + ), + ), + ), + AuthSessionStore(), + ); + + await tester.pumpWidget( + ChangeNotifierProvider.value( + value: auth, + child: MaterialApp( + theme: AppTheme.dark(), + darkTheme: AppTheme.dark(), + themeMode: ThemeMode.dark, + locale: const Locale('en'), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: const WatchRecordsPage(), + ), + ), + ); + await tester.pumpAndSettle(); + + final scaffold = tester.widget( + find.byKey(const Key('watch-records-page')), + ); + expect(scaffold.backgroundColor, AppColors.ink); + expect(find.byKey(const Key('auth-gate-panel')), findsOneWidget); + expect(find.byType(Scaffold), findsOneWidget); + }); +}