style(mine): 重构紧凑 VIP 会员入口
This commit is contained in:
1 parent
9ef78ad670
commit
6ef5429a52
7 files changed
+155
-178
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -57,20 +57,23 @@ abstract final class AppColors {
|
|||||||
static const vipGoldStart = Color(0xFFF5D9A0);
|
static const vipGoldStart = Color(0xFFF5D9A0);
|
||||||
static const vipGoldEnd = Color(0xFFE8B65A);
|
static const vipGoldEnd = Color(0xFFE8B65A);
|
||||||
|
|
||||||
/// 「我的」页 VIP 卡:品牌酒红、暖黑与香槟金共同形成会员层级。
|
/// 「我的」页 VIP 卡:品牌红、暖黑与中性灰形成统一层级。
|
||||||
static const vipCardStart = Color(0xFF351318);
|
static const vipCardStart = Color(0xFF21191C);
|
||||||
static const vipCardMiddle = Color(0xFF1B1112);
|
static const vipCardMiddle = Color(0xFF181719);
|
||||||
static const vipCardEnd = Color(0xFF0D0B0C);
|
static const vipCardEnd = Color(0xFF141416);
|
||||||
static const vipCardBorder = Color(0x66F5D9A0);
|
static const vipCardBorder = Color(0x2EFFFFFF);
|
||||||
static const vipCardRedGlow = Color(0x52F04E4E);
|
static const vipCardRedGlow = Color(0x2EF04E4E);
|
||||||
static const vipCardGoldGlow = Color(0x38F3B34C);
|
static const vipCardGoldGlow = Color(0x16F5D9A0);
|
||||||
static const vipCardText = Color(0xFFFFF5E6);
|
static const vipArtGlow = Color(0x55F04E4E);
|
||||||
static const vipCardMuted = Color(0xB3F5D9A0);
|
static const vipArtStart = Color(0xFFE45757);
|
||||||
static const vipBadgeSurface = Color(0x24FFFFFF);
|
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 vipBenefitSurface = Color(0x12FFFFFF);
|
||||||
static const vipBenefitBorder = Color(0x33F5D9A0);
|
static const vipBenefitBorder = Color(0x14FFFFFF);
|
||||||
static const vipButtonStart = brandRed;
|
static const vipButtonStart = Color(0xFFF04E4E);
|
||||||
static const vipButtonEnd = Color(0xFFFF745E);
|
static const vipButtonEnd = Color(0xFFC93648);
|
||||||
static const bronze = Color(0xFFCD7F32);
|
static const bronze = Color(0xFFCD7F32);
|
||||||
static const bronzeDeep = Color(0xFFA9713A);
|
static const bronzeDeep = Color(0xFFA9713A);
|
||||||
static const rankSilver = Color(0xFFC0C0C0);
|
static const rankSilver = Color(0xFFC0C0C0);
|
||||||
|
|||||||
@@ -273,20 +273,24 @@ abstract final class AppDimens {
|
|||||||
static const double mineSectionLinkFont = 12;
|
static const double mineSectionLinkFont = 12;
|
||||||
static const double mineSectionGap = 12;
|
static const double mineSectionGap = 12;
|
||||||
|
|
||||||
/// 「我的」页会员卡。高度由内容撑开,上下内边距恒为 [mineVipPadding]。
|
/// 「我的」页紧凑会员横幅尺寸。
|
||||||
static const double mineVipRadius = 18;
|
static const double mineVipRadius = 18;
|
||||||
static const double mineVipPadding = 16;
|
static const double mineVipPadding = 10;
|
||||||
static const double mineVipTitleFont = 22;
|
static const double mineVipTitleFont = 21;
|
||||||
static const double mineVipSubtitleFont = 12;
|
static const double mineVipSubtitleFont = 11;
|
||||||
static const double mineVipEmblem = 48;
|
static const double mineVipEmblem = 48;
|
||||||
static const double mineVipEmblemIcon = 28;
|
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 mineVipBenefitFont = 10.5;
|
||||||
static const double mineVipBenefitBox = 34;
|
static const double mineVipBenefitSurface = 28;
|
||||||
static const double mineVipBenefitBoxRadius = 11;
|
static const double mineVipBenefitGlyph = 16;
|
||||||
static const double mineVipBenefitGlyph = 19;
|
static const double mineVipBenefitRowGap = 2;
|
||||||
static const double mineVipBenefitRowGap = 6;
|
static const double mineVipButtonHeight = 26;
|
||||||
static const double mineVipButtonHeight = tapTarget;
|
|
||||||
static const double mineContinueCardWidth = 96;
|
static const double mineContinueCardWidth = 96;
|
||||||
static const double mineContinueRowHeight = 172;
|
static const double mineContinueRowHeight = 172;
|
||||||
static const double mineContinueTitleFont = 13;
|
static const double mineContinueTitleFont = 13;
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import '../../../core/widgets/app_press_scale.dart';
|
|||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
const double _vipMaxTextScale = 1.15;
|
const double _vipMaxTextScale = 1.15;
|
||||||
const double _vipGradientMiddleStop = 0.48;
|
const double _vipGradientMiddleStop = 0.62;
|
||||||
const double _vipGlowRadius = 0.9;
|
const double _vipGlowRadius = 0.9;
|
||||||
|
const double _vipButtonLetterSpacing = 0.2;
|
||||||
|
|
||||||
/// 「我的」页高级 VIP 卡:品牌信息、会员徽章、开通按钮与横排权益。
|
/// 「我的」页 VIP 会员卡:主视觉、权益横排与开通入口。
|
||||||
class MineVipBanner extends StatelessWidget {
|
class MineVipBanner extends StatelessWidget {
|
||||||
const MineVipBanner({super.key, required this.onOpen});
|
const MineVipBanner({super.key, required this.onOpen});
|
||||||
|
|
||||||
@@ -61,13 +62,7 @@ class MineVipBanner extends StatelessWidget {
|
|||||||
stops: [0, _vipGradientMiddleStop, 1],
|
stops: [0, _vipGradientMiddleStop, 1],
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(AppDimens.mineVipRadius),
|
borderRadius: BorderRadius.circular(AppDimens.mineVipRadius),
|
||||||
border: Border.all(color: AppColors.vipCardBorder),
|
|
||||||
boxShadow: const [
|
boxShadow: const [
|
||||||
BoxShadow(
|
|
||||||
color: AppColors.vipCardRedGlow,
|
|
||||||
blurRadius: AppDimens.spaceXl,
|
|
||||||
offset: Offset(0, AppDimens.spaceSm),
|
|
||||||
),
|
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: AppColors.shadowSoft,
|
color: AppColors.shadowSoft,
|
||||||
blurRadius: AppDimens.spaceLg,
|
blurRadius: AppDimens.spaceLg,
|
||||||
@@ -90,16 +85,16 @@ class _VipContent extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _VipCopy(l10n: l10n)),
|
Expanded(
|
||||||
|
child: _VipCopy(l10n: l10n, onOpen: onOpen),
|
||||||
|
),
|
||||||
const SizedBox(width: AppDimens.spaceSm),
|
const SizedBox(width: AppDimens.spaceSm),
|
||||||
_VipAction(l10n: l10n, onOpen: onOpen),
|
const _VipArt(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: AppDimens.spaceSm),
|
const SizedBox(height: AppDimens.spaceXs),
|
||||||
const Divider(height: 1, color: AppColors.vipBenefitBorder),
|
|
||||||
const SizedBox(height: AppDimens.spaceSm),
|
|
||||||
_VipBenefits(l10n: l10n),
|
_VipBenefits(l10n: l10n),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -107,9 +102,10 @@ class _VipContent extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _VipCopy extends StatelessWidget {
|
class _VipCopy extends StatelessWidget {
|
||||||
const _VipCopy({required this.l10n});
|
const _VipCopy({required this.l10n, required this.onOpen});
|
||||||
|
|
||||||
final AppLocalizations l10n;
|
final AppLocalizations l10n;
|
||||||
|
final VoidCallback onOpen;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -117,8 +113,6 @@ class _VipCopy extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_VipPill(label: l10n.vipMembership),
|
|
||||||
const SizedBox(height: AppDimens.spaceSm),
|
|
||||||
Text(
|
Text(
|
||||||
l10n.vipBrandName,
|
l10n.vipBrandName,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@@ -126,14 +120,14 @@ class _VipCopy extends StatelessWidget {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppColors.vipCardText,
|
color: AppColors.vipCardText,
|
||||||
fontSize: AppDimens.mineVipTitleFont,
|
fontSize: AppDimens.mineVipTitleFont,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w800,
|
||||||
letterSpacing: -0.4,
|
letterSpacing: 1.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: AppDimens.spaceXs),
|
const SizedBox(height: AppDimens.spaceXs),
|
||||||
Text(
|
Text(
|
||||||
l10n.mineVipSubtitle,
|
l10n.mineVipSubtitle,
|
||||||
maxLines: 2,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppColors.vipCardMuted,
|
color: AppColors.vipCardMuted,
|
||||||
@@ -141,90 +135,28 @@ class _VipCopy extends StatelessWidget {
|
|||||||
height: 1.3,
|
height: 1.3,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: AppDimens.spaceXs),
|
||||||
|
_VipOpenButton(label: l10n.vipOpenNow, onOpen: onOpen),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VipPill extends StatelessWidget {
|
class _VipArt extends StatelessWidget {
|
||||||
const _VipPill({required this.label});
|
const _VipArt();
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: AppDimens.mineVipActionWidth,
|
key: const Key('mine-vip-art'),
|
||||||
child: Column(
|
width: AppDimens.mineVipArtWidth,
|
||||||
mainAxisSize: MainAxisSize.min,
|
height: AppDimens.mineVipArtHeight,
|
||||||
children: [
|
child: Image.asset(
|
||||||
const _VipEmblem(),
|
'assets/icon/vip_membership_badge.png',
|
||||||
const SizedBox(height: AppDimens.spaceXs),
|
key: const Key('mine-vip-emblem'),
|
||||||
_VipOpenButton(label: l10n.vipOpenNow, onOpen: onOpen),
|
width: AppDimens.mineVipArtWidth,
|
||||||
],
|
height: AppDimens.mineVipArtHeight,
|
||||||
),
|
fit: BoxFit.contain,
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -242,34 +174,32 @@ class _VipOpenButton extends StatelessWidget {
|
|||||||
key: const Key('mine-vip-open'),
|
key: const Key('mine-vip-open'),
|
||||||
width: AppDimens.mineVipActionWidth,
|
width: AppDimens.mineVipActionWidth,
|
||||||
height: AppDimens.mineVipButtonHeight,
|
height: AppDimens.mineVipButtonHeight,
|
||||||
child: DecoratedBox(
|
child: InkWell(
|
||||||
decoration: BoxDecoration(
|
onTap: onOpen,
|
||||||
gradient: const LinearGradient(
|
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
|
||||||
colors: [AppColors.vipButtonStart, AppColors.vipButtonEnd],
|
child: Row(
|
||||||
),
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
|
children: [
|
||||||
boxShadow: const [
|
Flexible(
|
||||||
BoxShadow(color: AppColors.vipCardRedGlow, blurRadius: 14),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Material(
|
|
||||||
color: AppColors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: onOpen,
|
|
||||||
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppColors.textPrimary,
|
color: AppColors.vipGoldStart,
|
||||||
fontSize: AppDimens.body,
|
fontSize: AppDimens.body,
|
||||||
fontWeight: FontWeight.w700,
|
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.hd_outlined, l10n.vipBenefitHd),
|
||||||
(Icons.download_for_offline_outlined, l10n.vipBenefitFastCache),
|
(Icons.download_for_offline_outlined, l10n.vipBenefitFastCache),
|
||||||
];
|
];
|
||||||
return Row(
|
return Column(
|
||||||
key: const Key('mine-vip-benefits'),
|
key: const Key('mine-vip-benefits'),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
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(
|
Expanded(
|
||||||
child: _VipBenefit(
|
child: _VipBenefit(
|
||||||
itemKey: Key('mine-vip-benefit-$index'),
|
itemKey: Key('mine-vip-benefit-$index'),
|
||||||
@@ -319,44 +258,44 @@ class _VipBenefit extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Row(
|
||||||
key: itemKey,
|
key: itemKey,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Container(
|
_VipBenefitGlyph(icon: icon),
|
||||||
width: AppDimens.mineVipBenefitBox,
|
const SizedBox(width: AppDimens.spaceXs),
|
||||||
height: AppDimens.mineVipBenefitBox,
|
Expanded(
|
||||||
decoration: BoxDecoration(
|
child: Text(
|
||||||
color: AppColors.vipBenefitSurface,
|
label,
|
||||||
border: Border.all(color: AppColors.vipBenefitBorder),
|
maxLines: 1,
|
||||||
borderRadius: BorderRadius.circular(
|
overflow: TextOverflow.ellipsis,
|
||||||
AppDimens.mineVipBenefitBoxRadius,
|
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 {
|
class _VipAmbientGlow extends StatelessWidget {
|
||||||
const _VipAmbientGlow();
|
const _VipAmbientGlow();
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import 'package:fruitshorts/pages/mine/widgets/mine_action_grid.dart';
|
|||||||
import 'package:fruitshorts/pages/mine/widgets/mine_menu.dart';
|
import 'package:fruitshorts/pages/mine/widgets/mine_menu.dart';
|
||||||
|
|
||||||
const _layoutTolerance = 0.1;
|
const _layoutTolerance = 0.1;
|
||||||
const _minimumVipHeight = 190.0;
|
const _minimumVipHeight = 110.0;
|
||||||
const _previousVipPlaceholderHeight = 220.0;
|
const _premiumVipHeightLimit = 150.0;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('should_keep_equal_visible_vip_padding_and_reduce_height', (
|
testWidgets('should_keep_equal_visible_vip_padding_and_reduce_height', (
|
||||||
@@ -27,15 +27,30 @@ void main() {
|
|||||||
|
|
||||||
final cardRect = tester.getRect(find.byKey(const Key('mine-vip')));
|
final cardRect = tester.getRect(find.byKey(const Key('mine-vip')));
|
||||||
final emblemRect = tester.getRect(find.byKey(const Key('mine-vip-emblem')));
|
final emblemRect = tester.getRect(find.byKey(const Key('mine-vip-emblem')));
|
||||||
|
final emblem = tester.widget<Image>(
|
||||||
|
find.byKey(const Key('mine-vip-emblem')),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
(emblem.image as AssetImage).assetName,
|
||||||
|
'assets/icon/vip_membership_badge.png',
|
||||||
|
);
|
||||||
|
final titleRect = tester.getRect(find.text('VIP'));
|
||||||
final benefitsRect = tester.getRect(
|
final benefitsRect = tester.getRect(
|
||||||
find.byKey(const Key('mine-vip-benefits')),
|
find.byKey(const Key('mine-vip-benefits')),
|
||||||
);
|
);
|
||||||
expect(cardRect.height, lessThan(_previousVipPlaceholderHeight));
|
expect(cardRect.height, lessThan(_premiumVipHeightLimit));
|
||||||
expect(cardRect.height, greaterThanOrEqualTo(_minimumVipHeight));
|
expect(cardRect.height, greaterThanOrEqualTo(_minimumVipHeight));
|
||||||
expect(
|
expect(
|
||||||
emblemRect.top - cardRect.top,
|
emblemRect.top - cardRect.top,
|
||||||
closeTo(cardRect.bottom - benefitsRect.bottom, _layoutTolerance),
|
greaterThanOrEqualTo(AppDimens.mineVipPadding),
|
||||||
);
|
);
|
||||||
|
expect(benefitsRect.bottom, lessThan(cardRect.bottom));
|
||||||
|
expect(emblemRect.left, greaterThan(titleRect.right));
|
||||||
|
final first = tester.getRect(find.byKey(const Key('mine-vip-benefit-0')));
|
||||||
|
final second = tester.getRect(find.byKey(const Key('mine-vip-benefit-1')));
|
||||||
|
final third = tester.getRect(find.byKey(const Key('mine-vip-benefit-2')));
|
||||||
|
expect(first.top, second.top);
|
||||||
|
expect(third.top, greaterThan(first.top));
|
||||||
});
|
});
|
||||||
|
|
||||||
for (final localeTag in ['en', 'id']) {
|
for (final localeTag in ['en', 'id']) {
|
||||||
|
|||||||
@@ -173,10 +173,10 @@ void main() {
|
|||||||
AppColors.vipCardMiddle,
|
AppColors.vipCardMiddle,
|
||||||
AppColors.vipCardEnd,
|
AppColors.vipCardEnd,
|
||||||
]);
|
]);
|
||||||
expect(vipDecoration.border, isNotNull);
|
expect(vipDecoration.border, isNull);
|
||||||
expect(vipDecoration.boxShadow, isNotEmpty);
|
expect(vipDecoration.boxShadow, isNotEmpty);
|
||||||
|
|
||||||
// 卡片高度由内容撑开,上下内边距必须一致(边框各占 1pt)。
|
// 卡片高度由内容撑开,身份头部与底部 CTA 都保留安全内边距。
|
||||||
final vipRect = tester.getRect(find.byKey(const Key('mine-vip')));
|
final vipRect = tester.getRect(find.byKey(const Key('mine-vip')));
|
||||||
final emblemRect = tester.getRect(find.byKey(const Key('mine-vip-emblem')));
|
final emblemRect = tester.getRect(find.byKey(const Key('mine-vip-emblem')));
|
||||||
final benefitsRect = tester.getRect(
|
final benefitsRect = tester.getRect(
|
||||||
@@ -184,12 +184,12 @@ void main() {
|
|||||||
);
|
);
|
||||||
final topInset = emblemRect.top - vipRect.top;
|
final topInset = emblemRect.top - vipRect.top;
|
||||||
final bottomInset = vipRect.bottom - benefitsRect.bottom;
|
final bottomInset = vipRect.bottom - benefitsRect.bottom;
|
||||||
expect(topInset, closeTo(bottomInset, 0.01));
|
|
||||||
expect(topInset, greaterThanOrEqualTo(AppDimens.mineVipPadding));
|
expect(topInset, greaterThanOrEqualTo(AppDimens.mineVipPadding));
|
||||||
|
expect(bottomInset, greaterThanOrEqualTo(AppDimens.mineVipPadding));
|
||||||
expect(find.byKey(const Key('mine-vip-emblem')), findsOneWidget);
|
expect(find.byKey(const Key('mine-vip-emblem')), findsOneWidget);
|
||||||
expect(
|
expect(
|
||||||
tester.getSize(find.byKey(const Key('mine-vip-open'))).height,
|
tester.getSize(find.byKey(const Key('mine-vip-open'))).height,
|
||||||
greaterThanOrEqualTo(AppDimens.tapTarget),
|
greaterThanOrEqualTo(AppDimens.mineVipButtonHeight),
|
||||||
);
|
);
|
||||||
const benefitKeys = [
|
const benefitKeys = [
|
||||||
Key('mine-vip-benefit-0'),
|
Key('mine-vip-benefit-0'),
|
||||||
@@ -200,10 +200,18 @@ void main() {
|
|||||||
final benefitTops = benefitKeys
|
final benefitTops = benefitKeys
|
||||||
.map((key) => tester.getTopLeft(find.byKey(key)))
|
.map((key) => tester.getTopLeft(find.byKey(key)))
|
||||||
.toList();
|
.toList();
|
||||||
expect(benefitTops.map((position) => position.dy).toSet(), hasLength(1));
|
|
||||||
expect(
|
expect(
|
||||||
benefitTops.map((position) => position.dx).toSet(),
|
benefitTops.take(2).map((position) => position.dy).toSet(),
|
||||||
hasLength(benefitKeys.length),
|
hasLength(1),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
benefitTops.skip(2).map((position) => position.dy).toSet(),
|
||||||
|
hasLength(1),
|
||||||
|
);
|
||||||
|
expect(benefitTops[2].dy, greaterThan(benefitTops[0].dy));
|
||||||
|
expect(
|
||||||
|
benefitTops.take(2).map((position) => position.dx).toSet(),
|
||||||
|
hasLength(2),
|
||||||
);
|
);
|
||||||
|
|
||||||
final order = [
|
final order = [
|
||||||
|
|||||||
@@ -69,6 +69,14 @@ void main() {
|
|||||||
expect(tops.toSet(), hasLength(1));
|
expect(tops.toSet(), hasLength(1));
|
||||||
expect(tester.takeException(), isNull);
|
expect(tester.takeException(), isNull);
|
||||||
if (_captureEnabled) {
|
if (_captureEnabled) {
|
||||||
|
final context = tester.element(find.byKey(const Key('mine-vip-emblem')));
|
||||||
|
await tester.runAsync(() async {
|
||||||
|
await precacheImage(
|
||||||
|
const AssetImage('assets/icon/vip_membership_badge.png'),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await tester.pumpAndSettle();
|
||||||
await saveWidgetCapture(tester, _captureKey, _capturePath);
|
await saveWidgetCapture(tester, _captureKey, _capturePath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user