style(mine): 重构紧凑 VIP 会员入口
This commit is contained in:
1 parent
9ef78ad670
commit
6ef5429a52
7 files changed
+155
-178
No files matched your search
@@ -57,20 +57,23 @@ abstract final class AppColors {
|
||||
static const vipGoldStart = Color(0xFFF5D9A0);
|
||||
static const vipGoldEnd = Color(0xFFE8B65A);
|
||||
|
||||
/// 「我的」页 VIP 卡:品牌酒红、暖黑与香槟金共同形成会员层级。
|
||||
static const vipCardStart = Color(0xFF351318);
|
||||
static const vipCardMiddle = Color(0xFF1B1112);
|
||||
static const vipCardEnd = Color(0xFF0D0B0C);
|
||||
static const vipCardBorder = Color(0x66F5D9A0);
|
||||
static const vipCardRedGlow = Color(0x52F04E4E);
|
||||
static const vipCardGoldGlow = Color(0x38F3B34C);
|
||||
static const vipCardText = Color(0xFFFFF5E6);
|
||||
static const vipCardMuted = Color(0xB3F5D9A0);
|
||||
static const vipBadgeSurface = Color(0x24FFFFFF);
|
||||
/// 「我的」页 VIP 卡:品牌红、暖黑与中性灰形成统一层级。
|
||||
static const vipCardStart = Color(0xFF21191C);
|
||||
static const vipCardMiddle = Color(0xFF181719);
|
||||
static const vipCardEnd = Color(0xFF141416);
|
||||
static const vipCardBorder = Color(0x2EFFFFFF);
|
||||
static const vipCardRedGlow = Color(0x2EF04E4E);
|
||||
static const vipCardGoldGlow = Color(0x16F5D9A0);
|
||||
static const vipArtGlow = Color(0x55F04E4E);
|
||||
static const vipArtStart = Color(0xFFE45757);
|
||||
static const vipArtEnd = Color(0xFF6E1F2D);
|
||||
static const vipCardText = Color(0xFFF5F5F7);
|
||||
static const vipCardMuted = Color(0xB3B0B0B0);
|
||||
static const vipBadgeSurface = Color(0x24F04E4E);
|
||||
static const vipBenefitSurface = Color(0x12FFFFFF);
|
||||
static const vipBenefitBorder = Color(0x33F5D9A0);
|
||||
static const vipButtonStart = brandRed;
|
||||
static const vipButtonEnd = Color(0xFFFF745E);
|
||||
static const vipBenefitBorder = Color(0x14FFFFFF);
|
||||
static const vipButtonStart = Color(0xFFF04E4E);
|
||||
static const vipButtonEnd = Color(0xFFC93648);
|
||||
static const bronze = Color(0xFFCD7F32);
|
||||
static const bronzeDeep = Color(0xFFA9713A);
|
||||
static const rankSilver = Color(0xFFC0C0C0);
|
||||
|
||||
@@ -273,20 +273,24 @@ abstract final class AppDimens {
|
||||
static const double mineSectionLinkFont = 12;
|
||||
static const double mineSectionGap = 12;
|
||||
|
||||
/// 「我的」页会员卡。高度由内容撑开,上下内边距恒为 [mineVipPadding]。
|
||||
/// 「我的」页紧凑会员横幅尺寸。
|
||||
static const double mineVipRadius = 18;
|
||||
static const double mineVipPadding = 16;
|
||||
static const double mineVipTitleFont = 22;
|
||||
static const double mineVipSubtitleFont = 12;
|
||||
static const double mineVipPadding = 10;
|
||||
static const double mineVipTitleFont = 21;
|
||||
static const double mineVipSubtitleFont = 11;
|
||||
static const double mineVipEmblem = 48;
|
||||
static const double mineVipEmblemIcon = 28;
|
||||
static const double mineVipActionWidth = 100;
|
||||
static const double mineVipArtWidth = 84;
|
||||
static const double mineVipArtHeight = 84;
|
||||
static const double mineVipArtGlow = 56;
|
||||
static const double mineVipArtSurface = 44;
|
||||
static const double mineVipArtIcon = 22;
|
||||
static const double mineVipActionWidth = 86;
|
||||
static const double mineVipBenefitFont = 10.5;
|
||||
static const double mineVipBenefitBox = 34;
|
||||
static const double mineVipBenefitBoxRadius = 11;
|
||||
static const double mineVipBenefitGlyph = 19;
|
||||
static const double mineVipBenefitRowGap = 6;
|
||||
static const double mineVipButtonHeight = tapTarget;
|
||||
static const double mineVipBenefitSurface = 28;
|
||||
static const double mineVipBenefitGlyph = 16;
|
||||
static const double mineVipBenefitRowGap = 2;
|
||||
static const double mineVipButtonHeight = 26;
|
||||
static const double mineContinueCardWidth = 96;
|
||||
static const double mineContinueRowHeight = 172;
|
||||
static const double mineContinueTitleFont = 13;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in new issue
Block a user