commit
fe0e77775e
10 files changed
+206
-196
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 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();
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ class _Balance extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppDimens.spaceSm),
|
||||
const RewardCoinIcon(size: 32),
|
||||
const RewardCoinIcon(key: Key('reward-balance-coin-icon'), size: 32),
|
||||
],
|
||||
);
|
||||
if (onOpenWallet == null) return content;
|
||||
|
||||
@@ -7,6 +7,10 @@ import '../../core/theme/app_colors.dart';
|
||||
|
||||
const double _coinInnerInsetRatio = 0.1;
|
||||
const double _coinLetterRatio = 0.46;
|
||||
const double _coinHighlightRatio = 0.18;
|
||||
const double _coinHighlightOpacity = 0.54;
|
||||
const double _coinLetterShadowBlur = 1.5;
|
||||
const double _coinLetterShadowOffset = 0.8;
|
||||
const double _taskIconSize = 46;
|
||||
const double _taskIconRadius = 12;
|
||||
const double _taskGlyphSize = 24;
|
||||
@@ -20,6 +24,8 @@ const Color _coinOuter = Color(0xFFF6B84E);
|
||||
const Color _coinInnerStart = Color(0xFFFFD98B);
|
||||
const Color _coinInnerEnd = Color(0xFFE39B31);
|
||||
const Color _coinBorder = Color(0xFFFFE0A1);
|
||||
const Color _coinHighlight = Color(0xFFFFF4CC);
|
||||
const Color _coinLetterShadow = Color(0xFFB66A16);
|
||||
const Color _taskIconStart = Color(0xFF4C171E);
|
||||
const Color _taskIconEnd = Color(0xFF281014);
|
||||
const Color _taskIconSurface = Color(0xFF202022);
|
||||
@@ -42,26 +48,52 @@ class RewardCoinIcon extends StatelessWidget {
|
||||
color: _coinOuter,
|
||||
boxShadow: [BoxShadow(color: Color(0x4DF6B84E), blurRadius: 6)],
|
||||
),
|
||||
child: Container(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [_coinInnerStart, _coinInnerEnd],
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [_coinInnerStart, _coinInnerEnd],
|
||||
),
|
||||
border: Border.all(color: _coinBorder),
|
||||
),
|
||||
),
|
||||
border: Border.all(color: _coinBorder),
|
||||
),
|
||||
child: Text(
|
||||
'N',
|
||||
style: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: size * _coinLetterRatio,
|
||||
fontWeight: FontWeight.w900,
|
||||
height: 1,
|
||||
Positioned(
|
||||
top: size * 0.12,
|
||||
left: size * 0.18,
|
||||
child: Container(
|
||||
width: size * _coinHighlightRatio,
|
||||
height: size * _coinHighlightRatio,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _coinHighlight.withValues(alpha: _coinHighlightOpacity),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'N',
|
||||
style: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: size * _coinLetterRatio,
|
||||
fontWeight: FontWeight.w900,
|
||||
height: 1,
|
||||
shadows: const [
|
||||
Shadow(
|
||||
color: _coinLetterShadow,
|
||||
blurRadius: _coinLetterShadowBlur,
|
||||
offset: Offset(
|
||||
_coinLetterShadowOffset,
|
||||
_coinLetterShadowOffset,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import 'package:fruitshorts/pages/mine/widgets/mine_action_grid.dart';
|
||||
import 'package:fruitshorts/pages/mine/widgets/mine_menu.dart';
|
||||
|
||||
const _layoutTolerance = 0.1;
|
||||
const _minimumVipHeight = 190.0;
|
||||
const _previousVipPlaceholderHeight = 220.0;
|
||||
const _minimumVipHeight = 110.0;
|
||||
const _premiumVipHeightLimit = 150.0;
|
||||
|
||||
void main() {
|
||||
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 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(
|
||||
find.byKey(const Key('mine-vip-benefits')),
|
||||
);
|
||||
expect(cardRect.height, lessThan(_previousVipPlaceholderHeight));
|
||||
expect(cardRect.height, lessThan(_premiumVipHeightLimit));
|
||||
expect(cardRect.height, greaterThanOrEqualTo(_minimumVipHeight));
|
||||
expect(
|
||||
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']) {
|
||||
|
||||
@@ -173,10 +173,10 @@ void main() {
|
||||
AppColors.vipCardMiddle,
|
||||
AppColors.vipCardEnd,
|
||||
]);
|
||||
expect(vipDecoration.border, isNotNull);
|
||||
expect(vipDecoration.border, isNull);
|
||||
expect(vipDecoration.boxShadow, isNotEmpty);
|
||||
|
||||
// 卡片高度由内容撑开,上下内边距必须一致(边框各占 1pt)。
|
||||
// 卡片高度由内容撑开,身份头部与底部 CTA 都保留安全内边距。
|
||||
final vipRect = tester.getRect(find.byKey(const Key('mine-vip')));
|
||||
final emblemRect = tester.getRect(find.byKey(const Key('mine-vip-emblem')));
|
||||
final benefitsRect = tester.getRect(
|
||||
@@ -184,12 +184,12 @@ void main() {
|
||||
);
|
||||
final topInset = emblemRect.top - vipRect.top;
|
||||
final bottomInset = vipRect.bottom - benefitsRect.bottom;
|
||||
expect(topInset, closeTo(bottomInset, 0.01));
|
||||
expect(topInset, greaterThanOrEqualTo(AppDimens.mineVipPadding));
|
||||
expect(bottomInset, greaterThanOrEqualTo(AppDimens.mineVipPadding));
|
||||
expect(find.byKey(const Key('mine-vip-emblem')), findsOneWidget);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('mine-vip-open'))).height,
|
||||
greaterThanOrEqualTo(AppDimens.tapTarget),
|
||||
greaterThanOrEqualTo(AppDimens.mineVipButtonHeight),
|
||||
);
|
||||
const benefitKeys = [
|
||||
Key('mine-vip-benefit-0'),
|
||||
@@ -200,10 +200,18 @@ void main() {
|
||||
final benefitTops = benefitKeys
|
||||
.map((key) => tester.getTopLeft(find.byKey(key)))
|
||||
.toList();
|
||||
expect(benefitTops.map((position) => position.dy).toSet(), hasLength(1));
|
||||
expect(
|
||||
benefitTops.map((position) => position.dx).toSet(),
|
||||
hasLength(benefitKeys.length),
|
||||
benefitTops.take(2).map((position) => position.dy).toSet(),
|
||||
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 = [
|
||||
|
||||
@@ -69,6 +69,14 @@ void main() {
|
||||
expect(tops.toSet(), hasLength(1));
|
||||
expect(tester.takeException(), isNull);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -48,6 +48,7 @@ void main() {
|
||||
expect(find.byKey(const Key('reward-back-button')), findsOneWidget);
|
||||
expect(find.byKey(const Key('reward-help-button')), findsOneWidget);
|
||||
expect(find.byKey(const Key('reward-balance')), findsOneWidget);
|
||||
expect(find.byKey(const Key('reward-balance-coin-icon')), findsOneWidget);
|
||||
expect(find.byKey(const Key('reward-checkin-card')), findsOneWidget);
|
||||
expect(find.byKey(const Key('reward-checkin-action')), findsOneWidget);
|
||||
expect(find.byKey(const Key('reward-section-DAILY')), findsOneWidget);
|
||||
|
||||
Reference in new issue
Block a user