import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/modules/user/vip/vip_route.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/shared/business_logic/models/res/sc_vip_res.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_vip_repository_imp.dart'; import 'package:yumi/shared/tools/sc_loading_manager.dart'; import 'package:yumi/ui_kit/components/sc_debounce_widget.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; class VipDetailPage extends StatefulWidget { const VipDetailPage({super.key}); @override State createState() => _VipDetailPageState(); } class _VipDetailPageState extends State { final SCVipRepositoryImp _repository = SCVipRepositoryImp(); int _selectedLevel = 1; bool _isLoading = false; bool _isPurchasing = false; int? _previewingLevel; Object? _loadError; SCVipHomeRes? _home; SCVipStatusRes? _status; SCVipPreviewRes? _preview; @override void initState() { super.initState(); _loadVipData(); } Future _loadVipData({bool showLoading = true}) async { if (showLoading) { setState(() { _isLoading = true; _loadError = null; }); } try { final results = await Future.wait([ _repository.vipHome(), _repository.vipStatus().catchError((error) { debugPrint('VIP status load failed: $error'); return SCVipStatusRes(); }), ]); final home = results[0] as SCVipHomeRes; final status = results[1] as SCVipStatusRes; final resolvedStatus = status.levelInt > 0 ? status : home.state; final selectedLevel = _resolveInitialLevel(home, resolvedStatus); _logLoadedVipData(home, resolvedStatus, selectedLevel); if (!mounted) return; setState(() { _home = home; _status = resolvedStatus; _selectedLevel = selectedLevel; _preview = null; _previewingLevel = null; _isLoading = false; _loadError = null; }); _loadPreviewForLevel(selectedLevel); } catch (error) { if (!mounted) return; setState(() { _loadError = error; _isLoading = false; }); } } int _resolveInitialLevel(SCVipHomeRes home, SCVipStatusRes? status) { if (status?.isActive == true && status!.levelInt > 0) { return status.levelInt.clamp(1, 5).toInt(); } for (final level in home.levels) { if (level.enabled != false && level.canPurchase == true) { return level.levelInt.clamp(1, 5).toInt(); } } for (final level in home.levels) { if (level.enabled != false && level.levelInt > 0) { return level.levelInt.clamp(1, 5).toInt(); } } return 1; } SCVipLevelConfigRes? _levelConfigFor(int level) { final levels = _home?.levels ?? []; for (final config in levels) { if (config.levelInt == level) { return config; } } return null; } void _selectLevel(int level) { setState(() { _selectedLevel = level; _preview = null; }); _loadPreviewForLevel(level); } Future _loadPreviewForLevel(int level) async { final config = _levelConfigFor(level); if (_home?.configured == false || config?.enabled == false || config?.canPurchase != true) { debugPrint( '[VIP][Page] skip preview level=$level ' 'configured=${_home?.configured} enabled=${config?.enabled} ' 'canPurchase=${config?.canPurchase}', ); return; } setState(() => _previewingLevel = level); try { final preview = await _repository.vipPreview(targetLevel: level); if (!mounted || _selectedLevel != level) return; setState(() { _preview = preview; _previewingLevel = null; }); } catch (_) { if (!mounted || _selectedLevel != level) return; setState(() => _previewingLevel = null); } } Future _purchaseSelectedLevel() async { if (!_canPurchaseSelected || _isPurchasing) { return; } final profileManager = Provider.of( context, listen: false, ); setState(() => _isPurchasing = true); SCLoadingManager.show(); try { if (_preview?.targetLevelInt != _selectedLevel) { _preview = await _repository.vipPreview(targetLevel: _selectedLevel); } if (!mounted) return; final purchase = await _repository.vipPurchase( targetLevel: _selectedLevel, bizIdempotentKey: _buildBizIdempotentKey(), ); if (!mounted) return; if (purchase.success == true) { debugPrint( '[VIP][Page] purchase success targetLevel=$_selectedLevel ' 'orderId=${purchase.order?.id} paidGold=${purchase.order?.paidGold}', ); SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful); profileManager.fetchUserProfileData(loadGuardCount: false); profileManager.balance(); await _loadVipData(showLoading: false); } } catch (error) { debugPrint('VIP purchase failed: $error'); } finally { SCLoadingManager.hide(); if (mounted) { setState(() => _isPurchasing = false); } } } String _buildBizIdempotentKey() { final userId = AccountStorage().getCurrentUser()?.userProfile?.id ?? '0'; return 'vip-buy-$userId-${DateTime.now().millisecondsSinceEpoch}'; } void _logLoadedVipData( SCVipHomeRes home, SCVipStatusRes? status, int selectedLevel, ) { debugPrint( '[VIP][Page] home loaded configured=${home.configured} ' 'levels=${home.levels.length} selectedLevel=$selectedLevel ' 'statusActive=${status?.active} statusLevel=${status?.level} ' 'statusExpireAt=${status?.expireAt}', ); if (home.configured != true) { debugPrint( '[VIP][Page][Warn] vip home configured=${home.configured}; ' 'check backend VIP config for sysOrigin=${home.sysOrigin}', ); } if (home.levels.isEmpty) { debugPrint( '[VIP][Page][Warn] vip home levels is empty; ' 'frontend has no level data to render.', ); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: Stack( children: [ Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: const [Color(0xFF0D2B22), Color(0xFF0E0E0E)], stops: const [0, 0.40], ), ), ), ), Positioned( left: 0, right: 0, top: 0, child: Image.asset( 'sc_images/vip/sc_vip_page_bg.png', width: ScreenUtil().screenWidth, fit: BoxFit.fitWidth, alignment: Alignment.topCenter, ), ), Positioned( left: 0, right: 0, top: 0, height: 300.w, child: IgnorePointer( child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withValues(alpha: 0.36), Colors.black.withValues(alpha: 0.62), Colors.transparent, ], stops: const [0, 0.52, 1], ), ), ), ), ), SafeArea( bottom: false, child: Column( children: [ _buildTopBar(), _buildLevelTabs(), Expanded(child: _buildBodyContent()), _buildActivateBar(), ], ), ), ], ), ); } Widget _buildBodyContent() { if (_isLoading && _home == null) { return Center( child: CircularProgressIndicator( color: const Color(0xFFEBCB76), strokeWidth: 2.w, ), ); } if (_loadError != null && _home == null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Failed to load VIP data', style: TextStyle(color: Colors.white, fontSize: 14.sp), ), SizedBox(height: 12.w), SCDebounceWidget( onTap: () => _loadVipData(), child: Container( padding: EdgeInsets.symmetric(horizontal: 18.w, vertical: 8.w), decoration: BoxDecoration( borderRadius: BorderRadius.circular(18.w), border: Border.all( color: const Color(0xFFEBCB76), width: 1.w, ), ), child: Text( 'Retry', style: TextStyle( color: const Color(0xFFFFE8A7), fontSize: 13.sp, fontWeight: FontWeight.w600, ), ), ), ), ], ), ); } return SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: EdgeInsets.fromLTRB(12.w, 40.w, 12.w, 18.w), child: Column( children: [ _buildLevelCard(), SizedBox(height: 30.w), _buildLevelDivider(), SizedBox(height: 3.w), _buildSectionTitle('Decoration Privileges'), SizedBox(height: 10.w), _buildDecorationGrid(), SizedBox(height: 20.w), _buildSectionTitle('VIP Privileges'), SizedBox(height: 14.w), _buildPrivilegeGrid(), SizedBox(height: 76.w), ], ), ); } Widget _buildTopBar() { return SizedBox( height: 44.w, child: Stack( alignment: Alignment.center, children: [ Align( alignment: AlignmentDirectional.centerStart, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(context), child: SizedBox( width: 44.w, height: 44.w, child: Icon( SCGlobalConfig.lang == 'ar' ? Icons.keyboard_arrow_right : Icons.keyboard_arrow_left, color: Colors.white, size: 28.w, ), ), ), ), Text( 'VIP', style: TextStyle( color: Colors.white, fontSize: 18.sp, fontWeight: FontWeight.w500, ), ), Align( alignment: AlignmentDirectional.centerEnd, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => SCNavigatorUtils.push(context, VipRoute.instruction), child: Padding( padding: EdgeInsetsDirectional.only(end: 12.w), child: Image.asset( 'sc_images/vip/sc_vip_question.png', width: 24.w, height: 24.w, ), ), ), ), ], ), ); } Widget _buildLevelTabs() { return SizedBox( height: 43.w, child: ListView.separated( scrollDirection: Axis.horizontal, physics: const BouncingScrollPhysics(), padding: EdgeInsets.symmetric(horizontal: 12.w), itemCount: 5, separatorBuilder: (_, __) => SizedBox(width: 30.w), itemBuilder: (context, index) { final level = index + 1; final config = _levelConfigFor(level); final enabled = config?.enabled != false; final selected = level == _selectedLevel; return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _selectLevel(level), child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: 4.w), Text( config?.displayNameText ?? 'VIP$level', style: TextStyle( color: selected ? Colors.white : Colors.white.withValues( alpha: enabled ? 0.42 : 0.20, ), fontSize: 15.sp, fontWeight: FontWeight.w500, ), ), SizedBox(height: 6.w), SizedBox( width: 0, height: 9.5.w, child: OverflowBox( minWidth: 60.w, maxWidth: 60.w, alignment: Alignment.center, child: selected ? Image.asset( 'sc_images/vip/sc_vip_selected_line.png', width: 60.w, height: 9.5.w, fit: BoxFit.fill, ) : const SizedBox.shrink(), ), ), ], ), ); }, ), ); } Widget _buildLevelCard() { final assetLevel = _assetLevel; return Center( child: SizedBox( width: 351.w, height: 124.5.w, child: Stack( clipBehavior: Clip.none, children: [ Positioned.fill( child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8.w), border: Border.all( color: const Color(0xFFEBCB76), width: 1.w, ), image: DecorationImage( image: AssetImage( 'sc_images/vip/sc_vip_card_bg_$assetLevel.png', ), fit: BoxFit.fill, ), ), ), ), PositionedDirectional( start: 20.w, top: 33.w, child: _buildVipLevelMark(_selectedLevel, scale: 1.18), ), PositionedDirectional( start: 22.w, top: 80.w, end: 160.w, child: SizedBox( height: 32.w, child: Text( _selectedLevelStatusText, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( color: const Color(0xFFF2C766).withValues(alpha: 0.78), fontSize: 12.sp, fontWeight: FontWeight.w500, height: 1.2, ), ), ), ), PositionedDirectional( end: 24.w, top: -36.w, child: Image.asset( 'sc_images/vip/sc_vip_badge_$assetLevel.png', width: 135.w, height: 135.w, fit: BoxFit.contain, ), ), ], ), ), ); } Widget _buildLevelDivider() { final ornamentLayout = _ornamentLayoutForLevel(_assetLevel); return SizedBox( height: 38.w, child: Stack( clipBehavior: Clip.none, alignment: Alignment.center, children: [ Positioned( left: -12.w, right: -12.w, top: 0, height: 8.w, child: Image.asset( 'sc_images/vip/sc_vip_top_border_$_assetLevel.png', fit: BoxFit.fill, ), ), Positioned( left: 0, right: 0, top: ornamentLayout.top.w, child: Transform.translate( offset: Offset(ornamentLayout.dx.w, 0), child: Center( child: Image.asset( 'sc_images/vip/sc_vip_card_ornament_$_assetLevel.png', width: ornamentLayout.width.w, height: ornamentLayout.height.w, fit: BoxFit.contain, ), ), ), ), ], ), ); } Widget _buildSectionTitle(String title) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( 'sc_images/vip/sc_vip_title_left.png', width: 16.w, height: 20.w, fit: BoxFit.contain, ), SizedBox(width: 8.w), Text( title, style: TextStyle( color: Colors.white, fontSize: 18.sp, fontWeight: FontWeight.w500, ), ), SizedBox(width: 8.w), Image.asset( 'sc_images/vip/sc_vip_title_right.png', width: 16.w, height: 20.w, fit: BoxFit.contain, ), ], ); } Widget _buildDecorationGrid() { final items = [ _VipFeatureData( 'SVIP Badge', 'sc_images/vip/sc_vip_badge_$_assetLevel.png', ), _VipFeatureData('Frame', null, Icons.person_pin_circle_outlined), _VipFeatureData('Mount', null, Icons.auto_awesome_motion), _VipFeatureData('Mic Animation', null, Icons.radio_button_checked), _VipFeatureData('Entry Effect', null, Icons.airline_seat_recline_extra), ]; return Wrap( spacing: 10.w, runSpacing: 10.w, children: items .map( (item) => _buildFeatureTile( item, width: item.title == 'Entry Effect' ? 206.w : 98.w, height: 100.w, ), ) .toList(), ); } Widget _buildPrivilegeGrid() { final items = [ _VipFeatureData('Gif Profile\nPicture', null, Icons.badge_outlined), _VipFeatureData('Colorful\nNickname', null, Icons.credit_card), _VipFeatureData('Gif Room\nPicture', null, Icons.home_rounded), _VipFeatureData('Colorful ID', null, Icons.pin_rounded), ]; return Wrap( spacing: 34.w, runSpacing: 24.w, children: items.asMap().entries.map((entry) { final item = entry.value; return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => SCNavigatorUtils.push( context, '${VipRoute.benefit}?index=${entry.key}', ), child: SizedBox( width: 74.w, child: Column( children: [ Container( width: 46.w, height: 46.w, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withValues(alpha: 0.08), ), alignment: Alignment.center, child: Icon( item.icon, color: const Color(0xFFFF8E3D), size: 23.w, ), ), SizedBox(height: 7.w), Text( item.title, textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 12.sp, fontWeight: FontWeight.w400, height: 1.1, ), ), ], ), ), ); }).toList(), ); } Widget _buildRewardPlaceholder(IconData icon) { return Center( child: Container( width: 46.w, height: 46.w, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ const Color(0xFFFFD155).withValues(alpha: 0.28), Colors.white.withValues(alpha: 0.06), ], ), border: Border.all( color: const Color(0xFFFFD155).withValues(alpha: 0.45), width: 0.8.w, ), ), alignment: Alignment.center, child: Icon(icon, color: const Color(0xFFFFD155), size: 24.w), ), ); } Widget _buildWideRewardPlaceholder() { return Container( width: double.infinity, height: 40.w, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20.w), gradient: LinearGradient( colors: [ const Color(0xFFFFD155).withValues(alpha: 0.14), const Color(0xFF7F4DFF).withValues(alpha: 0.16), const Color(0xFFFFD155).withValues(alpha: 0.14), ], ), border: Border.all( color: const Color(0xFFFFD155).withValues(alpha: 0.35), width: 0.8.w, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ for (int i = 0; i < 5; i++) Container( width: 4.w, height: 4.w, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withValues(alpha: 0.45), ), ), ], ), ); } Widget _buildFeatureTile( _VipFeatureData item, { required double width, required double height, }) { final isWide = item.title == 'Entry Effect'; return Container( width: width, height: height, decoration: BoxDecoration( borderRadius: BorderRadius.circular(6.w), border: Border.all(color: const Color(0xFFEBCB76), width: 1.w), color: Colors.black.withValues(alpha: 0.38), ), padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.w), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (item.assetPath != null) Expanded(child: Image.asset(item.assetPath!, fit: BoxFit.contain)) else Expanded( child: isWide ? Center(child: _buildWideRewardPlaceholder()) : _buildRewardPlaceholder( item.icon ?? Icons.auto_awesome, ), ), SizedBox(height: 5.w), Text( item.title, maxLines: 2, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, style: TextStyle( color: Colors.white, fontSize: 12.sp, fontWeight: FontWeight.w400, ), ), ], ), ); } Widget _buildActivateBar() { final bottomInset = MediaQuery.of(context).padding.bottom; final actionButton = _actionButtonConfigForLevel(_assetLevel); final barOverflow = 20.w + bottomInset; final canPurchase = _canPurchaseSelected && !_isPurchasing && _previewingLevel != _selectedLevel; return SizedBox( height: 106.5.w, width: double.infinity, child: Stack( clipBehavior: Clip.none, children: [ Positioned( left: 0, right: 0, top: 0, bottom: -barOverflow, child: ClipRRect( borderRadius: BorderRadius.only( topLeft: Radius.circular(20.w), topRight: Radius.circular(20.w), ), child: Image.asset( 'sc_images/vip/sc_vip_card_bg_$_assetLevel.png', fit: BoxFit.fitWidth, ), ), ), Positioned.fill( child: Padding( padding: EdgeInsets.fromLTRB( 30.w, 30.w, 30.w, 10.w + bottomInset, ), child: Row( children: [ Container( width: 22.w, height: 22.w, decoration: const BoxDecoration( shape: BoxShape.circle, color: Color(0xFFFFA629), ), alignment: Alignment.center, child: Icon( Icons.stars_rounded, color: Colors.white, size: 14.w, ), ), SizedBox(width: 6.w), Text( _priceText, style: TextStyle( color: const Color(0xFFFFE8A7), fontSize: 16.sp, fontWeight: FontWeight.w700, ), ), const Spacer(), SCDebounceWidget( onTap: canPurchase ? _purchaseSelectedLevel : () {}, child: Opacity( opacity: canPurchase ? 1 : 0.52, child: Container( width: 106.w, height: 42.w, alignment: Alignment.center, decoration: BoxDecoration( image: DecorationImage( image: AssetImage(actionButton.backgroundAssetPath), fit: BoxFit.fill, ), ), child: _isPurchasing || _previewingLevel == _selectedLevel ? SizedBox( width: 16.w, height: 16.w, child: CircularProgressIndicator( strokeWidth: 2.w, color: actionButton.textColor, ), ) : Text( actionButton.label, textAlign: TextAlign.center, style: TextStyle( color: actionButton.textColor, fontFamily: actionButton.fontFamily, fontSize: actionButton.fontSize.sp, fontStyle: FontStyle.normal, fontWeight: FontWeight.w700, height: 1, ), ), ), ), ), ], ), ), ), ], ), ); } Widget _buildVipLevelMark(int level, {double scale = 1}) { return Transform.scale( scale: scale, alignment: Alignment.centerLeft, child: Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( 'sc_images/vip/sc_vip_letter_v.png', width: 28.w, height: 24.7.w, fit: BoxFit.fill, ), Image.asset( 'sc_images/vip/sc_vip_letter_i.png', width: 14.w, height: 24.7.w, fit: BoxFit.fill, ), Image.asset( 'sc_images/vip/sc_vip_letter_p.png', width: 28.w, height: 24.7.w, fit: BoxFit.fill, ), SizedBox(width: 3.w), _buildVipLevelNumber(level), ], ), ); } Widget _buildVipLevelNumber(int level) { return Image.asset( 'sc_images/vip/sc_vip_number_${level}_filled.png', height: 25.w, fit: BoxFit.fill, color: const Color(0xFFFFD155), colorBlendMode: BlendMode.srcIn, ); } _VipOrnamentLayout _ornamentLayoutForLevel(int level) { switch (level) { case 1: return const _VipOrnamentLayout( width: 176.5, height: 36, top: -10, dx: 0, ); case 2: return const _VipOrnamentLayout( width: 246, height: 61, top: -20, dx: 0, ); case 3: return const _VipOrnamentLayout( width: 277, height: 79.5, top: -35, dx: 0, ); case 4: return const _VipOrnamentLayout( width: 240.5, height: 79.5, top: -35, dx: 0, ); case 5: default: return const _VipOrnamentLayout( width: 282.5, height: 101.5, top: -48, dx: 0, ); } } _VipActionButtonConfig _actionButtonConfigForLevel(int level) { final config = _levelConfigFor(level); if (_home?.configured == false || config?.enabled == false) { return const _VipActionButtonConfig.disabled(label: 'Unavailable'); } final activeLevel = _activeVipLevel; if (_status?.isActive == true) { if (level < activeLevel) { return const _VipActionButtonConfig.disabled(label: 'Activated'); } if (level == activeLevel) { return config?.canPurchase == true ? const _VipActionButtonConfig.renew() : const _VipActionButtonConfig.disabled(label: 'Activated'); } return config?.canPurchase == true ? const _VipActionButtonConfig.update() : const _VipActionButtonConfig.disabled(label: 'Unavailable'); } return config?.canPurchase == true ? const _VipActionButtonConfig.activate() : const _VipActionButtonConfig.disabled(label: 'Unavailable'); } bool get _canPurchaseSelected { final config = _levelConfigFor(_selectedLevel); return _home?.configured != false && config?.enabled != false && config?.canPurchase == true; } int get _activeVipLevel { final status = _status; if (status?.isActive == true) { return status!.levelInt.clamp(1, 5).toInt(); } return 0; } String get _selectedLevelStatusText { if (_home?.configured == false) { return 'Not configured'; } final config = _levelConfigFor(_selectedLevel); if (config?.enabled == false) { return 'Unavailable'; } final activeLevel = _activeVipLevel; if (activeLevel > 0) { if (_selectedLevel == activeLevel) { final expireAt = _shortDate(_status?.expireAt); return expireAt.isEmpty ? 'Activated' : 'Active until $expireAt'; } if (_selectedLevel < activeLevel) { return 'VIP$activeLevel activated'; } return 'Upgrade from VIP$activeLevel'; } return 'Not activated'; } String get _priceText { final config = _levelConfigFor(_selectedLevel); final preview = _preview?.targetLevelInt == _selectedLevel ? _preview : null; final amount = preview?.payableGold ?? config?.payableGold ?? config?.priceGold; final days = (preview?.durationDays ?? config?.durationDays ?? 30).toInt(); final amountText = amount == null ? '--' : _formatGold(amount); return '$amountText / $days Days'; } String _formatGold(num value) { final doubleValue = value.toDouble(); if (doubleValue >= 1000000) { return '${_trimNumber(doubleValue / 1000000)} M'; } if (doubleValue >= 1000) { return '${_trimNumber(doubleValue / 1000)} K'; } return _trimNumber(doubleValue); } String _trimNumber(double value) { final fixed = value >= 10 ? value.toStringAsFixed(0) : value.toStringAsFixed(1); return fixed.endsWith('.0') ? fixed.substring(0, fixed.length - 2) : fixed; } String _shortDate(String? value) { final date = value?.trim() ?? ''; if (date.length >= 10) { return date.substring(0, 10); } return date; } int get _assetLevel => _selectedLevel.clamp(1, 5).toInt(); } class _VipOrnamentLayout { const _VipOrnamentLayout({ required this.width, required this.height, required this.top, required this.dx, }); final double width; final double height; final double top; final double dx; } class _VipFeatureData { const _VipFeatureData(this.title, this.assetPath, [this.icon]); final String title; final String? assetPath; final IconData? icon; } class _VipActionButtonConfig { const _VipActionButtonConfig({ required this.label, required this.backgroundAssetPath, required this.textColor, required this.fontSize, required this.fontFamily, }); const _VipActionButtonConfig.activate() : this( label: 'Activate', backgroundAssetPath: 'sc_images/vip/sc_vip_activate_button_bg.png', textColor: const Color(0xFF7B4B16), fontSize: 14, fontFamily: null, ); const _VipActionButtonConfig.update() : this( label: 'Update', backgroundAssetPath: 'sc_images/vip/sc_vip_update_button_bg.png', textColor: const Color(0xFF740000), fontSize: 16, fontFamily: 'Source Han Sans SC', ); const _VipActionButtonConfig.renew() : this( label: 'Renew', backgroundAssetPath: 'sc_images/vip/sc_vip_activate_button_bg.png', textColor: const Color(0xFF7B4B16), fontSize: 14, fontFamily: null, ); const _VipActionButtonConfig.disabled({required String label}) : this( label: label, backgroundAssetPath: 'sc_images/vip/sc_vip_activate_button_bg.png', textColor: const Color(0xFF7B4B16), fontSize: 13, fontFamily: null, ); final String label; final String backgroundAssetPath; final Color textColor; final double fontSize; final String? fontFamily; }