import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:gen_service/Screens/TransactionScreens/BillDetailScreen.dart'; import 'package:gen_service/Screens/TransactionScreens/PaymentDetails.dart'; import 'package:provider/provider.dart'; import 'package:razorpay_flutter/razorpay_flutter.dart'; import '../../Models/TransactionModels/TransactionListResponse.dart'; import '../../Notifiers/PayAmountProvider.dart'; import '../../Notifiers/TransactionsProvider.dart'; import '../../Utility/AppColors.dart'; import '../../Utility/CustomSnackbar.dart'; import '../../Utility/SharedpreferencesService.dart'; class TransactionListScreen extends StatefulWidget { final String accId; final String sessionId; const TransactionListScreen({ required this.accId, required this.sessionId, super.key, }); @override State createState() => _TransactionScreenState(); } class _TransactionScreenState extends State { bool _stretch = true; late Razorpay _razorpay; bool? isSuccess; var paymentMethod = ""; var User_contact = "0"; final ScrollController _scrollController = ScrollController(); final prefs = SharedPreferencesService.instance; @override void initState() { super.initState(); _razorpay = Razorpay(); // Initial data load Future.microtask(() { Provider.of(context, listen: false) .fetchTransactions(widget.accId, widget.sessionId); }); // Setup scroll listener for pagination _scrollController.addListener(_scrollListener); } //_________________________________________________________ void _handlePaymentSuccess(PaymentSuccessResponse response) { setState(() async { final provider = Provider.of(context, listen: false); await provider.getPaymentStatus( sessionId: widget.sessionId, empId: widget.accId, razorpayOrderId: response.orderId.toString() ); final data = provider.statusResponse; // Navigator.push( // context, // MaterialPageRoute(builder: (context) => PaymentSuccessFaillScreen( // total: "${data?.amount}", // date: "${data?.date}", // payMode: "UPI", // status: "Success", // )), // ); _razorpay.clear(); CustomSnackBar.showSuccess( context: context, message: data?.message ?? "Payment Success!", ); // buttonLoading = false; }); } void _handlePaymentError(PaymentFailureResponse response) { setState(() async { CustomSnackBar.showError( context: context, message: "Payment failed, please try again.", ); }); _razorpay.clear(); CustomSnackBar.showError( context: context, message: "Payment failed, please try again.", ); } void _handleExternalWallet(ExternalWalletResponse response) { _razorpay.clear(); } Future payAmountFunction(String amount) async { try { final provider = Provider.of(context, listen: false); await provider.payAmount( sessionId: widget.sessionId, empId: widget.accId, amount: amount, refType: "Payment", refId: "1", ); final data = provider.payResponse; if (data != null) { if (data.error == "0") { openCheckout(data.orderId, data.razorKey!); } else { CustomSnackBar.showError( context: context, message: "${data.message}", ); debugPrint("❌ Could not Complete Payment: ${data.message}"); } } else { debugPrint("❌ No response received from PayAmount API"); } } catch (e) { debugPrint("❌ 'Error occurred: $e'"); } } //razorpay payments__________________________________________________________ void openCheckout(razorPayOrderId, String razorpayKey) async { final String? mobNumber = await prefs.getString("mob_number"); _razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess); _razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError); _razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet); // _buildCheckWidget(); Map options = { 'key': razorpayKey, 'amount': int.parse("${((0) * 100).round()}"), 'name': 'Gen Service', 'order_id': razorPayOrderId, 'description': "Bill", 'currency': 'INR', 'method': 'upi', 'prefill': {'contact': mobNumber, 'email': ''} }; // print(options); try { _razorpay.open(options); } catch (e, s) { // FirebaseCrashlytics.instance.log('Error occurred: $e'); // FirebaseCrashlytics.instance.recordError(e, s); debugPrint(e.toString()); } } void verifyPayment(String orderId) { isSuccess = true; setState(() { // toast(context, "Order Placed Successfully"); // print("Verify Payment"); }); _razorpay.clear(); } // void onError(CFErrorResponse errorResponse, String orderId) { // isSuccess = false; // setState(() { // // print(errorResponse.getMessage()); // // print("Error while making payment"); // }); // } @override void dispose() { _scrollController.dispose(); super.dispose(); } void _scrollListener() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { _loadMore(); } } void _loadMore() { final provider = Provider.of(context, listen: false); if (!provider.isLoadingMore && provider.hasMore) { provider.loadMoreTransactions(widget.accId, widget.sessionId); } } @override Widget build(BuildContext context) { final provider = Provider.of(context); final balance = provider.transactionList?.balanceAmount ?? "0"; final balanceType = provider.transactionList?.balanceType ?? "Pending"; final lastPaid = provider.transactionList?.lastPaid; final totalCredit = provider.transactionList?.totalCredit ?? "0"; final totalDebit = provider.transactionList?.totalDebit ?? "0"; final transactions = provider.transactionList?.transactions ?? {}; bool isPending = balanceType.toLowerCase() == "pending balance"; return RefreshIndicator.adaptive( color: AppColors.amountText, onRefresh: () async { await Future.delayed(const Duration(milliseconds: 600)); Provider.of(context, listen: false) .fetchTransactions(widget.accId, widget.sessionId); }, child: Scaffold( backgroundColor: AppColors.backgroundRegular, body: CustomScrollView( controller: _scrollController, // Add controller here physics: const BouncingScrollPhysics(), slivers: [ /// Top SliverAppBar (balance card) - No changes SliverAppBar( stretch: _stretch, pinned: true, expandedHeight: isPending ? 252 : 210, backgroundColor: isPending ? AppColors.errorBg : Color(0xFF4076FF), elevation: 0, leading: Container(), toolbarHeight: 0, collapsedHeight: 0, flexibleSpace: FlexibleSpaceBar( stretchModes: const [StretchMode.zoomBackground], background: Container( decoration: BoxDecoration( gradient: isPending ? AppColors.balanceBarGradientP : AppColors.balanceBarGradientA, ), child: SafeArea( child: Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ SizedBox(width: 8,), InkResponse( onTap: () => Navigator.pop(context, true), child: SvgPicture.asset( color: Colors.white, "assets/svg/continue_left_ic.svg", height: 30, ), ), const SizedBox(width: 10), const Text( "Transactions", style: TextStyle( fontSize: 16, fontFamily: "Poppins", fontWeight: FontWeight.w400, color: Colors.white, ), ), ], ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 20), Text( "₹$balance", style: const TextStyle( color: Colors.white, fontSize: 34, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 2), Text( "$balanceType", style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w400), ), if(isPending == false) const SizedBox(height: 4), if(isPending == false) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.check_circle, color: Colors.green, size: 20, ), SizedBox(width: 2,), Text( "Last Paid on $lastPaid.", maxLines: 1, style: TextStyle( color: const Color(0xFeFFFFFF), fontSize: 12, fontWeight: FontWeight.w400, ), ), ], ), const SizedBox(height: 10), if (isPending) ElevatedButton( onPressed: () =>_openPaymentSheet(context, balance), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors.deepOrange, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), ), child: Text( "Pay Now", style: TextStyle( fontSize: 14, fontFamily: "Poppins", fontWeight: FontWeight.w500, color: AppColors.buttonColor, ), ), ), SizedBox(height: 8,), if (isPending) Text( "*Make sure to pay before you incur any fines.", maxLines: 1, style: TextStyle( color: const Color(0xAAFFFFFF), fontSize: 12, fontWeight: FontWeight.w400, ), ), ], ), ), ], ), ), ), ), ), /// Summary Row and Transaction List SliverToBoxAdapter( child: Container( color: isPending ? AppColors.errorBg : Color(0xFF4076FF), child: Container( decoration: const BoxDecoration( color: AppColors.backgroundRegular, borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), // Summary Card Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFD7F0FF), borderRadius: BorderRadius.circular(16), border: Border.all(width: 1.1, color: AppColors.buttonColor), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildSummaryItem("₹$totalCredit", "Bill Paid", Colors.green), Container(height: 30, width: 1, color: Colors.grey.shade300), _buildSummaryItem("₹$totalDebit", "Bill Pending", Colors.red), ], ), ), // Transaction List _buildTransactionList(provider, transactions), ], ), ), ), ), /// Loading indicator for pagination if (provider.isLoadingMore) const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.all(16.0), child: Center( child: CircularProgressIndicator(color: AppColors.buttonColor), ), ), ), /// No more data indicator if (!provider.hasMore && transactions.isNotEmpty) const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.all(16.0), child: Center( child: Text( "No more transactions", style: TextStyle( color: Colors.grey, fontSize: 14, ), ), ), ), ), ], ), ), ); } Widget _buildTransactionList(TransactionsProvider provider, Map> transactions) { if (provider.isLoading && transactions.isEmpty) { return const Padding( padding: EdgeInsets.all(16.0), child: Center(child: CircularProgressIndicator(color: AppColors.buttonColor)), ); } if (transactions.isEmpty && !provider.isLoading) { return const Padding( padding: EdgeInsets.all(16.0), child: Center( child: Text( "No transactions found", style: TextStyle(color: Colors.grey, fontSize: 16), ), ), ); } return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: transactions.length, itemBuilder: (context, index) { final month = transactions.keys.elementAt(index); final items = transactions[month]!; return Container( color: AppColors.backgroundRegular, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( month, style: const TextStyle( fontFamily: "Poppins", fontWeight: FontWeight.w500, fontSize: 14, ), ), const SizedBox(height: 8), ...items.map((item) { final type = item.atype ?? "Debit"; final title = item.narration ?? "No details"; final date = item.datetime ?? ""; final id = item.id ?? ""; final amount = type.toLowerCase() == "credit" ? "₹${item.cAmount ?? "0"}" : "₹${item.dAmount ?? "0"}"; return InkResponse( onTap: () { if (item.atype == "Credit") { showDialog( context: context, builder: (context) => PaymentdetailDialog( sessionId: widget.sessionId, accId: widget.accId, billId: item.billId.toString(), ), ); } else { Navigator.push( context, MaterialPageRoute( builder: (context) => BillDetailScreen( sessionId: widget.sessionId, accId: widget.accId, billId: item.billId.toString(), ), ), ); } }, child: _buildTransactionItem( type: type, title: title, id: id, date: date, amount: amount, ), ); }).toList(), ], ), ); }, ); } /// Summary Card Item Widget _buildSummaryItem(String value, String label, Color color) { return Row( children: [ CircleAvatar( radius: 20, backgroundColor: Colors.white, child: SvgPicture.asset( label== "Bill Paid" ? "assets/svg/cross_up_arrow.svg" : "assets/svg/cross_down_arrow.svg", height: 18, ), ), SizedBox(width: 6,), Column( children: [ Text( value, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: color, ), ), const SizedBox(height: 4), Text( label, style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ], ); } /// Transaction Item Widget _buildTransactionItem({ required String type, required String title, required String id, required String date, required String amount, }) { final isCredit = type.toLowerCase() == "credit"; return Container( margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Row( children: [ CircleAvatar( radius: 20, backgroundColor: isCredit ? Color(0xFFE7FFE5) : Color(0xffFFEFEF), child: SvgPicture.asset( color: isCredit ? Color(0xff4CAF50) : Color(0xFFF00000), isCredit ? "assets/svg/cross_up_arrow.svg" : "assets/svg/cross_down_arrow.svg", height: 18, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( "#$id | ", style: const TextStyle( fontFamily: "Poppins", fontSize: 12, fontWeight: FontWeight.w400, color: Colors.grey, ), ), Text( date, style: const TextStyle( fontFamily: "Poppins", fontSize: 12, fontWeight: FontWeight.w400, color: Colors.grey, ), ), ], ), const SizedBox(height: 4), Text( title, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, ), ), ], ), ), const SizedBox(width: 12), Text( amount, style: TextStyle( fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w400, color: isCredit ? Colors.green : Colors.red, ), ), ], ), ); } // payment sheet void _openPaymentSheet(BuildContext context, String totalAmountStr) { TextEditingController amountController = TextEditingController(); bool isPartPayment = false; final double totalAmount = double.tryParse(totalAmountStr) ?? 0; showModalBottomSheet( isScrollControlled: true, backgroundColor: Colors.white, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), context: context, builder: (context) { return StatefulBuilder( builder: (context, setState) { return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, top: 16, left: 16, right: 16, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Handle Bar Center( child: Container( width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(4), ), ), ), const SizedBox(height: 16), // Title const Text( "Balance Amount Bill", style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, fontFamily: "Poppins", ), ), const SizedBox(height: 6), Divider(), const SizedBox(height: 10), // Pay Total Option GestureDetector( onTap: () { setState(() => isPartPayment = false); }, child: Row( children: [ Radio( value: false, groupValue: isPartPayment, onChanged: (v) => setState(() => isPartPayment = v!), activeColor: const Color(0xFF008CDE), ), // Radio( // value: false, // groupValue: isPartPayment, // onChanged: (v) => setState(() => isPartPayment = v!), // ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Pay Total", style: TextStyle( fontSize: 14, fontFamily: "Poppins", fontWeight: FontWeight.w400, ), ), Text( "Avoid late payment fees.", style: TextStyle( color: Color(0xff5FBB54), fontSize: 12, fontFamily: "Poppins", ), ), ], ), const Spacer(), Text( "₹${totalAmount.toStringAsFixed(0)}", style: const TextStyle( fontSize: 14, fontFamily: "Poppins", fontWeight: FontWeight.w400, ), ), ], ), ), const SizedBox(height: 10), // Part Payment Option GestureDetector( onTap: () { setState(() => isPartPayment = true); }, child: Row( children: [ Radio( value: true, groupValue: isPartPayment, onChanged: (v) => setState(() => isPartPayment = v!), activeColor: const Color(0xFF008CDE), ), const Text( "Part Payment", style: TextStyle( fontSize: 14, fontFamily: "Poppins", fontWeight: FontWeight.w400, ), ), const SizedBox(width: 24), Expanded( child: Container( height: 50, alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(12), ), child: TextFormField( controller: amountController, enabled: isPartPayment, style: const TextStyle( fontSize: 14, fontFamily: "Poppins", color: Colors.black87, ), keyboardType: TextInputType.number, decoration: const InputDecoration( hintText: "Enter amount", hintStyle: TextStyle( fontSize: 14, fontFamily: "Poppins", color: Colors.grey, ), border: InputBorder.none, ), ), ), ), ], ), ), const SizedBox(height: 6), Divider(), const SizedBox(height: 6), // Continue Button SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { double enteredAmount = isPartPayment ? double.tryParse(amountController.text) ?? 0 : totalAmount; if (enteredAmount <= 0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Please enter a valid amount"), ), ); return; } if (isPartPayment && enteredAmount > totalAmount) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( "Entered amount cannot exceed total amount", ), ), ); return; } Navigator.pop(context); // Pass selected amount to your payAmountFunction payAmountFunction(enteredAmount.toStringAsFixed(2)); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF008CDE), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), padding: const EdgeInsets.symmetric(vertical: 16), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 22), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Continue Payment", style: TextStyle( fontFamily: "Poppins", color: Colors.white, fontSize: 16, ), ), SvgPicture.asset( "assets/svg/continue_ic.svg", color: Colors.white, height: 25, width: 25, ), ], ), ), ), ), const SizedBox(height: 16), ], ), ); }, ); }, ); } }