// Author: fruit.com.cn import 'package:flutter/material.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../../../core/widgets/app_press_scale.dart'; import '../../../l10n/app_localizations.dart'; const double _vipMaxTextScale = 1.15; const double _vipGradientMiddleStop = 0.62; const double _vipGlowRadius = 0.9; const double _vipButtonLetterSpacing = 0.2; /// 「我的」页 VIP 会员卡:主视觉、权益横排与开通入口。 class MineVipBanner extends StatelessWidget { const MineVipBanner({super.key, required this.onOpen}); final VoidCallback onOpen; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Container( key: const Key('mine-vip'), decoration: _decoration(), child: Material( color: AppColors.transparent, borderRadius: BorderRadius.circular(AppDimens.mineVipRadius), clipBehavior: Clip.antiAlias, child: InkWell( onTap: onOpen, child: AppPressScale( child: MediaQuery.withClampedTextScaling( maxScaleFactor: _vipMaxTextScale, child: Stack( children: [ const Positioned.fill(child: _VipAmbientGlow()), Padding( padding: const EdgeInsets.all(AppDimens.mineVipPadding), child: _VipContent(l10n: l10n, onOpen: onOpen), ), ], ), ), ), ), ), ); } BoxDecoration _decoration() { return BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ AppColors.vipCardStart, AppColors.vipCardMiddle, AppColors.vipCardEnd, ], stops: [0, _vipGradientMiddleStop, 1], ), borderRadius: BorderRadius.circular(AppDimens.mineVipRadius), boxShadow: const [ BoxShadow( color: AppColors.shadowSoft, blurRadius: AppDimens.spaceLg, offset: Offset(0, AppDimens.spaceSm), ), ], ); } } class _VipContent extends StatelessWidget { const _VipContent({required this.l10n, required this.onOpen}); final AppLocalizations l10n; final VoidCallback onOpen; @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: _VipCopy(l10n: l10n, onOpen: onOpen), ), const SizedBox(width: AppDimens.spaceSm), const _VipArt(), ], ), const SizedBox(height: AppDimens.spaceXs), _VipBenefits(l10n: l10n), ], ); } } class _VipCopy extends StatelessWidget { const _VipCopy({required this.l10n, required this.onOpen}); final AppLocalizations l10n; final VoidCallback onOpen; @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.vipBrandName, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.vipCardText, fontSize: AppDimens.mineVipTitleFont, fontWeight: FontWeight.w800, letterSpacing: 1.0, ), ), const SizedBox(height: AppDimens.spaceXs), Text( l10n.mineVipSubtitle, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.vipCardMuted, fontSize: AppDimens.mineVipSubtitleFont, height: 1.3, ), ), const SizedBox(height: AppDimens.spaceXs), _VipOpenButton(label: l10n.vipOpenNow, onOpen: onOpen), ], ); } } class _VipArt extends StatelessWidget { const _VipArt(); @override Widget build(BuildContext context) { return SizedBox( 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, ), ); } } class _VipOpenButton extends StatelessWidget { const _VipOpenButton({required this.label, required this.onOpen}); final String label; final VoidCallback onOpen; @override Widget build(BuildContext context) { return SizedBox( key: const Key('mine-vip-open'), width: AppDimens.mineVipActionWidth, height: AppDimens.mineVipButtonHeight, 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.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, ), ], ), ), ); } } class _VipBenefits extends StatelessWidget { const _VipBenefits({required this.l10n}); final AppLocalizations l10n; @override Widget build(BuildContext context) { final items = [ (Icons.all_inclusive_rounded, l10n.vipBenefitFree), (Icons.block_rounded, l10n.vipBenefitNoAds), (Icons.hd_outlined, l10n.vipBenefitHd), (Icons.download_for_offline_outlined, l10n.vipBenefitFastCache), ]; return Column( key: const Key('mine-vip-benefits'), children: [ _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'), icon: items[index].$1, label: items[index].$2, ), ), ], ); } } class _VipBenefit extends StatelessWidget { const _VipBenefit({ required this.itemKey, required this.icon, required this.label, }); final Key itemKey; final IconData icon; final String label; @override Widget build(BuildContext context) { return Row( key: itemKey, children: [ _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, ), ), ), ], ); } } 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(); @override Widget build(BuildContext context) { return Stack( children: const [ Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( gradient: RadialGradient( center: Alignment(0.85, -0.8), radius: _vipGlowRadius, colors: [AppColors.vipCardGoldGlow, AppColors.transparent], ), ), ), ), Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( gradient: RadialGradient( center: Alignment(-0.9, 1), radius: _vipGlowRadius, colors: [AppColors.vipCardRedGlow, AppColors.transparent], ), ), ), ), ], ); } }