import 'package:flutter/material.dart'; class InvoiceDetailView extends StatefulWidget { final Map invoice; const InvoiceDetailView({super.key, required this.invoice}); @override State createState() => _InvoiceDetailViewState(); } class _InvoiceDetailViewState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _slideAnimation; bool s1 = true; bool s2 = false; bool s3 = false; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 350), ); _slideAnimation = Tween( begin: const Offset(0, -0.1), end: Offset.zero, ).animate( CurvedAnimation(parent: _controller, curve: Curves.easeOut), ); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } // ------------------------------------------------------------------ // STATUS BADGE (WHITE + GRADIENT BORDER) // ------------------------------------------------------------------ Widget statusBadge(String status, double scale) { final s = status.toLowerCase(); LinearGradient gradient; Color textColor; switch (s) { case 'paid': gradient = const LinearGradient( colors: [Color(0xFF2ECC71), Color(0xFF27AE60)], ); textColor = const Color(0xFF27AE60); break; case 'pending': gradient = const LinearGradient( colors: [ Color(0xFF5B8DEF), // Soft Blue Color(0xFF7B5CFA), // Purple Blue ], ); textColor = const Color(0xFF5B8DEF); break; case 'overdue': gradient = const LinearGradient( colors: [ Color(0xFFFFB300), // Amber Color(0xFFFF6F00), // Deep Orange ], ); textColor = const Color(0xFFFF6F00); break; case 'cancelled': case 'failed': gradient = const LinearGradient( colors: [Color(0xFFE74C3C), Color(0xFFC0392B)], ); textColor = const Color(0xFFE74C3C); break; default: gradient = const LinearGradient( colors: [Color(0xFF95A5A6), Color(0xFF7F8C8D)], ); textColor = Colors.grey.shade700; } return Container( padding: const EdgeInsets.all(1.5), decoration: BoxDecoration( gradient: gradient, borderRadius: BorderRadius.circular(20 * scale), ), child: Container( padding: EdgeInsets.symmetric( horizontal: 14 * scale, vertical: 6 * scale, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(18 * scale), ), child: Text( status.toUpperCase(), style: TextStyle( fontSize: 12 * scale, fontWeight: FontWeight.w700, color: textColor, letterSpacing: .6, ), ), ), ); } // ------------------------------------------------------------------ // HEADER CARD // ------------------------------------------------------------------ Widget headerCard(Map invoice, double scale) { return Container( width: double.infinity, padding: EdgeInsets.all(16 * scale), margin: EdgeInsets.only(bottom: 14 * scale), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.blue.shade400, Colors.indigo.shade600], ), borderRadius: BorderRadius.circular(16 * scale), boxShadow: [ BoxShadow( blurRadius: 8 * scale, offset: Offset(0, 3 * scale), color: Colors.indigo.withOpacity(.3), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Invoice #${invoice['invoice_number']}", style: TextStyle( fontSize: 20 * scale, color: Colors.white, fontWeight: FontWeight.bold, ), ), SizedBox(height: 4 * scale), Text( "Date: ${invoice['invoice_date']}", style: TextStyle(color: Colors.white70, fontSize: 13 * scale), ), SizedBox(height: 10 * scale), /// STATUS BADGE statusBadge(invoice['status'] ?? 'Unknown', scale), ], ), ); } // ------------------------------------------------------------------ // SECTION HEADER // ------------------------------------------------------------------ Widget sectionHeader( String title, IconData icon, bool expanded, VoidCallback tap, double scale, ) { return GestureDetector( onTap: tap, child: Container( padding: EdgeInsets.all(12 * scale), margin: EdgeInsets.only(bottom: 8 * scale), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.indigo.shade500, Colors.blue.shade400], ), borderRadius: BorderRadius.circular(12 * scale), boxShadow: [ BoxShadow( blurRadius: 5 * scale, color: Colors.black.withOpacity(.15), offset: Offset(0, 2 * scale), ) ], ), child: Row( children: [ Icon(icon, color: Colors.white, size: 20 * scale), SizedBox(width: 8 * scale), Text( title, style: TextStyle( fontSize: 15 * scale, color: Colors.white, fontWeight: FontWeight.bold, ), ), const Spacer(), AnimatedRotation( turns: expanded ? .5 : 0, duration: const Duration(milliseconds: 250), child: Icon( Icons.keyboard_arrow_down, color: Colors.white, size: 20 * scale, ), ) ], ), ), ); } // ------------------------------------------------------------------ // SECTION BODY // ------------------------------------------------------------------ Widget sectionBody(bool visible, List children, double scale) { return AnimatedCrossFade( duration: const Duration(milliseconds: 250), crossFadeState: visible ? CrossFadeState.showSecond : CrossFadeState.showFirst, firstChild: const SizedBox.shrink(), secondChild: SlideTransition( position: _slideAnimation, child: Container( padding: EdgeInsets.all(14 * scale), margin: EdgeInsets.only(bottom: 10 * scale), decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(12 * scale), boxShadow: [ BoxShadow( blurRadius: 7 * scale, offset: Offset(0, 2 * scale), color: Colors.black.withOpacity(.07), ) ], ), child: Column(children: children), ), ), ); } // ------------------------------------------------------------------ // DETAIL ROW // ------------------------------------------------------------------ Widget detailRow(IconData icon, String label, dynamic value, double scale) { return Padding( padding: EdgeInsets.symmetric(vertical: 5 * scale), child: Row( children: [ Icon(icon, size: 18 * scale, color: Colors.blueGrey), SizedBox(width: 8 * scale), Expanded( flex: 3, child: Text( label, style: TextStyle( color: Colors.grey.shade600, fontSize: 13 * scale, ), ), ), Expanded( flex: 4, child: label == "Status" ? Align( alignment: Alignment.centerRight, child: statusBadge(value ?? 'Unknown', scale), ) : Text( value?.toString() ?? "N/A", textAlign: TextAlign.right, style: TextStyle( fontSize: 14 * scale, fontWeight: FontWeight.bold, ), ), ) ], ), ); } // ------------------------------------------------------------------ // MAIN BUILD // ------------------------------------------------------------------ @override Widget build(BuildContext context) { final invoice = widget.invoice; final width = MediaQuery.of(context).size.width; final scale = (width / 390).clamp(0.85, 1.25); return ListView( padding: EdgeInsets.all(14 * scale), children: [ headerCard(invoice, scale), sectionHeader( "Invoice Summary", Icons.receipt_long, s1, () => setState(() => s1 = !s1), scale, ), sectionBody(s1, [ detailRow(Icons.numbers, "Invoice No", invoice['invoice_number'], scale), detailRow(Icons.calendar_today, "Date", invoice['invoice_date'], scale), detailRow(Icons.label, "Status", invoice['status'], scale), ], scale), sectionHeader( "Customer Details", Icons.person, s2, () => setState(() => s2 = !s2), scale, ), sectionBody(s2, [ detailRow(Icons.person_outline, "Name", invoice['customer_name'], scale), detailRow(Icons.mail, "Email", invoice['customer_email'], scale), detailRow(Icons.phone, "Phone", invoice['customer_mobile'], scale), detailRow(Icons.location_on, "Address", invoice['customer_address'], scale), ], scale), sectionHeader( "Amount Details", Icons.currency_rupee, s3, () => setState(() => s3 = !s3), scale, ), sectionBody(s3, [ detailRow(Icons.money, "Amount", invoice['final_amount'], scale), detailRow(Icons.percent, "GST %", invoice['gst_percent'], scale), detailRow(Icons.percent, "GST Amount", invoice['gst_amount'], scale), detailRow(Icons.summarize, "Total", invoice['final_amount_with_gst'], scale), ], scale), ], ); } }