style(ui): 统一页面字号间距与状态样式
This commit is contained in:
1 parent
df5fbe5b08
commit
c5bfd006fa
23 files changed
+542
-569
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -103,7 +103,7 @@ class _AuthCard extends StatelessWidget {
|
||||
l10n.authSubtitle,
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontSize: 13,
|
||||
fontSize: AppDimens.body,
|
||||
height: 1.45,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -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
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -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')}';
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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: [
|
||||
|
||||
Reference in new issue
Block a user