diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index f69803f..d741512 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"image_picker_ios","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_ios-0.8.13\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_foundation-2.5.4\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_plugin_android_lifecycle","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_plugin_android_lifecycle-2.0.31\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"image_picker_android","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_android-0.8.13+1\\\\","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"path_provider_android","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_android-2.2.19\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_android-2.4.13\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"file_selector_macos","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\file_selector_macos-0.9.4+4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"image_picker_macos","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_macos-0.2.2\\\\","native_build":false,"dependencies":["file_selector_macos"],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_foundation-2.5.4\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"file_selector_linux","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\file_selector_linux-0.9.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"image_picker_linux","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_linux-0.2.2\\\\","native_build":false,"dependencies":["file_selector_linux"],"dev_dependency":false},{"name":"path_provider_linux","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_linux-2.2.1\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_linux-2.4.1\\\\","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false}],"windows":[{"name":"file_selector_windows","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\file_selector_windows-0.9.3+5\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"image_picker_windows","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_windows-0.2.2\\\\","native_build":false,"dependencies":["file_selector_windows"],"dev_dependency":false},{"name":"path_provider_windows","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_windows-2.3.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_windows-2.4.1\\\\","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false}],"web":[{"name":"image_picker_for_web","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_for_web-3.1.1\\\\","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"C:\\\\Users\\\\malia\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_web-2.4.3\\\\","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_macos","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"image_picker","dependencies":["image_picker_android","image_picker_for_web","image_picker_ios","image_picker_linux","image_picker_macos","image_picker_windows"]},{"name":"image_picker_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"image_picker_for_web","dependencies":[]},{"name":"image_picker_ios","dependencies":[]},{"name":"image_picker_linux","dependencies":["file_selector_linux"]},{"name":"image_picker_macos","dependencies":["file_selector_macos"]},{"name":"image_picker_windows","dependencies":["file_selector_windows"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2025-12-03 11:42:36.705771","version":"3.32.2","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"image_picker_ios","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_ios-0.8.13\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\share_plus-10.1.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_foundation-2.5.4\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_plugin_android_lifecycle","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_plugin_android_lifecycle-2.0.31\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"image_picker_android","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_android-0.8.13+1\\\\","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"path_provider_android","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_android-2.2.19\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\share_plus-10.1.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_android-2.4.13\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"file_selector_macos","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\file_selector_macos-0.9.4+4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"image_picker_macos","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_macos-0.2.2\\\\","native_build":false,"dependencies":["file_selector_macos"],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\share_plus-10.1.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_foundation-2.5.4\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"file_selector_linux","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\file_selector_linux-0.9.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"image_picker_linux","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_linux-0.2.2\\\\","native_build":false,"dependencies":["file_selector_linux"],"dev_dependency":false},{"name":"path_provider_linux","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_linux-2.2.1\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\share_plus-10.1.4\\\\","native_build":false,"dependencies":["url_launcher_linux"],"dev_dependency":false},{"name":"shared_preferences_linux","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_linux-2.4.1\\\\","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"url_launcher_linux","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\url_launcher_linux-3.2.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"file_selector_windows","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\file_selector_windows-0.9.3+5\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"image_picker_windows","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_windows-0.2.2\\\\","native_build":false,"dependencies":["file_selector_windows"],"dev_dependency":false},{"name":"path_provider_windows","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_windows-2.3.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\share_plus-10.1.4\\\\","native_build":true,"dependencies":["url_launcher_windows"],"dev_dependency":false},{"name":"shared_preferences_windows","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_windows-2.4.1\\\\","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"url_launcher_windows","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\url_launcher_windows-3.1.5\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"image_picker_for_web","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\image_picker_for_web-3.1.1\\\\","dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\share_plus-10.1.4\\\\","dependencies":["url_launcher_web"],"dev_dependency":false},{"name":"shared_preferences_web","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_web-2.4.3\\\\","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"C:\\\\Users\\\\Divya\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\url_launcher_web-2.4.1\\\\","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_macos","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"image_picker","dependencies":["image_picker_android","image_picker_for_web","image_picker_ios","image_picker_linux","image_picker_macos","image_picker_windows"]},{"name":"image_picker_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"image_picker_for_web","dependencies":[]},{"name":"image_picker_ios","dependencies":[]},{"name":"image_picker_linux","dependencies":["file_selector_linux"]},{"name":"image_picker_macos","dependencies":["file_selector_macos"]},{"name":"image_picker_windows","dependencies":["file_selector_windows"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"share_plus","dependencies":["url_launcher_web","url_launcher_windows","url_launcher_linux"]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2025-12-11 16:10:50.183176","version":"3.38.3","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 0000000..0fdcb48 --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.27.1" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 39514bc..95b3db4 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ windows/flutter/ephemeral/ # Logs *.log + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/assets/Images/K.png b/assets/Images/K.png new file mode 100644 index 0000000..e233a3a Binary files /dev/null and b/assets/Images/K.png differ diff --git a/lib/config/api_config.dart b/lib/config/api_config.dart index 4f285c1..079e0aa 100644 --- a/lib/config/api_config.dart +++ b/lib/config/api_config.dart @@ -1,3 +1,3 @@ class ApiConfig { - static const String baseUrl = "http://10.207.50.74:8000/api"; + static const String baseUrl = "http://192.168.0.100:8000/api"; } diff --git a/lib/main.dart b/lib/main.dart index b1c0a83..3eb265b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -64,7 +64,7 @@ class KentApp extends StatelessWidget { useMaterial3: true, textTheme: GoogleFonts.interTextTheme(), colorScheme: ColorScheme.fromSeed(seedColor: Colors.red), - scaffoldBackgroundColor: const Color(0xfff8f6ff), // your light background + scaffoldBackgroundColor: const Color(0xFFE8F0FF), // your light background appBarTheme: const AppBarTheme( backgroundColor: Colors.indigo, // FIX foregroundColor: Colors.white, // white text + icons diff --git a/lib/providers/invoice_installment_screen.dart b/lib/providers/invoice_installment_screen.dart index d73002f..6e8b04f 100644 --- a/lib/providers/invoice_installment_screen.dart +++ b/lib/providers/invoice_installment_screen.dart @@ -4,7 +4,11 @@ import '../services/invoice_service.dart'; class InvoiceInstallmentScreen extends StatefulWidget { final int invoiceId; - const InvoiceInstallmentScreen({super.key, required this.invoiceId}); + + const InvoiceInstallmentScreen({ + super.key, + required this.invoiceId, + }); @override State createState() => @@ -35,39 +39,188 @@ class _InvoiceInstallmentScreenState extends State { @override Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + return Scaffold( - appBar: AppBar(title: const Text("Installments")), + backgroundColor: Colors.grey.shade100, + appBar: AppBar( + title: const Text("Installments"), + elevation: 1, + ), body: loading ? const Center(child: CircularProgressIndicator()) : installments.isEmpty - ? const Center( - child: Text("Installments not created yet", - style: TextStyle(fontSize: 18)), - ) + ? _buildEmptyState() : ListView.builder( - padding: const EdgeInsets.all(16), + padding: EdgeInsets.symmetric( + horizontal: width * 0.04, + vertical: 16, + ), itemCount: installments.length, itemBuilder: (_, i) { - final inst = installments[i]; - return Card( - child: ListTile( - title: Text( - "Amount: ₹${inst['amount']?.toString() ?? '0'}"), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Date: ${inst['installment_date'] ?? 'N/A'}"), - Text( - "Payment: ${inst['payment_method'] ?? 'N/A'}"), - Text( - "Reference: ${inst['reference_no'] ?? 'N/A'}"), - ], - ), - ), - ); + return InstallmentCard(inst: installments[i]); }, ), ); } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.receipt_long, + size: 70, color: Colors.grey.shade400), + const SizedBox(height: 12), + Text( + "No Installments Created", + style: TextStyle( + fontSize: 18, + color: Colors.grey.shade600, + ), + ), + ], + ), + ); + } +} + +class InstallmentCard extends StatelessWidget { + final Map inst; + + const InstallmentCard({super.key, required this.inst}); + + String getString(key) => inst[key]?.toString() ?? "N/A"; + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + final isTablet = width > 600; + + final padding = isTablet ? 28.0 : 20.0; + final amountSize = isTablet ? 30.0 : 26.0; + + return LayoutBuilder( + builder: (context, constraints) { + return Container( + margin: const EdgeInsets.only(bottom: 18), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(18), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(padding), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Amount + Payment Method Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "₹${getString('amount')}", + style: TextStyle( + fontSize: amountSize, + fontWeight: FontWeight.bold, + letterSpacing: 0.2, + ), + ), + + // Payment Chip + Container( + padding: EdgeInsets.symmetric( + vertical: isTablet ? 8 : 6, + horizontal: isTablet ? 16 : 12, + ), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(50), + ), + child: Text( + getString('payment_method'), + style: TextStyle( + fontWeight: FontWeight.w600, + color: Colors.blue.shade700, + fontSize: isTablet ? 15 : 13.5, + ), + ), + ), + ], + ), + + SizedBox(height: isTablet ? 24 : 18), + + // Responsive Info Rows + buildInfoRow( + Icons.calendar_month, + "Date", + getString("installment_date"), + isTablet + ), + SizedBox(height: isTablet ? 14 : 10), + buildInfoRow( + Icons.confirmation_number, + "Reference", + getString("reference_no"), + isTablet + ), + + SizedBox(height: isTablet ? 24 : 18), + Divider(color: Colors.grey.shade300, thickness: 1), + SizedBox(height: isTablet ? 10 : 6), + + Align( + alignment: Alignment.centerRight, + child: Text( + "Installment #${inst['id'] ?? ''}", + style: TextStyle( + fontSize: isTablet ? 15 : 13, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ); + }, + ); + } + + /// Responsive info row builder + Widget buildInfoRow(IconData icon, String label, String value, bool isTablet) { + return Row( + children: [ + Icon(icon, size: isTablet ? 24 : 20, color: Colors.grey.shade600), + const SizedBox(width: 10), + Text( + "$label:", + style: TextStyle( + fontSize: isTablet ? 17 : 15, + fontWeight: FontWeight.w600, + color: Colors.grey.shade700, + ), + ), + const SizedBox(width: 6), + Expanded( + child: Text( + value, + style: TextStyle( + fontSize: isTablet ? 16 : 15, + color: Colors.grey.shade800, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ); + } } diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart index f51f4cc..e175511 100644 --- a/lib/screens/dashboard_screen.dart +++ b/lib/screens/dashboard_screen.dart @@ -12,250 +12,572 @@ class DashboardScreen extends StatefulWidget { State createState() => _DashboardScreenState(); } -class _DashboardScreenState extends State { +class _DashboardScreenState extends State + with TickerProviderStateMixin { + late AnimationController _scaleCtrl; + late AnimationController _shineCtrl; @override void initState() { super.initState(); + _scaleCtrl = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1200), + lowerBound: 0.97, + upperBound: 1.0, + )..repeat(reverse: true); + + _shineCtrl = AnimationController( + vsync: this, + duration: const Duration(seconds: 2), + )..repeat(); + WidgetsBinding.instance.addPostFrameCallback((_) async { final auth = Provider.of(context, listen: false); - - // STEP 1: Try refresh token BEFORE any API calls await auth.tryRefreshToken(context); - // STEP 2: Now safe to load dashboard final dash = Provider.of(context, listen: false); dash.init(context); await dash.loadSummary(context); - // STEP 3: Load marks AFTER refresh final marks = Provider.of(context, listen: false); marks.init(context); await marks.loadMarks(context); }); } + @override + void dispose() { + _scaleCtrl.dispose(); + _shineCtrl.dispose(); + super.dispose(); + } - void _showAddMarkForm() { + // ============================================================ + // CENTERED ADD MARK POPUP + // ============================================================ + void _showAddMarkForm(double scale) { final markCtrl = TextEditingController(); final originCtrl = TextEditingController(); final destCtrl = TextEditingController(); - showModalBottomSheet( + showDialog( context: context, - isScrollControlled: true, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)), + barrierDismissible: true, builder: (_) { - return Padding( - padding: EdgeInsets.fromLTRB( - 18, - 18, - 18, - MediaQuery.of(context).viewInsets.bottom + 20, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - "Add Mark No", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + return Center( + child: Material( + color: Colors.transparent, + child: Container( + width: MediaQuery.of(context).size.width * 0.88, + padding: EdgeInsets.all(20 * scale), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 16, + offset: const Offset(0, 6), + ), + ], ), - const SizedBox(height: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Add Mark No", + style: TextStyle( + fontSize: 20 * scale, + fontWeight: FontWeight.bold, + color: Colors.indigo, + ), + ), - TextField(controller: markCtrl, decoration: const InputDecoration(labelText: "Mark No")), - const SizedBox(height: 12), + SizedBox(height: 18 * scale), - TextField(controller: originCtrl, decoration: const InputDecoration(labelText: "Origin")), - const SizedBox(height: 12), + _inputField(markCtrl, "Mark No", scale), + SizedBox(height: 12 * scale), + _inputField(originCtrl, "Origin", scale), + SizedBox(height: 12 * scale), + _inputField(destCtrl, "Destination", scale), - TextField(controller: destCtrl, decoration: const InputDecoration(labelText: "Destination")), - const SizedBox(height: 20), + SizedBox(height: 22 * scale), - ElevatedButton( - onPressed: () async { - final mark = markCtrl.text.trim(); - final origin = originCtrl.text.trim(); - final dest = destCtrl.text.trim(); + SizedBox( + width: double.infinity, + child: DecoratedBox( + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Colors.indigo, Colors.deepPurple], + ), + borderRadius: BorderRadius.circular(12 * scale), + ), + child: ElevatedButton( + onPressed: () async { + final mark = markCtrl.text.trim(); + final origin = originCtrl.text.trim(); + final dest = destCtrl.text.trim(); - if (mark.isEmpty || origin.isEmpty || dest.isEmpty) { - ScaffoldMessenger.of(context) - .showSnackBar(const SnackBar(content: Text("All fields required"))); - return; - } + if (mark.isEmpty || origin.isEmpty || dest.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("All fields are required")), + ); + return; + } - final provider = Provider.of(context, listen: false); - final res = await provider.addMark(context, mark, origin, dest); + final provider = + Provider.of(context, listen: false); + final res = + await provider.addMark(context, mark, origin, dest); - if (res['success'] == true) { - Navigator.pop(context); - } else { - final msg = res['message'] ?? "Failed"; - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); - } - }, - child: const Text("Submit"), + if (res['success'] == true) { + await Provider.of(context, + listen: false) + .loadMarks(context); + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(res['message'] ?? "Failed")), + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + padding: EdgeInsets.symmetric(vertical: 14 * scale), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12 * scale), + ), + ), + child: Text( + "Submit", + style: TextStyle( + fontSize: 16 * scale, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ), + ), + + SizedBox(height: 6 * scale), + ], ), - ], + ), ), ); }, ); } + // ============================================================ + // MAIN UI (Responsive) + // ============================================================ @override Widget build(BuildContext context) { final auth = Provider.of(context); final dash = Provider.of(context); final marks = Provider.of(context); - final name = auth.user?['customer_name'] ?? 'User'; - - if (dash.loading) { - return const Center(child: CircularProgressIndicator()); - } + final name = auth.user?['customer_name'] ?? "User"; + final width = MediaQuery.of(context).size.width; + final scale = (width / 430).clamp(0.88, 1.08); return SingleChildScrollView( - padding: const EdgeInsets.all(18), + padding: EdgeInsets.all(16 * scale), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // HEADER - Text( - "Welcome, $name 👋", - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600), + // WELCOME CARD WITH ANIMATION + AnimatedBuilder( + animation: _scaleCtrl, + builder: (_, __) { + return Transform.scale( + scale: _scaleCtrl.value, + child: Stack( + children: [ + Container( + padding: EdgeInsets.all(20 * scale), + width: double.infinity, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFFFFA726), Color(0xFFFFEB3B)], + ), + borderRadius: BorderRadius.circular(18 * scale), + ), + child: Text( + "Welcome, $name 👋", + style: TextStyle( + fontSize: 24 * scale, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + + // Shine Animation + AnimatedBuilder( + animation: _shineCtrl, + builder: (_, __) { + final left = _shineCtrl.value * + (MediaQuery.of(context).size.width + 140) - + 140; + + return Positioned( + left: left, + top: -40 * scale, + bottom: -40 * scale, + child: Transform.rotate( + angle: -0.45, + child: Container( + width: 120 * scale, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.white.withOpacity(0), + Colors.white.withOpacity(.3), + Colors.white.withOpacity(0), + ], + ), + ), + ), + ), + ); + }, + ) + ], + ), + ); + }, ), - const SizedBox(height: 20), - // ORDER SUMMARY - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _statBox("Active", dash.activeOrders, Colors.blue), - _statBox("In Transit", dash.inTransitOrders, Colors.orange), - _statBox("Delivered", dash.deliveredOrders, Colors.green), - ], + SizedBox(height: 18 * scale), + + _summarySection( + dash, + rawAmount: "₹${dash.totalRaw ?? 0}", + scale: scale, ), - const SizedBox(height: 20), - _valueCard("Total Value", dash.totalValue), - const SizedBox(height: 10), - _valueCard("Raw Amount", "₹${dash.totalRaw.toStringAsFixed(2)}"), - const SizedBox(height: 30), + SizedBox(height: 26 * scale), - // ADD + VIEW ALL BUTTONS SIDE BY SIDE - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ElevatedButton.icon( - icon: const Icon(Icons.add), - label: const Text("Add Mark No"), - onPressed: _showAddMarkForm, - ), - - if (marks.marks.length > 0) - TextButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const MarkListScreen()), - ); - }, - child: const Text( - "View All →", - style: TextStyle( - fontSize: 16, - color: Colors.indigo, - fontWeight: FontWeight.w600, + // MARK LIST CARD + Container( + padding: EdgeInsets.all(16 * scale), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(14 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(.06), + blurRadius: 8 * scale, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Add Mark Button + Center( + child: SizedBox( + width: width * 0.95, + child: DecoratedBox( + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Colors.indigo, Colors.deepPurple], + ), + borderRadius: BorderRadius.circular(12 * scale), + ), + child: ElevatedButton( + onPressed: () => _showAddMarkForm(scale), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + padding: EdgeInsets.symmetric(vertical: 14 * scale), + ), + child: Text( + "Add Mark No", + style: TextStyle( + fontSize: 16 * scale, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), ), ), ), + + SizedBox(height: 16 * scale), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Latest Mark Numbers", + style: TextStyle( + fontSize: 18 * scale, + fontWeight: FontWeight.bold, + ), + ), + if (marks.marks.isNotEmpty) + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const MarkListScreen()), + ); + }, + child: Text( + "View All →", + style: TextStyle( + fontSize: 15 * scale, + color: Colors.indigo, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + + SizedBox(height: 12 * scale), + + // Scrollable Mark List + SizedBox( + height: 300 * scale, + child: Scrollbar( + thumbVisibility: true, + child: ListView.builder( + padding: EdgeInsets.zero, + itemCount: marks.marks.length, + itemBuilder: (context, i) => + _markTile(marks.marks[i], scale), + ), + ), + ), + ], + ), + ), + + SizedBox(height: 40 * scale), + ], + ), + ); + } + + // ============================================================ + // SUMMARY SECTION + // ============================================================ + Widget _summarySection(dash, + {required String rawAmount, required double scale}) { + return Container( + padding: EdgeInsets.all(14 * scale), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(14 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(.06), + blurRadius: 8 * scale, + ), + ], + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _summaryTile("Active Orders", dash.activeOrders ?? 0, + Colors.blue, Icons.inventory, scale), + _summaryTile("In Transit", dash.inTransitOrders ?? 0, + Colors.orange, Icons.local_shipping, scale), ], ), - const SizedBox(height: 20), + SizedBox(height: 12 * scale), - // MARK LIST (only 10 latest) - const Text( - "Latest Mark Numbers", - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _summaryTile("Delivered", dash.deliveredOrders ?? 0, + Colors.green, Icons.check_circle, scale), + _summaryTile("Total Value", "₹${dash.totalValue ?? 0}", + Colors.teal, Icons.money, scale), + ], ), - const SizedBox(height: 10), + SizedBox(height: 16 * scale), - if (marks.loading) - const Center(child: CircularProgressIndicator()) - else - Column( - children: List.generate( - marks.marks.length > 10 ? 10 : marks.marks.length, - (i) { - final m = marks.marks[i]; - return Card( - child: ListTile( - title: Text(m['mark_no']), - subtitle: Text("${m['origin']} → ${m['destination']}"), - trailing: Text( - m['status'], - style: const TextStyle(color: Colors.indigo), - ), - ), - ); - }, + _rawAmountTile("Raw Amount", rawAmount, scale), + ], + ), + ); + } + + Widget _summaryTile(String title, dynamic value, Color color, + IconData icon, double scale) { + return Container( + width: MediaQuery.of(context).size.width * 0.41, + padding: EdgeInsets.all(14 * scale), + decoration: BoxDecoration( + color: color.withOpacity(.12), + borderRadius: BorderRadius.circular(14 * scale), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + value.toString(), + style: TextStyle( + fontSize: 20 * scale, + fontWeight: FontWeight.bold, + color: color, + ), + ), + + SizedBox(height: 2 * scale), + + SizedBox( + width: 100 * scale, + child: Text( + title, + style: TextStyle( + fontSize: 13 * scale, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + Icon(icon, size: 28 * scale, color: color), + ], + ), + ); + } + + Widget _rawAmountTile(String title, String value, double scale) { + return Container( + padding: EdgeInsets.all(14 * scale), + decoration: BoxDecoration( + color: Colors.deepPurple.shade50, + borderRadius: BorderRadius.circular(14 * scale), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + value, + style: TextStyle( + fontSize: 20 * scale, + color: Colors.deepPurple, + fontWeight: FontWeight.bold, + ), + ), + Text( + title, + style: TextStyle( + fontSize: 13 * scale, + color: Colors.grey, + ), + ), + ], + ), + Icon(Icons.currency_rupee, + size: 28 * scale, color: Colors.deepPurple), + ], + ), + ); + } + + // ============================================================ + // MARK TILE + // ============================================================ + Widget _markTile(dynamic m, double scale) { + return Container( + margin: EdgeInsets.only(bottom: 12 * scale), + padding: EdgeInsets.all(14 * scale), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFF42A5F5), Color(0xFF80DEEA)], + ), + borderRadius: BorderRadius.circular(14 * scale), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + m['mark_no'], + style: TextStyle( + fontSize: 18 * scale, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + Text( + "${m['origin']} → ${m['destination']}", + style: TextStyle( + fontSize: 14 * scale, + color: Colors.white, + ), + ), + ], + ), + + Container( + padding: EdgeInsets.symmetric( + horizontal: 10 * scale, + vertical: 6 * scale, + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(.2), + borderRadius: BorderRadius.circular(8 * scale), + ), + child: Text( + m['status'], + style: TextStyle( + fontSize: 12 * scale, + color: Colors.white, + fontWeight: FontWeight.bold, ), ), - - const SizedBox(height: 30), - ], - ), - ); - } - - // UI WIDGETS - Widget _statBox(String title, int value, Color color) { - return Container( - width: 110, - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: color.withOpacity(0.15), - borderRadius: BorderRadius.circular(12), - ), - child: Column( - children: [ - Text(value.toString(), - style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), - const SizedBox(height: 4), - Text(title, style: const TextStyle(fontSize: 14)), - ], - ), - ); - } - - Widget _valueCard(String title, String value) { - return Container( - width: double.infinity, - padding: const EdgeInsets.all(18), - decoration: BoxDecoration( - color: Colors.indigo.shade50, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(title, - style: const TextStyle( - fontSize: 14, fontWeight: FontWeight.w600, color: Colors.grey)), - const SizedBox(height: 6), - Text( - value, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.indigo, - ), ), ], ), ); } + + // ============================================================ + // INPUT FIELD + // ============================================================ + Widget _inputField(TextEditingController controller, String label, double scale) { + return TextField( + controller: controller, + style: TextStyle(fontSize: 15 * scale), + decoration: InputDecoration( + labelText: label, + labelStyle: TextStyle(fontSize: 14 * scale), + filled: true, + fillColor: Colors.lightBlue.shade50, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12 * scale), + borderSide: BorderSide.none, + ), + contentPadding: + EdgeInsets.symmetric(horizontal: 14 * scale, vertical: 14 * scale), + ), + ); + } } diff --git a/lib/screens/edit_profile_screen.dart b/lib/screens/edit_profile_screen.dart index c8aa497..71d64ae 100644 --- a/lib/screens/edit_profile_screen.dart +++ b/lib/screens/edit_profile_screen.dart @@ -20,8 +20,8 @@ class _EditProfileScreenState extends State { @override void initState() { super.initState(); - final profile = Provider.of( - context, listen: false).profile; + final profile = + Provider.of(context, listen: false).profile; nameCtrl.text = profile?.customerName ?? ''; companyCtrl.text = profile?.companyName ?? ''; @@ -32,8 +32,7 @@ class _EditProfileScreenState extends State { } Future _submit() async { - final provider = - Provider.of(context, listen: false); + final provider = Provider.of(context, listen: false); final data = { "customer_name": nameCtrl.text, @@ -44,14 +43,16 @@ class _EditProfileScreenState extends State { "pincode": pincodeCtrl.text, }; - final success = - await provider.sendProfileUpdateRequest(context, data); + final success = await provider.sendProfileUpdateRequest(context, data); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(success + content: Text( + success ? "Request submitted. Wait for admin approval." - : "Failed to submit request")), + : "Failed to submit request", + ), + ), ); if (success) Navigator.pop(context); @@ -59,38 +60,179 @@ class _EditProfileScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text("Edit Profile")), - body: Padding( - padding: const EdgeInsets.all(18), - child: Column( - children: [ - _field("Name", nameCtrl), - _field("Company", companyCtrl), - _field("Email", emailCtrl), - _field("Mobile", mobileCtrl), - _field("Address", addressCtrl), - _field("Pincode", pincodeCtrl), + final darkBlue = const Color(0xFF003B73); + final screenWidth = MediaQuery.of(context).size.width; - const SizedBox(height: 20), - ElevatedButton( - onPressed: _submit, - child: const Text("Submit Update Request"), - ) - ], + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xFFB3E5FC), Color(0xFFE1F5FE)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Scaffold( + backgroundColor: Colors.transparent, + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + double scale = (screenWidth / 390).clamp(0.75, 1.3); + + return SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 18 * scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 8 * scale), + + /// BACK BUTTON + IconButton( + icon: Icon(Icons.arrow_back_ios_new, size: 22 * scale), + color: Colors.red, + onPressed: () => Navigator.pop(context), + ), + + SizedBox(height: 10 * scale), + + /// TITLE + Center( + child: Text( + "Edit Profile", + style: TextStyle( + color: darkBlue, + fontSize: 26 * scale, + fontWeight: FontWeight.bold, + ), + ), + ), + + SizedBox(height: 20 * scale), + + /// RESPONSIVE CENTERED FORM CARD + Center( + child: Container( + width: screenWidth > 650 ? 550 : double.infinity, + padding: EdgeInsets.all(24 * scale), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.95), + borderRadius: BorderRadius.circular(20 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black12.withOpacity(0.15), + blurRadius: 18 * scale, + offset: Offset(0, 6 * scale), + ), + ], + ), + child: Column( + children: [ + _buildField("Full Name", nameCtrl, scale), + _buildField("Company Name", companyCtrl, scale), + _buildField("Email Address", emailCtrl, scale), + _buildField("Mobile Number", mobileCtrl, scale), + _buildField("Address", addressCtrl, scale), + _buildField("Pincode", pincodeCtrl, scale), + + SizedBox(height: 25 * scale), + + /// RESPONSIVE GRADIENT SUBMIT BUTTON + SizedBox( + width: double.infinity, + height: 50 * scale, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14 * scale), + gradient: const LinearGradient( + colors: [ + Color(0xFF0052D4), + Color(0xFF4364F7), + Color(0xFF6FB1FC), + ], + ), + boxShadow: [ + BoxShadow( + color: Colors.blueAccent.withOpacity(0.4), + blurRadius: 10 * scale, + offset: Offset(0, 4 * scale), + ), + ], + ), + child: ElevatedButton( + onPressed: _submit, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(14 * scale), + ), + ), + child: Text( + "Submit Update Request", + style: TextStyle( + fontSize: 17 * scale, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ) + ], + ), + ), + ), + + SizedBox(height: 30 * scale), + ], + ), + ); + }, + ), ), ), ); } - Widget _field(String title, TextEditingController ctrl) { + /// Reusable Responsive TextField Builder + Widget _buildField( + String label, TextEditingController ctrl, double scale) { return Padding( - padding: const EdgeInsets.only(bottom: 14), - child: TextField( - controller: ctrl, - decoration: InputDecoration( - labelText: title, - border: OutlineInputBorder(), + padding: EdgeInsets.only(bottom: 18 * scale), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black12.withOpacity(0.06), + blurRadius: 10 * scale, + offset: Offset(0, 3 * scale), + ), + ], + ), + child: TextField( + controller: ctrl, + style: TextStyle(fontSize: 15 * scale), + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + labelText: label, + labelStyle: TextStyle( + color: const Color(0xFF003B73), + fontSize: 14 * scale, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16 * scale), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16 * scale), + borderSide: BorderSide( + color: const Color(0xFF003B73), + width: 2 * scale, + ), + ), + ), ), ), ); diff --git a/lib/screens/invoice_detail_screen.dart b/lib/screens/invoice_detail_screen.dart index 8a8f19e..a24a4ad 100644 --- a/lib/screens/invoice_detail_screen.dart +++ b/lib/screens/invoice_detail_screen.dart @@ -1,6 +1,12 @@ +import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:pdf/widgets.dart' as pw; +import 'package:share_plus/share_plus.dart'; + import '../services/dio_client.dart'; import '../services/invoice_service.dart'; +import '../widgets/invoice_detail_view.dart'; class InvoiceDetailScreen extends StatefulWidget { final int invoiceId; @@ -20,149 +26,127 @@ class _InvoiceDetailScreenState extends State { load(); } + // ------------------------------------------------------- + // ⭐ LOAD INVOICE FROM API + // ------------------------------------------------------- Future load() async { final service = InvoiceService(DioClient.getInstance(context)); - final res = await service.getInvoiceDetails(widget.invoiceId); + try { + final res = await service.getInvoiceDetails(widget.invoiceId); - if (res['success'] == true) { - invoice = res['invoice'] ?? {}; + if (res['success'] == true) { + invoice = res['invoice'] ?? {}; + } else { + invoice = {}; + } + } catch (e) { + invoice = {}; + } finally { + if (mounted) setState(() => loading = false); } - - loading = false; - setState(() {}); } - /// ---------- REUSABLE ROW ---------- - Widget row(String label, dynamic value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(label, style: const TextStyle(fontSize: 14, color: Colors.grey)), - Expanded( - child: Text( - value?.toString().isNotEmpty == true ? value.toString() : "N/A", - textAlign: TextAlign.end, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - ), + // ------------------------------------------------------- + // ⭐ GENERATE + SAVE PDF TO DOWNLOADS FOLDER + // (No Permission Needed) + // ------------------------------------------------------- + Future generatePDF() async { + final pdf = pw.Document(); + + pdf.addPage( + pw.Page( + build: (context) => pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Text( + "INVOICE DETAILS", + style: pw.TextStyle(fontSize: 26, fontWeight: pw.FontWeight.bold), ), - ), - ], + pw.SizedBox(height: 20), + + pw.Text("Invoice ID: ${invoice['id'] ?? '-'}"), + pw.Text("Amount: ₹${invoice['amount'] ?? '-'}"), + pw.Text("Status: ${invoice['status'] ?? '-'}"), + pw.Text("Date: ${invoice['date'] ?? '-'}"), + pw.Text("Customer: ${invoice['customer_name'] ?? '-'}"), + ], + ), ), ); + + // ⭐ SAFEST WAY (Android 10–14) + final downloadsDir = await getDownloadsDirectory(); + + final filePath = "${downloadsDir!.path}/invoice_${invoice['id']}.pdf"; + final file = File(filePath); + + await file.writeAsBytes(await pdf.save()); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("PDF saved to Downloads:\n$filePath")), + ); + } + + return file; + } + + // ------------------------------------------------------- + // ⭐ SHARE THE SAVED PDF FILE + // ------------------------------------------------------- + Future sharePDF() async { + final file = await generatePDF(); + + await Share.shareXFiles( + [XFile(file.path)], + text: "Invoice Details", + ); } @override Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + final scale = (width / 430).clamp(0.88, 1.08); + return Scaffold( - appBar: AppBar(title: const Text("Invoice Details")), + appBar: AppBar( + title: Text( + "Invoice Details", + style: TextStyle( + fontSize: 18 * scale, + fontWeight: FontWeight.w600, + ), + ), + + // ⭐ PDF + SHARE BUTTONS + actions: [ + IconButton( + icon: const Icon(Icons.picture_as_pdf), + onPressed: generatePDF, + ), + IconButton( + icon: const Icon(Icons.share), + onPressed: sharePDF, + ), + ], + ), + body: loading ? const Center(child: CircularProgressIndicator()) - - /// ================ INVOICE DATA ================ - : Padding( - padding: const EdgeInsets.all(16), - child: ListView( - children: [ - /// -------- Invoice Summary -------- - const Text( - "Invoice Summary", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - - row("Invoice No", invoice['invoice_number']), - row("Invoice Date", invoice['invoice_date']), - row("Due Date", invoice['due_date']), - row("Status", invoice['status']), - - const Divider(height: 30), - - /// -------- Customer Details -------- - const Text( - "Customer Details", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - - row("Name", invoice['customer_name']), - row("Company", invoice['company_name']), - row("Email", invoice['customer_email']), - row("Mobile", invoice['customer_mobile']), - row("Address", invoice['customer_address']), - row("Pincode", invoice['pincode']), - - const Divider(height: 30), - - /// -------- Amounts & Taxes -------- - const Text( - "Amounts", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - - row("Final Amount", invoice['final_amount']), - row("Tax Type", invoice['tax_type']), - row("GST %", invoice['gst_percent']), - row("GST Amount", invoice['gst_amount']), - row("Final with GST", invoice['final_amount_with_gst']), - - const Divider(height: 30), - - /// -------- Payment Details -------- - const Text( - "Payment Details", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - - row("Payment Method", invoice['payment_method']), - row("Reference No", invoice['reference_no']), - row("Notes", invoice['notes']), - - const Divider(height: 30), - - /// -------- PDF -------- - if (invoice['pdf_path'] != null) - ElevatedButton.icon( - icon: const Icon(Icons.picture_as_pdf), - label: const Text("Download PDF"), - onPressed: () {}, - style: - ElevatedButton.styleFrom(backgroundColor: Colors.red), - ), - - const SizedBox(height: 20), - - /// -------- Invoice Items -------- - if (invoice['items'] != null) - const Text( - "Invoice Items", - style: - TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - - ...List.generate(invoice['items']?.length ?? 0, (i) { - final item = invoice['items'][i]; - return Card( - child: ListTile( - title: Text(item['description'] ?? "Item"), - subtitle: Text("Qty: ${item['qty'] ?? 0}"), - trailing: Text( - "₹${item['ttl_amount'] ?? 0}", - style: const TextStyle( - fontWeight: FontWeight.bold, - color: Colors.indigo), - ), - ), - ); - }), - ], + : invoice.isEmpty + ? Center( + child: Text( + "No Invoice Data Found", + style: TextStyle( + fontSize: 18 * scale, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), ), + ) + : Padding( + padding: EdgeInsets.all(12 * scale), + child: InvoiceDetailView(invoice: invoice), ), ); } diff --git a/lib/screens/invoice_screen.dart b/lib/screens/invoice_screen.dart index 2838db9..3314638 100644 --- a/lib/screens/invoice_screen.dart +++ b/lib/screens/invoice_screen.dart @@ -2,11 +2,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/invoice_installment_screen.dart'; import '../providers/invoice_provider.dart'; -import '../services/dio_client.dart'; -import '../services/invoice_service.dart'; import 'invoice_detail_screen.dart'; - class InvoiceScreen extends StatefulWidget { const InvoiceScreen({super.key}); @@ -15,10 +12,11 @@ class InvoiceScreen extends StatefulWidget { } class _InvoiceScreenState extends State { + String searchQuery = ""; + @override void initState() { super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { Provider.of(context, listen: false) .loadInvoices(context); @@ -29,78 +27,263 @@ class _InvoiceScreenState extends State { Widget build(BuildContext context) { final provider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + final scale = (width / 430).clamp(0.88, 1.08); + if (provider.loading) { return const Center(child: CircularProgressIndicator()); } - if (provider.invoices.isEmpty) { - return const Center( - child: Text("No invoices found", style: TextStyle(fontSize: 18))); - } + // 🔍 Filter invoices based on search query + final filteredInvoices = provider.invoices.where((inv) { + final q = searchQuery.toLowerCase(); + return inv['invoice_number'].toString().toLowerCase().contains(q) || + inv['invoice_date'].toString().toLowerCase().contains(q) || + inv['formatted_amount'].toString().toLowerCase().contains(q) || + inv['status'].toString().toLowerCase().contains(q); + }).toList(); - return ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: provider.invoices.length, - itemBuilder: (_, i) { - final inv = provider.invoices[i]; + return Column( + children: [ + // 🔍 SEARCH BAR + Container( + margin: EdgeInsets.fromLTRB(16 * scale, 16 * scale, 16 * scale, 8 * scale), + padding: EdgeInsets.symmetric(horizontal: 14 * scale), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(14 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black12.withOpacity(0.08), + blurRadius: 8 * scale, + offset: Offset(0, 3 * scale), + ), + ], + ), + child: TextField( + onChanged: (v) => setState(() => searchQuery = v), + style: TextStyle(fontSize: 14 * scale), + decoration: InputDecoration( + icon: Icon(Icons.search, size: 22 * scale), + hintText: "Search Invoice Number, Date, Amount...", + border: InputBorder.none, + ), + ), + ), - return Card( - margin: const EdgeInsets.only(bottom: 12), - child: Padding( - padding: const EdgeInsets.all(14), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Invoice ${inv['invoice_number'] ?? 'N/A'}", - style: const TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), + // 📄 LIST OF INVOICES + Expanded( + child: filteredInvoices.isEmpty + ? Center( + child: Text( + "No invoices found", + style: TextStyle( + fontSize: 18 * scale, + fontWeight: FontWeight.w600, + ), + ), + ) + : ListView.builder( + padding: EdgeInsets.all(16 * scale), + itemCount: filteredInvoices.length, + itemBuilder: (_, i) { + final inv = filteredInvoices[i]; + + return Card( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16 * scale), ), - - const SizedBox(height: 6), - Text("Date: ${inv['invoice_date'] ?? 'N/A'}"), - Text("Status: ${inv['status'] ?? 'N/A'}"), - Text("Amount: ₹${inv['formatted_amount'] ?? '0'}"), - - const SizedBox(height: 10), - - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + elevation: 3, + margin: EdgeInsets.only(bottom: 14 * scale), + child: Stack( children: [ - OutlinedButton( - child: const Text("Invoice Details"), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => InvoiceDetailScreen( - invoiceId: inv['invoice_id'], + Padding( + padding: EdgeInsets.all(16 * scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// Invoice Number + Text( + "Invoice ${inv['invoice_number'] ?? 'N/A'}", + style: TextStyle( + fontSize: 20 * scale, + fontWeight: FontWeight.bold, + color: Colors.blue, ), ), - ); - }, + + SizedBox(height: 8 * scale), + + /// Date + Amount + Text( + "Date: ${inv['invoice_date'] ?? 'N/A'}", + style: TextStyle(fontSize: 15 * scale), + ), + Text( + "Amount: ₹${inv['formatted_amount'] ?? '0'}", + style: TextStyle(fontSize: 15 * scale), + ), + + SizedBox(height: 16 * scale), + + /// BUTTONS ROW + Row( + children: [ + Expanded( + child: GradientButton( + text: "Invoice Details", + fontSize: 15 * scale, + radius: 12 * scale, + padding: 14 * scale, + gradient: const LinearGradient( + colors: [ + Color(0xFF1976D2), + Color(0xFF42A5F5), + ], + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + InvoiceDetailScreen( + invoiceId: + inv['invoice_id']), + ), + ); + }, + ), + ), + + SizedBox(width: 12 * scale), + + Expanded( + child: GradientButton( + text: "Installments", + fontSize: 15 * scale, + radius: 12 * scale, + padding: 14 * scale, + gradient: const LinearGradient( + colors: [ + Color(0xFF43A047), + Color(0xFF81C784), + ], + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + InvoiceInstallmentScreen( + invoiceId: + inv['invoice_id']), + ), + ); + }, + ), + ), + ], + ), + ], + ), ), - OutlinedButton( - child: const Text("Installments"), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => InvoiceInstallmentScreen( - invoiceId: inv['invoice_id'], - ), + /// STATUS BADGE + Positioned( + right: 12 * scale, + top: 12 * scale, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10 * scale, + vertical: 6 * scale, + ), + decoration: BoxDecoration( + color: _getStatusColor(inv['status']), + borderRadius: BorderRadius.circular(12 * scale), + ), + child: Text( + inv['status'] ?? 'N/A', + style: TextStyle( + color: Colors.white, + fontSize: 12 * scale, + fontWeight: FontWeight.bold, ), - ); - }, + ), + ), ), ], ), - ], - ), + ); + }, ), - ); - }, + ), + ], + ); + } +} + +/// Status Color Helper +Color _getStatusColor(String? status) { + switch (status?.toLowerCase()) { + case 'pending': + return Colors.orange; + case 'in transit': + return Colors.blue; + case 'overdue': + return Colors.redAccent; + case 'paid': + return Colors.green; + default: + return Colors.grey; + } +} + +/// ------------------------------------------------------- +/// RESPONSIVE GRADIENT BUTTON +/// ------------------------------------------------------- +class GradientButton extends StatelessWidget { + final String text; + final Gradient gradient; + final VoidCallback onTap; + + final double fontSize; + final double padding; + final double radius; + + const GradientButton({ + super.key, + required this.text, + required this.gradient, + required this.onTap, + required this.fontSize, + required this.padding, + required this.radius, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + borderRadius: BorderRadius.circular(radius), + onTap: onTap, + child: Ink( + decoration: BoxDecoration( + gradient: gradient, + borderRadius: BorderRadius.circular(radius), + ), + child: Container( + padding: EdgeInsets.symmetric(vertical: padding), + alignment: Alignment.center, + child: Text( + text, + style: TextStyle( + color: Colors.white, + fontSize: fontSize, + fontWeight: FontWeight.w600, + ), + ), + ), + ), ); } } diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart index 7977597..a17e379 100644 --- a/lib/screens/login_screen.dart +++ b/lib/screens/login_screen.dart @@ -26,27 +26,27 @@ class _LoginScreenState extends State { Future _login() async { final auth = Provider.of(context, listen: false); - final loginId = cLoginId.text.trim(); - final password = cPassword.text.trim(); + final id = cLoginId.text.trim(); + final pass = cPassword.text.trim(); - if (loginId.isEmpty || password.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please enter login id and password')), - ); + if (id.isEmpty || pass.isEmpty) { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar(content: Text("Please fill all fields"))); return; } - final res = await auth.login(context, loginId, password); + final res = await auth.login(context, id, pass); if (res['success'] == true) { if (!mounted) return; - Navigator.of(context).pushReplacement( + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (_) => const MainBottomNav()), ); } else { - final msg = res['message']?.toString() ?? 'Login failed'; - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(res['message'] ?? "Login failed")), + ); } } @@ -55,38 +55,113 @@ class _LoginScreenState extends State { final auth = Provider.of(context); final width = MediaQuery.of(context).size.width; - return Scaffold( - appBar: AppBar( - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (_) => const WelcomeScreen()), - ); - }, - ), - title: const Text('Login'), - ), + /// ⭐ RESPONSIVE SCALE + final scale = (width / 390).clamp(0.85, 1.25); - body: Padding( - padding: EdgeInsets.symmetric(horizontal: width * 0.06, vertical: 20), - child: Column( - children: [ - RoundedInput( - controller: cLoginId, - hint: 'Email / Mobile / Customer ID', + return Scaffold( + backgroundColor: const Color(0xFFE8F0FF), + + body: Stack( + children: [ + /// 🔵 Floating Back Button (Responsive Position + Size) + Positioned( + top: 40 * scale, + left: 12 * scale, + child: Material( + elevation: 6 * scale, + color: Colors.indigo.shade700, + shape: const CircleBorder(), + child: InkWell( + onTap: () { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => const WelcomeScreen()), + ); + }, + child: Padding( + padding: EdgeInsets.all(10 * scale), + child: Icon(Icons.arrow_back, + color: Colors.white, size: 20 * scale), + ), + ), ), - const SizedBox(height: 12), - RoundedInput( - controller: cPassword, - hint: 'Password', - obscure: true, + ), + + /// 📦 Center White Card (Responsive) + Center( + child: Container( + width: width * 0.87, + padding: EdgeInsets.symmetric( + vertical: 28 * scale, + horizontal: 20 * scale, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(22 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 18 * scale, + spreadRadius: 1, + offset: Offset(0, 6 * scale), + ), + ], + ), + + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Login", + style: TextStyle( + fontSize: 26 * scale, + fontWeight: FontWeight.bold, + color: Colors.indigo.shade700, + ), + ), + + SizedBox(height: 25 * scale), + + /// Login ID Input + Container( + decoration: BoxDecoration( + color: const Color(0xFFD8E7FF), + borderRadius: BorderRadius.circular(14 * scale), + ), + child: RoundedInput( + controller: cLoginId, + hint: "Email / Mobile / Customer ID", + ), + ), + + SizedBox(height: 16 * scale), + + /// Password Input + Container( + decoration: BoxDecoration( + color: const Color(0xFFD8E7FF), + borderRadius: BorderRadius.circular(14 * scale), + ), + child: RoundedInput( + controller: cPassword, + hint: "Password", + obscure: true, + ), + ), + + SizedBox(height: 25 * scale), + + /// Login Button + PrimaryButton( + label: "Login", + onTap: _login, + busy: auth.loading, + ), + ], + ), ), - const SizedBox(height: 18), - PrimaryButton(label: 'Login', onTap: _login, busy: auth.loading), - ], - ), + ), + ], ), ); } diff --git a/lib/screens/main_bottom_nav.dart b/lib/screens/main_bottom_nav.dart index 2c6b519..ccb7b9e 100644 --- a/lib/screens/main_bottom_nav.dart +++ b/lib/screens/main_bottom_nav.dart @@ -17,9 +17,7 @@ class MainBottomNavState extends State { int _currentIndex = 0; void setIndex(int index) { - setState(() { - _currentIndex = index; - }); + setState(() => _currentIndex = index); } final List _screens = const [ @@ -30,26 +28,166 @@ class MainBottomNavState extends State { SettingsScreen(), ]; + final List _icons = const [ + Icons.dashboard_outlined, + Icons.shopping_bag_outlined, + Icons.receipt_long_outlined, + Icons.chat_bubble_outline, + Icons.settings_outlined, + ]; + + final List _labels = const [ + "Dashboard", + "Orders", + "Invoice", + "Chat", + "Settings", + ]; + @override Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + final scale = (width / 390).clamp(0.85, 1.20); + + final containerPadding = 8 * scale; + return Scaffold( appBar: const MainAppBar(), body: _screens[_currentIndex], - bottomNavigationBar: BottomNavigationBar( - currentIndex: _currentIndex, - selectedItemColor: Colors.red, - unselectedItemColor: Colors.black, - type: BottomNavigationBarType.fixed, - onTap: (index) { - setState(() => _currentIndex = index); - }, - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.dashboard_outlined), label: "Dashboard"), - BottomNavigationBarItem(icon: Icon(Icons.shopping_bag_outlined), label: "Orders"), - BottomNavigationBarItem(icon: Icon(Icons.receipt_long_outlined), label: "Invoice"), - BottomNavigationBarItem(icon: Icon(Icons.chat_bubble_outline), label: "Chat"), - BottomNavigationBarItem(icon: Icon(Icons.settings_outlined), label: "Settings"), - ], + + bottomNavigationBar: Padding( + padding: EdgeInsets.only( + left: 10 * scale, + right: 10 * scale, + bottom: 10 * scale, + ), + child: LayoutBuilder( + builder: (context, constraints) { + final totalWidth = constraints.maxWidth; + + // inner width (after padding) + final contentWidth = totalWidth - (containerPadding * 2); + + final safeContentWidth = + contentWidth > 0 ? contentWidth : totalWidth; + + final itemWidth = safeContentWidth / _icons.length; + + final indicatorWidth = 70 * scale; + final indicatorHeight = 70 * scale; + + double left = (_currentIndex * itemWidth) + + (itemWidth / 2) - + (indicatorWidth / 2); + + /// ⭐ FIX: explicitly convert clamp to double + final double safeLeft = left + .clamp(0, safeContentWidth - indicatorWidth) + .toDouble(); + + return Container( + height: 100 * scale, + padding: EdgeInsets.symmetric(horizontal: containerPadding), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(28 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 20 * scale, + offset: Offset(0, 8 * scale), + ) + ], + ), + child: Stack( + children: [ + /// ⭐ Indicator - safe positioned + AnimatedPositioned( + duration: const Duration(milliseconds: 350), + curve: Curves.easeOut, + top: 10 * scale, + left: safeLeft, + child: Container( + width: indicatorWidth, + height: indicatorHeight, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [ + Color(0xFF4F46E5), + Color(0xFF06B6D4), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(20 * scale), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + _icons[_currentIndex], + size: 22 * scale, + color: Colors.white, + ), + SizedBox(height: 2 * scale), + Text( + _labels[_currentIndex], + style: TextStyle( + fontSize: 10 * scale, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ), + ), + + /// ⭐ Icon Row + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: List.generate(_icons.length, (index) { + final selected = index == _currentIndex; + + return GestureDetector( + onTap: () => setIndex(index), + child: SizedBox( + width: itemWidth, + height: 100 * scale, + child: Center( + child: AnimatedOpacity( + duration: const Duration(milliseconds: 250), + opacity: selected ? 0 : 1, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + _icons[index], + size: 22 * scale, + color: Colors.black87, + ), + SizedBox(height: 4 * scale), + Text( + _labels[index], + style: TextStyle( + fontSize: 10 * scale, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ], + ), + ), + ), + ), + ); + }), + ), + ], + ), + ); + }, + ), ), ); } diff --git a/lib/screens/mark_list_screen.dart b/lib/screens/mark_list_screen.dart index ee17ca4..0e92ddd 100644 --- a/lib/screens/mark_list_screen.dart +++ b/lib/screens/mark_list_screen.dart @@ -10,15 +10,13 @@ class MarkListScreen extends StatefulWidget { } class _MarkListScreenState extends State { - @override void initState() { super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { final provider = Provider.of(context, listen: false); provider.init(context); - provider.loadMarks(context); // Load full list again + provider.loadMarks(context); }); } @@ -26,27 +24,112 @@ class _MarkListScreenState extends State { Widget build(BuildContext context) { final marks = Provider.of(context); + // Responsive scale factor + final screenWidth = MediaQuery.of(context).size.width; + final scale = (screenWidth / 390).clamp(0.82, 1.35); + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( - title: const Text("All Mark Numbers"), + backgroundColor: Colors.white, + elevation: 0, + centerTitle: true, + iconTheme: const IconThemeData(color: Colors.black), + + title: Text( + "All Mark Numbers", + style: TextStyle( + color: Colors.black, + fontSize: 20 * scale, + fontWeight: FontWeight.w700, + ), + ), ), body: marks.loading ? const Center(child: CircularProgressIndicator()) : ListView.builder( - padding: const EdgeInsets.all(12), + padding: EdgeInsets.all(10 * scale), // smaller padding itemCount: marks.marks.length, itemBuilder: (_, i) { final m = marks.marks[i]; - return Card( - child: ListTile( - title: Text(m['mark_no']), - subtitle: Text("${m['origin']} → ${m['destination']}"), - trailing: Text( - m['status'], - style: const TextStyle(color: Colors.indigo), + return Container( + margin: EdgeInsets.only(bottom: 10 * scale), // reduced margin + padding: EdgeInsets.all(12 * scale), // smaller padding + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [ + Color(0xFF2196F3), + Color(0xFF64B5F6), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, ), + borderRadius: BorderRadius.circular(14 * scale), // smaller radius + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.06), + blurRadius: 5 * scale, // smaller shadow + offset: Offset(0, 2 * scale), + ), + ], + ), + + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // LEFT TEXT + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // MARK NUMBER + Text( + m['mark_no'], + style: TextStyle( + color: Colors.white, + fontSize: 16 * scale, // reduced font + fontWeight: FontWeight.bold, + ), + ), + + SizedBox(height: 3 * scale), + + // ROUTE + Text( + "${m['origin']} → ${m['destination']}", + style: TextStyle( + color: Colors.white, + fontSize: 13 * scale, // reduced font + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + + // STATUS BADGE + Container( + padding: EdgeInsets.symmetric( + horizontal: 10 * scale, + vertical: 5 * scale, // smaller badge + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.92), + borderRadius: BorderRadius.circular(24 * scale), + ), + child: Text( + m['status'], + style: TextStyle( + fontSize: 11.5 * scale, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + ), + ], ), ); }, diff --git a/lib/screens/order_detail_screen.dart b/lib/screens/order_detail_screen.dart index 0ac3755..79ba78a 100644 --- a/lib/screens/order_detail_screen.dart +++ b/lib/screens/order_detail_screen.dart @@ -1,8 +1,8 @@ +import 'dart:ui'; import 'package:flutter/material.dart'; import '../services/dio_client.dart'; import '../services/order_service.dart'; - class OrderDetailScreen extends StatefulWidget { final String orderId; const OrderDetailScreen({super.key, required this.orderId}); @@ -14,6 +14,7 @@ class OrderDetailScreen extends StatefulWidget { class _OrderDetailScreenState extends State { bool loading = true; Map order = {}; + final Map _expanded = {}; @override void initState() { @@ -33,105 +34,298 @@ class _OrderDetailScreenState extends State { setState(() {}); } - Widget _row(String label, dynamic value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(label, - style: const TextStyle(fontSize: 14, color: Colors.grey)), - Text(value?.toString() ?? 'N/A', - style: const TextStyle( - fontSize: 15, fontWeight: FontWeight.w600)), - ], - ), - ); + String _initials(String? s) { + if (s == null || s.isEmpty) return "I"; + final parts = s.split(" "); + return parts.take(2).map((e) => e[0].toUpperCase()).join(); } @override Widget build(BuildContext context) { final items = order['items'] ?? []; + final width = MediaQuery.of(context).size.width; + final scale = (width / 430).clamp(0.85, 1.20); + return Scaffold( - appBar: AppBar(title: const Text("Order Details")), + backgroundColor: const Color(0xFFF0F6FF), + appBar: AppBar( + title: const Text("Order Details"), + elevation: 0, + ), body: loading ? const Center(child: CircularProgressIndicator()) : Padding( - padding: const EdgeInsets.all(16), - child: ListView( - children: [ - // ---------------- ORDER SUMMARY ---------------- - const Text( - "Order Summary", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - - _row("Order ID", order['order_id']), - _row("Mark No", order['mark_no']), - _row("Origin", order['origin']), - _row("Destination", order['destination']), - _row("Status", order['status']), - - const Divider(height: 30), - - const Text( - "Totals", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - - _row("CTN", order['ctn']), - _row("Qty", order['qty']), - _row("Total Qty", order['ttl_qty']), - _row("Amount", "₹${order['ttl_amount'] ?? 0}"), - _row("CBM", order['cbm']), - _row("Total CBM", order['ttl_cbm']), - _row("KG", order['kg']), - _row("Total KG", order['ttl_kg']), - - const Divider(height: 30), - - // ---------------- ORDER ITEMS ---------------- - const Text( - "Order Items", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - - ...List.generate(items.length, (i) { - final item = items[i]; - - return Card( - margin: const EdgeInsets.only(bottom: 12), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item['description'] ?? "No description", - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600), - ), - const SizedBox(height: 6), - - _row("Qty", item['qty']), - _row("Unit", item['unit']), - _row("CBM", item['cbm']), - _row("KG", item['kg']), - _row("Amount", "₹${item['ttl_amount'] ?? 0}"), - _row("Shop No", item['shop_no']), - ], - ), - ), - ); - }), - ], + padding: EdgeInsets.all(16 * scale), + child: SingleChildScrollView( + child: Column( + children: [ + _summaryCard(scale), + SizedBox(height: 18 * scale), + _itemsSection(items, scale), + SizedBox(height: 18 * scale), + _totalsSection(scale), + ], + ), ), ), ); } + + // ----------------------------- + // SUMMARY CARD + // ----------------------------- + Widget _summaryCard(double scale) { + return Container( + padding: EdgeInsets.all(18 * scale), + decoration: _cardDecoration(scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Order Summary", + style: TextStyle(fontSize: 20 * scale, fontWeight: FontWeight.bold)), + SizedBox(height: 12 * scale), + _infoRow("Order ID", order['order_id'], scale), + _infoRow("Mark No", order['mark_no'], scale), + _infoRow("Origin", order['origin'], scale), + _infoRow("Destination", order['destination'], scale), + ], + ), + ); + } + + Widget _infoRow(String title, dynamic value, double scale) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 6 * scale), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title, + style: TextStyle(color: Colors.grey, fontSize: 14 * scale)), + Text(value?.toString() ?? "-", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15 * scale)), + ], + ), + ); + } + + // ----------------------------- + // ORDER ITEMS SECTION + // ----------------------------- + Widget _itemsSection(List items, double scale) { + return Container( + padding: EdgeInsets.all(18 * scale), + decoration: _cardDecoration(scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Order Items", + style: TextStyle(fontSize: 18 * scale, fontWeight: FontWeight.bold)), + SizedBox(height: 16 * scale), + ...List.generate(items.length, (i) { + return _expandableItem(items[i], i, scale); + }) + ], + ), + ); + } + + // ----------------------------- + // EXPANDABLE ITEM + // ----------------------------- + Widget _expandableItem(Map item, int index, double scale) { + final id = "item_$index"; + _expanded[id] = _expanded[id] ?? false; + + final description = item['description'] ?? "Item"; + final initials = _initials(description); + final imageUrl = item['image'] ?? ""; + + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + margin: EdgeInsets.only(bottom: 16 * scale), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(14 * scale), + border: Border.all(color: Colors.black12), + ), + child: Column( + children: [ + ListTile( + minVerticalPadding: 10 * scale, + leading: _avatar(imageUrl, initials, scale), + title: Text(description, + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 15 * scale)), + trailing: Transform.rotate( + angle: (_expanded[id]! ? 3.14 : 0), + child: Icon(Icons.keyboard_arrow_down, size: 24 * scale), + ), + onTap: () { + setState(() { + _expanded[id] = !_expanded[id]!; + }); + }, + ), + + if (_expanded[id]!) + Padding( + padding: EdgeInsets.all(12 * scale), + child: Column( + children: [ + _pill("Qty", Icons.list_alt, "${item['qty']}", + Colors.blue.shade100, scale), + _pill("Unit", Icons.category, "${item['unit']}", + Colors.orange.shade100, scale), + _pill("KG", Icons.scale, "${item['kg']}", + Colors.red.shade100, scale), + _pill("CBM", Icons.straighten, "${item['cbm']}", + Colors.purple.shade100, scale), + _pill("Shop", Icons.storefront, "${item['shop_no']}", + Colors.grey.shade300, scale), + _pill("Amount", Icons.currency_rupee, + "₹${item['ttl_amount']}", + Colors.green.shade100, scale), + ], + ), + ), + ], + ), + ); + } + + // ----------------------------- + // AVATAR (RESPONSIVE) + // ----------------------------- + Widget _avatar(String url, String initials, double scale) { + return Container( + width: 48 * scale, + height: 48 * scale, + decoration: BoxDecoration( + color: Colors.blue.shade200, + borderRadius: BorderRadius.circular(10 * scale), + ), + child: url.isNotEmpty + ? ClipRRect( + borderRadius: BorderRadius.circular(10 * scale), + child: Image.network(url, fit: BoxFit.cover, + errorBuilder: (c, e, s) => Center( + child: Text(initials, + style: TextStyle( + fontSize: 18 * scale, + color: Colors.white, + fontWeight: FontWeight.bold)), + )), + ) + : Center( + child: Text(initials, + style: TextStyle( + fontSize: 18 * scale, + color: Colors.white, + fontWeight: FontWeight.bold)), + ), + ); + } + + // ----------------------------- + // COLOR-CODED PILL (RESPONSIVE) + // ----------------------------- + Widget _pill( + String title, IconData icon, String value, Color bgColor, double scale) { + return Container( + margin: EdgeInsets.only(bottom: 12 * scale), + padding: + EdgeInsets.symmetric(horizontal: 14 * scale, vertical: 12 * scale), + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(12 * scale), + ), + child: Row( + children: [ + Icon(icon, size: 20 * scale, color: Colors.black54), + SizedBox(width: 10 * scale), + Text("$title: ", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14 * scale)), + Expanded( + child: Text(value, + style: TextStyle( + fontWeight: FontWeight.w700, fontSize: 15 * scale)), + ), + ], + ), + ); + } + + // ----------------------------- + // TOTAL SECTION + // ----------------------------- + Widget _totalsSection(double scale) { + return Container( + padding: EdgeInsets.all(18 * scale), + decoration: _cardDecoration(scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Totals", + style: TextStyle(fontSize: 18 * scale, fontWeight: FontWeight.bold)), + SizedBox(height: 12 * scale), + _totalRow("Total Qty", order['ttl_qty'], scale), + _totalRow("Total KG", order['ttl_kg'], scale), + SizedBox(height: 12 * scale), + + Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 20 * scale), + decoration: BoxDecoration( + color: Colors.green.shade50, + borderRadius: BorderRadius.circular(12 * scale), + ), + child: Column( + children: [ + Text("₹${order['ttl_amount'] ?? 0}", + style: TextStyle( + color: Colors.green, + fontSize: 28 * scale, + fontWeight: FontWeight.bold, + )), + SizedBox(height: 4 * scale), + Text("Total Amount", + style: TextStyle( + color: Colors.black54, fontSize: 14 * scale)), + ], + ), + ), + ], + ), + ); + } + + Widget _totalRow(String title, dynamic value, double scale) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title, + style: TextStyle(color: Colors.grey, fontSize: 14 * scale)), + Text(value?.toString() ?? "0", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15 * scale)), + ], + ); + } + + // ----------------------------- + // CARD DECORATION + // ----------------------------- + BoxDecoration _cardDecoration(double scale) { + return BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 8 * scale, + offset: Offset(0, 3 * scale)), + ], + ); + } } diff --git a/lib/screens/order_invoice_screen.dart b/lib/screens/order_invoice_screen.dart index 51fc98a..5db3cfc 100644 --- a/lib/screens/order_invoice_screen.dart +++ b/lib/screens/order_invoice_screen.dart @@ -2,7 +2,6 @@ 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}); @@ -11,139 +10,406 @@ class OrderInvoiceScreen extends StatefulWidget { State createState() => _OrderInvoiceScreenState(); } -class _OrderInvoiceScreenState extends State { +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 (res["success"] == true) { + invoice = res["invoice"] ?? {}; } - loading = false; - setState(() {}); + if (mounted) setState(() => loading = false); } - Widget _row(String label, dynamic value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + @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: [ - Text(label, - style: const TextStyle(fontSize: 14, color: Colors.grey)), - Text(value?.toString() ?? "N/A", - style: - const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), + _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, + ), ], ), ); } - @override - Widget build(BuildContext context) { - final items = invoice['items'] ?? []; + // ---------------- 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), + ), + ) + ], + ), + ); + } - return Scaffold( - appBar: AppBar(title: const Text("Invoice")), - body: loading - ? const Center(child: CircularProgressIndicator()) - : Padding( - padding: const EdgeInsets.all(16), - child: ListView( + // ---------------- 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: [ - const Text("Invoice Summary", - style: - TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - const SizedBox(height: 10), - - _row("Invoice No", invoice['invoice_number']), - _row("Invoice Date", invoice['invoice_date']), - _row("Due Date", invoice['due_date']), - _row("Payment Method", invoice['payment_method']), - _row("Reference No", invoice['reference_no']), - _row("Status", invoice['status']), - - const Divider(height: 30), - - const Text("Amount Details", - style: - TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - const SizedBox(height: 10), - - _row("Amount (without GST)", invoice['final_amount']), - _row("GST Amount", invoice['gst_amount']), - _row("Final Amount With GST", - invoice['final_amount_with_gst']), - _row("Tax Type", invoice['tax_type']), - _row("CGST %", invoice['cgst_percent']), - _row("SGST %", invoice['sgst_percent']), - _row("IGST %", invoice['igst_percent']), - - const Divider(height: 30), - - _row("Customer Name", invoice['customer_name']), - _row("Company Name", invoice['company_name']), - _row("Email", invoice['customer_email']), - _row("Mobile", invoice['customer_mobile']), - _row("Address", invoice['customer_address']), - _row("Pincode", invoice['pincode']), - _row("Notes", invoice['notes']), - - const Divider(height: 30), - - // PDF DOWNLOAD - if (invoice['pdf_path'] != null) - TextButton.icon( - onPressed: () { - // open pdf - }, - icon: const Icon(Icons.picture_as_pdf, color: Colors.red), - label: const Text("Download PDF"), - ), - - const SizedBox(height: 20), - - const Text("Invoice Items", - style: - TextStyle(fontSize: 17, fontWeight: FontWeight.bold)), - const SizedBox(height: 10), - - ...List.generate(items.length, (i) { - final item = items[i]; - - return Card( - margin: const EdgeInsets.only(bottom: 12), - child: ListTile( - title: Text(item['description'] ?? "Item"), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("Qty: ${item['qty'] ?? 0}"), - Text("Price: ₹${item['price'] ?? 0}"), - ], - ), - trailing: Text( - "₹${item['ttl_amount'] ?? 0}", - style: const TextStyle( - fontWeight: FontWeight.bold, - color: Colors.indigo), - ), - ), - ); - }), + 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, + ), + ) + ], + ), + ); + } } diff --git a/lib/screens/order_screen.dart b/lib/screens/order_screen.dart index 9d4f559..5f48b89 100644 --- a/lib/screens/order_screen.dart +++ b/lib/screens/order_screen.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/order_provider.dart'; import 'order_detail_screen.dart'; -import 'order_shipment_screen.dart'; import 'order_invoice_screen.dart'; import 'order_track_screen.dart'; @@ -14,6 +13,7 @@ class OrdersScreen extends StatefulWidget { } class _OrdersScreenState extends State { + String searchQuery = ""; @override void initState() { @@ -31,77 +31,290 @@ class _OrdersScreenState extends State { return const Center(child: CircularProgressIndicator()); } - return ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: provider.orders.length, - itemBuilder: (_, i) { - final o = provider.orders[i]; + final screenWidth = MediaQuery.of(context).size.width; + final scale = (screenWidth / 420).clamp(0.85, 1.15); - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("Order ID: ${o['order_id']}", - style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold)), - const SizedBox(height: 4), - Text(o['description']), - Text("₹ ${o['amount']}"), - Text(o['status'], style: const TextStyle(color: Colors.indigo)), + // FILTER ORDERS + final filteredOrders = provider.orders.where((o) { + final q = searchQuery.toLowerCase(); + return o["order_id"].toString().toLowerCase().contains(q) || + o["status"].toString().toLowerCase().contains(q) || + o["description"].toString().toLowerCase().contains(q) || + o["amount"].toString().toLowerCase().contains(q); + }).toList(); - const SizedBox(height: 10), - - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _btn("Order", () => _openOrderDetails(o['order_id'])), - _btn("Shipment", () => _openShipment(o['order_id'])), - _btn("Invoice", () => _openInvoice(o['order_id'])), - _btn("Track", () => _openTrack(o['order_id'])), - ], - ) - ], - ), + return Column( + children: [ + // ⭐⭐ WHITE ELEVATED SEARCH BAR ⭐⭐ + Container( + margin: EdgeInsets.fromLTRB(16 * scale, 16 * scale, 16 * scale, 10 * scale), + padding: EdgeInsets.symmetric(horizontal: 14 * scale), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black12.withOpacity(0.12), + blurRadius: 10 * scale, + spreadRadius: 1 * scale, + offset: Offset(0, 4 * scale), + ), + ], ), - ); - }, + child: Row( + children: [ + Icon(Icons.search, + size: 22 * scale, color: Colors.grey.shade700), + + SizedBox(width: 10 * scale), + + Expanded( + child: TextField( + onChanged: (value) => setState(() => searchQuery = value), + style: TextStyle(fontSize: 15 * scale), + decoration: InputDecoration( + hintText: "Search orders...", + hintStyle: TextStyle( + fontSize: 14 * scale, + color: Colors.grey.shade600, + ), + border: InputBorder.none, + ), + ), + ), + ], + ), + ), + + // LIST OF ORDERS + Expanded( + child: ListView.builder( + padding: EdgeInsets.all(16 * scale), + itemCount: filteredOrders.length, + itemBuilder: (context, i) { + final order = filteredOrders[i]; + return _orderCard(order, scale); + }, + ), + ), + ], ); } - Widget _btn(String text, VoidCallback onTap) { - return InkWell( - onTap: onTap, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6), - color: Colors.indigo.shade50, + // ORDER CARD UI + Widget _orderCard(Map o, double scale) { + final progress = getProgress(o['status']); + final badgeColor = getStatusColor(o['status']); + + return Card( + elevation: 3 * scale, + color: Colors.white, + margin: EdgeInsets.only(bottom: 16 * scale), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16 * scale), + ), + child: Padding( + padding: EdgeInsets.all(16 * scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // TOP ROW + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Order #${o['order_id']}", + style: TextStyle( + fontSize: 18 * scale, + fontWeight: FontWeight.bold, + ), + ), + Container( + padding: EdgeInsets.symmetric( + horizontal: 12 * scale, + vertical: 6 * scale, + ), + decoration: BoxDecoration( + color: badgeColor.withOpacity(0.15), + borderRadius: BorderRadius.circular(12 * scale), + ), + child: Text( + o['status'], + style: TextStyle( + color: badgeColor, + fontWeight: FontWeight.bold, + fontSize: 13 * scale, + ), + ), + ), + ], + ), + + SizedBox(height: 10 * scale), + + Text( + o['description'], + style: TextStyle(fontSize: 14 * scale), + ), + + SizedBox(height: 5 * scale), + + Text( + "₹${o['amount']}", + style: TextStyle( + fontSize: 16 * scale, + fontWeight: FontWeight.w600, + ), + ), + + SizedBox(height: 18 * scale), + + _AnimatedProgressBar(progress: progress, scale: scale), + + SizedBox(height: 18 * scale), + + // BUTTONS + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _btn(Icons.visibility, "View", Colors.green.shade800, + Colors.green.shade50, () => _openOrderDetails(o['order_id']), scale), + + _btn(Icons.receipt_long, "Invoice", Colors.orange.shade800, + Colors.orange.shade50, () => _openInvoice(o['order_id']), scale), + + _btn(Icons.local_shipping, "Track", Colors.blue.shade800, + Colors.blue.shade50, () => _openTrack(o['order_id']), scale), + ], + ) + ], ), - child: Text(text, style: const TextStyle(color: Colors.indigo)), ), ); } - void _openOrderDetails(String id) { - Navigator.push(context, MaterialPageRoute( - builder: (_) => OrderDetailScreen(orderId: id))); + // BUTTON UI + Widget _btn(IconData icon, String text, Color fg, Color bg, + VoidCallback onTap, double scale) { + return InkWell( + borderRadius: BorderRadius.circular(12 * scale), + onTap: onTap, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 20 * scale, + vertical: 12 * scale, + ), + decoration: BoxDecoration( + color: bg, + borderRadius: BorderRadius.circular(12 * scale), + ), + child: Row( + children: [ + Icon(icon, size: 18 * scale, color: fg), + SizedBox(width: 8 * scale), + Text( + text, + style: TextStyle( + color: fg, + fontWeight: FontWeight.w600, + fontSize: 14 * scale, + ), + ), + ], + ), + ), + ); } - void _openShipment(String id) { - Navigator.push(context, MaterialPageRoute( - builder: (_) => OrderShipmentScreen(orderId: id))); + // NAVIGATION + void _openOrderDetails(String id) { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => OrderDetailScreen(orderId: id)), + ); } void _openInvoice(String id) { - Navigator.push(context, MaterialPageRoute( - builder: (_) => OrderInvoiceScreen(orderId: id))); + Navigator.push( + context, + MaterialPageRoute(builder: (_) => OrderInvoiceScreen(orderId: id)), + ); } void _openTrack(String id) { - Navigator.push(context, MaterialPageRoute( - builder: (_) => OrderTrackScreen(orderId: id))); + Navigator.push( + context, + MaterialPageRoute(builder: (_) => OrderTrackScreen(orderId: id)), + ); } } + +// PROGRESS BAR +class _AnimatedProgressBar extends StatelessWidget { + final double progress; + final double scale; + + const _AnimatedProgressBar({required this.progress, required this.scale}); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final maxW = constraints.maxWidth; + + return Stack( + children: [ + Container( + height: 10 * scale, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(20 * scale), + ), + ), + AnimatedContainer( + duration: const Duration(milliseconds: 650), + curve: Curves.easeInOut, + height: 10 * scale, + width: maxW * progress, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20 * scale), + gradient: const LinearGradient( + colors: [ + Color(0xFF4F8CFF), + Color(0xFF8A4DFF), + ], + ), + ), + ), + ], + ); + }, + ); + } +} + +// PROGRESS VALUES +double getProgress(String? status) { + final s = (status ?? '').toLowerCase(); + + if (s == "pending") return 0.25; + if (s == "loading") return 0.40; + if (s == "in transit" || s == "intransit") return 0.65; + if (s == "dispatched") return 0.85; + if (s == "delivered") return 1.0; + + return 0.05; +} + +// STATUS COLORS +Color getStatusColor(String? status) { + final s = (status ?? '').toLowerCase(); + + if (s == "pending") return Colors.orange; + if (s == "loading") return Colors.amber.shade800; + if (s == "in transit" || s == "intransit") return Colors.red; + if (s == "dispatched") return Colors.blue.shade700; + if (s == "delivered") return Colors.green.shade700; + + return Colors.black54; +} diff --git a/lib/screens/order_track_screen.dart b/lib/screens/order_track_screen.dart index 4e405d2..0760c2d 100644 --- a/lib/screens/order_track_screen.dart +++ b/lib/screens/order_track_screen.dart @@ -1,8 +1,9 @@ +// lib/screens/order_track_screen.dart +import 'dart:math'; import 'package:flutter/material.dart'; import '../services/dio_client.dart'; import '../services/order_service.dart'; - class OrderTrackScreen extends StatefulWidget { final String orderId; const OrderTrackScreen({super.key, required this.orderId}); @@ -11,54 +12,579 @@ class OrderTrackScreen extends StatefulWidget { State createState() => _OrderTrackScreenState(); } -class _OrderTrackScreenState extends State { +class _OrderTrackScreenState extends State + with TickerProviderStateMixin { bool loading = true; - Map data = {}; + + Map? shipment; + Map trackData = {}; + + late final AnimationController progressController; + late final AnimationController shipController; @override void initState() { super.initState(); - load(); - } - Future load() async { - final service = OrderService(DioClient.getInstance(context)); - final res = await service.trackOrder(widget.orderId); + progressController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 900), + ); - if (res['success'] == true) { - data = res['track']; - } + shipController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1600), + )..repeat(reverse: true); - loading = false; - setState(() {}); + WidgetsBinding.instance.addPostFrameCallback((_) => _loadData()); } + @override + void dispose() { + progressController.dispose(); + shipController.dispose(); + super.dispose(); + } + + // ---------------- LOAD DATA ---------------- + Future _loadData() async { + if (!mounted) return; + + setState(() => loading = true); + + try { + final service = OrderService(DioClient.getInstance(context)); + + final results = await Future.wait([ + service.getShipment(widget.orderId).catchError((_) => {"success": false}), + service.trackOrder(widget.orderId).catchError((_) => {"success": false}), + ]); + + final shipRes = results[0] as Map; + final trackRes = results[1] as Map; + + /// ------------------- SHIPMENT DATA ------------------- + shipment = shipRes["success"] == true + ? Map.from(shipRes["shipment"] ?? {}) + : null; + + /// ------------------- TRACKING DATA ------------------- + trackData = trackRes["success"] == true + ? Map.from(trackRes["track"] ?? {}) + : {}; + } catch (_) {} + + final target = _computeProgress(); + + if (mounted) { + try { + await progressController.animateTo( + target, + curve: Curves.easeInOutCubic, + ); + } catch (_) {} + } + + if (mounted) setState(() => loading = false); + } + + // ---------------- PROGRESS LOGIC ---------------- + double _computeProgress() { + final status = (trackData["shipment_status"] ?? "") + .toString() + .toLowerCase(); + + if (status.contains("delivered")) return 1.0; + if (status.contains("dispatched")) return 0.85; + if (status.contains("transit")) return 0.65; + if (status.contains("loading")) return 0.40; + if (status.contains("pending")) return 0.25; + + if (_hasTimestamp("delivered_at")) return 1.0; + if (_hasTimestamp("dispatched_at")) return 0.85; + if (_hasTimestamp("in_transit_at")) return 0.65; + if (_hasTimestamp("loading_at")) return 0.40; + if (_hasTimestamp("pending_at")) return 0.25; + + return 0.05; + } + + bool _hasTimestamp(String key) { + final v = trackData[key]; + if (v == null) return false; + final s = v.toString().trim().toLowerCase(); + return s.isNotEmpty && s != "null"; + } + + String _fmt(dynamic v) { + if (v == null) return "-"; + try { + final d = DateTime.parse(v.toString()).toLocal(); + return "${d.day}/${d.month}/${d.year}"; + } catch (_) { + return v.toString(); + } + } + + // ---------------- UI BUILD ---------------- @override Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + final scale = (width / 430).clamp(0.75, 1.25); + return Scaffold( - appBar: AppBar(title: const Text("Track Order")), + appBar: AppBar( + title: const Text("Shipment & Tracking"), + elevation: 0.8, + ), body: loading ? const Center(child: CircularProgressIndicator()) - : Padding( - padding: const EdgeInsets.all(16), + : SingleChildScrollView( + padding: EdgeInsets.all(16 * scale), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Order ID: ${data['order_id']}"), - Text("Shipment Status: ${data['shipment_status']}"), - Text("Shipment Date: ${data['shipment_date']}"), + _headerCard(scale), + SizedBox(height: 16 * scale), - const SizedBox(height: 20), - Center( - child: Icon( - Icons.local_shipping, - size: 100, - color: Colors.indigo.shade300, - ), - ), + _shipmentSummary(scale), + SizedBox(height: 16 * scale), + + _shipmentTotals(scale), + SizedBox(height: 16 * scale), + + _shipmentItems(scale), // USING SHIPMENT API ONLY + SizedBox(height: 20 * scale), + + _trackingStatus(scale), + SizedBox(height: 16 * scale), + + _progressBar(scale, width), + SizedBox(height: 16 * scale), + + _detailsCard(scale), ], ), ), ); } + + // ---------------- HEADER ---------------- + Widget _headerCard(double scale) { + return Container( + padding: EdgeInsets.all(14 * scale), + decoration: _boxDecoration(scale), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(10 * scale), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(12 * scale), + ), + child: Icon(Icons.local_shipping, + color: Colors.blue, size: 28 * scale), + ), + SizedBox(width: 12 * scale), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Order #${trackData['order_id'] ?? '-'}", + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 16 * scale), + ), + SizedBox(height: 6 * scale), + Text( + trackData["shipment_status"] ?? "-", + style: TextStyle(color: Colors.black54, fontSize: 13 * scale), + ), + ], + ) + ], + ), + ); + } + + // ---------------- SHIPMENT SUMMARY ---------------- + Widget _shipmentSummary(double scale) { + if (shipment == null) { + return _simpleCard(scale, "Shipment not created yet"); + } + + return Container( + padding: EdgeInsets.all(16 * scale), + decoration: _boxDecoration(scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Shipment Summary", + style: + TextStyle(fontSize: 18 * scale, fontWeight: FontWeight.bold)), + SizedBox(height: 12 * scale), + + // Shipment ID + Container( + padding: EdgeInsets.all(12 * scale), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(12 * scale), + ), + child: Row( + children: [ + Icon(Icons.qr_code_2, color: Colors.blue, size: 22 * scale), + SizedBox(width: 10 * scale), + Text("Shipment ID: ${shipment!['shipment_id']}", + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 15 * scale)), + ], + ), + ), + + SizedBox(height: 14 * scale), + _twoCol("Status", shipment!['status'], scale), + _twoCol("Shipment Date", _fmt(shipment!['shipment_date']), scale), + _twoCol("Origin", shipment!['origin'], scale), + _twoCol("Destination", shipment!['destination'], scale), + ], + ), + ); + } + + Widget _simpleCard(double scale, String text) { + return Container( + padding: EdgeInsets.all(14 * scale), + decoration: _boxDecoration(scale), + child: Text(text, + style: TextStyle(fontSize: 15 * scale, color: Colors.grey.shade700)), + ); + } + + Widget _twoCol(String title, dynamic value, double scale) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 6 * scale), + child: Row( + children: [ + Expanded( + child: Text(title, + style: TextStyle(color: Colors.grey, fontSize: 13 * scale)), + ), + Expanded( + child: Text( + value?.toString() ?? "-", + textAlign: TextAlign.right, + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 13 * scale), + ), + ), + ], + ), + ); + } + + // ---------------- SHIPMENT TOTALS ---------------- + Widget _shipmentTotals(double scale) { + if (shipment == null) return const SizedBox.shrink(); + + return Container( + padding: EdgeInsets.all(14 * scale), + decoration: _boxDecoration(scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Totals", + style: + TextStyle(fontSize: 18 * scale, fontWeight: FontWeight.bold)), + SizedBox(height: 10 * scale), + _twoCol("Total CTN", shipment!['total_ctn'], scale), + _twoCol("Total Qty", shipment!['total_qty'], scale), + _twoCol("Total Amount", shipment!['total_amount'], scale), + _twoCol("Total CBM", shipment!['total_cbm'], scale), + _twoCol("Total KG", shipment!['total_kg'], scale), + ], + ), + ); + } + + // ---------------- SHIPMENT ITEMS (FROM SHIPMENT API ONLY) ---------------- + Widget _shipmentItems(double scale) { + if (shipment == null) return const SizedBox.shrink(); + + final items = (shipment!['items'] as List?) ?? []; + + return Container( + padding: EdgeInsets.all(14 * scale), + decoration: _boxDecoration(scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Shipment Items", + style: + TextStyle(fontSize: 18 * scale, fontWeight: FontWeight.bold)), + SizedBox(height: 14 * scale), + + ...items.map((item) { + final orderId = item["order_id"]?.toString() ?? "-"; + final qty = item["total_ttl_qty"]?.toString() ?? "-"; + final cbm = item["total_ttl_cbm"]?.toString() ?? "-"; + final kg = item["total_ttl_kg"]?.toString() ?? "-"; + final amount = item["total_amount"]?.toString() ?? "-"; + + return Card( + elevation: 2, + margin: EdgeInsets.only(bottom: 12 * scale), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14 * scale), + ), + child: Padding( + padding: EdgeInsets.all(14 * scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Row( + children: [ + Container( + padding: EdgeInsets.all(8 * scale), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(12 * scale), + ), + child: Icon(Icons.inventory_2, + color: Colors.blue, size: 20 * scale), + ), + SizedBox(width: 12 * scale), + Text( + "Order ID: $orderId", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15 * scale), + ), + ], + ), + + SizedBox(height: 12 * scale), + + if (item["mark_no"] != null) + Text("Mark No: ${item["mark_no"]}", + style: TextStyle(fontSize: 13 * scale)), + + SizedBox(height: 6 * scale), + + Text("Quantity: $qty", + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14 * scale)), + + Text("CBM: $cbm", style: TextStyle(fontSize: 13 * scale)), + Text("KG: $kg", style: TextStyle(fontSize: 13 * scale)), + + SizedBox(height: 10 * scale), + + Container( + padding: EdgeInsets.symmetric( + vertical: 8 * scale, horizontal: 12 * scale), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(10 * scale), + border: Border.all(color: Colors.blue, width: 1), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Icon(Icons.currency_rupee, + color: Colors.blue), + Text( + "Amount: ₹$amount", + style: TextStyle( + fontSize: 15 * scale, + fontWeight: FontWeight.bold, + color: Colors.blue), + ), + ], + ), + ), + ], + ), + ), + ); + }).toList(), + ], + ), + ); + } + + // ---------------- TRACKING STATUS ---------------- + Widget _trackingStatus(double scale) { + final p = _computeProgress(); + final delivered = p >= 1.0; + + return Container( + padding: + EdgeInsets.symmetric(vertical: 10 * scale, horizontal: 14 * scale), + decoration: BoxDecoration( + gradient: delivered + ? const LinearGradient(colors: [Colors.green, Colors.lightGreen]) + : const LinearGradient(colors: [Colors.blue, Colors.purple]), + borderRadius: BorderRadius.circular(40 * scale), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + delivered ? Icons.verified : Icons.local_shipping, + color: Colors.white, + size: 16 * scale, + ), + SizedBox(width: 8 * scale), + Text( + (trackData["shipment_status"] ?? "-") + .toString() + .toUpperCase(), + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 13 * scale), + ), + ], + ), + ); + } + + // ---------------- PROGRESS BAR ---------------- + Widget _progressBar(double scale, double width) { + final usableWidth = width - (48 * scale); + + return Container( + padding: EdgeInsets.all(12 * scale), + decoration: _boxDecoration(scale), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Shipment Progress", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 15 * scale)), + SizedBox(height: 12 * scale), + + Stack( + alignment: Alignment.centerLeft, + children: [ + // Background + Container( + width: usableWidth, + height: 10 * scale, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(20 * scale)), + ), + + // Progress Bar + AnimatedBuilder( + animation: progressController, + builder: (_, __) { + final w = usableWidth * + progressController.value.clamp(0.0, 1.0); + return Container( + width: w, + height: 10 * scale, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFF4F8CFF), Color(0xFF8A4DFF)]), + borderRadius: BorderRadius.circular(20 * scale), + ), + ); + }, + ), + + // Moving Truck Icon + AnimatedBuilder( + animation: shipController, + builder: (_, __) { + final progress = progressController.value.clamp(0.0, 1.0); + final bob = sin(shipController.value * 2 * pi) * (4 * scale); + + return Positioned( + left: (usableWidth - 26 * scale) * progress, + top: -6 * scale + bob, + child: Container( + width: 26 * scale, + height: 26 * scale, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFF4F8CFF), Color(0xFF8A4DFF)], + ), + borderRadius: BorderRadius.circular(8 * scale), + ), + child: Icon( + Icons.local_shipping, + size: 16 * scale, + color: Colors.white, + ), + ), + ); + }, + ), + ], + ), + + SizedBox(height: 10 * scale), + + AnimatedBuilder( + animation: progressController, + builder: (_, __) => Text( + "${(progressController.value * 100).toInt()}% Completed", + style: TextStyle(color: Colors.black54, fontSize: 13 * scale), + ), + ) + ], + ), + ); + } + + // ---------------- DETAILS CARD ---------------- + Widget _detailsCard(double scale) { + return Container( + padding: EdgeInsets.all(14 * scale), + decoration: _boxDecoration(scale), + child: Column( + children: [ + _detailsRow("Order ID", trackData["order_id"], scale), + SizedBox(height: 10 * scale), + _detailsRow("Status", trackData["shipment_status"], scale), + SizedBox(height: 10 * scale), + _detailsRow("Date", _fmt(trackData["shipment_date"]), scale), + ], + ), + ); + } + + Widget _detailsRow(String title, dynamic value, double scale) { + return Row( + children: [ + Expanded( + child: Text(title, + style: TextStyle( + fontWeight: FontWeight.w700, fontSize: 14 * scale))), + Expanded( + child: Text(value?.toString() ?? "-", + textAlign: TextAlign.right, + style: TextStyle(color: Colors.black87, fontSize: 14 * scale))), + ], + ); + } + + // ---------------- BOX DECORATION ---------------- + BoxDecoration _boxDecoration(double scale) { + return BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black12.withOpacity(0.05), + blurRadius: 10 * scale, + offset: Offset(0, 4 * scale), + ) + ], + ); + } } diff --git a/lib/screens/otp_screen.dart b/lib/screens/otp_screen.dart index c882471..b855556 100644 --- a/lib/screens/otp_screen.dart +++ b/lib/screens/otp_screen.dart @@ -16,52 +16,124 @@ class _OtpScreenState extends State { final otpController = TextEditingController(); bool verifying = false; - static const String defaultOtp = '123456'; // default OTP as you said + static const String defaultOtp = '123456'; void _verifyAndSubmit() async { final entered = otpController.text.trim(); + if (entered.length != 6) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Enter 6 digit OTP'))); + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar(content: Text('Enter 6 digit OTP'))); return; } if (entered != defaultOtp) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Invalid OTP'))); + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar(content: Text('Invalid OTP'))); return; } setState(() => verifying = true); - - // send signup payload to backend final res = await RequestService(context).sendSignup(widget.signupPayload); - setState(() => verifying = false); if (res['status'] == true || res['status'] == 'success') { - // navigate to waiting screen - Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const WaitingScreen())); + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const WaitingScreen())); } else { final message = res['message']?.toString() ?? 'Failed'; - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message))); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(message))); } } @override Widget build(BuildContext context) { - final pad = MediaQuery.of(context).size.width * 0.06; + final width = MediaQuery.of(context).size.width; + + /// 📌 Universal scale factor for responsiveness + final scale = (width / 390).clamp(0.85, 1.25); + return Scaffold( - appBar: AppBar(title: const Text("OTP Verification")), - body: Padding( - padding: EdgeInsets.symmetric(horizontal: pad, vertical: 20), - child: Column(children: [ - const Text("Enter the 6-digit OTP sent to your mobile/email. (Default OTP: 123456)"), - const SizedBox(height: 20), - RoundedInput(controller: otpController, hint: "Enter OTP", keyboardType: TextInputType.number), - const SizedBox(height: 14), - PrimaryButton(label: "Verify & Submit", onTap: _verifyAndSubmit, busy: verifying), - ]), + body: SafeArea( + child: Stack( + children: [ + /// 🔙 Back Button + Positioned( + top: 18 * scale, + left: 18 * scale, + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + height: 42 * scale, + width: 42 * scale, + decoration: const BoxDecoration( + color: Colors.indigo, + shape: BoxShape.circle, + ), + child: Icon(Icons.arrow_back, + color: Colors.white, size: 22 * scale), + ), + ), + ), + + /// 🟦 Center Card + Center( + child: Container( + width: width * 0.90, + padding: EdgeInsets.symmetric( + horizontal: 20 * scale, vertical: 28 * scale), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 12 * scale, + offset: Offset(0, 4 * scale), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "OTP Verification", + style: TextStyle( + fontSize: 22 * scale, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 10 * scale), + + Text( + "Enter the 6-digit OTP sent to your mobile/email.\n(Default OTP: 123456)", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 13.5 * scale), + ), + + SizedBox(height: 18 * scale), + + RoundedInput( + controller: otpController, + hint: "Enter OTP", + keyboardType: TextInputType.number, + ), + + SizedBox(height: 22 * scale), + + PrimaryButton( + label: "Verify & Submit", + onTap: _verifyAndSubmit, + busy: verifying, + ), + ], + ), + ), + ), + ], + ), ), ); } } - diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 2d020fa..edd4603 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -9,13 +9,11 @@ import 'login_screen.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); - @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { - @override void initState() { super.initState(); @@ -27,32 +25,33 @@ class _SettingsScreenState extends State { await Future.delayed(const Duration(milliseconds: 100)); } - final profileProvider = - Provider.of(context, listen: false); - profileProvider.init(context); - - await profileProvider.loadProfile(context); + final profile = Provider.of(context, listen: false); + profile.init(context); + await profile.loadProfile(context); }); } Future _pickImage() async { - final picked = await ImagePicker().pickImage( - source: ImageSource.gallery, - imageQuality: 80, - ); - - if (picked != null) { - final file = File(picked.path); - final profileProvider = - Provider.of(context, listen: false); - - profileProvider.init(context); - - final success = await profileProvider.updateProfileImage(context, file); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(success ? "Profile updated" : "Failed to update")), + try { + final picked = await ImagePicker().pickImage( + source: ImageSource.gallery, + imageQuality: 80, ); + if (picked != null) { + final file = File(picked.path); + final profile = Provider.of(context, listen: false); + profile.init(context); + final ok = await profile.updateProfileImage(context, file); + + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(ok ? "Profile updated" : "Failed to update")), + ); + } + } catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text("Error: $e"))); } } @@ -62,131 +61,309 @@ class _SettingsScreenState extends State { final confirm = await showDialog( context: context, builder: (_) => AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), title: const Text("Logout"), content: const Text("Are you sure you want to logout?"), actions: [ + TextButton(onPressed: () => Navigator.pop(context, false), child: const Text("Cancel")), TextButton( - child: const Text("Cancel"), - onPressed: () => Navigator.pop(context, false), - ), - TextButton( - child: const Text("Logout", style: TextStyle(color: Colors.red)), - onPressed: () => Navigator.pop(context, true), - ), + onPressed: () => Navigator.pop(context, true), + child: const Text("Logout", style: TextStyle(color: Colors.red))), ], ), ); if (confirm == true) { await auth.logout(context); - if (!mounted) return; - Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (_) => const LoginScreen()), - (route) => false, + (r) => false, ); } } - @override - Widget build(BuildContext context) { - final profileProvider = Provider.of(context); - - if (profileProvider.loading || profileProvider.profile == null) { - return const Center(child: CircularProgressIndicator()); - } - - final p = profileProvider.profile!; - - return SingleChildScrollView( - padding: const EdgeInsets.all(18), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // ------------------ PROFILE IMAGE ------------------ - Center( - child: GestureDetector( - onTap: _pickImage, - child: CircleAvatar( - radius: 55, - backgroundImage: p.profileImage != null - ? NetworkImage(p.profileImage!) - : null, - child: p.profileImage == null - ? const Icon(Icons.person, size: 55) - : null, - ), - ), - ), - - const SizedBox(height: 25), - - // ------------------ PROFILE INFO ------------------ - _infoRow("Customer ID", p.customerId), - _infoRow("Name", p.customerName), - _infoRow("Company", p.companyName), - _infoRow("Email", p.email), - _infoRow("Mobile", p.mobile), - _infoRow("Address", p.address ?? "Not provided"), - _infoRow("Pincode", p.pincode ?? "Not provided"), - _infoRow("Status", p.status ?? "N/A"), - _infoRow("Customer Type", p.customerType ?? "N/A"), - - const SizedBox(height: 40), - - Center( - child: ElevatedButton( - child: const Text("Edit Profile"), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const EditProfileScreen()), - ); - }, - ), - ), - - // ------------------ LOGOUT BUTTON ------------------ - Center( - child: ElevatedButton.icon( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 14), - ), - icon: const Icon(Icons.logout, color: Colors.white), - label: const Text( - "Logout", - style: TextStyle(color: Colors.white, fontSize: 16), - ), - onPressed: _logout, - ), - ), - - - - - const SizedBox(height: 30), - ], - ), + // ------------------------- REUSABLE FIELD ROW ------------------------- + Widget _fieldRow(IconData icon, String label, String value, double scale) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 12 * scale), + child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Icon(icon, size: 26 * scale, color: Colors.blueGrey.shade700), + SizedBox(width: 14 * scale), + Expanded( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(label, + style: TextStyle( + fontSize: 14 * scale, + color: Colors.grey[700], + fontWeight: FontWeight.w700)), + SizedBox(height: 4 * scale), + Text(value, + style: TextStyle( + fontSize: 16 * scale, fontWeight: FontWeight.bold)), + ]), + ) + ]), ); } - Widget _infoRow(String title, String value) { - return Padding( - padding: const EdgeInsets.only(bottom: 14), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(title, - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - color: Colors.grey)), - const SizedBox(height: 4), - Text(value, style: const TextStyle(fontSize: 16)), - ], + // ------------------------- INFO TILE ------------------------- + Widget _infoTile(IconData icon, String title, String value, double scale) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(icon, size: 26 * scale, color: Colors.orange.shade800), + SizedBox(width: 14 * scale), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, + style: TextStyle( + fontSize: 13 * scale, + fontWeight: FontWeight.w600, + color: Colors.black54)), + SizedBox(height: 4 * scale), + Text(value, + style: TextStyle( + fontSize: 17 * scale, fontWeight: FontWeight.bold)) + ]), + ) + ], + ); + } + + @override + Widget build(BuildContext context) { + final profile = Provider.of(context); + + if (profile.loading || profile.profile == null) { + return const Scaffold(body: Center(child: CircularProgressIndicator())); + } + + // ----------- RESPONSIVE SCALE ----------- + final width = MediaQuery.of(context).size.width; + final scale = (width / 390).clamp(0.80, 1.25); + + final p = profile.profile!; + final img = p.profileImage; + final name = p.customerName ?? "Unknown"; + final email = p.email ?? "Not provided"; + final status = p.status ?? "Active"; + final cid = p.customerId ?? "—"; + final company = p.companyName ?? "—"; + final type = p.customerType ?? "—"; + final mobile = p.mobile ?? "—"; + final address = p.address ?? "Not provided"; + final pincode = p.pincode ?? "—"; + final isPartner = type.toLowerCase().contains("partner"); + + return Scaffold( + backgroundColor: const Color(0xFFE9F2FF), + body: SafeArea( + child: SingleChildScrollView( + padding: EdgeInsets.all(18 * scale), + child: Column( + children: [ + // -------------------- PROFILE SECTION -------------------- + Center( + child: Column( + children: [ + GestureDetector( + onTap: _pickImage, + child: Stack( + clipBehavior: Clip.none, + children: [ + CircleAvatar( + radius: 64 * scale, + backgroundColor: Colors.grey[200], + backgroundImage: + img != null ? NetworkImage(img) : null, + child: img == null + ? Icon(Icons.person, + size: 70 * scale, color: Colors.grey[600]) + : null, + ), + + // ------------------ FIXED STATUS BADGE ------------------ + Positioned( + bottom: 8 * scale, + right: 8 * scale, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 12 * scale, + vertical: 6 * scale), + decoration: BoxDecoration( + color: status.toLowerCase() == 'active' + ? Colors.green + : Colors.orange, + borderRadius: BorderRadius.circular(20 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 8 * scale) + ], + ), + child: Text( + status, + style: TextStyle( + color: Colors.white, + fontSize: 13 * scale, + fontWeight: FontWeight.bold, + ), + ), + ), + ) + ], + ), + ), + + SizedBox(height: 14 * scale), + Text(name, + style: TextStyle( + fontSize: 20 * scale, + fontWeight: FontWeight.bold)), + SizedBox(height: 6 * scale), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.email, + size: 18 * scale, color: Colors.blueGrey), + SizedBox(width: 8 * scale), + Text(email, + style: TextStyle( + fontSize: 14 * scale, + color: Colors.grey[700])), + ], + ), + ], + ), + ), + + SizedBox(height: 26 * scale), + + // ---------------------- YELLOW SUMMARY CARD ---------------------- + Container( + padding: EdgeInsets.all(18 * scale), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFFFFF8A3), Color(0xFFFFE275)], + ), + borderRadius: BorderRadius.circular(16 * scale), + boxShadow: [ + BoxShadow( + color: Colors.orange.withOpacity(0.25), + blurRadius: 14 * scale, + offset: Offset(0, 8 * scale)) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _infoTile(Icons.badge, "Customer ID", cid, scale), + Divider(height: 30 * scale), + _infoTile(Icons.business, "Company Name", company, scale), + Divider(height: 30 * scale), + _infoTile(Icons.category, "Customer Type", type, scale), + SizedBox(height: 20 * scale), + + if (isPartner) + Container( + padding: EdgeInsets.all(14 * scale), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(14 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 10 * scale) + ], + ), + child: Row(children: [ + Icon(Icons.workspace_premium, + size: 32 * scale, color: Colors.amber[800]), + SizedBox(width: 12 * scale), + Text("Partner", + style: TextStyle( + fontSize: 16 * scale, + fontWeight: FontWeight.bold)), + ]), + ), + ]), + ), + + SizedBox(height: 24 * scale), + + // ---------------------- DETAILS CARD ---------------------- + Container( + padding: EdgeInsets.all(16 * scale), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(14 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.06), + blurRadius: 12 * scale) + ]), + child: Column( + children: [ + _fieldRow(Icons.phone_android, "Mobile", mobile, scale), + const Divider(), + _fieldRow(Icons.location_on, "Address", address, scale), + const Divider(), + _fieldRow(Icons.local_post_office, "Pincode", pincode, scale), + SizedBox(height: 20 * scale), + + Row(children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + const EditProfileScreen())); + }, + icon: Icon(Icons.edit, + color: Colors.white, size: 18 * scale), + label: Text("Edit Profile", + style: TextStyle( + color: Colors.white, fontSize: 14 * scale)), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue[700], + padding: + EdgeInsets.symmetric(vertical: 14 * scale), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12 * scale)), + ), + )), + SizedBox(width: 12 * scale), + Expanded( + child: ElevatedButton.icon( + onPressed: _logout, + icon: Icon(Icons.logout, + size: 18 * scale, color: Colors.white), + label: Text("Logout", + style: TextStyle( + color: Colors.white, fontSize: 14 * scale)), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red[600], + padding: + EdgeInsets.symmetric(vertical: 14 * scale), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12 * scale)), + ), + )), + ]) + ], + ), + ), + + SizedBox(height: 30 * scale), + ], + ), + ), ), ); } diff --git a/lib/screens/signup_screen.dart b/lib/screens/signup_screen.dart index cbf8a6b..9dbfd7b 100644 --- a/lib/screens/signup_screen.dart +++ b/lib/screens/signup_screen.dart @@ -21,16 +21,20 @@ class _SignupScreenState extends State { bool sending = false; void _sendOtp() async { - // We don't call backend for OTP here per your flow - OTP is default 123456. - // Validate minimal - if (cName.text.trim().isEmpty || cCompany.text.trim().isEmpty || cEmail.text.trim().isEmpty || cMobile.text.trim().isEmpty) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Please fill the required fields'))); + if (cName.text.trim().isEmpty || + cCompany.text.trim().isEmpty || + cEmail.text.trim().isEmpty || + cMobile.text.trim().isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please fill the required fields')), + ); return; } + setState(() => sending = true); - await Future.delayed(const Duration(milliseconds: 600)); // UI feel + await Future.delayed(const Duration(milliseconds: 600)); setState(() => sending = false); - // Navigate to OTP screen with collected data + final data = { 'customer_name': cName.text.trim(), 'company_name': cCompany.text.trim(), @@ -40,40 +44,152 @@ class _SignupScreenState extends State { 'address': cAddress.text.trim(), 'pincode': cPincode.text.trim(), }; - Navigator.of(context).push(MaterialPageRoute(builder: (_) => OtpScreen(signupPayload: data))); + + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => OtpScreen(signupPayload: data), + ), + ); } @override Widget build(BuildContext context) { - final pad = MediaQuery.of(context).size.width * 0.06; + final width = MediaQuery.of(context).size.width; + return Scaffold( - appBar: AppBar(title: const Text("Create Account")), + backgroundColor: const Color(0xFFE8F0FF), // Same as Login background + body: SafeArea( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: pad), - child: SingleChildScrollView( - child: Column(children: [ - const SizedBox(height: 16), - RoundedInput(controller: cName, hint: "Customer name"), - const SizedBox(height: 12), - RoundedInput(controller: cCompany, hint: "Company name"), - const SizedBox(height: 12), - RoundedInput(controller: cDesignation, hint: "Designation (optional)"), - const SizedBox(height: 12), - RoundedInput(controller: cEmail, hint: "Email", keyboardType: TextInputType.emailAddress), - const SizedBox(height: 12), - RoundedInput(controller: cMobile, hint: "Mobile", keyboardType: TextInputType.phone), - const SizedBox(height: 12), - RoundedInput(controller: cAddress, hint: "Address", maxLines: 3), - const SizedBox(height: 12), - RoundedInput(controller: cPincode, hint: "Pincode", keyboardType: TextInputType.number), - const SizedBox(height: 20), - PrimaryButton(label: "Send OTP", onTap: _sendOtp, busy: sending), - const SizedBox(height: 14), - ]), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), + + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// 🔵 Back Button (scrolls with form) + Material( + elevation: 6, + shape: const CircleBorder(), + color: Colors.indigo.shade700, + child: InkWell( + borderRadius: BorderRadius.circular(30), + onTap: () => Navigator.pop(context), + child: const Padding( + padding: EdgeInsets.all(10), + child: Icon(Icons.arrow_back, color: Colors.white), + ), + ), + ), + + const SizedBox(height: 20), + + /// 📦 White Elevated Signup Box + Center( + child: Container( + width: width * 0.88, + padding: + const EdgeInsets.symmetric(vertical: 28, horizontal: 22), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(22), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 18, + spreadRadius: 2, + offset: const Offset(0, 6), + ), + ], + ), + + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Create Account", + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.indigo.shade700, + ), + ), + + const SizedBox(height: 25), + + _blueInput(cName, "Customer name"), + const SizedBox(height: 14), + + _blueInput(cCompany, "Company name"), + const SizedBox(height: 14), + + _blueInput(cDesignation, "Designation (optional)"), + const SizedBox(height: 14), + + _blueInput( + cEmail, + "Email", + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 14), + + _blueInput( + cMobile, + "Mobile", + keyboardType: TextInputType.phone, + ), + const SizedBox(height: 14), + + _blueInput( + cAddress, + "Address", + maxLines: 3, + ), + const SizedBox(height: 14), + + _blueInput( + cPincode, + "Pincode", + keyboardType: TextInputType.number, + ), + + const SizedBox(height: 25), + + PrimaryButton( + label: "Send OTP", + onTap: _sendOtp, + busy: sending, + ), + ], + ), + ), + ), + ], + ), ), ), ), ); } + + /// 🔵 Blue soft background input wrapper (same as Login) + Widget _blueInput( + TextEditingController controller, + String hint, { + TextInputType? keyboardType, + int maxLines = 1, + }) { + return Container( + decoration: BoxDecoration( + color: const Color(0xFFD8E7FF), + borderRadius: BorderRadius.circular(14), + ), + child: RoundedInput( + controller: controller, + hint: hint, + // keyboardType: keyboardType, + maxLines: maxLines, + ), + ); + } } diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart index 6932d3b..b939f81 100644 --- a/lib/screens/splash_screen.dart +++ b/lib/screens/splash_screen.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/auth_provider.dart'; -import 'dashboard_screen.dart'; import 'main_bottom_nav.dart'; import 'welcome_screen.dart'; @@ -13,61 +12,166 @@ class SplashScreen extends StatefulWidget { State createState() => _SplashScreenState(); } -class _SplashScreenState extends State { +class _SplashScreenState extends State + with TickerProviderStateMixin { + late AnimationController _mainController; + late Animation _scaleAnim; + late Animation _fadeAnim; + + late AnimationController _floatController; + late Animation _floatAnim; + @override void initState() { super.initState(); + + // MAIN splash animation + _mainController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1200), + ); + + _scaleAnim = Tween(begin: 0.6, end: 1.0).animate( + CurvedAnimation(parent: _mainController, curve: Curves.easeOutBack), + ); + + _fadeAnim = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _mainController, curve: Curves.easeIn), + ); + + // FLOATING animation (infinite) + _floatController = AnimationController( + vsync: this, + duration: const Duration(seconds: 2), + )..repeat(reverse: true); + + _floatAnim = Tween(begin: -10, end: 10).animate( + CurvedAnimation(parent: _floatController, curve: Curves.easeInOut), + ); + + _mainController.forward(); _init(); } - void _init() async { - await Future.delayed(const Duration(milliseconds: 500)); + Future _init() async { + await Future.delayed(const Duration(milliseconds: 700)); final auth = Provider.of(context, listen: false); - - // 🟢 IMPORTANT → WAIT FOR PREFERENCES TO LOAD await auth.init(); if (!mounted) return; - if (auth.isLoggedIn) { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const MainBottomNav()), + Future.delayed(const Duration(milliseconds: 900), () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (_) => + auth.isLoggedIn ? const MainBottomNav() : const WelcomeScreen(), + ), ); - } else { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const WelcomeScreen()), - ); - } + }); + } + + @override + void dispose() { + _mainController.dispose(); + _floatController.dispose(); + super.dispose(); } @override Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; + final width = MediaQuery.of(context).size.width; + + // Responsive scale factor + final scale = (width / 430).clamp(0.9, 1.3); + return Scaffold( - body: Center( - child: Column(mainAxisSize: MainAxisSize.min, children: [ - Container( - width: size.width * 0.34, - height: size.width * 0.34, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).primaryColor.withOpacity(0.14), - ), - child: Center( - child: Text( - "K", - style: TextStyle( - fontSize: 48, - fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor), - ), - ), + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.blue.shade50, + Colors.white, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, ), - const SizedBox(height: 18), - const Text("Kent Logistics", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)), - ]), + ), + width: double.infinity, + height: double.infinity, + child: Center( + child: AnimatedBuilder( + animation: _mainController, + builder: (_, __) { + return Opacity( + opacity: _fadeAnim.value, + child: Transform.translate( + offset: Offset(0, _floatAnim.value), // ⭐ Floating animation + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // ⭐ Animated Floating White Circle Logo + Transform.scale( + scale: _scaleAnim.value, + child: AnimatedBuilder( + animation: _floatController, + builder: (_, __) { + return Container( + width: width * 0.50 * scale, + height: width * 0.50 * scale, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.08 + (_floatAnim.value.abs() / 200)), + blurRadius: 25 * scale, + spreadRadius: 4 * scale, + offset: Offset(0, 8 * scale), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(28 * scale), + child: Image.asset( + "assets/Images/K.png", + fit: BoxFit.contain, + ), + ), + ); + }, + ), + ), + + SizedBox(height: 22 * scale), + + Text( + "Kent Logistics", + style: TextStyle( + fontSize: 22 * scale, + fontWeight: FontWeight.w700, + letterSpacing: 1.1, + ), + ), + + SizedBox(height: 6 * scale), + + Text( + "Delivering Excellence", + style: TextStyle( + fontSize: 14 * scale, + color: Colors.black54, + ), + ) + ], + ), + ), + ); + }, + ), + ), ), ); } diff --git a/lib/screens/waiting_screen.dart b/lib/screens/waiting_screen.dart index 757b1eb..87aa8b4 100644 --- a/lib/screens/waiting_screen.dart +++ b/lib/screens/waiting_screen.dart @@ -1,37 +1,198 @@ +import 'dart:math'; import 'package:flutter/material.dart'; -class WaitingScreen extends StatelessWidget { +class WaitingScreen extends StatefulWidget { const WaitingScreen({super.key}); + @override + State createState() => _WaitingScreenState(); +} + +class _WaitingScreenState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _ctrl; + late final Animation _anim; + + @override + void initState() { + super.initState(); + + _ctrl = AnimationController( + vsync: this, duration: const Duration(milliseconds: 1200)); + + _anim = Tween(begin: 0.0, end: pi).animate( + CurvedAnimation(parent: _ctrl, curve: Curves.easeInOut)); + + _ctrl.repeat(); + } + + @override + void dispose() { + _ctrl.dispose(); + super.dispose(); + } + + Matrix4 _buildTransform(double value) { + return Matrix4.identity() + ..setEntry(3, 2, 0.001) + ..rotateX(value); + } + @override Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + + /// ⭐ Universal scaling factor for responsiveness + final scale = (width / 390).clamp(0.85, 1.3); + return Scaffold( - appBar: AppBar(title: const Text("Request Submitted")), - body: Padding( - padding: const EdgeInsets.all(18.0), - child: Center( - child: Column(mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.hourglass_top, size: 72, color: Theme.of(context).primaryColor), - const SizedBox(height: 16), - const Text( - "Signup request submitted successfully.", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600), - textAlign: TextAlign.center, + body: SafeArea( + child: Stack( + children: [ + /// BACK BUTTON + Positioned( + top: 12 * scale, + left: 12 * scale, + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + height: 42 * scale, + width: 42 * scale, + decoration: const BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + colors: [ + Color(0xFF0D47A1), + Color(0xFF6A1B9A), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Icon(Icons.arrow_back, + color: Colors.white, size: 20 * scale), + ), + ), ), - const SizedBox(height: 8), - const Text( - "Please wait up to 24 hours for admin approval. You will receive an email once approved.", - textAlign: TextAlign.center, + + /// MAIN WHITE BOX + Center( + child: Container( + width: width * 0.90, + padding: EdgeInsets.symmetric( + horizontal: 20 * scale, vertical: 30 * scale), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16 * scale), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.10), + blurRadius: 14 * scale, + offset: Offset(0, 4 * scale), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + /// FLIPPING ICON + SizedBox( + height: 92 * scale, + child: AnimatedBuilder( + animation: _anim, + builder: (context, child) { + final angle = _anim.value; + final isBack = angle > (pi / 2); + + return Transform( + transform: _buildTransform(angle), + alignment: Alignment.center, + child: Transform( + transform: isBack + ? Matrix4.rotationY(pi) + : Matrix4.identity(), + alignment: Alignment.center, + child: child, + ), + ); + }, + child: Icon( + Icons.hourglass_top, + size: 72 * scale, + color: Colors.indigo, + ), + ), + ), + + SizedBox(height: 16 * scale), + + Text( + "Request Submitted", + style: TextStyle( + fontSize: 22 * scale, + fontWeight: FontWeight.w600), + textAlign: TextAlign.center, + ), + + SizedBox(height: 10 * scale), + + Text( + "Signup request submitted successfully.", + style: TextStyle( + fontSize: 18 * scale, + fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + + SizedBox(height: 8 * scale), + + Text( + "Please wait up to 24 hours for admin approval. You will receive an email once approved.", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14 * scale), + ), + + SizedBox(height: 24 * scale), + + /// BUTTON WITH GRADIENT WRAPPER + Container( + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [ + Color(0xFF0D47A1), + Color(0xFF6A1B9A), + ], + ), + borderRadius: BorderRadius.circular(12 * scale), + ), + child: ElevatedButton( + onPressed: () { + Navigator.of(context) + .popUntil((route) => route.isFirst); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 20 * scale, + vertical: 14 * scale), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(12 * scale), + ), + ), + child: Text( + "Back to Home", + style: TextStyle(fontSize: 16 * scale), + ), + ), + ), + ], + ), + ), ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: () { - Navigator.of(context).popUntil((route) => route.isFirst); - }, - child: const Padding(padding: EdgeInsets.symmetric(horizontal: 14, vertical: 12), child: Text("Back to Home")), - style: ElevatedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), - ), - ]), + ], ), ), ); diff --git a/lib/screens/welcome_screen.dart b/lib/screens/welcome_screen.dart index a00855f..111b1de 100644 --- a/lib/screens/welcome_screen.dart +++ b/lib/screens/welcome_screen.dart @@ -1,39 +1,188 @@ import 'package:flutter/material.dart'; import 'signup_screen.dart'; import 'login_screen.dart'; +import '../widgets/primary_button.dart'; -class WelcomeScreen extends StatelessWidget { +class WelcomeScreen extends StatefulWidget { const WelcomeScreen({super.key}); + @override + State createState() => _WelcomeScreenState(); +} + +class _WelcomeScreenState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _fade; + late Animation _slide; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 900), + ); + + _fade = CurvedAnimation( + parent: _controller, + curve: Curves.easeOut, + ); + + _slide = Tween( + begin: const Offset(0, -0.2), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeInOutBack, + ), + ); + + _controller.forward(); + + _controller.addStatusListener((status) { + if (status == AnimationStatus.completed) { + _controller.repeat(reverse: false); + } + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Widget _shinyWelcomeText() { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + double shineX = _controller.value % 1; + + return Stack( + alignment: Alignment.center, + children: [ + Text( + "Welcome", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 42, + fontWeight: FontWeight.bold, + color: Colors.indigo.shade700, + letterSpacing: 1.2, + ), + ), + + Positioned.fill( + child: IgnorePointer( + child: ShaderMask( + blendMode: BlendMode.srcATop, + shaderCallback: (rect) { + final pos = shineX * rect.width; + + return LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [ + (pos - 50) / rect.width, + pos / rect.width, + (pos + 50) / rect.width, + ], + colors: [ + Colors.transparent, + Colors.blueAccent, + Colors.transparent, + ], + ).createShader(rect); + }, + child: Text( + "Welcome", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 42, + fontWeight: FontWeight.bold, + color: Colors.indigo.shade900, + letterSpacing: 1.2, + ), + ), + ), + ), + ), + ], + ); + }, + ); + } + @override Widget build(BuildContext context) { - final w = MediaQuery.of(context).size.width; + final height = MediaQuery.of(context).size.height; + final width = MediaQuery.of(context).size.width; + return Scaffold( + backgroundColor: const Color(0xFFE8F0FF), body: SafeArea( child: Padding( - padding: EdgeInsets.symmetric(horizontal: w * 0.06), + padding: EdgeInsets.symmetric(horizontal: width * 0.07), child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox(height: 28), - Align(alignment: Alignment.centerLeft, child: Text("Welcome", style: Theme.of(context).textTheme.headlineSmall)), - const SizedBox(height: 12), + /// Animated Welcome text + SlideTransition( + position: _slide, + child: _shinyWelcomeText(), + ), + + SizedBox(height: height * 0.01), + + /// LOGO SECTION + Image.asset( + 'assets/Images/K.png', + height: height * 0.28, + ), + + SizedBox(height: height * 0.015), + + /// Description Text const Text( "Register to access Kent Logistics services. After signup admin will review and approve your request. Approval may take up to 24 hours.", - style: TextStyle(fontSize: 15), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + height: 1.4, + color: Colors.black87, + ), ), - const Spacer(), - ElevatedButton( - onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const SignupScreen())), - child: const SizedBox(width: double.infinity, child: Center(child: Padding(padding: EdgeInsets.all(14.0), child: Text("Create Account")))), - style: ElevatedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), + + SizedBox(height: height * 0.04), + + /// 🌈 Create Account Button (Gradient) + PrimaryButton( + label: "Create Account", + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const SignupScreen()), + ); + }, ), - const SizedBox(height: 12), - OutlinedButton( - onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const LoginScreen())), - child: const SizedBox(width: double.infinity, child: Center(child: Padding(padding: EdgeInsets.all(14.0), child: Text("Login")))), - style: OutlinedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), + + SizedBox(height: height * 0.015), + + /// 🌈 Login Button (Gradient) + PrimaryButton( + label: "Login", + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const LoginScreen()), + ); + }, ), - const SizedBox(height: 24), + + SizedBox(height: height * 0.02), ], ), ), diff --git a/lib/services/invoice_service.dart b/lib/services/invoice_service.dart index 9fb4a48..b81f9ce 100644 --- a/lib/services/invoice_service.dart +++ b/lib/services/invoice_service.dart @@ -4,30 +4,55 @@ class InvoiceService { final Dio dio; InvoiceService(this.dio); + // ------------------------------------------------------- + // ⭐ GET ALL INVOICES + // ------------------------------------------------------- Future> getAllInvoices() async { try { final res = await dio.get("/user/invoices"); + + print("🔵 ALL INVOICES RESPONSE:"); + print(res.data); + return Map.from(res.data); } catch (e) { + print("❌ ERROR (All Invoices): $e"); return {"success": false, "message": e.toString()}; } } + // ------------------------------------------------------- + // ⭐ GET INSTALLMENTS + // ------------------------------------------------------- Future> getInstallments(int invoiceId) async { try { - final res = await dio.get("/user/invoice/$invoiceId/installments"); + final res = + await dio.get("/user/invoice/$invoiceId/installments"); + + print("🔵 INSTALLMENTS RESPONSE:"); + print(res.data); + return Map.from(res.data); } catch (e) { + print("❌ ERROR (Installments): $e"); return {"success": false, "message": e.toString()}; } } - /// 🔵 NEW FUNCTION — Fetch Full Invoice Details + // ------------------------------------------------------- + // ⭐ GET FULL INVOICE DETAILS (PRINT JSON HERE) + // ------------------------------------------------------- Future> getInvoiceDetails(int invoiceId) async { try { final res = await dio.get("/user/invoice/$invoiceId/details"); + + print("👇👇👇 INVOICE API RESPONSE START 👇👇👇"); + print(res.data); // <-- THIS IS WHAT YOU NEED + print("👆👆👆 INVOICE API RESPONSE END 👆👆👆"); + return Map.from(res.data); } catch (e) { + print("❌ ERROR (Invoice Details): $e"); return {"success": false, "message": e.toString()}; } } diff --git a/lib/widgets/invoice_detail_view.dart b/lib/widgets/invoice_detail_view.dart new file mode 100644 index 0000000..baa0cb2 --- /dev/null +++ b/lib/widgets/invoice_detail_view.dart @@ -0,0 +1,315 @@ +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; + bool s4 = 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(); + } + + // ------------------------------------------------------------------ + // HEADER SUMMARY CARD + // ------------------------------------------------------------------ + Widget headerCard(Map invoice, double scale) { + return Container( + width: double.infinity, + padding: EdgeInsets.all(16 * scale), // tighter + margin: EdgeInsets.only(bottom: 14 * scale), // closer + 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: 8 * scale), + Container( + padding: EdgeInsets.symmetric( + vertical: 5 * scale, horizontal: 12 * scale), + decoration: BoxDecoration( + color: Colors.white.withOpacity(.2), + borderRadius: BorderRadius.circular(40 * scale), + ), + child: Text( + invoice['status'] ?? "Unknown", + style: TextStyle( + color: Colors.white, + fontSize: 13 * scale, + ), + ), + ) + ], + ), + ); + } + + // ------------------------------------------------------------------ + // SECTION HEADER (Closer Spacing) + // ------------------------------------------------------------------ + Widget sectionHeader( + String title, IconData icon, bool expanded, Function() tap, double scale) { + return GestureDetector( + onTap: tap, + child: Container( + padding: EdgeInsets.all(12 * scale), // tighter + margin: EdgeInsets.only(bottom: 8 * scale), // closer + 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 (Closer Spacing) + // ------------------------------------------------------------------ + 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( + width: double.infinity, + padding: EdgeInsets.all(14 * scale), // tighter + margin: EdgeInsets.only(bottom: 10 * scale), // closer + 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 (Closer Spacing) + // ------------------------------------------------------------------ + Widget detailRow(IconData icon, String label, dynamic value, double scale) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 5 * scale), // closer + 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: Text( + value?.toString() ?? "N/A", + textAlign: TextAlign.right, + style: + TextStyle(fontSize: 14 * scale, fontWeight: FontWeight.bold), + ), + ) + ], + ), + ); + } + + // ------------------------------------------------------------------ + // TIMELINE (Closer Spacing) + // ------------------------------------------------------------------ + Widget invoiceTimeline(double scale) { + final steps = ["Invoice Created", "Payment Received", "Out for Delivery", "Completed"]; + final icons = [Icons.receipt_long, Icons.payments, Icons.local_shipping, Icons.verified]; + + return Container( + padding: EdgeInsets.all(16 * scale), + margin: EdgeInsets.only(bottom: 16 * scale), // closer + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: BorderRadius.circular(14 * scale), + boxShadow: [ + BoxShadow( + blurRadius: 7 * scale, + color: Colors.black.withOpacity(.1), + offset: Offset(0, 3 * scale), + ) + ], + ), + child: Column( + children: List.generate(steps.length, (i) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 4 * scale), // closer + child: Row( + children: [ + Column( + children: [ + Icon(icons[i], size: 22 * scale, color: Colors.indigo), + if (i < steps.length - 1) + Container( + height: 28 * scale, + width: 2 * scale, + color: Colors.indigo.shade300, + ) + ], + ), + SizedBox(width: 10 * scale), + Expanded( + child: Text( + steps[i], + style: + TextStyle(fontSize: 14 * scale, fontWeight: FontWeight.w600), + ), + ) + ], + ), + ); + }), + ), + ); + } + + // ------------------------------------------------------------------ + // 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), // slightly tighter + children: [ + headerCard(invoice, scale), + invoiceTimeline(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.summarize, "Total", invoice['final_amount_with_gst'], scale), + ], scale), + + sectionHeader("Payment Details", Icons.payment, s4, + () => setState(() => s4 = !s4), scale), + sectionBody(s4, [ + detailRow(Icons.credit_card, "Method", invoice['payment_method'], scale), + detailRow(Icons.confirmation_number, "Reference", + invoice['reference_no'], scale), + ], scale), + ], + ); + } +} diff --git a/lib/widgets/main_app_bar.dart b/lib/widgets/main_app_bar.dart index 3f40b6f..c36fae3 100644 --- a/lib/widgets/main_app_bar.dart +++ b/lib/widgets/main_app_bar.dart @@ -13,7 +13,7 @@ class MainAppBar extends StatelessWidget implements PreferredSizeWidget { final profileUrl = profileProvider.profile?.profileImage; return AppBar( - backgroundColor: Colors.lightGreen, + backgroundColor: Colors.white, elevation: 0.8, surfaceTintColor: Colors.transparent, automaticallyImplyLeading: false, diff --git a/lib/widgets/primary_button.dart b/lib/widgets/primary_button.dart index 5fafed4..fe6ddde 100644 --- a/lib/widgets/primary_button.dart +++ b/lib/widgets/primary_button.dart @@ -1,19 +1,62 @@ import 'package:flutter/material.dart'; + class PrimaryButton extends StatelessWidget { final String label; final VoidCallback onTap; final bool busy; - const PrimaryButton({super.key, required this.label, required this.onTap, this.busy = false}); + + const PrimaryButton({ + super.key, + required this.label, + required this.onTap, + this.busy = false, + }); + @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, - child: ElevatedButton( - onPressed: busy ? null : onTap, - style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), - child: busy ? const SizedBox(height: 18, width: 18, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : Text(label, style: const TextStyle(fontSize: 16)), + + child: Container( + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [ + Color(0xFF0D47A1), // Blue + Color(0xFF6A1B9A), // Purple + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(12), + ), + + child: ElevatedButton( + onPressed: busy ? null : onTap, + style: ElevatedButton.styleFrom( + backgroundColor: Colors + .transparent, // IMPORTANT: keep transparent to see gradient + shadowColor: Colors.transparent, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + + child: busy + ? const SizedBox( + height: 18, + width: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : Text( + label, + style: const TextStyle(fontSize: 16, color: Colors.white), + ), + ), ), ); } } - diff --git a/lib/widgets/rounded_input.dart b/lib/widgets/rounded_input.dart index 90b1275..e280e79 100644 --- a/lib/widgets/rounded_input.dart +++ b/lib/widgets/rounded_input.dart @@ -19,16 +19,27 @@ class RoundedInput extends StatelessWidget { @override Widget build(BuildContext context) { final radius = BorderRadius.circular(12); + return TextField( controller: controller, keyboardType: keyboardType, obscureText: obscure, - maxLines: maxLines, + maxLines: obscure ? 1 : maxLines, + style: const TextStyle( + color: Colors.grey, // grey input text + ), decoration: InputDecoration( filled: true, + fillColor: const Color(0xFFD8E7FF), // light blue background hintText: hint, + hintStyle: const TextStyle( + color: Colors.grey, // grey hint text + ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - border: OutlineInputBorder(borderRadius: radius, borderSide: BorderSide.none), + border: OutlineInputBorder( + borderRadius: radius, + borderSide: BorderSide.none, + ), ), ); } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 64a0ece..7299b5c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2db3c22..786ff5c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 4b4e1ac..9d157c1 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,10 +7,12 @@ import Foundation import file_selector_macos import path_provider_foundation +import share_plus import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index ec71720..69c47f0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" async: dependency: transitive description: @@ -9,6 +17,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + barcode: + dependency: transitive + description: + name: barcode + sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" + url: "https://pub.dev" + source: hosted + version: "2.2.9" + bidi: + dependency: transitive + description: + name: bidi + sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" + url: "https://pub.dev" + source: hosted + version: "2.0.13" boolean_selector: dependency: transitive description: @@ -137,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+5" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -192,6 +224,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" image_picker: dependency: "direct main" description: @@ -260,26 +300,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -308,10 +348,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -336,8 +376,16 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" - path_provider: + path_parsing: dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" @@ -384,6 +432,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + pdf: + dependency: "direct main" + description: + name: pdf + sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" + url: "https://pub.dev" + source: hosted + version: "3.11.3" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" platform: dependency: transitive description: @@ -400,6 +464,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" provider: dependency: "direct main" description: @@ -408,6 +480,30 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da + url: "https://pub.dev" + source: hosted + version: "10.1.4" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b + url: "https://pub.dev" + source: hosted + version: "5.0.2" shared_preferences: dependency: "direct main" description: @@ -513,10 +609,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.7" typed_data: dependency: transitive description: @@ -525,14 +621,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: transitive + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -549,6 +685,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" xdg_directories: dependency: transitive description: @@ -557,6 +701,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" sdks: dart: ">=3.8.1 <4.0.0" flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index 327abd4..190dd26 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,10 @@ dependencies: google_fonts: ^4.0.3 image_picker: ^1.0.7 + share_plus: ^10.0.0 + path_provider: ^2.1.2 + pdf: ^3.11.0 + # The following adds the Cupertino Icons font to your application. @@ -64,6 +68,8 @@ flutter: # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true + assets: + - assets/Images/K.png # To add assets to your application, add an assets section, like this: # assets: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 77ab7a0..5e62361 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,14 @@ #include "generated_plugin_registrant.h" #include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a423a02..281df51 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,8 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows + share_plus + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST