import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart' show PolylinePoints; import 'package:flutter_svg/svg.dart'; import 'package:gen_service/Notifiers/HelpAndComplaintProvider.dart'; import 'package:gen_service/Notifiers/technicianTrackingProvider.dart'; import 'package:gen_service/Screens/FileViewer.dart'; import 'package:gen_service/Screens/HelpAndComplaintScreens/TechnicianTrackingScreen.dart'; import 'package:gen_service/Screens/HelpAndComplaintScreens/jobcardListScreen.dart'; import 'package:gen_service/Screens/HelpAndComplaintScreens/serviceListScreen.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:provider/provider.dart'; import 'package:razorpay_flutter/razorpay_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../Models/HelpAndComplaintModels/complaintDetailsResponse.dart'; import '../../Notifiers/PayAmountProvider.dart'; import '../../Utility/AppColors.dart'; import '../../Utility/CustomSnackbar.dart'; import '../../Utility/SharedpreferencesService.dart'; class ComplaintDetailsScreen extends StatefulWidget { final accId; final sessionId; final complaintId; const ComplaintDetailsScreen({ super.key, required this.accId, required this.sessionId, required this.complaintId, }); @override State createState() => _ComplaintDetailsScreenState(); } class _ComplaintDetailsScreenState extends State { late Razorpay _razorpay; bool? isSuccess; var paymentMethod = ""; var User_contact = "0"; bool bottomSheetButtonClicked = false; final prefs = SharedPreferencesService.instance; @override void initState() { // TODO: implement initState super.initState(); _razorpay = Razorpay(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { final provider = Provider.of( context, listen: false, ); provider.fetchComplaintDetailsAPI( widget.accId, widget.sessionId, widget.complaintId, ); final mapProvider = Provider.of( context, listen: false, ); mapProvider.getLocationPermission(context); mapProvider.polylinePoints = PolylinePoints( apiKey: "AIzaSyAA2ukvrb1kWQZ2dttsNIMynLJqVCYYrhw", ); }); } //_________________________________________________________ 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: "Gen Service", 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"); // }); // } Widget sectionHeading(text,[ComplaintDetails? complaintData]) { return Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( text, style: TextStyle(color: AppColors.nearDarkText, fontSize: 14), ), if (text != "Complaint Details") ...[ InkResponse( onTap: () async { var redirectScreen; switch (text) { case "Job Cards": redirectScreen = Jobcardlistscreen( accId: widget.accId, sessionId: widget.sessionId, complaintId: widget.complaintId, ); break; case "Service Details": redirectScreen = serviceListScreen( accId: widget.accId, sessionId: widget.sessionId, complaintId: widget.complaintId, complaintData:complaintData ); break; default: break; } await Navigator.push( context, MaterialPageRoute(builder: (context) => redirectScreen), ); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( "See All", style: TextStyle(color: AppColors.buttonColor, fontSize: 14), ), ), ), ], ], ), ); } @override Widget build(BuildContext context) { return Consumer( builder: (context, provider, child) { final isLoading = provider.isLoading; final error = provider.errorMessage; final data = provider.compDetailsResponse; final complaintData = data?.complaintDetails?.firstOrNull; final jobCardsData = data?.jobCardList?.firstOrNull; final serviceData = data?.serviceDetails?.firstOrNull; List headings = ["Generator", "Description", "Complaint Note"]; List subHeadings = [ complaintData?.productName ?? "-", complaintData?.complaintDesc ?? "-", complaintData?.complaintNote ?? "-", ]; List serviceHeadings = [ "Date", "In/Out Time", "Running Hrs.", "FSR File", "FSR Number", "Feedback", ]; List serviceSubHeadings = [ serviceData?.date ?? "-", serviceData?.inOrOutTime ?? "-", serviceData?.runningHrs ?? "-", serviceData?.fsrExt ?? "-", serviceData?.fsrNo ?? "-", serviceData?.feedback ?? "-", ]; if (isLoading) { return const Scaffold( backgroundColor: AppColors.backgroundRegular, body: Center( child: CircularProgressIndicator(color: AppColors.buttonColor), ), ); } if (error != null) { return Scaffold( resizeToAvoidBottomInset: true, backgroundColor: AppColors.backgroundRegular, body: Center( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Error Icon Container( width: 120, height: 120, decoration: BoxDecoration( color: Colors.red.withOpacity(0.1), shape: BoxShape.circle, ), child: const Icon( Icons.error_outline_rounded, size: 60, color: Colors.red, ), ), const SizedBox(height: 24), // Error Title const Text( "Oops! Something went wrong", style: TextStyle( fontSize: 20, fontWeight: FontWeight.w600, color: Colors.black87, fontFamily: "Poppins", ), ), const SizedBox(height: 12), // Error Message Text( error, textAlign: TextAlign.center, style: const TextStyle( fontSize: 14, color: Colors.grey, fontFamily: "Poppins", height: 1.4, ), ), const SizedBox(height: 32), // Retry Button ElevatedButton.icon( onPressed: () async { // Show loading state setState(() {}); await Future.delayed(const Duration(milliseconds: 300)); // Retry fetching data final provider = Provider.of( context, listen: false, ); await provider.fetchComplaintDetailsAPI( widget.accId, widget.sessionId, widget.complaintId, ); }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.buttonColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), ), elevation: 2, ), icon: const Icon(Icons.refresh_rounded, size: 20), label: const Text( "Try Again", style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, fontFamily: "Poppins", ), ), ), const SizedBox(height: 16), // Alternative Action TextButton( onPressed: () { // Go back or navigate to home Navigator.maybePop(context); }, child: const Text( "Go Back", style: TextStyle( fontSize: 14, color: Colors.grey, fontFamily: "Poppins", ), ), ), ], ), ), ), ); } if (data == null) { return SafeArea( maintainBottomViewPadding: true, top: false, bottom: Platform.isIOS ? false : true, child: const Scaffold( backgroundColor: AppColors.backgroundRegular, body: Center(child: Text("No data found.")), ), ); } return SafeArea( bottom: Platform.isIOS ? false : true, maintainBottomViewPadding: true, top: false, child: RefreshIndicator.adaptive( color: AppColors.amountText, onRefresh: () async { await Future.delayed(const Duration(milliseconds: 600)); }, child: Scaffold( resizeToAvoidBottomInset: true, backgroundColor: AppColors.backgroundRegular, body: CustomScrollView( slivers: [ SliverAppBar( stretch: true, pinned: true, expandedHeight: 175, backgroundColor: AppColors.backgroundRegular, elevation: 0, leading: Container(), toolbarHeight: 0, collapsedHeight: 0, flexibleSpace: FlexibleSpaceBar( stretchModes: const [StretchMode.zoomBackground], background: Container( decoration: BoxDecoration( gradient: AppColors.balanceBarGradientA, ), child: SafeArea( child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 20, ), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.symmetric( horizontal: 0, vertical: 20, ), child: SizedBox( child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ InkResponse( onTap: () { HapticFeedback.selectionClick(); Navigator.pop(context, true); }, child: SvgPicture.asset( "assets/svg/appbar_back.svg", height: 25, ), ), SizedBox(width: 10), Expanded( flex: 4, child: InkResponse( onTap: () { HapticFeedback.selectionClick(); Navigator.pop(context, true); }, child: Text( "Complaint Details", overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( fontSize: 16, color: Colors.white, height: 1.1, ), ), ), ), ], ), ), ), const SizedBox(height: 20), Row( children: [ Expanded( flex: 5, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( text: TextSpan( style: const TextStyle( fontFamily: 'Poppins', color: Color(0xFF48F3FF), fontSize: 12, ), children: [ TextSpan( text: "#${complaintData?.id!}", ), TextSpan( text: (complaintData?.complaintName ?? "") .isNotEmpty ? " | ${complaintData?.complaintName!}" : "", ), ], ), ), Text( "${complaintData?.registredDate ?? ""}", maxLines: 2, style: const TextStyle( color: Colors.white, fontSize: 14, ), ), ], ), ), Expanded( flex: 2, child: Container( padding: EdgeInsets.symmetric( horizontal: 15, vertical: 7, ), decoration: BoxDecoration( color: complaintData?.openStatus == "Open" ? AppColors.successBG : AppColors.yellowBG, borderRadius: BorderRadius.circular( 10, ), ), child: Center( child: Text( "${complaintData?.openStatus}", style: TextStyle( fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w400, color: complaintData?.openStatus == "Open" ? AppColors.success : AppColors.normalText, ), ), ), ), ), ], ), ], ), ), ), ), ), ), SliverToBoxAdapter( child: Container( color: Color(0xFF4076FF), child: Container( padding: EdgeInsets.symmetric( horizontal: 10, vertical: 8, ), decoration: const BoxDecoration( color: AppColors.backgroundRegular, borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (data.jobCardList!.isNotEmpty) ...[ sectionHeading("Job Cards"), Container( margin: const EdgeInsets.all(5), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(16), border: Border.all( width: 1.1, color: AppColors.buttonColor, ), ), child: Column( children: [ Row( children: [ Expanded( flex: 5, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${jobCardsData?.date}", maxLines: 2, style: const TextStyle( color: AppColors.subtitleText, fontSize: 12, ), ), SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "Job Card", style: TextStyle( fontSize: 14, color: AppColors .nearDarkText, ), ), InkResponse( onTap: bottomSheetButtonClicked ? null : () { HapticFeedback.mediumImpact(); setState(() { bottomSheetButtonClicked = true; }); provider.fetchJobCardProductDetails( widget.accId, widget .sessionId, jobCardsData! .id, ); Future.delayed( Duration( milliseconds: 600, ), () { _showJobCardProductSheet( context, ); }, ); }, child: Text( " ⓘ View Details", style: TextStyle( fontSize: 14, color: AppColors .buttonColor, ), ), ), ], ), ], ), ), Expanded( flex: 2, child: Text( "₹${jobCardsData?.totalPrice}", textAlign: TextAlign.right, style: TextStyle( fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.nearDarkText, ), ), ), ], ), Divider( thickness: 0.3, color: AppColors.subtitleText, ), InkResponse( onTap: () => _openPaymentSheet( context, jobCardsData!.totalPrice.toString(), ), child: Container( padding: EdgeInsets.symmetric( vertical: 16, horizontal: 10, ), decoration: BoxDecoration( color: AppColors.buttonColor, borderRadius: BorderRadius.circular( 25, ), ), child: Center( child: Text( "Pay Now", style: TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600, ), ), ), ), ), ], ), ), ], if (data.complaintDetails!.isNotEmpty) ...[ sectionHeading("Complaint Details"), Container( margin: const EdgeInsets.all(5), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(16), ), child: Column( children: [ ...List.generate(headings.length, (i) { return Padding( padding: const EdgeInsets.symmetric( vertical: 3, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Text( headings[i] ?? "-", style: const TextStyle( fontSize: 14, color: AppColors.subtitleText, ), ), ), Expanded( child: Text( subHeadings[i].isEmpty ? "-" : subHeadings[i] ?? "-", maxLines: 3, overflow: TextOverflow.ellipsis, textAlign: TextAlign.end, style: const TextStyle( fontSize: 14, color: AppColors.nearDarkText, ), ), ), ], ), ); }), ], ), ), ], if (data.serviceDetails!.isNotEmpty) ...[ sectionHeading("Service Details"), Container( margin: const EdgeInsets.all(5), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(16), ), child: Column( children: [ if (serviceData?.openStatus == "Open") ...[ Container( height: 220, margin: const EdgeInsets.symmetric( horizontal: 5, vertical: 10, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular( 18, ), ), child: ClipRRect( borderRadius: BorderRadius.circular( 18, ), child: Consumer< Techniciantrackingprovider >( builder: ( context, mapProvider, child, ) { return Stack( clipBehavior: Clip.none, children: [ GoogleMap( initialCameraPosition: CameraPosition( target: mapProvider.currentLocation != null ? LatLng( mapProvider .currentLocation! .latitude!, mapProvider .currentLocation! .longitude!, ) : mapProvider .startLocation, zoom: 15, ), myLocationEnabled: true, myLocationButtonEnabled: false, zoomControlsEnabled: false, mapType: MapType.normal, markers: mapProvider.markers, polylines: mapProvider.polylines, onMapCreated: ( GoogleMapController controller, ) { mapProvider .mapController = controller; mapProvider.setMapStyle( controller, ); final double? techLat1 = 17.4484769; final double? techLng1 = 78.3710461; final double? techLat = double.tryParse( serviceData?.lat?.trim() ?? '${techLat1}', )??techLat1; final double? techLng = double.tryParse( serviceData?.lng?.trim() ?? '${techLng1}', )??techLng1; if (techLat != null && techLng != null && techLat != 0 && techLng != 0) { mapProvider .updateTechnicianLocation( techLat, techLng, ); } }, ), Positioned( bottom: 0, left: 0, right: 0, child: Align( alignment: Alignment .bottomCenter, child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Color( 0xFFFFFFFF, ).withOpacity( 0.01, ), Color(0xFFFFFFFF), Color(0xFFFFFFFF), Color( 0xFFFFFFFF, ).withOpacity( 0.01, ), ], begin: Alignment .bottomCenter, end: Alignment .topCenter, ), ), child: Column( children: [ InkResponse( onTap: () async { await Navigator.push( context, MaterialPageRoute( builder: ( context, ) => TechnicianTrackingScreen( details: serviceData!, from: "Single", complaintData:complaintData ), ), ); }, child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Color( 0xFFFFFFFF, ), Color( 0xFFFFFFFF, ), Color( 0xFFFFFFFF, ), Color( 0xFFFFFFFF, ).withOpacity( 0.8, ), Color( 0xFFFFFFFF, ).withOpacity( 0.5, ), Color( 0xFFFFFFFF, ).withOpacity( 0.1, ), Color( 0xFFFFFFFF, ).withOpacity( 0.01, ), ], begin: Alignment .bottomCenter, end: Alignment .topCenter, ), ), padding: EdgeInsets.symmetric( vertical: 10, ), child: Center( child: Text( "Tap to Track Engineer", textAlign: TextAlign .center, style: TextStyle( fontSize: 14, color: AppColors .buttonColor, ), ), ), ), ), Align( alignment: Alignment .bottomCenter, child: Container( height: 60, padding: EdgeInsets.symmetric( horizontal: 0, vertical: 10, ), decoration: BoxDecoration( color: Color( 0xFFE8F6FF, ), borderRadius: BorderRadius.circular( 18, ), ), child: Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Expanded( flex: 1, child: CircleAvatar( radius: 25, backgroundImage: serviceData?.profileImg == "https://erp.gengroup.in/" ? AssetImage( "assets/images/user_img.png", ) : NetworkImage( "${serviceData?.profileImg}", ), ), ), Expanded( flex: 3, child: SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${serviceData?.empName}", maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( color: AppColors.nearDarkText, fontSize: 14, ), ), Text( "${serviceData?.techRoleName}", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: AppColors.subtitleText, fontSize: 12, ), ), ], ), ), ), Expanded( flex: 1, child: SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ InkResponse( onTap: () async { var url = "tel:${serviceData?.mobNum}"; if (await canLaunch( url, )) { await launch( url, ); } else { throw 'Could not launch $url'; } }, child: Image.asset( "assets/images/call_technicain_ic.png", fit: BoxFit.cover, height: 40, width: 40, ), ), ], ), ), ), ], ), ), ), ], ), ), ), ), ], ); }, ), ), ), ] else ...[ ...List.generate(serviceHeadings.length, ( i, ) { return Padding( padding: const EdgeInsets.symmetric( vertical: 3, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Text( serviceHeadings[i] ?? "-", style: const TextStyle( fontSize: 14, color: AppColors.subtitleText, ), ), ), Expanded( child: InkResponse( onTap: serviceHeadings[i] == "FSR File" && (serviceData ?.fsrFilePath != null) ? () async { await Navigator.push( context, MaterialPageRoute( builder: ( context, ) => Fileviewer( fileName: serviceData ?.fsrExt ?? "-", fileUrl: serviceData ?.fsrFilePath ?? "-", ), ), ); } : null, child: Text( serviceSubHeadings[i] .isEmpty ? "-" : serviceSubHeadings[i] ?? "-", maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.end, style: TextStyle( fontSize: 14, color: (serviceHeadings[i] == "FSR File") ? AppColors .buttonColor : AppColors .nearDarkText, decoration: (serviceHeadings[i] == "FSR File") ? TextDecoration .underline : TextDecoration .none, decorationColor: (serviceHeadings[i] == "FSR File") ? AppColors .buttonColor : AppColors .nearDarkText, ), ), ), ), ], ), ); }), ], if (serviceData?.openStatus == "Closed") ...[ SizedBox(height: 5), Container( padding: EdgeInsets.symmetric( horizontal: 0, vertical: 10, ), decoration: BoxDecoration( color: Color(0xFFE8F6FF), borderRadius: BorderRadius.circular( 18, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( flex: 1, child: CircleAvatar( radius: 25, backgroundImage: serviceData?.profileImg == "https://erp.gengroup.in/" ? AssetImage( "assets/images/user_img.png", ) : NetworkImage( "${serviceData?.profileImg}", ), ), ), Expanded( flex: 3, child: SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${serviceData?.empName}", maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( color: AppColors .nearDarkText, fontSize: 14, ), ), Text( "${serviceData?.techRoleName}", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: AppColors .subtitleText, fontSize: 12, ), ), ], ), ), ), Expanded( flex: serviceData?.openStatus == "Open" ? 1 : 3, child: SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ if (serviceData ?.openStatus == "Open") ...[ InkResponse( onTap: () async { var url = "tel:${serviceData?.mobNum}"; if (await canLaunch( url, )) { await launch(url); } else { throw 'Could not launch $url'; } }, child: Image.asset( "assets/images/call_technicain_ic.png", fit: BoxFit.cover, height: 40, width: 40, ), ), ] else ...[ Row( mainAxisAlignment: MainAxisAlignment .center, children: List.generate( provider .starStates .length, (index) => InkWell( onTap: serviceData?.rating != "0" ? null : () { provider.rating = index + 1; for ( int i = 0; i < provider.starStates.length; i++ ) { provider.starStates[i] = i <= index; } provider .notifyListeners(); provider.updateRatingForTechnician( widget .accId, widget .sessionId, widget .complaintId, provider .rating, ); }, child: Row( children: [ Icon( Icons .star_rate_rounded, color: provider.starStates[index] ? Color( 0xffFFB703, ) : Color( 0xffCECECE, ), size: 25, ), ], ), ), ), ), Text( "Your Rating", maxLines: 1, overflow: TextOverflow .ellipsis, style: TextStyle( color: AppColors .subtitleText, fontSize: 12, ), ), ], ], ), ), ), ], ), ), ], ], ), ), ], ], ), ), ), ), ], ), ), ), ); }, ); } Future _showJobCardProductSheet(context) { return showModalBottomSheet( useSafeArea: true, isDismissible: true, isScrollControlled: true, showDragHandle: true, backgroundColor: Colors.white, enableDrag: true, context: context, builder: (context) { return StatefulBuilder( builder: (context, setState) { return SafeArea( child: Consumer( builder: (context, provider, child) { final jobcardProductData = provider.jobCardResponse; final list = jobcardProductData?.jobCardProducts; final data = provider.compDetailsResponse; final jobCardsData = data!.jobCardList?.firstOrNull; return Container( margin: EdgeInsets.only( bottom: 15, left: 15, right: 15, top: 10, ), padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${jobCardsData?.date}", style: TextStyle( color: AppColors.subtitleText, fontSize: 12, ), ), Text( "Job Card Details", style: TextStyle( color: AppColors.nearDarkText, fontSize: 14, ), ), ], ), ), SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( "Total Amount", style: TextStyle( color: AppColors.subtitleText, fontSize: 12, ), ), Text( "₹${jobCardsData?.totalPrice}", style: TextStyle( color: AppColors.buttonColor, fontSize: 14, ), ), ], ), ), ], ), Divider( thickness: 0.3, color: AppColors.subtitleText, ), ListView.builder( shrinkWrap: true, itemCount: list?.length, physics: NeverScrollableScrollPhysics(), itemBuilder: (BuildContext context, int j) { return Padding( padding: EdgeInsets.symmetric(vertical: 5), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( flex: 3, child: Text( "${j + 1}. ${list?[j].partName}", maxLines: 3, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, color: AppColors.nearDarkText, ), ), ), Spacer(), Text( "₹${list?[j].price} * ${list?[j].qty}", style: TextStyle( fontSize: 13, color: AppColors.nearDarkText, ), ), Spacer(), Text( "₹${list?[j].totalPrice}", style: TextStyle( fontSize: 13, color: AppColors.nearDarkText, ), ), ], ), ); }, ), ], ), ), ); }, ), ); }, ); }, ).whenComplete(() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { setState(() { bottomSheetButtonClicked = false; }); }); }); } 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 SafeArea( child: 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), ], ), ), ); }, ); }, ); } }