feat(mine): 统一我的页深色视觉风格

This commit is contained in:
Zhou Weidong committed 2026-10-22 17:07:09 +08:00
1 parent 47bf6458e8
commit 83129acae4
6 files changed
+208 -82

No files matched your search

+1
View File
@@ -70,6 +70,7 @@ abstract final class AppColors {
static const contentBorder = Color(0x1AFFFFFF);
/// 次级文字与列表 chevron。
static const textPrimary = Color(0xFFFFFFFF);
static const textSecondary = Color(0xFF8E8E93);
/// 原生开屏与冷启动过渡底色。
+6
View File
@@ -87,4 +87,10 @@ abstract final class AppDimens {
static const double homeSectionIndicator = 2;
static const double homeIconSlot = 44;
static const double homeHeaderIcon = 22;
/// 「我的」页中性视觉。
static const double mineCardRadius = 16;
static const double mineRowHeight = 52;
static const double minePageTitle = 21;
static const double mineSectionTitle = 15;
}
+27 -18
View File
@@ -80,21 +80,25 @@ class _MyPageState extends State<MyPage> {
Widget _page(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final content = SafeArea(
bottom: false,
child: Column(
children: [
_topBar(l10n),
Expanded(child: _body(context, l10n)),
if (!embedded) _bottomNav(),
],
final content = ColoredBox(
key: const Key('mine-page-surface'),
color: AppColors.appBackground,
child: SafeArea(
bottom: false,
child: Column(
children: [
_topBar(l10n),
Expanded(child: _body(context, l10n)),
if (!embedded) _bottomNav(),
],
),
),
);
return Theme(
data: AppTheme.dark(),
child: embedded
? content
: Scaffold(backgroundColor: AppColors.ink, body: content),
: Scaffold(backgroundColor: AppColors.appBackground, body: content),
);
}
@@ -111,9 +115,9 @@ class _MyPageState extends State<MyPage> {
l10n.myTab,
style: const TextStyle(
fontFamily: 'Outfit',
color: AppColors.onSurface,
fontSize: AppDimens.pageTitle,
fontWeight: FontWeight.w800,
color: AppColors.textPrimary,
fontSize: AppDimens.minePageTitle,
fontWeight: FontWeight.w700,
),
),
),
@@ -280,8 +284,10 @@ class _LanguageSheet extends StatelessWidget {
];
return Material(
key: const Key('mine-language-sheet'),
color: AppColors.card,
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
color: AppColors.contentCard,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(AppDimens.mineCardRadius),
),
clipBehavior: Clip.antiAlias,
child: SafeArea(
top: false,
@@ -297,9 +303,9 @@ class _LanguageSheet extends StatelessWidget {
child: Text(
l10n.language,
style: const TextStyle(
color: AppColors.onSurface,
color: AppColors.textPrimary,
fontSize: 20,
fontWeight: FontWeight.w800,
fontWeight: FontWeight.w700,
),
),
),
@@ -316,7 +322,10 @@ class _LanguageSheet extends StatelessWidget {
contentPadding: const EdgeInsets.symmetric(horizontal: 4),
title: Text(option.$3),
trailing: selected?.languageCode == option.$1.languageCode
? const Icon(Icons.check_rounded, color: AppColors.blue)
? const Icon(
Icons.check_rounded,
color: AppColors.textPrimary,
)
: null,
onTap: () => Navigator.pop(context, option.$1),
),
@@ -351,7 +360,7 @@ class _MinePlaceholderPage extends StatelessWidget {
child: Text(
l10n.comingSoon,
style: const TextStyle(
color: AppColors.muted,
color: AppColors.textSecondary,
fontSize: AppDimens.body,
),
),
+10 -11
View File
@@ -6,7 +6,6 @@ import 'package:provider/provider.dart';
import '../../../core/data/services/auth_controller.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 '../../../l10n/app_localizations.dart';
@@ -31,13 +30,13 @@ class MineHeader extends StatelessWidget {
return Container(
key: const Key('mine-user-row'),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
boxShadow: const [AppTheme.cardShadow],
color: AppColors.contentCard,
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
border: Border.all(color: AppColors.contentBorder),
),
child: Material(
color: AppColors.transparent,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
@@ -50,7 +49,7 @@ class MineHeader extends StatelessWidget {
Expanded(child: _labels(name, subtitle)),
const Icon(
Icons.chevron_right_rounded,
color: AppColors.subtle,
color: AppColors.textSecondary,
size: 20,
),
],
@@ -67,14 +66,14 @@ class MineHeader extends StatelessWidget {
width: 64,
height: 64,
decoration: const BoxDecoration(
color: AppColors.badgePink,
color: AppColors.contentSurface,
shape: BoxShape.circle,
),
clipBehavior: Clip.antiAlias,
child: path.isEmpty
? const Icon(
Icons.person_rounded,
color: AppColors.badgePinkText,
color: AppColors.textPrimary,
size: 30,
)
: RemoteImage(path, fit: BoxFit.cover),
@@ -91,9 +90,9 @@ class MineHeader extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: 'Outfit',
color: AppColors.onSurface,
color: AppColors.textPrimary,
fontSize: 18,
fontWeight: FontWeight.w800,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: AppDimens.spaceXs),
@@ -102,7 +101,7 @@ class MineHeader extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.muted,
color: AppColors.textSecondary,
fontSize: AppDimens.caption,
),
),
+60 -53
View File
@@ -4,7 +4,6 @@ import 'package:flutter/material.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_types.dart';
@@ -22,12 +21,12 @@ class MineShortcutGrid extends StatelessWidget {
Expanded(
child: _ShortcutTile(
key: const Key('mine-watchlist'),
icon: Icons.bookmark_rounded,
icon: Icons.bookmark_border_rounded,
label: l10n.watchlist,
onTap: () => onOpen(MineDestination.watchlist),
),
),
const SizedBox(width: 10),
const SizedBox(width: AppDimens.homeGridGap),
Expanded(
child: _ShortcutTile(
key: const Key('mine-history'),
@@ -36,11 +35,11 @@ class MineShortcutGrid extends StatelessWidget {
onTap: () => onOpen(MineDestination.history),
),
),
const SizedBox(width: 10),
const SizedBox(width: AppDimens.homeGridGap),
Expanded(
child: _ShortcutTile(
key: const Key('mine-downloads'),
icon: Icons.download_rounded,
icon: Icons.download_outlined,
label: l10n.downloads,
onTap: () => onOpen(MineDestination.downloads),
),
@@ -66,13 +65,13 @@ class _ShortcutTile extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
boxShadow: const [AppTheme.cardShadow],
color: AppColors.contentCard,
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
border: Border.all(color: AppColors.contentBorder),
),
child: Material(
color: AppColors.transparent,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
@@ -81,16 +80,16 @@ class _ShortcutTile extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: AppColors.blue, size: 24),
Icon(icon, color: AppColors.textPrimary, size: 24),
const SizedBox(height: AppDimens.spaceSm),
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.onSurface,
color: AppColors.textPrimary,
fontSize: AppDimens.caption,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w500,
),
),
],
@@ -122,20 +121,16 @@ class MineWalletCard extends StatelessWidget {
key: const Key('mine-wallet'),
padding: const EdgeInsets.all(AppDimens.cardPadding),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.goldCard, AppColors.card],
),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
boxShadow: const [AppTheme.cardShadow],
color: AppColors.contentCard,
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
border: Border.all(color: AppColors.contentBorder),
),
child: Row(
children: [
const Icon(
Icons.monetization_on_rounded,
color: AppColors.gold,
size: 30,
Icons.toll_outlined,
color: AppColors.textSecondary,
size: 28,
),
const SizedBox(width: AppDimens.spaceMd),
Expanded(
@@ -147,7 +142,7 @@ class MineWalletCard extends StatelessWidget {
Text(
l10n.coinBalance,
style: const TextStyle(
color: AppColors.muted,
color: AppColors.textSecondary,
fontSize: AppDimens.caption,
),
),
@@ -155,9 +150,9 @@ class MineWalletCard extends StatelessWidget {
Text(
balance,
style: const TextStyle(
color: AppColors.onSurface,
color: AppColors.textPrimary,
fontSize: AppDimens.pageTitle,
fontWeight: FontWeight.w800,
fontWeight: FontWeight.w700,
),
),
],
@@ -171,7 +166,9 @@ class MineWalletCard extends StatelessWidget {
backgroundColor: AppColors.blue,
foregroundColor: AppColors.onPrimary,
minimumSize: const Size(64, AppDimens.buttonCompact),
shape: const StadiumBorder(),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppDimens.buttonRadius),
),
),
child: Text(l10n.topUpWallet),
),
@@ -193,13 +190,13 @@ class MineVipCard extends StatelessWidget {
return Container(
key: const Key('mine-vip'),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
boxShadow: const [AppTheme.cardShadow],
color: AppColors.contentCard,
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
border: Border.all(color: AppColors.contentBorder),
),
child: Material(
color: AppColors.transparent,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onOpen,
@@ -211,12 +208,12 @@ class MineVipCard extends StatelessWidget {
width: AppDimens.buttonLarge,
height: AppDimens.buttonLarge,
decoration: const BoxDecoration(
color: AppColors.badgePink,
color: AppColors.contentSurface,
shape: BoxShape.circle,
),
child: const Icon(
Icons.workspace_premium_rounded,
color: AppColors.badgePinkText,
color: AppColors.textPrimary,
size: 22,
),
),
@@ -228,9 +225,9 @@ class MineVipCard extends StatelessWidget {
Text(
l10n.vipMembership,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
fontSize: AppDimens.mineSectionTitle,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 3),
@@ -239,7 +236,7 @@ class MineVipCard extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.muted,
color: AppColors.textSecondary,
fontSize: AppDimens.caption,
),
),
@@ -250,7 +247,7 @@ class MineVipCard extends StatelessWidget {
l10n.joinVip,
style: const TextStyle(
color: AppColors.blue,
fontSize: AppDimens.caption,
fontSize: AppDimens.body,
fontWeight: FontWeight.w700,
),
),
@@ -282,13 +279,13 @@ class MineMenuList extends StatelessWidget {
return Container(
key: const Key('mine-menu'),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
boxShadow: const [AppTheme.cardShadow],
color: AppColors.contentCard,
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
border: Border.all(color: AppColors.contentBorder),
),
child: Material(
color: AppColors.transparent,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
clipBehavior: Clip.antiAlias,
child: Column(
children: [
@@ -338,18 +335,19 @@ class MineMenuList extends StatelessWidget {
final l10n = AppLocalizations.of(context)!;
return ListTile(
key: const Key('mine-language'),
minTileHeight: 48,
minTileHeight: AppDimens.mineRowHeight,
contentPadding: const EdgeInsets.symmetric(horizontal: 14),
leading: const Icon(
Icons.language_rounded,
color: AppColors.blue,
color: AppColors.textSecondary,
size: 20,
),
title: Text(
l10n.language,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: AppDimens.body,
color: AppColors.textPrimary,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w500,
),
),
trailing: Row(
@@ -357,12 +355,15 @@ class MineMenuList extends StatelessWidget {
children: [
Text(
languageLabel,
style: const TextStyle(color: AppColors.muted, fontSize: 11),
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: AppDimens.caption,
),
),
const SizedBox(width: 4),
const Icon(
Icons.chevron_right_rounded,
color: AppColors.subtle,
color: AppColors.textSecondary,
size: 18,
),
],
@@ -379,19 +380,20 @@ class MineMenuList extends StatelessWidget {
}) {
return ListTile(
key: key,
minTileHeight: 48,
minTileHeight: AppDimens.mineRowHeight,
contentPadding: const EdgeInsets.symmetric(horizontal: 14),
leading: Icon(icon, color: AppColors.blue, size: 20),
leading: Icon(icon, color: AppColors.textSecondary, size: 20),
title: Text(
title,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: AppDimens.body,
color: AppColors.textPrimary,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w500,
),
),
trailing: const Icon(
Icons.chevron_right_rounded,
color: AppColors.subtle,
color: AppColors.textSecondary,
size: 18,
),
onTap: () => onOpen(destination),
@@ -399,6 +401,11 @@ class MineMenuList extends StatelessWidget {
}
Widget _divider() {
return const Divider(height: 1, indent: 14, endIndent: 14);
return const Divider(
height: 1,
indent: 14,
endIndent: 14,
color: AppColors.contentSurface,
);
}
}
+104
View File
@@ -6,6 +6,8 @@ import 'package:fruitshorts/app.dart';
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/core/theme/app_dimens.dart';
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
@@ -69,6 +71,108 @@ void main() {
expect(find.byKey(const Key('mine-library-tab-0')), findsNothing);
expect(find.byKey(const Key('mine-library-tab-1')), findsNothing);
expect(find.byKey(const Key('mine-library-tab-2')), findsNothing);
final surface = tester.widget<ColoredBox>(
find.byKey(const Key('mine-page-surface')),
);
expect(surface.color, AppColors.appBackground);
final title = tester.widget<Text>(
find.descendant(
of: find.byKey(const Key('mine-page-surface')),
matching: find.text('My'),
),
);
expect(title.style?.color, AppColors.textPrimary);
expect(title.style?.fontSize, AppDimens.minePageTitle);
expect(title.style?.fontWeight, FontWeight.w700);
for (final key in const [
Key('mine-user-row'),
Key('mine-wallet'),
Key('mine-vip'),
Key('mine-menu'),
]) {
final container = tester.widget<Container>(find.byKey(key));
final decoration = container.decoration! as BoxDecoration;
expect(decoration.color, AppColors.contentCard);
expect(
decoration.borderRadius,
BorderRadius.circular(AppDimens.mineCardRadius),
);
expect(decoration.gradient, isNull);
expect(decoration.boxShadow, isNull);
expect((decoration.border! as Border).top.color, AppColors.contentBorder);
}
final shortcutIcon = tester.widget<Icon>(
find.descendant(
of: find.byKey(const Key('mine-watchlist')),
matching: find.byType(Icon),
),
);
expect(shortcutIcon.size, 24);
expect(shortcutIcon.color, AppColors.textPrimary);
final shortcutKeys = [
const Key('mine-watchlist'),
const Key('mine-history'),
const Key('mine-downloads'),
];
final shortcutWidths = shortcutKeys
.map((key) => tester.getSize(find.byKey(key)).width)
.toList();
expect(shortcutWidths.toSet(), hasLength(1));
final shortcutCard = tester.widget<Container>(
find
.descendant(
of: find.byKey(const Key('mine-watchlist')),
matching: find.byType(Container),
)
.first,
);
final shortcutDecoration = shortcutCard.decoration! as BoxDecoration;
expect(shortcutDecoration.color, AppColors.contentCard);
expect(
shortcutDecoration.borderRadius,
BorderRadius.circular(AppDimens.mineCardRadius),
);
expect(shortcutDecoration.boxShadow, isNull);
final menuRows = find.descendant(
of: find.byKey(const Key('mine-menu')),
matching: find.byType(ListTile),
);
expect(menuRows, findsNWidgets(6));
for (final row in tester.widgetList<ListTile>(menuRows)) {
expect(row.minTileHeight, AppDimens.mineRowHeight);
}
final settingsRow = tester.widget<ListTile>(
find.byKey(const Key('mine-settings')),
);
final settingsTitle = settingsRow.title! as Text;
expect(settingsTitle.style?.color, AppColors.textPrimary);
expect(settingsTitle.style?.fontSize, AppDimens.listTitle);
expect((settingsRow.trailing! as Icon).color, AppColors.textSecondary);
final recharge = tester.widget<TextButton>(
find.byKey(const Key('mine-wallet-recharge')),
);
expect(recharge.style?.backgroundColor?.resolve(const {}), AppColors.blue);
expect(
tester.widget<Text>(find.text('Join VIP')).style?.color,
AppColors.blue,
);
final dividers = tester.widgetList<Divider>(
find.descendant(
of: find.byKey(const Key('mine-menu')),
matching: find.byType(Divider),
),
);
expect(dividers, hasLength(5));
expect(
dividers.every((divider) => divider.color == AppColors.contentSurface),
isTrue,
);
expect(tester.takeException(), isNull);
await tester.tap(find.byKey(const Key('mine-watchlist')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('favorites-page')), findsOneWidget);
});
}