import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:provider/provider.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/main.dart'; import 'package:yumi/modules/wallet/recharge/recharge_page.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/services/payment/google_payment_manager.dart'; import 'package:yumi/shared/tools/sc_loading_manager.dart'; import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart'; import 'package:yumi/ui_kit/components/sc_debounce_widget.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; class RoomRechargeBottomSheet extends StatefulWidget { const RoomRechargeBottomSheet({super.key}); static const String dialogTag = 'showRoomRechargeBottomSheet'; static void show(BuildContext context) { SmartDialog.dismiss(tag: dialogTag); SmartDialog.show( tag: dialogTag, alignment: Alignment.bottomCenter, animationType: SmartAnimationType.fade, maskColor: Colors.black.withValues(alpha: 0.6), clickMaskDismiss: true, builder: (_) => const SCRoomTopLayerSuppressor( reason: 'room_recharge_bottom_sheet', child: RoomRechargeBottomSheet(), ), ); } @override State createState() => _RoomRechargeBottomSheetState(); } class _RoomRechargeBottomSheetState extends State { static const Color _sheetColor = Color(0xFF072121); static const Color _cardColor = Color(0xFF011414); static const Color _accentColor = Color(0xFF18F2B1); static const String _coinAsset = 'sc_images/room/sc_room_recharge_modal_coin.png'; int _fallbackSelectedIndex = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { _initializeRecharge(); } }); } void _initializeRecharge() { final paymentContext = navigatorKey.currentState?.context ?? context; Provider.of( paymentContext, listen: false, ).balance(); if (!Platform.isAndroid) { return; } final processor = Provider.of( paymentContext, listen: false, ); if (processor.products.isEmpty) { processor.initializePaymentProcessor(paymentContext); } } @override Widget build(BuildContext context) { return SizedBox( width: ScreenUtil().screenWidth, height: 306.w, child: Stack( children: [ Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( color: _sheetColor, borderRadius: BorderRadius.vertical(top: Radius.circular(12.w)), ), ), ), Positioned( top: 0, left: 0, right: 0, child: Container( height: 24.w, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.vertical(top: Radius.circular(12.w)), ), ), ), PositionedDirectional( top: 7.w, start: 16.w, child: Text( 'Your Balance Is Low. Please Add Funds.', style: TextStyle( color: Colors.white, fontSize: 10.sp, height: 1, fontWeight: FontWeight.w400, decoration: TextDecoration.none, ), ), ), Positioned( top: 36.w, left: 0, right: 0, child: Center( child: Text( 'Payment Methods', style: TextStyle( color: Colors.white, fontSize: 18.sp, height: 1, fontWeight: FontWeight.w500, decoration: TextDecoration.none, ), ), ), ), PositionedDirectional( top: 74.w, start: 16.w, end: 16.w, child: _buildPaymentMethodRow(), ), PositionedDirectional( top: 126.w, start: 16.w, end: 16.w, child: _buildProductOptions(), ), PositionedDirectional( top: 222.w, start: 24.w, end: 24.w, child: _buildConfirmButton(), ), ], ), ); } Widget _buildPaymentMethodRow() { return Container( height: 40.w, padding: EdgeInsets.symmetric(horizontal: 8.w), decoration: BoxDecoration( color: _cardColor, borderRadius: BorderRadius.circular(8.w), ), child: Row( children: [ SizedBox( width: 28.w, height: 28.w, child: Center( child: Text( 'G', style: TextStyle( color: Colors.white, fontSize: 22.sp, height: 1, fontWeight: FontWeight.w700, decoration: TextDecoration.none, ), ), ), ), SizedBox(width: 6.w), Expanded( child: Text( 'Google Pay', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: Colors.white, fontSize: 12.sp, height: 1, fontWeight: FontWeight.w400, decoration: TextDecoration.none, ), ), ), _buildSelectedRadio(), SizedBox(width: 7.w), ], ), ); } Widget _buildSelectedRadio() { return Container( width: 16.w, height: 16.w, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 1.w), ), alignment: Alignment.center, child: Container( width: 10.w, height: 10.w, decoration: const BoxDecoration( color: _accentColor, shape: BoxShape.circle, ), ), ); } Widget _buildProductOptions() { if (!Platform.isAndroid) { return Row( children: [ Expanded(child: _buildFallbackProductItem(0)), SizedBox(width: 13.w), Expanded(child: _buildFallbackProductItem(1)), ], ); } return Consumer( builder: (context, processor, child) { final products = processor.products.take(2).toList(); if (products.isEmpty) { return Row( children: [ Expanded(child: _buildFallbackProductItem(0)), SizedBox(width: 13.w), Expanded(child: _buildFallbackProductItem(1)), ], ); } return Row( children: [ for (int index = 0; index < products.length; index++) ...[ if (index > 0) SizedBox(width: 13.w), Expanded( child: _buildStoreProductItem( processor: processor, productConfig: products[index], index: index, ), ), ], if (products.length == 1) ...[ SizedBox(width: 13.w), const Spacer(), ], ], ); }, ); } Widget _buildFallbackProductItem(int index) { return _RechargeProductCard( selected: index == _fallbackSelectedIndex, amount: '100,000', price: 'USD 4.99', onTap: () => setState(() => _fallbackSelectedIndex = index), ); } Widget _buildStoreProductItem({ required AndroidPaymentProcessor processor, required SelecteProductConfig productConfig, required int index, }) { final product = processor.productMap[productConfig.produc.id]; return _RechargeProductCard( selected: productConfig.isSelecte, amount: _formatAmount((product?.obtainCandy ?? 0).round()), price: productConfig.produc.price, onTap: () => processor.chooseProductConfig(index), ); } String _formatAmount(int value) { final sign = value < 0 ? '-' : ''; final digits = value.abs().toString(); final buffer = StringBuffer(); for (int index = 0; index < digits.length; index++) { final remaining = digits.length - index; buffer.write(digits[index]); if (remaining > 1 && remaining % 3 == 1) { buffer.write(','); } } return '$sign$buffer'; } Widget _buildConfirmButton() { return SCDebounceWidget( onTap: _processGooglePurchase, child: Container( height: 44.w, alignment: Alignment.center, decoration: BoxDecoration( color: _accentColor, borderRadius: BorderRadius.circular(22.w), ), child: Text( 'Confirm Top-Up', style: TextStyle( color: Colors.black, fontSize: 20.sp, height: 1, fontWeight: FontWeight.w500, decoration: TextDecoration.none, ), ), ), ); } Future _processGooglePurchase() async { if (!Platform.isAndroid) { SCTts.show('Google Play payment is only available on Android'); return; } final pleaseSelectMessage = SCAppLocalizations.of(context)!.pleaseSelectaItem; final paymentContext = navigatorKey.currentState?.context ?? context; final processor = Provider.of( paymentContext, listen: false, ); if (processor.products.isEmpty) { await processor.initializePaymentProcessor(paymentContext); if (!mounted) { return; } } final product = _selectedProduct(processor.products); if (product == null) { SCTts.show(pleaseSelectMessage); return; } try { await processor.recoverTransactions(); SCLoadingManager.show(); final success = await processor.iap.buyConsumable( purchaseParam: PurchaseParam(productDetails: product.produc), autoConsume: false, ); if (!success) { SCTts.show('Purchase failed, please check your network connection'); } } catch (error) { SCTts.show('Purchase failed: $error'); } finally { SCLoadingManager.hide(); } } SelecteProductConfig? _selectedProduct(List products) { for (final product in products) { if (product.isSelecte) { return product; } } return products.isEmpty ? null : products.first; } } class _RechargeProductCard extends StatelessWidget { const _RechargeProductCard({ required this.selected, required this.amount, required this.price, required this.onTap, }); final bool selected; final String amount; final String price; final VoidCallback onTap; @override Widget build(BuildContext context) { return SCDebounceWidget( onTap: onTap, child: Container( height: 72.w, clipBehavior: Clip.antiAlias, decoration: BoxDecoration( color: _RoomRechargeBottomSheetState._cardColor, borderRadius: BorderRadius.circular(8.w), border: selected ? Border.all( color: _RoomRechargeBottomSheetState._accentColor, width: 1.w, ) : null, ), child: Column( children: [ Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( _RoomRechargeBottomSheetState._coinAsset, width: 16.w, height: 16.w, fit: BoxFit.contain, ), SizedBox(width: 2.w), Flexible( child: Text( amount, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: Colors.white, fontSize: 16.sp, height: 1, fontWeight: FontWeight.w500, decoration: TextDecoration.none, ), ), ), ], ), ], ), ), Container( height: 20.w, alignment: Alignment.center, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.08), borderRadius: BorderRadius.vertical( bottom: Radius.circular(8.w), ), ), child: Text( price, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: Colors.white, fontSize: 12.sp, height: 1, fontWeight: FontWeight.w400, decoration: TextDecoration.none, ), ), ), ], ), ), ); } }