import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:gen_rentals/Screens/HelpScreens/EnquiryScreen.dart'; import 'package:gen_rentals/Screens/HelpScreens/HelpScreen.dart'; import 'package:gen_rentals/Screens/ProductsDetailScreen.dart'; import 'package:gen_rentals/Screens/ProfileScreen.dart'; import 'package:gen_rentals/Screens/TransactionScreens/TransactionsScreen.dart'; import 'package:gen_rentals/Utility/AppColors.dart'; import 'package:gen_rentals/Utility/CustomSnackbar.dart'; import 'package:gen_rentals/Utility/Reusablewidgets.dart'; import 'package:provider/provider.dart'; import '../Models/DashboardResponse.dart'; import '../Notifier/DashboardProvider.dart'; import '../Notifier/PayAmountProvider.dart'; import 'TransactionScreens/PaymentSuccessfailScreen.dart'; import 'authScreen/LoginScreen.dart'; import 'package:razorpay_flutter/razorpay_flutter.dart'; class DashboardScreen extends StatefulWidget { final String accId; final String sessionId; const DashboardScreen({super.key, required this.accId, required this.sessionId}); @override State createState() => _DashboardScreenState(); } class _DashboardScreenState extends State with WidgetsBindingObserver { DateTime? currentBackPressTime; late Razorpay _razorpay; bool? isSuccess; var paymentMethod = ""; var User_contact = "0"; @override void initState() { super.initState(); _razorpay = Razorpay(); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { _checkSessionAndFetchData(); }); } 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 { final provider = Provider.of(context, listen: false); await provider.getPaymentStatus( sessionId: widget.sessionId, empId: widget.accId, razorpayOrderId: "" ); final data = provider.statusResponse; Navigator.push( context, MaterialPageRoute(builder: (context) => PaymentSuccessFaillScreen( total: "${data?.amount}", date: "${data?.date}", payMode: "UPI", status: "Fail", )), ); 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 { _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 Rentals', 'order_id': razorPayOrderId, 'description': "Payment", 'currency': 'INR', 'method': 'upi', 'prefill': {'contact': User_contact, '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() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { // App is going to background } } // Handle back button press Future _onWillPop() async { DateTime now = DateTime.now(); if (currentBackPressTime == null || now.difference(currentBackPressTime!) > Duration(seconds: 2)) { currentBackPressTime = now; CustomSnackBar.showExit( context: context, title: "Exit", message: 'Press back again to exit' ); return false; } // Close the entire app immediately exit(0); } // Responsive text size function double getResponsiveTextSize(BuildContext context, double baseSize) { final double width = MediaQuery.of(context).size.width; if (width < 360) { // Small phones return baseSize * 0.85; } else if (width < 400) { // Medium phones return baseSize; } else { // Large phones return baseSize * 1.1; } } // Responsive padding function double getResponsivePadding(BuildContext context) { final double width = MediaQuery.of(context).size.width; return width * 0.04; } // Responsive icon size function double getResponsiveIconSize(BuildContext context, double baseSize) { final double width = MediaQuery.of(context).size.width; if (width < 360) { return baseSize * 0.8; } else if (width < 400) { return baseSize; } else { return baseSize * 1.2; } } void _checkSessionAndFetchData() async { if (widget.accId.isEmpty || widget.accId == "null") { _navigateToLogin(); return; } final dashboardProvider = Provider.of(context, listen: false); try { await dashboardProvider.fetchDashboard(accId: widget.accId, sessionId: widget.sessionId); if (dashboardProvider.dashboardData?.sessionExists == 0) { _navigateToLogin(); return; } if (dashboardProvider.errorMessage!.toLowerCase().contains('session') || dashboardProvider.errorMessage!.toLowerCase().contains('unauthorized')) { _navigateToLogin(); return; } } catch (e) { if (e.toString().toLowerCase().contains('session') || e.toString().toLowerCase().contains('unauthorized') || e.toString().toLowerCase().contains('401')) { _navigateToLogin(); return; } } } void _navigateToLogin() { WidgetsBinding.instance.addPostFrameCallback((_) { Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => const LoginScreen()), (route) => false, ); }); } @override Widget build(BuildContext context) { final dashboardProvider = Provider.of(context); final dashboardData = dashboardProvider.dashboardData; final screenWidth = MediaQuery.of(context).size.width; final screenHeight = MediaQuery.of(context).size.height; final isSmallScreen = screenWidth < 360; if (dashboardProvider.isLoading && dashboardData == null) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: screenHeight * 0.02), Text( "Loading dashboard...", style: TextStyle(fontSize: getResponsiveTextSize(context, 14)), ), ], ), ), ); } return WillPopScope( onWillPop: _onWillPop, child: SafeArea( top: false, child: RefreshIndicator.adaptive( color: AppColors.subtitleText, onRefresh: () async { await Future.delayed(const Duration(milliseconds: 600)); final dashboardProvider = Provider.of(context, listen: false); dashboardProvider.fetchDashboard(sessionId: widget.sessionId, accId: widget.accId); }, child: Scaffold( body: Container( color: const Color(0xFFF3F3F3), height: screenHeight, child: SingleChildScrollView( child: Column( children: [ // Top background image section Stack( children: [ // Background image Container( width: double.infinity, decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.white54, Color(0xFFF3F3F3), ], ), ), child: Image.asset( 'assets/images/sky_blue_bg.jpg', width: double.infinity, height: screenHeight * 0.5, fit: BoxFit.cover, ), ), // Content overlay Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header with profile Container( width: double.infinity, height: screenHeight * 0.51, decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0x22FFFFFF), Color(0x33FFFFFF), Color(0x88FFFFFF), Color(0xFFF3F3F3), Color(0xFFF3F3F3), ], ), ), child: Column( children: [ SizedBox(height: screenHeight * 0.09), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ InkResponse( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => ProfileScreen( sessionId: widget.sessionId, accId: widget.accId, )), ); }, child: CircleAvatar( radius: getResponsiveIconSize(context, 20), backgroundColor: Color(0xDDE0F4FF), child: SvgPicture.asset( height: getResponsiveIconSize(context, 20), "assets/svg/person_ic.svg", fit: BoxFit.contain, ), ), ), SizedBox(width: screenWidth * 0.05), ], ), Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start, children: [ Image.asset( 'assets/images/dashboard_gen.png', height: screenHeight * 0.25, width: screenWidth * 0.6, ), SizedBox(height: 5,), Text( "Welcome!", style: TextStyle( fontFamily: "Poppins", color: Colors.black, fontSize: getResponsiveTextSize(context, 24), fontWeight: FontWeight.w500, ), ), Text( dashboardData?.raname ?? "Loading...", style: TextStyle( fontFamily: "Poppins", color: Colors.grey, fontSize: getResponsiveTextSize(context, 16), fontWeight: FontWeight.w400, ), ), ], ), ], ), ), // Main content section Container( padding: EdgeInsets.symmetric(horizontal: getResponsivePadding(context)), child: Column( children: [ // Balance Amount Card Container( width: double.infinity, padding: EdgeInsets.all(screenWidth * 0.048), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(screenWidth * 0.038), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InkResponse( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => TransactionsScreen( sessionId: widget.sessionId, accId: widget.accId, )), ); }, child: Row( children: [ Text( "Balance Amount", style: TextStyle( fontFamily: "Poppins", color: Color(0xFFF00000), fontSize: getResponsiveTextSize(context, 13.5), fontWeight: FontWeight.w400, ), ), SizedBox(width: screenWidth * 0.01), SvgPicture.asset( "assets/svg/continue_ic.svg", color: const Color(0xFFF00000), height: getResponsiveIconSize(context, 16), width: getResponsiveIconSize(context, 16), ), ], ), ), SizedBox(height: screenHeight * 0.01), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "₹", style: TextStyle( color: Colors.black, fontSize: getResponsiveTextSize(context, 20), height: 2, fontWeight: FontWeight.w500, ), ), Text( dashboardData?.balanceAmount?.toString() ?? "0", style: TextStyle( color: Colors.black, fontSize: getResponsiveTextSize(context, 34), fontWeight: FontWeight.w500, ), ), ], ), if(dashboardData?.balanceAmount?.toString()!="0") InkResponse( onTap: () => _openPaymentSheet(context, dashboardData!.balanceAmount.toString()), child: Text( "Pay Now", style: TextStyle( color: Colors.blue, fontFamily: "Poppins", fontSize: getResponsiveTextSize(context, 14), fontWeight: FontWeight.w500, ), ), ), ], ), SizedBox(height: screenHeight * 0.015), Text( "*Make sure to pay before you incur any fines.", style: TextStyle( fontFamily: "Poppins", color: Colors.grey.shade500, fontSize: getResponsiveTextSize(context, 11), fontStyle: FontStyle.normal, fontWeight: FontWeight.w400, ), ), ], ), ), SizedBox(height: screenHeight * 0.025), // Subscribed Orders Container( width: double.infinity, padding: EdgeInsets.symmetric(horizontal: screenWidth * 0.0009, vertical: screenHeight * 0.012), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SectionHeading( title: "Subscribed Orders", textStyle: TextStyle( fontFamily: "Poppins", color: Colors.grey.shade800, fontSize: getResponsiveTextSize(context, 14), fontWeight: FontWeight.w500, ), ), SizedBox(height: screenHeight * 0.012), if (dashboardProvider.isLoading && dashboardData == null) Center( child: CircularProgressIndicator(), ) else if (dashboardData?.orders == null || dashboardData!.orders!.isEmpty) Text( "No products subscribed", style: TextStyle( fontFamily: "Poppins", color: Colors.grey, fontSize: getResponsiveTextSize(context, 14), ), ) else Column( children: dashboardData!.orders!.map((product) { return Column( children: [ InkResponse( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => ProductsDetailScreen( sessionId: widget.sessionId, accId: widget.accId, orderId: product.orderid.toString(), )), ); }, child: _buildProductItemFromApi(product, screenWidth, screenHeight), ), SizedBox(height: screenHeight * 0.02), ], ); }).toList(), ), ], ), ), // Feature cards grid Container( width: double.infinity, padding: EdgeInsets.all(screenWidth * 0.005), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Big card - Any Requirements Expanded( flex: 2, child: InkResponse( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => EnquiryScreen( sessionId: widget.sessionId, accId: widget.accId, )), ); }, child: Container( padding: EdgeInsets.symmetric( horizontal: screenWidth * 0.035, vertical: screenHeight * 0.01, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(screenWidth * 0.05), border: Border.all( color: Colors.grey.shade200, width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: screenHeight * 0.005), Text( "Any \nRequirements ?", style: TextStyle( fontFamily: "Poppins", color: Color(0xFF008CDE), fontSize: getResponsiveTextSize(context, 13), fontWeight: FontWeight.w400, ), ), SizedBox(height: screenHeight * 0.0075), Text( "Submit your enquiry for requirement", style: TextStyle( fontFamily: "Poppins", fontWeight: FontWeight.w400, color: Colors.grey.shade600, fontSize: getResponsiveTextSize(context, 11), ), ), ], ), SizedBox(height: screenHeight * 0.031), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ SvgPicture.asset( "assets/svg/requirements.svg", height: getResponsiveIconSize(context, 55), width: getResponsiveIconSize(context, 55), ), ], ), ], ), ), ), ), SizedBox(width: screenWidth * 0.012), // Side cards column Expanded( flex: 2, child: Column( children: [ // Have Complaints card Container( width: double.infinity, padding: EdgeInsets.symmetric( horizontal: screenWidth * 0.026, vertical: screenHeight * 0.015, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(screenWidth * 0.05), border: Border.all( color: Colors.grey.shade200, width: 1, ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( flex: 5, child: InkResponse( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => HelpScreen( sessionId: widget.sessionId, accId: widget.accId, )), ); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( flex: 5, child: Text( "Need Help?", style: TextStyle( fontFamily: "Poppins", color: Color(0xFF008CDE), height: 1, fontSize: getResponsiveTextSize(context, 13), fontWeight: FontWeight.w400, ), ), ), Expanded( flex: 2, child: SvgPicture.asset( "assets/svg/have_compaints.svg", height: getResponsiveIconSize(context, 34), width: getResponsiveIconSize(context, 34), ), ), ], ), SizedBox(height: screenHeight * 0.001), Text( "Raise a ticket to \nresolve your issues.", style: TextStyle( fontFamily: "Poppins", height: 1, color: Colors.grey.shade600, fontWeight: FontWeight.w400, fontSize: getResponsiveTextSize(context, 11), ), ), SizedBox(height: 1,) ], ), ), ), ], ), ), SizedBox(height: screenHeight * 0.005), // Know Your Payments card InkResponse( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => TransactionsScreen( sessionId: widget.sessionId, accId: widget.accId, )), ); }, child: Container( width: double.infinity, padding: EdgeInsets.symmetric( horizontal: screenWidth * 0.025, vertical: screenHeight * 0.012, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(screenWidth * 0.05), border: Border.all( color: Colors.grey.shade200, width: 1, ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( flex: 5, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( flex: 5, child: Text( "Transactions!", style: TextStyle( fontFamily: "Poppins", color: Color(0xFF008CDE), fontSize: getResponsiveTextSize(context, 13), fontWeight: FontWeight.w400, ), ), ), Expanded( flex: 2, child: SvgPicture.asset( "assets/svg/know_pay.svg", height: getResponsiveIconSize(context, 35), width: getResponsiveIconSize(context, 35), ), ), ], ), SizedBox(height: screenHeight * 0.0001), Text( "View your all \ntransactions with us.", style: TextStyle( fontFamily: "Poppins", color: Colors.grey.shade600, fontWeight: FontWeight.w400, fontSize: getResponsiveTextSize(context, 11), ), ), SizedBox(height: 1,) ], ), ), ], ), ), ), ], ), ), ], ), ), SizedBox(height: screenHeight * 0.012), ], ), ), ], ), ], ), ], ), ), ), ), ), ), ); } // Helper widget for product item from API data Widget _buildProductItemFromApi(Orders product, double screenWidth, double screenHeight) { final bool hasPending = product.hasPendingPayment == true; final productList = product.products ?? []; return Container( margin: EdgeInsets.symmetric(vertical: screenHeight * 0.007), decoration: BoxDecoration( borderRadius: BorderRadius.circular(screenWidth * 0.04), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.15), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Stack( children: [ // ===== Red Strip (Behind Card) ===== if (hasPending) Container( decoration: BoxDecoration( color: Color(0xFFFFE2E0), borderRadius: BorderRadius.circular(screenWidth * 0.06), ), padding: EdgeInsets.symmetric(horizontal: 0, vertical: 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// Header Row (image, order id, date, badge) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( padding: EdgeInsets.all(screenWidth * 0.03), decoration: BoxDecoration( color: const Color(0xffF2F2F2), borderRadius: BorderRadius.circular(screenWidth * 0.035), ), child: Image.network( product.productImage ?? "", height: getResponsiveIconSize(context, 40), width: getResponsiveIconSize(context, 40), fit: BoxFit.contain, errorBuilder: (context, error, stack) => Image.asset('assets/images/gene_png.png', height: getResponsiveIconSize(context, 40), width: getResponsiveIconSize(context, 40)), ), ), SizedBox(width: screenWidth * 0.02), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "#${product.orderid ?? "0"}", style: TextStyle( fontFamily: "Poppins", color: Color(0xFF008CDE), fontSize: getResponsiveTextSize(context, 16), fontWeight: FontWeight.w500, height: 1.2, ), ), Text( product.rentedDate ?? "Rented date not available", style: TextStyle( fontFamily: "Poppins", color: Colors.grey.shade600, fontSize: getResponsiveTextSize(context, 12), ), ), ], ), ], ), // Gradient expiry badge if (product.expiringText != null && product.expiringText!.isNotEmpty) Container( padding: EdgeInsets.symmetric( horizontal: screenWidth * 0.025, vertical: screenHeight * 0.007, ), decoration: BoxDecoration( gradient: _getGradientByColor(product.expiringInColor), borderRadius: BorderRadius.circular(screenWidth * 0.02), ), child: Text( product.expiringText!, style: TextStyle( fontFamily: "Poppins", color: Colors.black87, fontSize: getResponsiveTextSize(context, 12), fontWeight: FontWeight.w500, ), ), ), ], ), SizedBox(height: screenHeight * 0.002), Divider(), /// ===== Product List (with +3 More on same line) ===== Builder( builder: (context) { final visibleItems = productList.take(2).toList(); final remaining = productList.length - visibleItems.length; return Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Left side → Product list (bulleted) Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ for (int i = 0; i < visibleItems.length; i++) Padding( padding: EdgeInsets.only(bottom: screenHeight * 0.005), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(width: screenWidth * 0.02), Text( "• ", style: TextStyle( fontFamily: "Poppins", color: Colors.black, fontSize: getResponsiveTextSize(context, 14) ), ), Expanded( child: Text( visibleItems[i], style: TextStyle( fontFamily: "Poppins", color: Colors.black, fontSize: getResponsiveTextSize(context, 14), fontWeight: FontWeight.w400, ), ), ), ], ), ), ], ), ), // Right side → +x More (vertically centered) if (remaining > 0) Padding( padding: EdgeInsets.only(left: screenWidth * 0.02, right: screenWidth * 0.01), child: Align( alignment: Alignment.center, child: Text( "+$remaining More", style: TextStyle( fontFamily: "Poppins", color: Color(0xFF008CDE), fontSize: getResponsiveTextSize(context, 14), fontWeight: FontWeight.w500, ), ), ), ), ], ); }, ), SizedBox(height: screenHeight * 0.030), // Stripe Container( padding: EdgeInsets.symmetric(vertical: 0, horizontal: screenWidth * 0.01), height: screenHeight * 0.05, decoration: BoxDecoration( color: Color(0x22FFE2E0), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(screenWidth * 0.05), bottomRight: Radius.circular(screenWidth * 0.05), ), ), child: Center( child: Row( children: [ SizedBox(width: screenWidth * 0.02), Icon(Icons.info_outline, color: Colors.red, size: getResponsiveIconSize(context, 15)), SizedBox(width: screenWidth * 0.01), Expanded( child: Text( product.pendingPaymentText ?? "Payment Pending. Please Pay before incurring fines.", style: TextStyle( fontFamily: "Poppins", color: Colors.black87, fontSize: getResponsiveTextSize(context, 10), fontWeight: FontWeight.w400, ), ), ), ], ), ), ), ], ), ), // ===== Main White Card ===== Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(screenWidth * 0.045), ), padding: EdgeInsets.symmetric( horizontal: screenWidth * 0.025, vertical: screenHeight * 0.012, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// Header Row (image, order id, date, badge) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( padding: EdgeInsets.all(screenWidth * 0.03), decoration: BoxDecoration( color: const Color(0xffF2F2F2), borderRadius: BorderRadius.circular(screenWidth * 0.035), ), child: Image.network( product.productImage ?? "", height: getResponsiveIconSize(context, 40), width: getResponsiveIconSize(context, 40), fit: BoxFit.contain, errorBuilder: (context, error, stack) => Image.asset('assets/images/dashboard_gen.png', height: getResponsiveIconSize(context, 40), width: getResponsiveIconSize(context, 40)), ), ), SizedBox(width: screenWidth * 0.02), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "#${product.orderNum ?? "0"}", style: TextStyle( fontFamily: "Poppins", color: Color(0xFF008CDE), fontSize: getResponsiveTextSize(context, 13), fontWeight: FontWeight.w400, height: 1.2, ), ), Text( product.rentedDate ?? "Rented date not available", style: TextStyle( fontFamily: "Poppins", color: Colors.grey.shade600, fontWeight: FontWeight.w400, fontSize: getResponsiveTextSize(context, 13), ), ), ], ), ], ), // Gradient expiry badge if (product.expiringText != null && product.expiringText!.isNotEmpty) Container( padding: EdgeInsets.symmetric( horizontal: screenWidth * 0.025, vertical: screenHeight * 0.007, ), decoration: BoxDecoration( gradient: _getGradientByColor(product.expiringInColor), borderRadius: BorderRadius.circular(screenWidth * 0.02), ), child: Text( product.expiringText!, style: TextStyle( fontFamily: "Poppins", color: Colors.black87, fontSize: getResponsiveTextSize(context, 11), fontWeight: FontWeight.w400, ), ), ), ], ), SizedBox(height: screenHeight * 0.008), Divider(), /// ===== Product List (with +3 More on same line) ===== Builder( builder: (context) { final visibleItems = productList.take(2).toList(); final remaining = productList.length - visibleItems.length; return Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Left side → Product list (bulleted) Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ for (int i = 0; i < visibleItems.length; i++) Padding( padding: EdgeInsets.only(bottom: screenHeight * 0.005), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(width: screenWidth * 0.02), Text( "• ", style: TextStyle( color: Colors.black, fontSize: getResponsiveTextSize(context, 14) ), ), Expanded( child: Text( visibleItems[i], style: TextStyle( fontFamily: "Poppins", color: Colors.black, fontSize: getResponsiveTextSize(context, 13), fontWeight: FontWeight.w400, ), ), ), ], ), ), ], ), ), // Right side → +x More (vertically centered) if (remaining > 0) Padding( padding: EdgeInsets.only(left: screenWidth * 0.02, right: screenWidth * 0.01), child: Align( alignment: Alignment.center, child: Text( "+$remaining More", style: TextStyle( fontFamily: "Poppins", color: Color(0xFF008CDE), fontSize: getResponsiveTextSize(context, 13), fontWeight: FontWeight.w500, ), ), ), ), ], ); }, ), ], ), ), ], ), ); } // Gradient helper LinearGradient _getGradientByColor(String? color) { switch (color) { case "Red": return const LinearGradient( colors: [Color(0xFFFFEBEB), Color(0xFFFFEBEB)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); case "Green": default: return const LinearGradient( colors: [Color(0xFFE5FFE2), Color(0xFFE5FFE2)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); } } /// Pay balance 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), ], ), ); }, ); }, ); } void showPaymentBottomSheet( BuildContext context, { String? payTotal = "4218", String? payBill = "", }) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, isDismissible: true, enableDrag: true, builder: (BuildContext context) { return PaymentBottomSheetContent( payTotal: payTotal, payBill: payBill, billFlag: false, partFlag: true, ); }, ); } } // PaymentBottomSheetContent class remains the same as your original code // ... (keep your existing PaymentBottomSheetContent class as is) class PaymentBottomSheetContent extends StatefulWidget { final String? payTotal; final String? payBill; final bool billFlag; final bool partFlag; const PaymentBottomSheetContent({ super.key, this.payTotal, this.payBill, required this.billFlag, required this.partFlag, }); @override State createState() => _PaymentBottomSheetContentState(); } class _PaymentBottomSheetContentState extends State { int selectedOption = -1; // -1 = none, 0 = total, 1 = bill, 2 = part final TextEditingController partAmountController = TextEditingController(); final FocusNode partAmountFocusNode = FocusNode(); @override void initState() { super.initState(); // Auto-focus when part payment is selected partAmountFocusNode.addListener(() { if (selectedOption == 2 && partAmountFocusNode.hasFocus) { // Ensure the bottom sheet scrolls to show the text field Future.delayed(const Duration(milliseconds: 300), () { Scrollable.ensureVisible( partAmountFocusNode.context!, duration: const Duration(milliseconds: 300), ); }); } }); } @override void dispose() { partAmountController.dispose(); partAmountFocusNode.dispose(); super.dispose(); } void _handleRadioChange(int? value) { setState(() { selectedOption = value!; }); // Auto-focus on part amount field when part payment is selected if (value == 2) { Future.delayed(const Duration(milliseconds: 100), () { partAmountFocusNode.requestFocus(); }); } else { // Clear focus when other options are selected partAmountFocusNode.unfocus(); } } void _handleContinuePayment() { // Validation if (selectedOption == -1) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Please select a payment option."), backgroundColor: Colors.redAccent, ), ); return; } if (selectedOption == 2) { if (partAmountController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Please enter amount for part payment."), backgroundColor: Colors.redAccent, ), ); return; } final amount = double.tryParse(partAmountController.text.trim()); if (amount == null || amount <= 0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Please enter a valid amount."), backgroundColor: Colors.redAccent, ), ); return; } } Navigator.pop(context); String selectedText = selectedOption == 0 ? "Pay Total ₹${widget.payTotal}" : selectedOption == 1 ? "Pay Bill ₹${widget.payBill}" : "Part Payment ₹${partAmountController.text}"; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text("Selected: $selectedText"), backgroundColor: Colors.green, ), ); } @override Widget build(BuildContext context) { final mediaQuery = MediaQuery.of(context); final bottomPadding = mediaQuery.viewInsets.bottom; return AnimatedPadding( padding: EdgeInsets.only(bottom: bottomPadding), duration: const Duration(milliseconds: 300), child: SafeArea( child: Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), ), ), child: SingleChildScrollView( // Add this to make it scrollable when keyboard is open child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header handle Center( child: Container( width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(2), ), ), ), const SizedBox(height: 20), Text( "Balance Amount Bill", style: TextStyle( fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.normalText, ), ), const SizedBox(height: 16), const Divider(height: 1, color: Color(0xFFEEEEEE)), const SizedBox(height: 16), // ====== PAY OPTIONS ====== Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Pay Total Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Radio( value: 0, groupValue: selectedOption, onChanged: _handleRadioChange, activeColor: const Color(0xFF008CDE), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Pay Total", style: TextStyle( fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black87, ), ), Text( "Avoid late payment fees.", style: TextStyle( fontFamily: "Poppins", fontSize: 13, fontWeight: FontWeight.w400, color: Colors.green.shade600, ), ), ], ), ], ), Text( "₹${widget.payTotal}", style: const TextStyle( fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.black, ), ), ], ), // Pay Bill if (widget.billFlag == true) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Radio( value: 1, groupValue: selectedOption, onChanged: _handleRadioChange, activeColor: const Color(0xFF008CDE), ), const Text( "Pay Bill", style: TextStyle( fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black87, ), ), ], ), Text( "₹${widget.payBill}", style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.black, ), ), ], ), // Part Payment if (widget.partFlag == true) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Row( children: [ Radio( value: 2, groupValue: selectedOption, onChanged: _handleRadioChange, activeColor: const Color(0xFF008CDE), ), const Text( "Part Payment", style: TextStyle( fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black87, ), ), const SizedBox(width: 10), if (selectedOption == 2) Expanded( child: Container( height: 50, alignment: Alignment.center, decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(12), ), child: TextField( controller: partAmountController, focusNode: partAmountFocusNode, keyboardType: TextInputType.number, textAlign: TextAlign.right, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.black, ), decoration: const InputDecoration( hintText: "Enter amount", hintStyle: TextStyle( fontFamily: "Poppins", color: Colors.grey, fontSize: 14, ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), prefixText: "₹", prefixStyle: TextStyle( fontFamily: "Poppins", color: Colors.black, fontSize: 14, fontWeight: FontWeight.w500, ), ), onChanged: (value) { // Optional: Add real-time validation if needed setState(() {}); }, ), ), ), ], ), ), ], ), ], ), const SizedBox(height: 18), const Divider(height: 1, color: Color(0xFFEEEEEE)), const SizedBox(height: 18), // Continue Payment Button SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _handleContinuePayment, 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, ), ], ), ), ), ), // Add extra space at bottom for better visibility SizedBox(height: mediaQuery.viewInsets.bottom > 0 ? 20 : 0), ], ), ), ), ), ), ); } }