style(ui): 合并 K20 客户端视觉统一

This commit is contained in:
Zhou Weidong committed 2026-10-19 10:23:38 +08:00
commit e44fbdf6f2
28 files changed
+803 -569

No files matched your search

+11 -1
View File
@@ -3,6 +3,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'core/config/locale_controller.dart';
@@ -39,6 +40,7 @@ import 'core/data/services/playback_session.dart';
import 'core/data/services/video_player_port.dart';
import 'core/platform/haptics.dart';
import 'core/theme/app_theme.dart';
import 'core/theme/app_colors.dart';
import 'l10n/app_localizations.dart';
import 'pages/home/home_view.dart';
@@ -218,9 +220,17 @@ class _LumaDramaAppState extends State<LumaDramaApp> {
localeResolutionCallback: (deviceLocale, _) =>
resolveAppLocale(locale.locale, deviceLocale),
debugShowCheckedModeBanner: false,
themeMode: ThemeMode.system,
themeMode: ThemeMode.dark,
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
builder: (context, child) => AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light.copyWith(
statusBarColor: Colors.transparent,
systemNavigationBarColor: AppColors.bar,
systemNavigationBarIconBrightness: Brightness.light,
),
child: child ?? const SizedBox.shrink(),
),
home: const HomePage(),
),
),
+3
View File
@@ -20,6 +20,9 @@ abstract final class AppColors {
/// Coins, rewards, and the playback progress line.
static const gold = Color(0xFFF6C453);
/// Favorite/like heart without competing with the chili primary action.
static const favorite = Color(0xFFD74A7D);
/// Text placed on [gold].
static const onGold = Color(0xFF2A1810);
+43
View File
@@ -0,0 +1,43 @@
// Author: fruit.com.cn
/// K20 cross-page spacing, type, radius, and control sizes.
abstract final class AppDimens {
static const double spaceXs = 4;
static const double spaceSm = 8;
static const double spaceMd = 12;
static const double spaceLg = 16;
static const double spaceXl = 24;
static const double pageHorizontal = 16;
static const double cardRadius = 16;
static const double authCardRadius = 20;
static const double cardPadding = 16;
static const double innerRadius = 12;
static const double innerPadding = 12;
static const double pillRadius = 20;
static const double caption = 12;
static const double emptyStateSubtitle = 12.5;
static const double emptyStateAction = 13;
static const double body = 14;
static const double listTitle = 15;
static const double sectionTitle = 16;
static const double pageTitle = 20;
static const double detailHeadline = 24;
static const double navLabel = 11;
static const double buttonHeight = 48;
static const double primaryButtonHeight = 52;
static const double tapTarget = 44;
static const double navHeight = 64;
static const double navIcon = 22;
static const double progressHeight = 4;
static const double progressRadius = 4;
static const double accountCardHeight = 64;
static const double accountIcon = 18;
static const double accountLabel = 11;
static const double watchPlaceholderHeight = 114;
}
+28
View File
@@ -1,8 +1,10 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'app_colors.dart';
import 'app_dimens.dart';
/// Light paper and warm-cocoa themes for the Indonesia evening palette.
abstract final class AppTheme {
@@ -80,6 +82,32 @@ abstract final class AppTheme {
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith(_buttonColor),
foregroundColor: const WidgetStatePropertyAll(AppColors.onPrimary),
minimumSize: const WidgetStatePropertyAll(
Size(0, AppDimens.buttonHeight),
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
foregroundColor: WidgetStatePropertyAll(
dark ? AppColors.blueSoft : AppColors.bluePressed,
),
minimumSize: const WidgetStatePropertyAll(
Size(0, AppDimens.buttonHeight),
),
side: WidgetStatePropertyAll(
BorderSide(
color: dark ? AppColors.outlineDark : AppColors.outlineLight,
),
),
),
),
appBarTheme: const AppBarTheme(
systemOverlayStyle: SystemUiOverlayStyle.light,
titleTextStyle: TextStyle(
color: AppColors.cream,
fontSize: AppDimens.pageTitle,
fontWeight: FontWeight.w800,
),
),
switchTheme: SwitchThemeData(
+112
View File
@@ -0,0 +1,112 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../theme/app_colors.dart';
import '../theme/app_dimens.dart';
/// One destination rendered by [AppBottomNav].
class AppBottomNavItem {
const AppBottomNavItem({
required this.tabKey,
required this.icon,
required this.selectedIcon,
required this.label,
required this.onTap,
this.selected = false,
});
final Key tabKey;
final IconData icon;
final IconData selectedIcon;
final String label;
final VoidCallback onTap;
final bool selected;
}
/// Shared bottom navigation used by all root tabs and the player fallback.
class AppBottomNav extends StatelessWidget {
const AppBottomNav({
super.key,
required this.items,
this.backgroundColor = AppColors.bar,
this.barKey,
});
final List<AppBottomNavItem> items;
final Color backgroundColor;
final Key? barKey;
@override
Widget build(BuildContext context) => SafeArea(
top: false,
child: Container(
key: barKey,
height: AppDimens.navHeight,
decoration: BoxDecoration(
color: backgroundColor,
border: const Border(
top: BorderSide(color: AppColors.outlineVariantDark),
),
),
child: Row(
children: [for (final item in items) _NavDestination(item: item)],
),
),
);
}
class _NavDestination extends StatelessWidget {
const _NavDestination({required this.item});
final AppBottomNavItem item;
@override
Widget build(BuildContext context) => Expanded(
child: Semantics(
button: true,
selected: item.selected,
label: item.label,
child: InkWell(
key: item.tabKey,
onTap: item.onTap,
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: AppDimens.tapTarget),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 160),
curve: Curves.easeOut,
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 3,
),
decoration: BoxDecoration(
color: item.selected ? AppColors.blue : Colors.transparent,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Icon(
item.selected ? item.selectedIcon : item.icon,
size: AppDimens.navIcon,
color: item.selected ? AppColors.onPrimary : AppColors.muted,
),
),
const SizedBox(height: 2),
Text(
item.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: AppDimens.navLabel,
fontWeight: item.selected ? FontWeight.w700 : FontWeight.w500,
color: item.selected ? AppColors.blueSoft : AppColors.muted,
),
),
],
),
),
),
),
);
}
+7 -3
View File
@@ -3,6 +3,7 @@
import 'package:flutter/material.dart';
import '../theme/app_colors.dart';
import '../theme/app_dimens.dart';
const double _regularIconSize = 48;
const double _compactIconSize = 32;
@@ -139,7 +140,7 @@ class _EmptyCopy extends StatelessWidget {
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.muted,
fontSize: 12.5,
fontSize: AppDimens.emptyStateSubtitle,
height: 1.45,
),
),
@@ -151,11 +152,14 @@ class _EmptyCopy extends StatelessWidget {
key: actionKey ?? const Key('empty-state-action'),
onPressed: onAction,
style: TextButton.styleFrom(
foregroundColor: scheme.primary,
foregroundColor: AppColors.blueSoft,
minimumSize: const Size(44, _actionMinHeight),
padding: const EdgeInsets.symmetric(horizontal: 14),
),
child: Text(actionLabel!, style: const TextStyle(fontSize: 13)),
child: Text(
actionLabel!,
style: const TextStyle(fontSize: AppDimens.emptyStateAction),
),
),
],
],
+6 -5
View File
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import '../data/models/drama.dart';
import '../theme/app_colors.dart';
import '../theme/app_dimens.dart';
import 'remote_image.dart';
/// 收藏等账号列表复用的剧集卡片,视觉与"我的"页列表保持一致。
@@ -23,16 +24,16 @@ class LibraryDramaTile extends StatelessWidget {
Widget build(BuildContext context) {
return Material(
color: AppColors.card,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(9),
padding: const EdgeInsets.all(AppDimens.innerPadding),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
child: RemoteImage(drama.posterUrl, width: 74, height: 103),
),
const SizedBox(width: 11),
@@ -46,7 +47,7 @@ class LibraryDramaTile extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontSize: 15,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w700,
),
),
@@ -57,7 +58,7 @@ class LibraryDramaTile extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.muted,
fontSize: 12,
fontSize: AppDimens.caption,
),
),
],
+16 -9
View File
@@ -9,6 +9,7 @@ import '../../core/data/repositories/account_repository.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/formatting/localized_date_time.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/auth_gate.dart';
import '../../l10n/app_localizations.dart';
@@ -58,12 +59,15 @@ class _AccountDeactivatePageState extends State<AccountDeactivatePage> {
foregroundColor: AppColors.onPrimary,
title: Text(l10n.accountDeactivate),
),
body: canShow
? ListenableBuilder(
listenable: _logic,
builder: (context, _) => _body(l10n),
)
: const AuthGatePanel(),
body: SafeArea(
top: false,
child: canShow
? ListenableBuilder(
listenable: _logic,
builder: (context, _) => _body(l10n),
)
: const AuthGatePanel(),
),
),
);
}
@@ -90,7 +94,7 @@ class _AccountDeactivatePageState extends State<AccountDeactivatePage> {
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: errorColor.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(14),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
border: Border.all(color: errorColor.withValues(alpha: 0.35)),
),
child: Text(
@@ -105,7 +109,7 @@ class _AccountDeactivatePageState extends State<AccountDeactivatePage> {
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(14),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
),
child: Text(
l10n.accountScheduledFor(
@@ -161,7 +165,10 @@ class _AccountDeactivatePageState extends State<AccountDeactivatePage> {
Widget _errorText(String value) => Text(
value,
style: TextStyle(color: Theme.of(context).colorScheme.error, fontSize: 12),
style: TextStyle(
color: Theme.of(context).colorScheme.error,
fontSize: AppDimens.caption,
),
);
Future<void> _confirm() async {
+22 -16
View File
@@ -11,6 +11,7 @@ import '../../core/data/services/auth_controller.dart';
import '../../core/formatting/error_text.dart';
import '../../core/formatting/localized_date_time.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/empty_state.dart';
@@ -58,12 +59,15 @@ class _AccountDevicesPageState extends State<AccountDevicesPage> {
foregroundColor: AppColors.onPrimary,
title: Text(l10n.accountDevices),
),
body: signedIn
? ListenableBuilder(
listenable: _logic,
builder: (context, _) => _body(l10n),
)
: const AuthGatePanel(),
body: SafeArea(
top: false,
child: signedIn
? ListenableBuilder(
listenable: _logic,
builder: (context, _) => _body(l10n),
)
: const AuthGatePanel(),
),
),
);
}
@@ -109,10 +113,10 @@ class _DeviceTile extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
key: ValueKey('account-device-${device.id}'),
padding: const EdgeInsets.all(14),
padding: const EdgeInsets.all(AppDimens.cardPadding),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(14),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
border: Border.all(color: AppColors.outlineVariantDark),
),
child: Column(
@@ -131,7 +135,7 @@ class _DeviceTile extends StatelessWidget {
? device.platform
: device.deviceModel,
style: const TextStyle(
fontSize: 15,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w700,
),
),
@@ -164,14 +168,17 @@ class _DeviceTile extends StatelessWidget {
Expanded(
child: Text(
label,
style: const TextStyle(color: AppColors.muted, fontSize: 12),
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
),
Flexible(
child: Text(
value.isEmpty ? '-' : value,
textAlign: TextAlign.right,
style: const TextStyle(fontSize: 12),
style: const TextStyle(fontSize: AppDimens.caption),
),
),
],
@@ -188,20 +195,19 @@ class _StatusChip extends StatelessWidget {
@override
Widget build(BuildContext context) {
final active = status.toLowerCase() == 'active';
final scheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: active
? scheme.tertiary.withValues(alpha: 0.16)
? AppColors.gold.withValues(alpha: 0.16)
: AppColors.raised,
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Text(
active ? l10n.accountDeviceActive : l10n.accountDeviceInactive,
style: TextStyle(
color: active ? scheme.tertiary : AppColors.muted,
fontSize: 11,
color: active ? AppColors.gold : AppColors.muted,
fontSize: AppDimens.caption,
),
),
);
+12 -5
View File
@@ -3,13 +3,13 @@
import 'package:flutter/material.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../l10n/app_localizations.dart';
import 'account_deactivate_page.dart';
import 'account_devices_page.dart';
import 'account_export_page.dart';
import 'account_profile_page.dart';
const _accountCardHeight = 48.0;
const _accountTitleWidth = 64.0;
/// 我的页账号入口卡;只在登录态由父页面展示。
@@ -25,7 +25,7 @@ class AccountEntryCard extends StatelessWidget {
borderRadius: BorderRadius.circular(16),
clipBehavior: Clip.antiAlias,
child: SizedBox(
height: _accountCardHeight,
height: AppDimens.accountCardHeight,
child: Row(
children: [
SizedBox(
@@ -35,7 +35,7 @@ class AccountEntryCard extends StatelessWidget {
child: Text(
l10n.accountCenter,
style: const TextStyle(
fontSize: 12,
fontSize: AppDimens.caption,
fontWeight: FontWeight.w800,
),
),
@@ -109,14 +109,21 @@ class AccountEntryCard extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: AppColors.blueSoft, size: 16),
Icon(
icon,
color: AppColors.blueSoft,
size: AppDimens.accountIcon,
),
const SizedBox(height: 2),
Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 9.5, height: 1.05),
style: const TextStyle(
fontSize: AppDimens.accountLabel,
height: 1.15,
),
),
],
),
+28 -12
View File
@@ -11,6 +11,7 @@ import '../../core/data/repositories/account_repository.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/formatting/error_text.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/empty_state.dart';
@@ -59,12 +60,15 @@ class _AccountExportPageState extends State<AccountExportPage> {
foregroundColor: AppColors.onPrimary,
title: Text(l10n.accountDataExport),
),
body: signedIn
? ListenableBuilder(
listenable: _logic,
builder: (context, _) => _body(l10n),
)
: const AuthGatePanel(),
body: SafeArea(
top: false,
child: signedIn
? ListenableBuilder(
listenable: _logic,
builder: (context, _) => _body(l10n),
)
: const AuthGatePanel(),
),
),
);
}
@@ -106,7 +110,10 @@ class _AccountExportPageState extends State<AccountExportPage> {
const SizedBox(height: 4),
Text(
'${l10n.accountUid}: ${data.profile.uid}',
style: const TextStyle(color: AppColors.muted, fontSize: 12),
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
],
);
@@ -115,7 +122,10 @@ class _AccountExportPageState extends State<AccountExportPage> {
children: [
Text(
l10n.accountCredentials,
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700),
style: const TextStyle(
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w700,
),
),
for (final credential in data.credentials)
ListTile(
@@ -128,7 +138,10 @@ class _AccountExportPageState extends State<AccountExportPage> {
subtitle: Text(credential.type),
trailing: Text(
credential.verified ? l10n.accountVerified : l10n.accountUnverified,
style: const TextStyle(color: AppColors.muted, fontSize: 11),
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
),
_summary(l10n.accountConsentRecords, '${data.consentCount}'),
@@ -144,10 +157,10 @@ class _AccountExportPageState extends State<AccountExportPage> {
);
Widget _card({required List<Widget> children}) => Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(AppDimens.cardPadding),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
border: Border.all(color: AppColors.outlineVariantDark),
),
child: Column(
@@ -163,7 +176,10 @@ class _AccountExportPageState extends State<AccountExportPage> {
Expanded(
child: Text(
label,
style: const TextStyle(color: AppColors.muted, fontSize: 12),
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
),
Text(value, style: const TextStyle(fontWeight: FontWeight.w700)),
+31 -15
View File
@@ -11,6 +11,7 @@ import '../../core/data/services/auth_controller.dart';
import '../../core/formatting/error_text.dart';
import '../../core/formatting/localized_date_time.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/empty_state.dart';
@@ -79,12 +80,15 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
foregroundColor: AppColors.onPrimary,
title: Text(l10n.accountProfile),
),
body: signedIn
? ListenableBuilder(
listenable: _logic,
builder: (context, _) => _body(l10n),
)
: const AuthGatePanel(),
body: SafeArea(
top: false,
child: signedIn
? ListenableBuilder(
listenable: _logic,
builder: (context, _) => _body(l10n),
)
: const AuthGatePanel(),
),
),
);
}
@@ -139,14 +143,17 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
Text(
profile.nickname,
style: const TextStyle(
fontSize: 20,
fontSize: AppDimens.pageTitle,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 4),
Text(
'${l10n.accountUid}: ${profile.uid}',
style: const TextStyle(color: AppColors.muted, fontSize: 12),
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
],
),
@@ -211,7 +218,10 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
const SizedBox(height: 6),
Text(
l10n.accountBirthdayHint,
style: const TextStyle(color: AppColors.muted, fontSize: 12),
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
],
);
@@ -222,7 +232,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
errorText(l10n, _logic.error, l10n.accountProfileSaveFailed),
style: TextStyle(
color: Theme.of(context).colorScheme.error,
fontSize: 12,
fontSize: AppDimens.caption,
),
),
);
@@ -250,7 +260,10 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
children: [
Text(
l10n.accountReadOnlyInfo,
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700),
style: const TextStyle(
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 10),
_info(l10n.accountMaskedAccount, profile.maskedTarget),
@@ -283,14 +296,17 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
Expanded(
child: Text(
label,
style: const TextStyle(color: AppColors.muted, fontSize: 12),
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
),
Flexible(
child: Text(
value.isEmpty ? '-' : value,
textAlign: TextAlign.right,
style: const TextStyle(fontSize: 13),
style: const TextStyle(fontSize: AppDimens.body),
),
),
],
@@ -337,10 +353,10 @@ class _AccountCard extends StatelessWidget {
@override
Widget build(BuildContext context) => Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(AppDimens.cardPadding),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
border: Border.all(color: AppColors.outlineVariantDark),
),
child: child,
+6 -4
View File
@@ -9,6 +9,7 @@ import '../../core/data/remote/api_client.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/platform/haptics.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../l10n/app_localizations.dart';
import 'auth_logic.dart';
@@ -19,8 +20,9 @@ part 'auth_form_widgets.dart';
const double _minTapTarget = 44;
const double _brandMarkSize = 48;
const double _contentMaxWidth = 440;
const double _cardRadius = 20;
const double _cardPadding = 20;
// Auth keeps its approved 20px card silhouette.
const double _cardRadius = AppDimens.authCardRadius;
const double _cardPadding = AppDimens.cardPadding;
const double _cardBorderWidth = 1;
const double _fieldHeight = 52;
const double _fieldRadius = 14;
@@ -163,9 +165,9 @@ class _AuthPageState extends State<AuthPage> {
keyboardDismissBehavior:
ScrollViewKeyboardDismissBehavior.onDrag,
padding: EdgeInsets.fromLTRB(
20,
AppDimens.pageHorizontal,
12,
20,
AppDimens.pageHorizontal,
28 + MediaQuery.viewInsetsOf(context).bottom,
),
children: [
+1 -1
View File
@@ -103,7 +103,7 @@ class _AuthCard extends StatelessWidget {
l10n.authSubtitle,
style: const TextStyle(
color: AppColors.muted,
fontSize: 13,
fontSize: AppDimens.body,
height: 1.45,
),
),
+87 -115
View File
@@ -10,7 +10,9 @@ import '../../core/data/repositories/watch_history_repository.dart';
import '../../core/data/services/catalog_controller.dart';
import '../../core/data/services/category_controller.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/app_bottom_nav.dart';
import '../../core/widgets/empty_state.dart';
import '../../core/widgets/remote_image.dart';
import '../../l10n/app_localizations.dart';
@@ -261,7 +263,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
];
return SizedBox(
key: const Key('home-category-chips'),
height: 36,
height: AppDimens.tapTarget,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: choices.length,
@@ -269,24 +271,31 @@ class _DiscoverPageState extends State<DiscoverPage> {
itemBuilder: (context, index) {
final choice = choices[index];
final selected = categories.selectedCode == choice.code;
return GestureDetector(
onTap: () => categories.selectCode(choice.code),
child: AnimatedContainer(
duration: const Duration(milliseconds: 180),
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration(
color: selected ? AppColors.blue : AppColors.raised,
borderRadius: BorderRadius.circular(18),
),
child: Text(
choice.label,
maxLines: 1,
style: TextStyle(
color: selected ? AppColors.onPrimary : AppColors.cream,
fontSize: 13,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
height: 1.1,
return Semantics(
selected: selected,
button: true,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => categories.selectCode(choice.code),
child: Center(
child: AnimatedContainer(
duration: const Duration(milliseconds: 180),
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration(
color: selected ? AppColors.blue : AppColors.raised,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Text(
choice.label,
maxLines: 1,
style: TextStyle(
color: selected ? AppColors.onPrimary : AppColors.cream,
fontSize: AppDimens.body,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
height: 1.1,
),
),
),
),
),
@@ -296,83 +305,41 @@ class _DiscoverPageState extends State<DiscoverPage> {
);
}
Widget _bottomNav(AppLocalizations l10n) => Container(
height: 63,
decoration: const BoxDecoration(
color: AppColors.bar,
border: Border(top: BorderSide(color: AppColors.outlineVariantDark)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_DiscoverNavItem(
icon: Icons.play_circle_outline_rounded,
label: l10n.homeTab,
onTap: () => logic.act(
context,
DiscoverPageAction.home,
embedded: widget.embedded,
onAction: widget.onAction,
),
Widget _bottomNav(AppLocalizations l10n) => AppBottomNav(
items: [
AppBottomNavItem(
tabKey: const Key('discover-nav-home'),
icon: Icons.play_circle_outline_rounded,
selectedIcon: Icons.play_circle_fill_rounded,
label: l10n.homeTab,
onTap: () => logic.act(
context,
DiscoverPageAction.home,
embedded: widget.embedded,
onAction: widget.onAction,
),
_DiscoverNavItem(
icon: Icons.explore_outlined,
label: l10n.discoverTab,
selected: true,
onTap: () {},
),
_DiscoverNavItem(
icon: Icons.video_library_outlined,
label: l10n.myTab,
onTap: () => logic.act(
context,
DiscoverPageAction.my,
embedded: widget.embedded,
onAction: widget.onAction,
),
),
],
),
);
}
class _DiscoverNavItem extends StatelessWidget {
const _DiscoverNavItem({
required this.icon,
required this.label,
required this.onTap,
this.selected = false,
});
final IconData icon;
final String label;
final VoidCallback onTap;
final bool selected;
@override
Widget build(BuildContext context) => InkWell(
onTap: onTap,
child: SizedBox(
width: 72,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 21,
color: selected ? AppColors.blueSoft : AppColors.muted,
),
Text(
label,
style: TextStyle(
color: selected ? AppColors.blueSoft : AppColors.muted,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
],
),
),
AppBottomNavItem(
tabKey: const Key('discover-nav-current'),
icon: Icons.explore_outlined,
selectedIcon: Icons.explore_rounded,
label: l10n.discoverTab,
selected: true,
onTap: () {},
),
AppBottomNavItem(
tabKey: const Key('discover-nav-my'),
icon: Icons.video_library_outlined,
selectedIcon: Icons.video_library_rounded,
label: l10n.myTab,
onTap: () => logic.act(
context,
DiscoverPageAction.my,
embedded: widget.embedded,
onAction: widget.onAction,
),
),
],
);
}
@@ -421,24 +388,27 @@ class _HeaderPill extends StatelessWidget {
child: InkWell(
customBorder: const StadiumBorder(),
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 15, color: _coin),
const SizedBox(width: 4),
Text(
label,
maxLines: 1,
style: const TextStyle(
color: AppColors.cream,
fontSize: 12,
fontWeight: FontWeight.w700,
height: 1.1,
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: AppDimens.tapTarget),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 15, color: _coin),
const SizedBox(width: 4),
Text(
label,
maxLines: 1,
style: const TextStyle(
color: AppColors.cream,
fontSize: AppDimens.caption,
fontWeight: FontWeight.w700,
height: 1.1,
),
),
),
],
],
),
),
),
),
@@ -459,11 +429,13 @@ class _LoginButton extends StatelessWidget {
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: AppColors.blue,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: const EdgeInsets.symmetric(horizontal: 14),
minimumSize: const Size(AppDimens.tapTarget, AppDimens.tapTarget),
shape: const StadiumBorder(),
textStyle: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600),
textStyle: const TextStyle(
fontSize: AppDimens.body,
fontWeight: FontWeight.w600,
),
),
child: Text(label, maxLines: 1),
);
@@ -59,7 +59,7 @@ class _HomeDramaGrid extends StatelessWidget {
Widget _dramaGrid(List<Drama> dramas) => SliverPadding(
key: const Key('home-all-dramas-section'),
padding: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: AppDimens.pageHorizontal),
sliver: SliverGrid(
key: const Key('home-all-dramas-grid'),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
@@ -101,12 +101,12 @@ class _HomeDramaCard extends StatelessWidget {
return InkWell(
key: Key('home-drama-card-${drama.id}'),
onTap: onTap,
borderRadius: BorderRadius.circular(14),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
child: AspectRatio(
key: Key('home-drama-cover-${drama.id}'),
aspectRatio: 2 / 3,
child: ClipRRect(
borderRadius: BorderRadius.circular(14),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
child: Stack(
fit: StackFit.expand,
children: [
@@ -143,7 +143,7 @@ class _HomeDramaCard extends StatelessWidget {
l10n.hot,
style: const TextStyle(
color: AppColors.onPrimary,
fontSize: 10,
fontSize: AppDimens.caption,
fontWeight: FontWeight.w800,
height: 1.1,
),
@@ -166,7 +166,7 @@ class _HomeDramaCard extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.cream,
fontSize: 13,
fontSize: AppDimens.body,
fontWeight: FontWeight.w800,
height: 1.15,
),
@@ -178,8 +178,8 @@ class _HomeDramaCard extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Color(0xE6FFFFFF),
fontSize: 11,
color: AppColors.cream,
fontSize: AppDimens.caption,
fontWeight: FontWeight.w400,
height: 1.2,
),
+32 -14
View File
@@ -13,6 +13,7 @@ import '../../core/formatting/error_text.dart';
import '../../core/formatting/localized_date_time.dart';
import '../../core/platform/haptics.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/empty_state.dart';
@@ -60,9 +61,12 @@ class _DownloadsPageState extends State<DownloadsPage> {
foregroundColor: Colors.white,
title: Text(l10n.downloads),
),
body: signedIn
? _DownloadsBody(downloads: context.watch<DownloadController>())
: const AuthGatePanel(),
body: SafeArea(
top: false,
child: signedIn
? _DownloadsBody(downloads: context.watch<DownloadController>())
: const AuthGatePanel(),
),
),
);
}
@@ -194,11 +198,11 @@ class _PolicyPanel extends StatelessWidget {
final l10n = AppLocalizations.of(context)!;
return Container(
key: const Key('download-policy'),
padding: const EdgeInsets.fromLTRB(14, 14, 14, 10),
padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFF2A2C33)),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
border: Border.all(color: AppColors.outlineVariantDark),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -219,15 +223,21 @@ class _PolicyPanel extends StatelessWidget {
const SizedBox(height: 6),
Text(
policy.expirePolicyText,
style: const TextStyle(color: Color(0xFFB7B3B8), fontSize: 12),
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
const Divider(height: 22, color: Color(0xFF2A2C33)),
const Divider(height: 22, color: AppColors.outlineVariantDark),
Row(
children: [
Expanded(
child: Text(
l10n.downloadWifiOnly,
style: const TextStyle(color: Colors.white, fontSize: 13),
style: const TextStyle(
color: Colors.white,
fontSize: AppDimens.body,
),
),
),
Switch(
@@ -242,7 +252,10 @@ class _PolicyPanel extends StatelessWidget {
Expanded(
child: Text(
l10n.downloadQuality,
style: const TextStyle(color: Colors.white, fontSize: 13),
style: const TextStyle(
color: Colors.white,
fontSize: AppDimens.body,
),
),
),
DropdownButton<String>(
@@ -302,7 +315,7 @@ class _LicenseTile extends StatelessWidget {
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -355,10 +368,12 @@ class _LicenseTile extends StatelessWidget {
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(4),
borderRadius: BorderRadius.circular(AppDimens.progressRadius),
child: LinearProgressIndicator(
minHeight: 4,
minHeight: AppDimens.progressHeight,
value: (license.progressPercent / 100).clamp(0.0, 1.0),
backgroundColor: Colors.white24,
valueColor: const AlwaysStoppedAnimation(AppColors.blueSoft),
),
),
if (license.expireAt != null) ...[
@@ -367,7 +382,10 @@ class _LicenseTile extends StatelessWidget {
l10n.downloadExpiresAt(
formatLocalizedDateTime(context, license.expireAt!),
),
style: const TextStyle(color: Color(0xFF8D8990), fontSize: 11),
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
],
],
+45 -15
View File
@@ -13,6 +13,7 @@ import '../../core/formatting/error_text.dart';
import '../../core/formatting/localized_content.dart';
import '../../core/platform/haptics.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/remote_image.dart';
import '../../core/widgets/auth_gate.dart';
@@ -78,14 +79,24 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
Widget _page(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
if (logic.loading) {
return const Scaffold(
return Scaffold(
backgroundColor: AppColors.ink,
body: Center(child: CircularProgressIndicator()),
appBar: AppBar(
backgroundColor: AppColors.ink,
foregroundColor: AppColors.onPrimary,
title: Text(localizedDramaTitle(l10n, widget.drama)),
),
body: const Center(child: CircularProgressIndicator()),
);
}
if (logic.failed) {
return Scaffold(
backgroundColor: AppColors.ink,
appBar: AppBar(
backgroundColor: AppColors.ink,
foregroundColor: AppColors.onPrimary,
title: Text(localizedDramaTitle(l10n, widget.drama)),
),
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
@@ -140,11 +151,25 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
_favoriteAction(context, l10n),
],
flexibleSpace: FlexibleSpaceBar(
background: RemoteImage(drama.posterUrl),
background: Stack(
fit: StackFit.expand,
children: [
RemoteImage(drama.posterUrl),
const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.center,
colors: [Color(0x99000000), Color(0x00000000)],
),
),
),
],
),
),
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 40),
padding: const EdgeInsets.fromLTRB(16, 16, 16, 40),
sliver: SliverList(
delegate: SliverChildListDelegate([
Text(
@@ -158,16 +183,17 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
const SizedBox(height: 8),
Text(
localizedDramaTitle(l10n, drama),
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
style: const TextStyle(
fontSize: AppDimens.detailHeadline,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 10),
Text(
localizedDramaSynopsis(l10n, drama),
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
style: Theme.of(
context,
).textTheme.bodyLarge?.copyWith(color: AppColors.muted),
),
const SizedBox(height: 20),
Column(
@@ -222,15 +248,16 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
const SizedBox(height: 30),
Text(
l10n.episodes,
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontSize: AppDimens.sectionTitle,
fontWeight: FontWeight.w700,
),
),
]),
),
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 40),
padding: const EdgeInsets.fromLTRB(16, 0, 16, 40),
sliver: SliverList.builder(
itemCount: drama.episodes.length,
itemBuilder: (context, index) {
@@ -364,10 +391,10 @@ class _ResumeBar extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(2),
borderRadius: BorderRadius.circular(AppDimens.progressRadius),
child: LinearProgressIndicator(
value: fraction,
minHeight: 3,
minHeight: AppDimens.progressHeight,
backgroundColor: Colors.white24,
valueColor: const AlwaysStoppedAnimation(AppColors.blueSoft),
),
@@ -376,7 +403,10 @@ class _ResumeBar extends StatelessWidget {
const SizedBox(height: 4),
Text(
label!,
style: const TextStyle(color: Colors.white54, fontSize: 11),
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
],
],
+7 -4
View File
@@ -59,9 +59,12 @@ class _FavoritesPageState extends State<FavoritesPage> {
foregroundColor: Colors.white,
title: Text(l10n.myFavorites),
),
body: signedIn
? _FavoritesBody(favorites: context.watch<FavoriteController>())
: const AuthGatePanel(),
body: SafeArea(
top: false,
child: signedIn
? _FavoritesBody(favorites: context.watch<FavoriteController>())
: const AuthGatePanel(),
),
),
);
}
@@ -114,7 +117,7 @@ class _FavoritesBody extends StatelessWidget {
onPressed: () => _remove(context, drama.id),
icon: const Icon(
Icons.favorite_rounded,
color: Color(0xFFD74A7D),
color: AppColors.favorite,
),
),
);
+35 -113
View File
@@ -13,6 +13,7 @@ import '../../core/data/services/catalog_controller.dart';
import '../../core/platform/haptics.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/app_bottom_nav.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/empty_state.dart';
import '../../core/widgets/server_endpoint_dialog.dart';
@@ -239,57 +240,42 @@ class _HomeTabsState extends State<_HomeTabs> {
),
bottomNavigationBar: logic.videoImmersive
? null
: SafeArea(
top: false,
minimum: const EdgeInsets.only(bottom: 2),
child: Container(
key: const Key('home-tabs-bar'),
height: 64,
decoration: BoxDecoration(
color: AppColors.bar,
border: Border(
top: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant,
),
),
: AppBottomNav(
barKey: const Key('home-tabs-bar'),
items: [
AppBottomNavItem(
tabKey: const Key('tab-discover'),
icon: Icons.home_outlined,
selectedIcon: Icons.home_rounded,
label: l10n.homeTab,
selected: logic.tab == 1,
onTap: () => _selectTab(context, 1),
),
child: Row(
children: [
_TabButton(
key: const Key('tab-discover'),
icon: Icons.home_outlined,
selectedIcon: Icons.home_rounded,
label: l10n.homeTab,
selected: logic.tab == 1,
onTap: () => _selectTab(context, 1),
),
_TabButton(
key: const Key('tab-home'),
icon: Icons.movie_outlined,
selectedIcon: Icons.movie_rounded,
label: l10n.shortsTab,
selected: logic.tab == 0,
onTap: () => _selectTab(context, 0),
),
_TabButton(
key: const Key('tab-my-list'),
icon: Icons.bookmark_border_rounded,
selectedIcon: Icons.bookmark_rounded,
label: l10n.myListTab,
selected: logic.tab == 2 && !logic.profile,
onTap: () => _selectTab(context, 2),
),
_TabButton(
key: const Key('tab-my'),
icon: Icons.person_outline_rounded,
selectedIcon: Icons.person_rounded,
label: l10n.myTab,
selected: logic.tab == 2 && logic.profile,
onTap: () => _selectTab(context, 2, myProfile: true),
),
],
AppBottomNavItem(
tabKey: const Key('tab-home'),
icon: Icons.movie_outlined,
selectedIcon: Icons.movie_rounded,
label: l10n.shortsTab,
selected: logic.tab == 0,
onTap: () => _selectTab(context, 0),
),
),
AppBottomNavItem(
tabKey: const Key('tab-my-list'),
icon: Icons.bookmark_border_rounded,
selectedIcon: Icons.bookmark_rounded,
label: l10n.myListTab,
selected: logic.tab == 2 && !logic.profile,
onTap: () => _selectTab(context, 2),
),
AppBottomNavItem(
tabKey: const Key('tab-my'),
icon: Icons.person_outline_rounded,
selectedIcon: Icons.person_rounded,
label: l10n.myTab,
selected: logic.tab == 2 && logic.profile,
onTap: () => _selectTab(context, 2, myProfile: true),
),
],
),
),
);
@@ -309,67 +295,3 @@ class _HomeTabsState extends State<_HomeTabs> {
}
}
}
class _TabButton extends StatelessWidget {
const _TabButton({
super.key,
required this.icon,
required this.selectedIcon,
required this.label,
required this.selected,
required this.onTap,
});
final IconData icon;
final IconData selectedIcon;
final String label;
final bool selected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) => Expanded(
child: Semantics(
button: true,
selected: selected,
label: label,
child: InkWell(
onTap: onTap,
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 48),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 160),
curve: Curves.easeOut,
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 3,
),
decoration: BoxDecoration(
color: selected ? AppColors.blue : null,
borderRadius: BorderRadius.circular(14),
),
child: Icon(
selected ? selectedIcon : icon,
size: 22,
color: selected ? AppColors.onPrimary : AppColors.muted,
),
),
const SizedBox(height: 2),
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 10,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
color: selected ? AppColors.blueSoft : AppColors.muted,
),
),
],
),
),
),
),
);
}
+11 -8
View File
@@ -7,6 +7,7 @@ import '../../../core/config/locale_controller.dart';
import '../../../core/data/models/drama.dart';
import '../../../core/formatting/localized_content.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/remote_image.dart';
import '../../../l10n/app_localizations.dart';
@@ -51,7 +52,7 @@ class HomeQuickDrawer extends StatelessWidget {
children: [
const CircleAvatar(
radius: 30,
backgroundColor: Color(0xFF4B4142),
backgroundColor: AppColors.raised,
child: Icon(
Icons.person_rounded,
size: 34,
@@ -74,7 +75,7 @@ class HomeQuickDrawer extends StatelessWidget {
const SizedBox(height: 3),
Text(
l10n.localDemoProfile,
style: const TextStyle(color: Color(0xFFE7E2E0)),
style: const TextStyle(color: AppColors.cream),
),
],
),
@@ -91,7 +92,9 @@ class HomeQuickDrawer extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(
AppDimens.innerRadius,
),
child: RemoteImage(
drama.posterUrl,
width: 74,
@@ -115,7 +118,7 @@ class HomeQuickDrawer extends StatelessWidget {
const SizedBox(height: 7),
Text(
l10n.episodeCount(drama.episodeCount),
style: const TextStyle(color: Color(0xFFE7E2E0)),
style: const TextStyle(color: AppColors.cream),
),
],
),
@@ -229,7 +232,7 @@ class _SectionCard extends StatelessWidget {
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: HomeQuickDrawer._card,
borderRadius: BorderRadius.circular(18),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -259,11 +262,11 @@ class _QuickAction extends StatelessWidget {
@override
Widget build(BuildContext context) => Material(
color: const Color(0xFF3B3B3F),
borderRadius: BorderRadius.circular(12),
color: AppColors.raised,
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 7),
child: Column(
+103 -125
View File
@@ -14,8 +14,10 @@ import '../../core/data/services/interaction_controller.dart';
import '../../core/data/services/favorite_controller.dart';
import '../../core/formatting/error_text.dart';
import '../../core/widgets/empty_state.dart';
import '../../core/widgets/app_bottom_nav.dart';
import '../../core/widgets/remote_image.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../l10n/app_localizations.dart';
import 'mine_binding.dart';
@@ -139,9 +141,9 @@ class _MyPageState extends State<MyPage> {
Center(
child: Text(
l10n.brandMark,
style: TextStyle(
color: Color(0xFFD2CDD0),
fontSize: 10,
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
letterSpacing: 1.3,
),
),
@@ -173,7 +175,10 @@ class _MyPageState extends State<MyPage> {
const SizedBox(width: 7),
Text(
l10n.myListTab,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w800),
style: const TextStyle(
fontSize: AppDimens.pageTitle,
fontWeight: FontWeight.w800,
),
),
const Spacer(),
IconButton(
@@ -212,7 +217,7 @@ class _MyPageState extends State<MyPage> {
l10n.preferencesHeading,
style: const TextStyle(
color: AppColors.muted,
fontSize: 12,
fontSize: AppDimens.caption,
fontWeight: FontWeight.w800,
letterSpacing: 1.1,
),
@@ -316,10 +321,10 @@ class _MyPageState extends State<MyPage> {
Widget _wallet(AppLocalizations l10n) {
return Container(
padding: const EdgeInsets.all(14),
padding: const EdgeInsets.all(AppDimens.cardPadding),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -374,7 +379,7 @@ class _MyPageState extends State<MyPage> {
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.ink,
borderRadius: BorderRadius.circular(14),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
border: Border.all(color: color.withValues(alpha: 0.45)),
),
child: Column(
@@ -396,7 +401,6 @@ class _MyPageState extends State<MyPage> {
foregroundColor: color == AppColors.blue
? Colors.white
: AppColors.onGold,
visualDensity: VisualDensity.compact,
shape: const StadiumBorder(),
),
child: Text(action, maxLines: 1, overflow: TextOverflow.ellipsis),
@@ -417,6 +421,7 @@ class _MyPageState extends State<MyPage> {
ListTile(
dense: true,
visualDensity: VisualDensity.compact,
minTileHeight: AppDimens.tapTarget,
leading: const Icon(
Icons.card_giftcard_rounded,
color: AppColors.blueSoft,
@@ -432,6 +437,7 @@ class _MyPageState extends State<MyPage> {
ListTile(
dense: true,
visualDensity: VisualDensity.compact,
minTileHeight: AppDimens.tapTarget,
key: const Key('mine-notices'),
leading: const Icon(
Icons.notifications_none_rounded,
@@ -447,6 +453,7 @@ class _MyPageState extends State<MyPage> {
ListTile(
dense: true,
visualDensity: VisualDensity.compact,
minTileHeight: AppDimens.tapTarget,
leading: const Icon(
Icons.history_rounded,
color: AppColors.blueSoft,
@@ -461,6 +468,7 @@ class _MyPageState extends State<MyPage> {
ListTile(
dense: true,
visualDensity: VisualDensity.compact,
minTileHeight: AppDimens.tapTarget,
leading: const Icon(
Icons.favorite_outline_rounded,
color: AppColors.blueSoft,
@@ -475,6 +483,7 @@ class _MyPageState extends State<MyPage> {
ListTile(
dense: true,
visualDensity: VisualDensity.compact,
minTileHeight: AppDimens.tapTarget,
leading: const Icon(
Icons.download_outlined,
color: AppColors.blueSoft,
@@ -489,6 +498,7 @@ class _MyPageState extends State<MyPage> {
ListTile(
dense: true,
visualDensity: VisualDensity.compact,
minTileHeight: AppDimens.tapTarget,
leading: const Icon(
Icons.settings_rounded,
color: AppColors.blueSoft,
@@ -534,30 +544,36 @@ class _MyPageState extends State<MyPage> {
child: Row(
children: [
for (var index = 0; index < tabs.length; index++) ...[
InkWell(
onTap: () {
logic.selectSection(index);
// 打开收藏页签时以服务端为准刷新一次(多设备/多入口都能对齐)
if (index == 1) unawaited(favorites.load());
},
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 8,
),
decoration: BoxDecoration(
color: logic.section == index
? AppColors.blue
: AppColors.raised,
borderRadius: BorderRadius.circular(20),
),
child: Text(
tabs[index],
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w700,
Semantics(
selected: logic.section == index,
button: true,
child: InkWell(
onTap: () {
logic.selectSection(index);
// 打开收藏页签时以服务端为准刷新一次(多设备/多入口都能对齐)
if (index == 1) unawaited(favorites.load());
},
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 8,
),
decoration: BoxDecoration(
color: logic.section == index
? AppColors.blue
: AppColors.raised,
borderRadius: BorderRadius.circular(
AppDimens.pillRadius,
),
),
child: Text(
tabs[index],
style: const TextStyle(
color: Colors.white,
fontSize: AppDimens.body,
fontWeight: FontWeight.w700,
),
),
),
),
@@ -581,14 +597,14 @@ class _MyPageState extends State<MyPage> {
selected: logic.filter == index,
onSelected: (_) => logic.selectFilter(index),
showCheckmark: false,
visualDensity: VisualDensity.compact,
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: AppColors.raised,
selectedColor: AppColors.blue,
labelStyle: TextStyle(
color: logic.filter == index
? Colors.white
: const Color(0xFFD5CED0),
fontSize: 11,
: AppColors.muted,
fontSize: AppDimens.caption,
fontWeight: FontWeight.w700,
),
side: BorderSide.none,
@@ -606,12 +622,12 @@ class _MyPageState extends State<MyPage> {
embedded: widget.embedded,
onAction: widget.onAction,
),
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
child: Container(
padding: const EdgeInsets.all(9),
padding: const EdgeInsets.all(AppDimens.innerPadding),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
),
child: Row(
children: [
@@ -626,7 +642,7 @@ class _MyPageState extends State<MyPage> {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 15,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w700,
),
),
@@ -634,8 +650,8 @@ class _MyPageState extends State<MyPage> {
Text(
l10n.dramaTag,
style: const TextStyle(
color: Color(0xFFE6E1E3),
fontSize: 12,
color: AppColors.cream,
fontSize: AppDimens.caption,
),
),
const SizedBox(height: 32),
@@ -645,7 +661,7 @@ class _MyPageState extends State<MyPage> {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.blueSoft,
fontSize: 10,
fontSize: AppDimens.caption,
),
),
],
@@ -703,7 +719,7 @@ class _MyPageState extends State<MyPage> {
l10n.history,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontSize: AppDimens.body,
fontWeight: FontWeight.w700,
),
),
@@ -714,7 +730,7 @@ class _MyPageState extends State<MyPage> {
Expanded(
child: Text(
l10n.progressError,
style: const TextStyle(color: Color(0xFFE6E1E3)),
style: const TextStyle(color: AppColors.cream),
),
),
TextButton(
@@ -740,7 +756,7 @@ class _MyPageState extends State<MyPage> {
Expanded(
child: Text(
errorText(l10n, logic.recentError, l10n.progressError),
style: const TextStyle(color: Color(0xFFE6E1E3)),
style: const TextStyle(color: AppColors.cream),
),
),
TextButton(
@@ -794,17 +810,17 @@ class _MyPageState extends State<MyPage> {
);
}
},
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
child: Container(
padding: const EdgeInsets.all(9),
padding: const EdgeInsets.all(AppDimens.innerPadding),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
child: RemoteImage(drama.posterUrl, width: 74, height: 103),
),
const SizedBox(width: 11),
@@ -817,7 +833,7 @@ class _MyPageState extends State<MyPage> {
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 15,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w700,
),
),
@@ -825,56 +841,54 @@ class _MyPageState extends State<MyPage> {
Text(
l10n.episodeCount(drama.episodeCount),
style: const TextStyle(
color: Color(0xFFE6E1E3),
fontSize: 12,
color: AppColors.cream,
fontSize: AppDimens.caption,
),
),
],
),
),
const Icon(Icons.chevron_right_rounded, color: Color(0xFFF2EEEF)),
const Icon(Icons.chevron_right_rounded, color: AppColors.cream),
],
),
),
);
Widget _bottomNav(AppLocalizations l10n) => Container(
height: 63,
decoration: const BoxDecoration(
color: AppColors.bar,
border: Border(top: BorderSide(color: AppColors.outlineVariantDark)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_NavItem(
icon: Icons.play_circle_outline_rounded,
label: l10n.homeTab,
onTap: () => logic.act(
context,
MyPageAction.home,
embedded: widget.embedded,
onAction: widget.onAction,
),
Widget _bottomNav(AppLocalizations l10n) => AppBottomNav(
items: [
AppBottomNavItem(
tabKey: const Key('mine-nav-home'),
icon: Icons.play_circle_outline_rounded,
selectedIcon: Icons.play_circle_fill_rounded,
label: l10n.homeTab,
onTap: () => logic.act(
context,
MyPageAction.home,
embedded: widget.embedded,
onAction: widget.onAction,
),
_NavItem(
icon: Icons.explore_outlined,
label: l10n.discoverTab,
onTap: () => logic.act(
context,
MyPageAction.discover,
embedded: widget.embedded,
onAction: widget.onAction,
),
),
AppBottomNavItem(
tabKey: const Key('mine-nav-discover'),
icon: Icons.explore_outlined,
selectedIcon: Icons.explore_rounded,
label: l10n.discoverTab,
onTap: () => logic.act(
context,
MyPageAction.discover,
embedded: widget.embedded,
onAction: widget.onAction,
),
_NavItem(
icon: Icons.video_library_outlined,
label: l10n.myTab,
selected: true,
onTap: () {},
),
],
),
),
AppBottomNavItem(
tabKey: const Key('mine-nav-current'),
icon: Icons.video_library_outlined,
selectedIcon: Icons.video_library_rounded,
label: l10n.myTab,
selected: true,
onTap: () {},
),
],
);
Widget _poster({required double width, required double height}) => ClipRRect(
@@ -883,42 +897,6 @@ class _MyPageState extends State<MyPage> {
);
}
class _NavItem extends StatelessWidget {
const _NavItem({
required this.icon,
required this.label,
required this.onTap,
this.selected = false,
});
final IconData icon;
final String label;
final VoidCallback onTap;
final bool selected;
@override
Widget build(BuildContext context) => InkWell(
onTap: onTap,
child: SizedBox(
width: 72,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 21, color: selected ? _accent : AppColors.muted),
Text(
label,
style: TextStyle(
color: selected ? _accent : AppColors.muted,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
],
),
),
);
}
String _clock(Duration duration) {
final seconds = duration.inSeconds;
return '${(seconds ~/ 60).toString().padLeft(2, '0')}:${(seconds % 60).toString().padLeft(2, '0')}';
+16 -10
View File
@@ -11,6 +11,7 @@ import '../../core/data/services/notices_controller.dart';
import '../../core/formatting/error_text.dart';
import '../../core/formatting/localized_date_time.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/empty_state.dart';
import '../../l10n/app_localizations.dart';
@@ -52,9 +53,12 @@ class _NoticesPageState extends State<NoticesPage> {
foregroundColor: AppColors.onPrimary,
title: Text(l10n.notices),
),
body: ListenableBuilder(
listenable: _controller,
builder: (context, _) => _body(l10n),
body: SafeArea(
top: false,
child: ListenableBuilder(
listenable: _controller,
builder: (context, _) => _body(l10n),
),
),
),
);
@@ -214,12 +218,12 @@ class _NoticeTile extends StatelessWidget {
return Material(
key: ValueKey('notice-item-${notice.id}'),
color: AppColors.card,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(14),
padding: const EdgeInsets.all(AppDimens.innerPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -233,7 +237,9 @@ class _NoticeTile extends StatelessWidget {
),
decoration: BoxDecoration(
color: AppColors.bluePressed,
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(
AppDimens.pillRadius,
),
),
child: Text(
type,
@@ -241,7 +247,7 @@ class _NoticeTile extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.onPrimary,
fontSize: 11,
fontSize: AppDimens.caption,
),
),
),
@@ -257,7 +263,7 @@ class _NoticeTile extends StatelessWidget {
textAlign: TextAlign.right,
style: const TextStyle(
color: AppColors.muted,
fontSize: 11,
fontSize: AppDimens.caption,
),
),
),
@@ -271,7 +277,7 @@ class _NoticeTile extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.onPrimary,
fontSize: 16,
fontSize: AppDimens.sectionTitle,
fontWeight: FontWeight.w700,
),
),
@@ -283,7 +289,7 @@ class _NoticeTile extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.muted,
fontSize: 13,
fontSize: AppDimens.body,
height: 1.35,
),
),
+28 -66
View File
@@ -18,6 +18,7 @@ import '../../core/formatting/localized_content.dart';
import '../../core/platform/haptics.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/app_bottom_nav.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/download_action.dart';
import '../../l10n/app_localizations.dart';
@@ -734,14 +735,14 @@ class _PlaybackOverlay extends StatelessWidget {
children: [
_RailAction(
key: const Key('rail-like'),
icon: Icons.favorite_border,
icon: liked ? Icons.favorite : Icons.favorite_border,
label: l10n.like,
selected: liked,
onTap: onLike,
),
_RailAction(
key: const Key('rail-save'),
icon: Icons.bookmark_border,
icon: saved ? Icons.bookmark : Icons.bookmark_border,
label: l10n.save,
selected: saved,
onTap: onSave,
@@ -787,30 +788,31 @@ class _PlaybackOverlay extends StatelessWidget {
],
);
Widget _fallbackNav() => Container(
height: 62,
color: const Color(0xF20C0C0F),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_BottomAction(
icon: Icons.play_circle_outline_rounded,
label: l10n.homeTab,
selected: true,
onTap: () {},
),
_BottomAction(
icon: Icons.explore_outlined,
label: l10n.discoverTab,
onTap: onDiscover,
),
_BottomAction(
icon: Icons.video_library_outlined,
label: l10n.myTab,
onTap: onMy,
),
],
),
Widget _fallbackNav() => AppBottomNav(
items: [
AppBottomNavItem(
tabKey: const Key('player-nav-home'),
icon: Icons.play_circle_outline_rounded,
selectedIcon: Icons.play_circle_fill_rounded,
label: l10n.homeTab,
selected: true,
onTap: () {},
),
AppBottomNavItem(
tabKey: const Key('player-nav-discover'),
icon: Icons.explore_outlined,
selectedIcon: Icons.explore_rounded,
label: l10n.discoverTab,
onTap: onDiscover,
),
AppBottomNavItem(
tabKey: const Key('player-nav-my'),
icon: Icons.video_library_outlined,
selectedIcon: Icons.video_library_rounded,
label: l10n.myTab,
onTap: onMy,
),
],
);
}
@@ -844,43 +846,3 @@ class _RailAction extends StatelessWidget {
icon: Icon(icon, size: _railIconSize, weight: _railIconWeight, fill: 0),
);
}
class _BottomAction extends StatelessWidget {
const _BottomAction({
required this.icon,
required this.label,
required this.onTap,
this.selected = false,
});
final IconData icon;
final String label;
final VoidCallback onTap;
final bool selected;
@override
Widget build(BuildContext context) => InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
color: selected ? AppColors.blueSoft : const Color(0xFFF3EEEA),
),
const SizedBox(height: 2),
Text(
label,
style: TextStyle(
fontSize: 11,
color: selected ? AppColors.blueSoft : const Color(0xFFF3EEEA),
),
),
],
),
),
);
}
+5 -2
View File
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import '../../../core/data/models/comment.dart';
import '../../../core/data/services/comment_controller.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../l10n/app_localizations.dart';
const String _deleteAction = 'delete';
@@ -43,7 +44,9 @@ class CommentTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final name = comment.nickname.isEmpty ? l10n.anonymousUser : comment.nickname;
final name = comment.nickname.isEmpty
? l10n.anonymousUser
: comment.nickname;
return Padding(
key: Key('comment-item-${comment.id}'),
padding: const EdgeInsets.symmetric(vertical: 8),
@@ -111,7 +114,7 @@ class CommentTile extends StatelessWidget {
key: Key('comment-pending-${comment.id}'),
style: const TextStyle(
color: AppColors.blueSoft,
fontSize: 11,
fontSize: AppDimens.caption,
),
),
),
+32 -19
View File
@@ -15,6 +15,7 @@ import '../../core/formatting/error_text.dart';
import '../../core/formatting/localized_date_time.dart';
import '../../core/platform/haptics.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/empty_state.dart';
@@ -41,7 +42,10 @@ class WatchRecordsPage extends StatelessWidget {
foregroundColor: Colors.white,
title: Text(l10n.watchHistory),
),
body: signedIn ? const _WatchRecordsBody() : const AuthGatePanel(),
body: SafeArea(
top: false,
child: signedIn ? const _WatchRecordsBody() : const AuthGatePanel(),
),
),
);
}
@@ -207,16 +211,19 @@ class _RecordTile extends StatelessWidget {
if (drama == null || record.detailLoading) {
return Container(
key: ValueKey('watch-record-${record.dramaId}'),
height: 112,
height: AppDimens.watchPlaceholderHeight,
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
),
child: const Center(
child: SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(strokeWidth: 2),
child: const Padding(
padding: EdgeInsets.all(AppDimens.innerPadding),
child: Center(
child: SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
),
);
@@ -227,12 +234,12 @@ class _RecordTile extends StatelessWidget {
return Material(
key: ValueKey('watch-record-${record.dramaId}'),
color: AppColors.card,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onOpen,
child: Padding(
padding: const EdgeInsets.all(9),
padding: const EdgeInsets.all(AppDimens.innerPadding),
child: Row(
children: [
ClipRRect(
@@ -250,7 +257,7 @@ class _RecordTile extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontSize: 15,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w700,
),
),
@@ -260,15 +267,21 @@ class _RecordTile extends StatelessWidget {
key: ValueKey('watch-record-episode-${record.dramaId}'),
style: const TextStyle(
color: AppColors.muted,
fontSize: 12,
fontSize: AppDimens.caption,
),
),
const SizedBox(height: 7),
ClipRRect(
borderRadius: BorderRadius.circular(4),
borderRadius: BorderRadius.circular(
AppDimens.progressRadius,
),
child: LinearProgressIndicator(
minHeight: 4,
minHeight: AppDimens.progressHeight,
value: record.progress.fraction,
backgroundColor: Colors.white24,
valueColor: const AlwaysStoppedAnimation(
AppColors.blueSoft,
),
),
),
const SizedBox(height: 6),
@@ -279,7 +292,7 @@ class _RecordTile extends StatelessWidget {
),
style: const TextStyle(
color: AppColors.blueSoft,
fontSize: 11,
fontSize: AppDimens.caption,
),
),
if (record.progress.updatedAt != null)
@@ -292,8 +305,8 @@ class _RecordTile extends StatelessWidget {
),
key: ValueKey('watch-record-updated-${record.dramaId}'),
style: const TextStyle(
color: Color(0xFF8D8990),
fontSize: 11,
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
],
@@ -324,10 +337,10 @@ class _FailedRecordTile extends StatelessWidget {
final l10n = AppLocalizations.of(context)!;
return Container(
key: ValueKey('watch-record-failed-${record.dramaId}'),
padding: const EdgeInsets.all(14),
padding: const EdgeInsets.all(AppDimens.innerPadding),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
),
child: Row(
children: [
+46
View File
@@ -0,0 +1,46 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/theme/app_dimens.dart';
import 'package:fruitshorts/core/widgets/app_bottom_nav.dart';
void main() {
testWidgets('should_render_64_high_navigation_and_keep_item_keys', (
tester,
) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
bottomNavigationBar: AppBottomNav(
items: [
AppBottomNavItem(
tabKey: const Key('tab-home'),
icon: Icons.movie_outlined,
selectedIcon: Icons.movie_rounded,
label: 'Home',
selected: true,
onTap: () {},
),
AppBottomNavItem(
tabKey: const Key('tab-my'),
icon: Icons.person_outline_rounded,
selectedIcon: Icons.person_rounded,
label: 'Me',
onTap: () {},
),
],
),
),
),
);
expect(find.byKey(const Key('tab-home')), findsOneWidget);
expect(find.byKey(const Key('tab-my')), findsOneWidget);
expect(find.text('Home'), findsOneWidget);
expect(
tester.getSize(find.byType(AppBottomNav)).height,
AppDimens.navHeight,
);
});
}
+23
View File
@@ -0,0 +1,23 @@
// Author: fruit.com.cn
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', () {
expect(AppDimens.pageHorizontal, 16);
expect(AppDimens.cardRadius, 16);
expect(AppDimens.innerRadius, 12);
expect(AppDimens.caption, 12);
expect(AppDimens.body, 14);
expect(AppDimens.listTitle, 15);
expect(AppDimens.sectionTitle, 16);
expect(AppDimens.pageTitle, 20);
expect(AppDimens.navLabel, 11);
expect(AppDimens.buttonHeight, 48);
expect(AppDimens.primaryButtonHeight, 52);
expect(AppDimens.tapTarget, 44);
expect(AppDimens.navHeight, 64);
expect(AppDimens.progressHeight, 4);
});
}