style(mine): 重构紧凑 VIP 会员入口

This commit is contained in:
Zhou Weidong committed 2026-11-13 17:41:47 +08:00
1 parent 9ef78ad670
commit 6ef5429a52
7 files changed
+155 -178

No files matched your search

+83 -144
View File
@@ -8,10 +8,11 @@ import '../../../core/widgets/app_press_scale.dart';
import '../../../l10n/app_localizations.dart';
const double _vipMaxTextScale = 1.15;
const double _vipGradientMiddleStop = 0.48;
const double _vipGradientMiddleStop = 0.62;
const double _vipGlowRadius = 0.9;
const double _vipButtonLetterSpacing = 0.2;
/// 「我的」页高级 VIP 卡:品牌信息、会员徽章、开通按钮与横排权益。
/// 「我的」页 VIP 会员卡:主视觉、权益横排与开通入口。
class MineVipBanner extends StatelessWidget {
const MineVipBanner({super.key, required this.onOpen});
@@ -61,13 +62,7 @@ class MineVipBanner extends StatelessWidget {
stops: [0, _vipGradientMiddleStop, 1],
),
borderRadius: BorderRadius.circular(AppDimens.mineVipRadius),
border: Border.all(color: AppColors.vipCardBorder),
boxShadow: const [
BoxShadow(
color: AppColors.vipCardRedGlow,
blurRadius: AppDimens.spaceXl,
offset: Offset(0, AppDimens.spaceSm),
),
BoxShadow(
color: AppColors.shadowSoft,
blurRadius: AppDimens.spaceLg,
@@ -90,16 +85,16 @@ class _VipContent extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: _VipCopy(l10n: l10n)),
Expanded(
child: _VipCopy(l10n: l10n, onOpen: onOpen),
),
const SizedBox(width: AppDimens.spaceSm),
_VipAction(l10n: l10n, onOpen: onOpen),
const _VipArt(),
],
),
const SizedBox(height: AppDimens.spaceSm),
const Divider(height: 1, color: AppColors.vipBenefitBorder),
const SizedBox(height: AppDimens.spaceSm),
const SizedBox(height: AppDimens.spaceXs),
_VipBenefits(l10n: l10n),
],
);
@@ -107,9 +102,10 @@ class _VipContent extends StatelessWidget {
}
class _VipCopy extends StatelessWidget {
const _VipCopy({required this.l10n});
const _VipCopy({required this.l10n, required this.onOpen});
final AppLocalizations l10n;
final VoidCallback onOpen;
@override
Widget build(BuildContext context) {
@@ -117,8 +113,6 @@ class _VipCopy extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_VipPill(label: l10n.vipMembership),
const SizedBox(height: AppDimens.spaceSm),
Text(
l10n.vipBrandName,
maxLines: 1,
@@ -126,14 +120,14 @@ class _VipCopy extends StatelessWidget {
style: const TextStyle(
color: AppColors.vipCardText,
fontSize: AppDimens.mineVipTitleFont,
fontWeight: FontWeight.w700,
letterSpacing: -0.4,
fontWeight: FontWeight.w800,
letterSpacing: 1.0,
),
),
const SizedBox(height: AppDimens.spaceXs),
Text(
l10n.mineVipSubtitle,
maxLines: 2,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipCardMuted,
@@ -141,90 +135,28 @@ class _VipCopy extends StatelessWidget {
height: 1.3,
),
),
const SizedBox(height: AppDimens.spaceXs),
_VipOpenButton(label: l10n.vipOpenNow, onOpen: onOpen),
],
);
}
}
class _VipPill extends StatelessWidget {
const _VipPill({required this.label});
final String label;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.spaceSm,
vertical: AppDimens.spaceXs,
),
decoration: BoxDecoration(
color: AppColors.vipBadgeSurface,
border: Border.all(color: AppColors.vipBenefitBorder),
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipGoldStart,
fontSize: AppDimens.chipFont,
fontWeight: FontWeight.w700,
letterSpacing: 1.1,
),
),
);
}
}
class _VipAction extends StatelessWidget {
const _VipAction({required this.l10n, required this.onOpen});
final AppLocalizations l10n;
final VoidCallback onOpen;
class _VipArt extends StatelessWidget {
const _VipArt();
@override
Widget build(BuildContext context) {
return SizedBox(
width: AppDimens.mineVipActionWidth,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const _VipEmblem(),
const SizedBox(height: AppDimens.spaceXs),
_VipOpenButton(label: l10n.vipOpenNow, onOpen: onOpen),
],
),
);
}
}
class _VipEmblem extends StatelessWidget {
const _VipEmblem();
@override
Widget build(BuildContext context) {
return Container(
key: const Key('mine-vip-emblem'),
width: AppDimens.mineVipEmblem,
height: AppDimens.mineVipEmblem,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.vipGoldStart, AppColors.vipGoldEnd],
),
border: Border.all(color: AppColors.vipCardText),
boxShadow: const [
BoxShadow(color: AppColors.vipCardGoldGlow, blurRadius: 18),
],
),
child: const Icon(
Icons.workspace_premium_rounded,
color: AppColors.vipCardStart,
size: AppDimens.mineVipEmblemIcon,
key: const Key('mine-vip-art'),
width: AppDimens.mineVipArtWidth,
height: AppDimens.mineVipArtHeight,
child: Image.asset(
'assets/icon/vip_membership_badge.png',
key: const Key('mine-vip-emblem'),
width: AppDimens.mineVipArtWidth,
height: AppDimens.mineVipArtHeight,
fit: BoxFit.contain,
),
);
}
@@ -242,34 +174,32 @@ class _VipOpenButton extends StatelessWidget {
key: const Key('mine-vip-open'),
width: AppDimens.mineVipActionWidth,
height: AppDimens.mineVipButtonHeight,
child: DecoratedBox(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [AppColors.vipButtonStart, AppColors.vipButtonEnd],
),
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
boxShadow: const [
BoxShadow(color: AppColors.vipCardRedGlow, blurRadius: 14),
],
),
child: Material(
color: AppColors.transparent,
child: InkWell(
onTap: onOpen,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
child: Center(
child: InkWell(
onTap: onOpen,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
color: AppColors.vipGoldStart,
fontSize: AppDimens.body,
fontWeight: FontWeight.w700,
letterSpacing: _vipButtonLetterSpacing,
),
),
),
),
const SizedBox(width: AppDimens.spaceXs),
const Icon(
Icons.arrow_forward_rounded,
color: AppColors.vipGoldStart,
size: AppDimens.mineVipBenefitGlyph,
),
],
),
),
);
@@ -289,11 +219,20 @@ class _VipBenefits extends StatelessWidget {
(Icons.hd_outlined, l10n.vipBenefitHd),
(Icons.download_for_offline_outlined, l10n.vipBenefitFastCache),
];
return Row(
return Column(
key: const Key('mine-vip-benefits'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (var index = 0; index < items.length; index++)
_row(items, 0),
const SizedBox(height: AppDimens.spaceXs),
_row(items, 2),
],
);
}
Widget _row(List<(IconData, String)> items, int start) {
return Row(
children: [
for (var index = start; index < start + 2; index++)
Expanded(
child: _VipBenefit(
itemKey: Key('mine-vip-benefit-$index'),
@@ -319,44 +258,44 @@ class _VipBenefit extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
return Row(
key: itemKey,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: AppDimens.mineVipBenefitBox,
height: AppDimens.mineVipBenefitBox,
decoration: BoxDecoration(
color: AppColors.vipBenefitSurface,
border: Border.all(color: AppColors.vipBenefitBorder),
borderRadius: BorderRadius.circular(
AppDimens.mineVipBenefitBoxRadius,
_VipBenefitGlyph(icon: icon),
const SizedBox(width: AppDimens.spaceXs),
Expanded(
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipCardMuted,
fontSize: AppDimens.mineVipBenefitFont,
fontWeight: FontWeight.w500,
height: 1.15,
),
),
child: Icon(
icon,
color: AppColors.vipGoldStart,
size: AppDimens.mineVipBenefitGlyph,
),
),
const SizedBox(height: AppDimens.mineVipBenefitRowGap),
Text(
label,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.vipCardMuted,
fontSize: AppDimens.mineVipBenefitFont,
fontWeight: FontWeight.w500,
height: 1.15,
),
),
],
);
}
}
class _VipBenefitGlyph extends StatelessWidget {
const _VipBenefitGlyph({required this.icon});
final IconData icon;
@override
Widget build(BuildContext context) {
return Icon(
icon,
color: AppColors.vipGoldStart,
size: AppDimens.mineVipBenefitGlyph,
);
}
}
class _VipAmbientGlow extends StatelessWidget {
const _VipAmbientGlow();