style(ui): 统一页面字号间距与状态样式

This commit is contained in:
Zhou Weidong committed 2026-10-19 10:21:28 +08:00
1 parent df5fbe5b08
commit c5bfd006fa
23 files changed
+542 -569

No files matched your search

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