import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/ui_kit/components/sc_debounce_widget.dart'; import 'package:yumi/ui_kit/theme/socialchat_theme.dart'; enum RechargeMethodType { miFaPay, nativeStore } class RechargeMethodOption { const RechargeMethodOption({ required this.type, required this.title, this.assetIconPath, this.iconData, }); final RechargeMethodType type; final String title; final String? assetIconPath; final IconData? iconData; } class RechargePackageOption { const RechargePackageOption({ required this.id, required this.coins, required this.bonusCoins, required this.priceLabel, required this.badge, }); final String id; final int coins; final int bonusCoins; final String priceLabel; final String badge; } class RechargeChannelOption { const RechargeChannelOption({ required this.code, required this.name, this.typeLabel = '', }); final String code; final String name; final String typeLabel; } class RechargeMethodBottomSheet extends StatefulWidget { const RechargeMethodBottomSheet({ super.key, required this.method, required this.packages, required this.channels, required this.onConfirm, this.initialPackageId, this.initialChannelCode, }); final RechargeMethodOption method; final List packages; final List channels; final void Function( RechargePackageOption package, RechargeChannelOption channel, ) onConfirm; final String? initialPackageId; final String? initialChannelCode; @override State createState() => _RechargeMethodBottomSheetState(); } class _RechargeMethodBottomSheetState extends State { late String _selectedPackageId; late String _selectedChannelCode; @override void initState() { super.initState(); _selectedPackageId = widget.packages.isEmpty ? '' : widget.initialPackageId ?? widget.packages.first.id; _selectedChannelCode = widget.channels.isEmpty ? '' : widget.initialChannelCode ?? widget.channels.first.code; } @override Widget build(BuildContext context) { final bool canConfirm = widget.packages.isNotEmpty && widget.channels.isNotEmpty && _selectedPackageId.isNotEmpty && _selectedChannelCode.isNotEmpty; return SafeArea( top: false, child: Container( width: ScreenUtil().screenWidth, height: MediaQuery.of(context).size.height * 0.72, padding: EdgeInsets.fromLTRB(14.w, 14.w, 14.w, 14.w), decoration: BoxDecoration( color: const Color(0xff03523a), borderRadius: BorderRadius.only( topLeft: Radius.circular(14.w), topRight: Radius.circular(14.w), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( widget.method.title, style: TextStyle( fontSize: 18.sp, color: Colors.white, fontWeight: FontWeight.w700, ), ), ), SCDebounceWidget( onTap: () => Navigator.of(context).pop(), child: Icon( CupertinoIcons.clear_thick_circled, size: 22.w, color: Colors.white.withValues(alpha: 0.86), ), ), ], ), SizedBox(height: 6.w), Text( 'Select amount', style: TextStyle( fontSize: 12.sp, color: Colors.white.withValues(alpha: 0.68), fontWeight: FontWeight.w500, ), ), SizedBox(height: 16.w), Expanded( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.packages.isEmpty) _buildEmptyHint('MiFaPay products are loading') else GridView.builder( itemCount: widget.packages.length, padding: EdgeInsets.zero, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 10.w, crossAxisSpacing: 10.w, mainAxisExtent: 136.w, ), itemBuilder: (BuildContext context, int index) { final RechargePackageOption item = widget.packages[index]; final bool isSelected = item.id == _selectedPackageId; return _buildPackageCard(item, index, isSelected); }, ), SizedBox(height: 16.w), Text( 'Payment channel', style: TextStyle( fontSize: 12.sp, color: Colors.white.withValues(alpha: 0.68), fontWeight: FontWeight.w500, ), ), SizedBox(height: 10.w), if (widget.channels.isEmpty) _buildEmptyHint('No channel available') else Wrap( spacing: 10.w, runSpacing: 10.w, children: widget.channels .map(_buildChannelChip) .toList(growable: false), ), ], ), ), ), SizedBox(height: 8.w), SCDebounceWidget( onTap: () { if (!canConfirm) { return; } _confirmSelection(); }, child: Container( width: double.infinity, height: 42.w, alignment: Alignment.center, decoration: BoxDecoration( color: canConfirm ? SCGlobalConfig.businessLogicStrategy .getRechargePageButtonBackgroundColor() : Colors.white.withValues(alpha: 0.18), borderRadius: BorderRadius.circular(10.w), ), child: Text( 'Confirm', style: TextStyle( fontSize: 15.sp, color: SCGlobalConfig.businessLogicStrategy .getRechargePageButtonTextColor(), fontWeight: FontWeight.w700, ), ), ), ), ], ), ), ); } void _confirmSelection() { if (widget.packages.isEmpty || widget.channels.isEmpty) { return; } final RechargePackageOption selected = widget.packages.firstWhere( (RechargePackageOption item) => item.id == _selectedPackageId, orElse: () => widget.packages.first, ); final RechargeChannelOption selectedChannel = widget.channels.firstWhere( (RechargeChannelOption item) => item.code == _selectedChannelCode, orElse: () => widget.channels.first, ); widget.onConfirm(selected, selectedChannel); Navigator.of(context).pop(); } Widget _buildPackageCard( RechargePackageOption item, int index, bool isSelected, ) { return SCDebounceWidget( onTap: () { setState(() { _selectedPackageId = item.id; }); }, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10.w), border: Border.all( color: isSelected ? SocialChatTheme.primaryLight : Colors.transparent, width: 1.4, ), ), child: Column( children: [ SizedBox( height: 24.w, child: item.badge.isEmpty ? null : Align( alignment: Alignment.topRight, child: Container( margin: EdgeInsets.only(top: 8.w, right: 8.w), padding: EdgeInsets.symmetric( horizontal: 7.w, vertical: 2.w, ), decoration: BoxDecoration( color: SocialChatTheme.primaryLight.withValues( alpha: 0.14, ), borderRadius: BorderRadius.circular(999.w), ), child: Text( item.badge, style: TextStyle( fontSize: 9.sp, color: const Color(0xff03523a), fontWeight: FontWeight.w700, ), ), ), ), ), Image.asset( SCGlobalConfig.businessLogicStrategy .getRechargePageGoldIconByIndex(index), width: 38.w, height: 38.w, ), SizedBox(height: 6.w), Padding( padding: EdgeInsets.symmetric(horizontal: 6.w), child: Text( _formatWholeNumber(item.coins), maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: TextStyle( fontSize: 15.sp, color: Colors.black.withValues(alpha: 0.82), fontWeight: FontWeight.w700, ), ), ), SizedBox(height: 2.w), Padding( padding: EdgeInsets.symmetric(horizontal: 6.w), child: Text( item.bonusCoins > 0 ? '+${_formatWholeNumber(item.bonusCoins)}' : 'No bonus', maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: TextStyle( fontSize: 11.sp, color: item.bonusCoins > 0 ? SocialChatTheme.primaryLight : Colors.black.withValues(alpha: 0.45), fontWeight: FontWeight.w600, ), ), ), const Spacer(), Container( width: double.infinity, height: 26.w, alignment: Alignment.center, decoration: BoxDecoration( color: const Color(0xffF5C550), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(10.w), bottomRight: Radius.circular(10.w), ), ), child: Text( item.priceLabel, style: TextStyle( fontSize: 11.sp, color: const Color(0xff6F4B00), fontWeight: FontWeight.w700, ), ), ), ], ), ), ); } Widget _buildChannelChip(RechargeChannelOption option) { final bool isSelected = option.code == _selectedChannelCode; return SCDebounceWidget( onTap: () { setState(() { _selectedChannelCode = option.code; }); }, child: AnimatedContainer( duration: const Duration(milliseconds: 180), padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w), decoration: BoxDecoration( color: isSelected ? Colors.white.withValues(alpha: 0.16) : Colors.white.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(8.w), border: Border.all( color: isSelected ? SocialChatTheme.primaryLight : Colors.white.withValues(alpha: 0.14), width: 1.1, ), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( option.name, style: TextStyle( fontSize: 13.sp, color: Colors.white, fontWeight: FontWeight.w700, ), ), if (option.typeLabel.isNotEmpty) ...[ SizedBox(height: 2.w), Text( option.typeLabel, style: TextStyle( fontSize: 10.sp, color: Colors.white.withValues(alpha: 0.62), fontWeight: FontWeight.w500, ), ), ], ], ), ), ); } Widget _buildEmptyHint(String message) { return Container( width: double.infinity, padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 14.w), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(10.w), ), child: Text( message, textAlign: TextAlign.center, style: TextStyle( fontSize: 12.sp, color: Colors.white.withValues(alpha: 0.68), fontWeight: FontWeight.w500, ), ), ); } String _formatWholeNumber(int value) { final String raw = value.toString(); final StringBuffer buffer = StringBuffer(); for (int i = 0; i < raw.length; i++) { final int position = raw.length - i; buffer.write(raw[i]); if (position > 1 && position % 3 == 1) { buffer.write(','); } } return buffer.toString(); } }