merge: 客户端切换深色视觉风格
This commit is contained in:
commit
ec3cb8f163
75 files changed
+732
-330
No files matched your search
@@ -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` 漂移。
|
||||
- 不启停服务、不操作真机。
|
||||
@@ -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|(?<!App)Colors\." lib | rg -v '^lib/core/theme/'
|
||||
(no output)
|
||||
|
||||
$ rg -n "SystemUiOverlayStyle\.dark" lib
|
||||
(no output)
|
||||
```
|
||||
|
||||
解读:pubspec.lock 漂移已还原;页面/组件层不再直接写颜色,所有状态栏均使用浅色图标。
|
||||
|
||||
```text
|
||||
$ dart analyze lib test
|
||||
Analyzing lib, test...
|
||||
No issues found!
|
||||
```
|
||||
|
||||
```text
|
||||
$ flutter test --no-pub
|
||||
00:51 +644: All tests passed!
|
||||
```
|
||||
|
||||
```text
|
||||
$ flutter build apk --debug
|
||||
Running Gradle task 'assembleDebug'...
|
||||
2,058ms
|
||||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||||
```
|
||||
|
||||
解读:静态分析零告警,644 项测试全部通过,debug APK 构建成功;测试与构建后再次确认
|
||||
`pubspec.lock` 无改动,未启停服务、未操作真机。
|
||||
+10
-8
@@ -53,13 +53,15 @@ import 'core/theme/app_colors.dart';
|
||||
import 'l10n/app_localizations.dart';
|
||||
import 'pages/home/home_view.dart';
|
||||
|
||||
/// 全局系统栏样式:浅暖导航栏、关闭 Android 对比遮罩。
|
||||
final SystemUiOverlayStyle appSystemOverlayStyle = SystemUiOverlayStyle.dark
|
||||
/// 全局系统栏样式:深色底 + 浅色图标,关闭 Android 对比遮罩。
|
||||
final SystemUiOverlayStyle appSystemOverlayStyle = SystemUiOverlayStyle.light
|
||||
.copyWith(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarColor: AppColors.transparent,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
statusBarBrightness: Brightness.dark,
|
||||
systemNavigationBarColor: AppColors.ink,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
systemNavigationBarIconBrightness: Brightness.dark,
|
||||
systemNavigationBarDividerColor: AppColors.transparent,
|
||||
systemNavigationBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarContrastEnforced: false,
|
||||
);
|
||||
|
||||
@@ -280,9 +282,9 @@ class _LumaDramaAppState extends State<LumaDramaApp> {
|
||||
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<SystemUiOverlayStyle>(
|
||||
value: appSystemOverlayStyle,
|
||||
child: child ?? const SizedBox.shrink(),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+140
-24
@@ -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);
|
||||
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ Future<bool> 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,
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -51,7 +51,7 @@ class _AccountDeactivatePageState extends State<AccountDeactivatePage> {
|
||||
final signedIn = context.watch<AuthController>().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'),
|
||||
|
||||
@@ -51,7 +51,7 @@ class _AccountDevicesPageState extends State<AccountDevicesPage> {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final signedIn = context.watch<AuthController>().isSignedIn;
|
||||
return Theme(
|
||||
data: AppTheme.light(),
|
||||
data: AppTheme.dark(),
|
||||
child: PlainPageScaffold(
|
||||
pageKey: const Key('account-devices-page'),
|
||||
backKey: const Key('account-devices-back'),
|
||||
|
||||
@@ -52,7 +52,7 @@ class _AccountExportPageState extends State<AccountExportPage> {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final signedIn = context.watch<AuthController>().isSignedIn;
|
||||
return Theme(
|
||||
data: AppTheme.light(),
|
||||
data: AppTheme.dark(),
|
||||
child: PlainPageScaffold(
|
||||
pageKey: const Key('account-export-page'),
|
||||
backKey: const Key('account-export-back'),
|
||||
|
||||
@@ -70,7 +70,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final signedIn = context.watch<AuthController>().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<AccountProfilePage> {
|
||||
|
||||
Widget _genderField(AppLocalizations l10n) => DropdownButtonFormField<String>(
|
||||
key: const Key('account-gender'),
|
||||
isExpanded: true,
|
||||
initialValue: _gender,
|
||||
decoration: InputDecoration(labelText: l10n.accountGender),
|
||||
items: [
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -258,7 +258,7 @@ class _AuthPageState extends State<AuthPage> {
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Theme(
|
||||
data: AppTheme.light(),
|
||||
data: AppTheme.dark(),
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: AppColors.ink,
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
|
||||
@@ -28,8 +28,8 @@ Future<bool?> 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),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ const double _arrowSize = 36;
|
||||
const double _illustrationWidth = 156;
|
||||
const double _illustrationHeight = 88;
|
||||
const double _mottoLineWidth = 96;
|
||||
const List<Color> _aiGradientStart = [Color(0xFFFFE3D0), Color(0xFFFFC9AC)];
|
||||
const List<Color> _uploadGradient = [Color(0xFFFFEAD9), Color(0xFFFFD3B3)];
|
||||
const List<Color> _aiGradientStart = [AppColors.raised, AppColors.goldCard];
|
||||
const List<Color> _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(
|
||||
|
||||
@@ -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<DiscoverPage> {
|
||||
),
|
||||
);
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
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<DiscoverPage> {
|
||||
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<DiscoverPage> {
|
||||
_categoryUnderlineHeight,
|
||||
),
|
||||
painter: CategoryUnderlinePainter(
|
||||
color: selected ? AppColors.blue : Colors.transparent,
|
||||
color: selected ? AppColors.blue : AppColors.transparent,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -97,7 +97,7 @@ class _DiscoverSearchFieldState extends State<DiscoverSearchField> {
|
||||
color: AppColors.card,
|
||||
borderRadius: BorderRadius.circular(_searchRadius),
|
||||
border: Border.all(
|
||||
color: _focused ? AppColors.blue : Colors.transparent,
|
||||
color: _focused ? AppColors.blue : AppColors.transparent,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -183,8 +183,8 @@ class _FeedBannerCarouselState extends State<FeedBannerCarousel> {
|
||||
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<FeedBannerCarousel> {
|
||||
child: dramaId == null
|
||||
? card
|
||||
: Material(
|
||||
color: Colors.transparent,
|
||||
color: AppColors.transparent,
|
||||
borderRadius: borderRadius,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
|
||||
@@ -56,7 +56,7 @@ class _DownloadsPageState extends State<DownloadsPage> {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final signedIn = context.watch<AuthController>().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),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -88,15 +88,15 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -75,7 +75,7 @@ class _FavoritesPageState extends State<FavoritesPage> {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final signedIn = context.watch<AuthController>().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(
|
||||
|
||||
@@ -58,7 +58,7 @@ class _FilterPageState extends State<FilterPage> {
|
||||
);
|
||||
final results = _filtered(catalog.dramas);
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.dark,
|
||||
value: SystemUiOverlayStyle.light,
|
||||
child: Scaffold(
|
||||
key: const Key('filter-page'),
|
||||
backgroundColor: AppColors.ink,
|
||||
|
||||
@@ -100,7 +100,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
final catalog = context.watch<CatalogController>();
|
||||
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<HomePage> 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<HomePage> 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<SystemUiOverlayStyle>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -91,7 +91,7 @@ class _MyPageState extends State<MyPage> {
|
||||
),
|
||||
);
|
||||
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<MyPage> {
|
||||
Future<void> _openLanguage() async {
|
||||
final selected = await showModalBottomSheet<Locale>(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundColor: AppColors.transparent,
|
||||
showDragHandle: false,
|
||||
builder: (_) =>
|
||||
_LanguageSheet(selected: context.read<LocaleController>().locale),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -102,7 +102,7 @@ class _NewReleasesPageState extends State<NewReleasesPage> {
|
||||
final hero = visible.isEmpty ? null : visible.first;
|
||||
final more = pager.items.skip(1).take(_moreLimit).toList();
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
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<NewReleasesPage> {
|
||||
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<NewReleasesPage> {
|
||||
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<NewReleasesPage> {
|
||||
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<NewReleasesPage> {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
color: AppColors.muted,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
@@ -338,7 +341,7 @@ class _NewReleasesPageState extends State<NewReleasesPage> {
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.play_arrow_rounded,
|
||||
color: Colors.white,
|
||||
color: AppColors.onPrimary,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
@@ -490,7 +493,7 @@ class _NewReleasesPageState extends State<NewReleasesPage> {
|
||||
child: Text(
|
||||
l10n.newBadge,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: compact ? 9 : 10,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
|
||||
@@ -46,7 +46,7 @@ class _NoticesPageState extends State<NoticesPage> {
|
||||
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'),
|
||||
|
||||
@@ -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<void>(
|
||||
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<MyPageAction>(MaterialPageRoute(builder: (_) => const MyPage()));
|
||||
final action = await Navigator.of(
|
||||
context,
|
||||
).push<MyPageAction>(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<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundColor: AppColors.transparent,
|
||||
showDragHandle: false,
|
||||
isDismissible: false,
|
||||
enableDrag: false,
|
||||
|
||||
@@ -393,19 +393,19 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
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<PlayerPage>
|
||||
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<PlayerPage>
|
||||
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<PlayerPage>
|
||||
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<PlayerPage>
|
||||
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<PlayerPage>
|
||||
? l10n.lockedTitle(episode.number)
|
||||
: l10n.noMediaNotice,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
style: const TextStyle(color: AppColors.muted),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -6,7 +6,7 @@ extension _PlayerViewActions on _PlayerPageState {
|
||||
Future<void> _openSpeedSheet(BuildContext context) async {
|
||||
final selected = await showModalBottomSheet<double>(
|
||||
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<void>(
|
||||
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<bool>(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundColor: AppColors.transparent,
|
||||
showDragHandle: false,
|
||||
isScrollControlled: true,
|
||||
builder: (_) => PlayerUnlockSheet(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -40,11 +40,11 @@ class EpisodePickerSheet extends StatelessWidget {
|
||||
final episodes = drama.episodes;
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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<Shadow> _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<HomeFeedOverlay> {
|
||||
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<HomeFeedOverlay> {
|
||||
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<HomeFeedOverlay> {
|
||||
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<HomeFeedOverlay> {
|
||||
tag,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: _tagFontSize,
|
||||
),
|
||||
),
|
||||
@@ -369,7 +369,7 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
||||
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<HomeFeedOverlay> {
|
||||
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<HomeFeedOverlay> {
|
||||
),
|
||||
const Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: Colors.white,
|
||||
color: AppColors.onPrimary,
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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)],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Shadow> _iconShadows = [
|
||||
Shadow(color: Colors.black54, blurRadius: 12),
|
||||
Shadow(color: AppColors.shadow, blurRadius: 12),
|
||||
];
|
||||
|
||||
/// 首页沉浸流与全屏播放器共用的播放/暂停按钮。
|
||||
|
||||
@@ -271,7 +271,7 @@ class _PlayerMoreSheetState extends State<PlayerMoreSheet> {
|
||||
}) {
|
||||
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),
|
||||
),
|
||||
|
||||
@@ -18,7 +18,7 @@ const double _topBarHeight = 58;
|
||||
const double _boostHintIconSize = 16;
|
||||
const double _boostHintFontSize = 13;
|
||||
const List<Shadow> _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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<PlayerVideoSurface> {
|
||||
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<PlayerVideoSurface> {
|
||||
maxLines: _errorMaxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 13),
|
||||
style: const TextStyle(color: AppColors.muted, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -164,7 +164,7 @@ class _ScrubPreviewCardState extends State<ScrubPreviewCard> {
|
||||
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<ScrubPreviewCard> {
|
||||
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<ScrubPreviewCard> {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.2,
|
||||
|
||||
@@ -233,7 +233,7 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
||||
'${_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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<ReservePage> {
|
||||
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<ReservePage> {
|
||||
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<ReservePage> {
|
||||
),
|
||||
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<ReservePage> {
|
||||
child: Text(
|
||||
'${index + 1}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
|
||||
@@ -23,7 +23,7 @@ class SettingsPage extends StatelessWidget {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final auth = context.watch<AuthController>();
|
||||
return Theme(
|
||||
data: AppTheme.light(),
|
||||
data: AppTheme.dark(),
|
||||
child: PlainPageScaffold(
|
||||
pageKey: const Key('settings-page'),
|
||||
backKey: const Key('settings-back'),
|
||||
|
||||
@@ -57,7 +57,7 @@ class _StorePageState extends State<StorePage> {
|
||||
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'),
|
||||
|
||||
@@ -46,7 +46,7 @@ class _WalletPageState extends State<WalletPage> {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final wallet = context.watch<WalletController>();
|
||||
return Theme(
|
||||
data: AppTheme.light(),
|
||||
data: AppTheme.dark(),
|
||||
child: PlainPageScaffold(
|
||||
pageKey: const Key('wallet-page'),
|
||||
backKey: const Key('wallet-back'),
|
||||
|
||||
@@ -38,7 +38,7 @@ class WatchRecordsPage extends StatelessWidget {
|
||||
final signedIn = context.watch<AuthController>().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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<Container>(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));
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -33,7 +33,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
tester.widget<Icon>(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<Icon>(find.byKey(const Key('empty-state-icon'))).color,
|
||||
AppColors.blue,
|
||||
AppColors.blueSoft,
|
||||
);
|
||||
|
||||
await _pump(
|
||||
@@ -121,7 +121,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
tester.widget<Icon>(find.byKey(const Key('empty-state-icon'))).color,
|
||||
AppColors.blue,
|
||||
AppColors.blueSoft,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -144,6 +144,6 @@ void main() {
|
||||
matching: find.byType(ColoredBox),
|
||||
),
|
||||
);
|
||||
expect(coloredBox.color, Colors.black);
|
||||
expect(coloredBox.color, AppColors.bar);
|
||||
});
|
||||
}
|
||||
@@ -52,9 +52,9 @@ void main() {
|
||||
final bar = tester.widget<Container>(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<Text>(
|
||||
@@ -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<Container>(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<Container>(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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<ColoredBox>(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
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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<Card>(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<AuthController>.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<Scaffold>(
|
||||
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);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user