import 'package:flutter/material.dart'; import '../services/dio_client.dart'; import '../services/order_service.dart'; class OrderInvoiceScreen extends StatefulWidget { final String orderId; const OrderInvoiceScreen({super.key, required this.orderId}); @override State createState() => _OrderInvoiceScreenState(); } class _OrderInvoiceScreenState extends State with SingleTickerProviderStateMixin { bool loading = true; bool controllerInitialized = false; Map invoice = {}; late AnimationController _controller; late Animation _slideAnimation; bool s1 = true; bool s2 = false; bool s3 = false; bool s4 = false; @override void initState() { super.initState(); initializeController(); load(); } void initializeController() { _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 280), ); _slideAnimation = Tween( begin: const Offset(0, -0.05), end: Offset.zero, ).animate( CurvedAnimation(parent: _controller, curve: Curves.easeOut), ); controllerInitialized = true; _controller.forward(); } @override void dispose() { if (controllerInitialized) _controller.dispose(); super.dispose(); } Future load() async { final service = OrderService(DioClient.getInstance(context)); final res = await service.getInvoice(widget.orderId); if (res["success"] == true) { invoice = res["invoice"] ?? {}; } if (mounted) setState(() => loading = false); } @override Widget build(BuildContext context) { final items = invoice["items"] as List? ?? []; final width = MediaQuery.of(context).size.width; final scale = (width / 430).clamp(0.85, 1.18); return Scaffold( appBar: AppBar( title: const Text("Invoice"), ), body: loading || !controllerInitialized ? const Center(child: CircularProgressIndicator()) : ListView( padding: EdgeInsets.all(16 * scale), children: [ _headerCard(scale), // SUMMARY SECTION _sectionHeader("Invoice Summary", Icons.receipt, s1, () { setState(() => s1 = !s1); }, scale), _sectionBody(s1, [ _detailRow(Icons.numbers, "Invoice No", invoice['invoice_number'], scale), _detailRow(Icons.calendar_month, "Invoice Date", invoice['invoice_date'], scale), _detailRow(Icons.date_range, "Due Date", invoice['due_date'], scale), _detailRow(Icons.payment, "Payment Method", invoice['payment_method'], scale), _detailRow(Icons.confirmation_number, "Reference No", invoice['reference_no'], scale), ], scale), // AMOUNT SECTION _sectionHeader("Amount Details", Icons.currency_rupee, s2, () { setState(() => s2 = !s2); }, scale), _sectionBody(s2, [ _detailRow(Icons.money, "Amount", invoice['final_amount'], scale), _detailRow(Icons.percent, "GST Amount", invoice['gst_amount'], scale), _detailRow(Icons.summarize, "Final With GST", invoice['final_amount_with_gst'], scale), ], scale), // CUSTOMER SECTION _sectionHeader("Customer Details", Icons.person, s3, () { setState(() => s3 = !s3); }, scale), _sectionBody(s3, [ _detailRow(Icons.person, "Name", invoice['customer_name'], scale), _detailRow(Icons.business, "Company", invoice['company_name'], scale), _detailRow(Icons.mail, "Email", invoice['customer_email'], scale), _detailRow(Icons.phone, "Mobile", invoice['customer_mobile'], scale), _detailRow(Icons.location_on, "Address", invoice['customer_address'], scale), ], scale), // ITEMS SECTION _sectionHeader("Invoice Items", Icons.shopping_cart, s4, () { setState(() => s4 = !s4); }, scale), _sectionBody( s4, items.isEmpty ? [Text("No items found", style: TextStyle(fontSize: 14 * scale))] : items.map((item) => _itemTile(item, scale)).toList(), scale, ), ], ), ); } // ---------------- HEADER CARD ---------------- Widget _headerCard(double scale) { return Container( padding: EdgeInsets.all(18 * scale), margin: EdgeInsets.only(bottom: 18 * scale), decoration: BoxDecoration( gradient: LinearGradient(colors: [Colors.indigo.shade400, Colors.blue.shade600]), borderRadius: BorderRadius.circular(16 * scale), boxShadow: [ BoxShadow( blurRadius: 10 * scale, color: Colors.black.withOpacity(.15), offset: Offset(0, 3 * scale)) ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Invoice #${invoice['invoice_number'] ?? '-'}", style: TextStyle( fontSize: 22 * scale, fontWeight: FontWeight.bold, color: Colors.white)), SizedBox(height: 6 * scale), Text("Date: ${invoice['invoice_date'] ?? '-'}", style: TextStyle(color: Colors.white70, fontSize: 14 * scale)), SizedBox(height: 10 * scale), Container( padding: EdgeInsets.symmetric( vertical: 6 * scale, horizontal: 14 * scale), decoration: BoxDecoration( color: Colors.white.withOpacity(.2), borderRadius: BorderRadius.circular(50 * scale), ), child: Text( invoice["status"]?.toString() ?? "Unknown", style: TextStyle(color: Colors.white, fontSize: 14 * scale), ), ) ], ), ); } // ---------------- SECTION HEADER ---------------- Widget _sectionHeader( String title, IconData icon, bool expanded, Function toggle, double scale) { return GestureDetector( onTap: () => toggle(), child: Container( padding: EdgeInsets.all(14 * scale), margin: EdgeInsets.only(bottom: 10 * scale), decoration: BoxDecoration( gradient: LinearGradient(colors: [Colors.blue.shade400, Colors.indigo.shade500]), borderRadius: BorderRadius.circular(14 * scale), ), child: Row( children: [ Icon(icon, color: Colors.white, size: 20 * scale), SizedBox(width: 10 * scale), Text(title, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 15 * scale, color: Colors.white)), const Spacer(), AnimatedRotation( turns: expanded ? .5 : 0, duration: const Duration(milliseconds: 250), child: Icon(Icons.keyboard_arrow_down, color: Colors.white, size: 22 * scale), ) ], ), ), ); } // ---------------- SECTION BODY ---------------- Widget _sectionBody(bool visible, List children, double scale) { if (!controllerInitialized) return const SizedBox(); return AnimatedCrossFade( duration: const Duration(milliseconds: 250), firstChild: const SizedBox.shrink(), secondChild: SlideTransition( position: _slideAnimation, child: Container( padding: EdgeInsets.all(16 * scale), margin: EdgeInsets.only(bottom: 14 * scale), decoration: BoxDecoration( borderRadius: BorderRadius.circular(14 * scale), color: Colors.white, boxShadow: [ BoxShadow( blurRadius: 8 * scale, offset: Offset(0, 3 * scale), color: Colors.black.withOpacity(.08)), ], ), child: Column(children: children), ), ), crossFadeState: visible ? CrossFadeState.showSecond : CrossFadeState.showFirst, ); } // ---------------- DETAIL ROW ---------------- Widget _detailRow(IconData icon, String label, dynamic value, double scale) { return Padding( padding: EdgeInsets.symmetric(vertical: 6 * scale), child: Row( children: [ Icon(icon, color: Colors.blueGrey, size: 20 * scale), SizedBox(width: 10 * scale), Expanded( child: Text(label, style: TextStyle( color: Colors.grey.shade700, fontSize: 14 * scale)), ), Expanded( child: Text( value?.toString() ?? "N/A", textAlign: TextAlign.end, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 15 * scale), ), ) ], ), ); } // ---------------- ITEM TILE ---------------- Widget _itemTile(Map item, double scale) { final qty = item['qty'] ?? 0; final price = item['price'] ?? 0; final total = item['ttl_amount'] ?? 0; return Container( padding: EdgeInsets.all(16 * scale), margin: EdgeInsets.only(bottom: 14 * scale), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16 * scale), color: Colors.white, boxShadow: [ BoxShadow( blurRadius: 8 * scale, offset: Offset(0, 3 * scale), color: Colors.black.withOpacity(.08)), ], border: Border.all(color: Colors.grey.shade300, width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // TITLE Row( children: [ Container( padding: EdgeInsets.all(8 * scale), decoration: BoxDecoration( color: Colors.indigo.shade50, borderRadius: BorderRadius.circular(10 * scale), ), child: Icon(Icons.inventory_2, color: Colors.indigo, size: 20 * scale), ), SizedBox(width: 12 * scale), Expanded( child: Text( item['description'] ?? "Item", style: TextStyle( fontSize: 16 * scale, fontWeight: FontWeight.w600), ), ), ], ), SizedBox(height: 14 * scale), // QTY & PRICE Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _itemBadge(Icons.numbers, "Qty", qty.toString(), false, scale), _itemBadge(Icons.currency_rupee, "Price", "₹$price", false, scale), ], ), SizedBox(height: 12 * scale), // TOTAL Container( padding: EdgeInsets.symmetric( vertical: 10 * scale, horizontal: 14 * scale), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12 * scale), color: Colors.indigo.shade50, border: Border.all(color: Colors.indigo, width: 1.5 * scale), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon(Icons.summarize, size: 18 * scale, color: Colors.indigo), SizedBox(width: 6 * scale), Text( "Total:", style: TextStyle( fontSize: 15 * scale, fontWeight: FontWeight.w600, color: Colors.indigo, ), ), ], ), Text( "₹$total", style: TextStyle( fontSize: 17 * scale, fontWeight: FontWeight.bold, color: Colors.indigo, ), ), ], ), ) ], ), ); } // ---------------- BADGE ---------------- Widget _itemBadge( IconData icon, String label, String value, bool highlight, double scale) { return Container( padding: EdgeInsets.symmetric(vertical: 8 * scale, horizontal: 12 * scale), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12 * scale), color: highlight ? Colors.indigo.shade50 : Colors.grey.shade100, border: Border.all( color: highlight ? Colors.indigo : Colors.grey.shade300, width: highlight ? 1.5 * scale : 1), ), child: Row( children: [ Icon(icon, size: 16 * scale, color: highlight ? Colors.indigo : Colors.grey), SizedBox(width: 4 * scale), Text( "$label: ", style: TextStyle( fontSize: 13 * scale, fontWeight: FontWeight.w600, color: highlight ? Colors.indigo : Colors.grey.shade700, ), ), Text( value, style: TextStyle( fontSize: 14 * scale, fontWeight: FontWeight.bold, color: highlight ? Colors.indigo : Colors.black, ), ) ], ), ); } }