From 3f2a804a319e797b9ccc38468a5440b2b63190a0 Mon Sep 17 00:00:00 2001 From: "sridhar.r" Date: Wed, 10 Jun 2026 14:47:50 +0530 Subject: [PATCH] Refactor contacts using flutter_contacts, unify InteractiveScale animations and 16px corner radius across welcome and setup complete screens, and compile final release APK --- flutter_app/android/build.gradle.kts | 22 + flutter_app/lib/route_transitions.dart | 116 +++ .../lib/screens/billing_dashboard_screen.dart | 318 ++++--- .../lib/screens/business_setup_screen.dart | 874 ++++++++++-------- .../lib/screens/customize_setup_screen.dart | 238 ++--- .../lib/screens/mobile_login_screen.dart | 694 +++++++------- .../lib/screens/otp_verification_screen.dart | 419 +++++---- .../lib/screens/setup_complete_screen.dart | 76 +- flutter_app/lib/screens/welcome_screen.dart | 51 +- flutter_app/lib/theme.dart | 2 +- flutter_app/pubspec.lock | 24 +- flutter_app/pubspec.yaml | 2 +- 12 files changed, 1580 insertions(+), 1256 deletions(-) diff --git a/flutter_app/android/build.gradle.kts b/flutter_app/android/build.gradle.kts index 89176ef..5573ea6 100644 --- a/flutter_app/android/build.gradle.kts +++ b/flutter_app/android/build.gradle.kts @@ -11,6 +11,28 @@ rootProject.layout.buildDirectory.value(newBuildDir) subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) + + afterEvaluate { + if (extensions.findByName("android") != null) { + val android = extensions.findByName("android") as com.android.build.gradle.BaseExtension + if (android.namespace == null) { + var manifestPackage: String? = null + val manifestFile = project.projectDir.resolve("src/main/AndroidManifest.xml") + if (manifestFile.exists()) { + val content = manifestFile.readText() + val match = Regex("package=\"([^\"]+)\"").find(content) + if (match != null) { + manifestPackage = match.groupValues[1] + } + } + if (manifestPackage != null) { + android.namespace = manifestPackage + } else { + android.namespace = "co.gittouch." + project.name.replace("-", "_") + } + } + } + } } subprojects { project.evaluationDependsOn(":app") diff --git a/flutter_app/lib/route_transitions.dart b/flutter_app/lib/route_transitions.dart index 18a00bd..6a59a28 100644 --- a/flutter_app/lib/route_transitions.dart +++ b/flutter_app/lib/route_transitions.dart @@ -186,3 +186,119 @@ class _TopAlertWidgetState extends State<_TopAlertWidget> with SingleTickerProvi ); } } + +class SlideFadeEntrance extends StatefulWidget { + final Widget child; + final Duration delay; + final Duration duration; + + const SlideFadeEntrance({ + super.key, + required this.child, + this.delay = Duration.zero, + this.duration = const Duration(milliseconds: 500), + }); + + @override + State createState() => _SlideFadeEntranceState(); +} + +class _SlideFadeEntranceState extends State with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _opacityAnimation; + late Animation _slideAnimation; + Timer? _timer; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: widget.duration, + ); + + _opacityAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeIn), + ); + + _slideAnimation = Tween(begin: 12.0, end: 0.0).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic), + ); + + if (widget.delay == Duration.zero) { + _controller.forward(); + } else { + _timer = Timer(widget.delay, () { + if (mounted) { + _controller.forward(); + } + }); + } + } + + @override + void dispose() { + _timer?.cancel(); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Opacity( + opacity: _opacityAnimation.value, + child: Transform.translate( + offset: Offset(0.0, _slideAnimation.value), + child: child, + ), + ); + }, + child: widget.child, + ); + } +} + +class InteractiveScale extends StatefulWidget { + final Widget child; + final VoidCallback? onTap; + final double hoverScale; + final double tapScale; + + const InteractiveScale({ + super.key, + required this.child, + this.onTap, + this.hoverScale = 1.03, + this.tapScale = 0.96, + }); + + @override + State createState() => _InteractiveScaleState(); +} + +class _InteractiveScaleState extends State { + double _scale = 1.0; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState(() => _scale = widget.hoverScale), + onExit: (_) => setState(() => _scale = 1.0), + child: AnimatedScale( + scale: _scale, + duration: const Duration(milliseconds: 120), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (_) => setState(() => _scale = widget.tapScale), + onTapUp: (_) => setState(() => _scale = widget.hoverScale), + onTapCancel: () => setState(() => _scale = 1.0), + onTap: widget.onTap, + child: widget.child, + ), + ), + ); + } +} diff --git a/flutter_app/lib/screens/billing_dashboard_screen.dart b/flutter_app/lib/screens/billing_dashboard_screen.dart index 2067383..3207a67 100644 --- a/flutter_app/lib/screens/billing_dashboard_screen.dart +++ b/flutter_app/lib/screens/billing_dashboard_screen.dart @@ -248,21 +248,23 @@ class _BillingDashboardScreenState extends State { padding: const EdgeInsets.only(right: 8.0), child: Directionality( textDirection: TextDirection.rtl, - child: ElevatedButton.icon( - onPressed: _simulateBarcodeScan, - style: ElevatedButton.styleFrom( - minimumSize: const Size(0, 32), - backgroundColor: theme.colorScheme.primary, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + child: InteractiveScale( + child: ElevatedButton.icon( + onPressed: _simulateBarcodeScan, + style: ElevatedButton.styleFrom( + minimumSize: const Size(0, 32), + backgroundColor: theme.colorScheme.primary, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + icon: const Icon(Icons.qr_code_scanner, size: 16), + label: const Text( + 'Scan Item', + style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold), ), - ), - icon: const Icon(Icons.qr_code_scanner, size: 16), - label: const Text( - 'Scan Item', - style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold), ), ), ), @@ -407,95 +409,103 @@ class _BillingDashboardScreenState extends State { final isLowStock = widget.inventoryAlerts && remainingStock <= 3; final isOutOfStock = widget.inventoryAlerts && remainingStock <= 0; - return InkWell( - onTap: isOutOfStock ? null : () => _handleAddItem(prod), - borderRadius: BorderRadius.circular(16), - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: countInCart > 0 - ? const Color(0xFF1E1B4B) // Deep indigo accent - : const Color(0xFF1E293B).withOpacity(0.8), + return SlideFadeEntrance( + delay: Duration(milliseconds: index * 40), + duration: const Duration(milliseconds: 300), + child: InteractiveScale( + tapScale: isOutOfStock ? 1.0 : 0.95, + hoverScale: isOutOfStock ? 1.0 : 1.03, + child: InkWell( + onTap: isOutOfStock ? null : () => _handleAddItem(prod), borderRadius: BorderRadius.circular(16), - border: Border.all( - color: countInCart > 0 ? theme.colorScheme.primary : const Color(0xFF334155), - width: countInCart > 0 ? 1.5 : 1.0, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - prod.name, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 11, - ), - ), - const SizedBox(height: 4), - Text( - '₹${prod.price.toStringAsFixed(0)}', - style: TextStyle( - color: theme.colorScheme.primary, - fontWeight: FontWeight.w800, - fontSize: 13, - ), - ), - ], + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: countInCart > 0 + ? const Color(0xFF1E1B4B) // Deep indigo accent + : const Color(0xFF1E293B).withOpacity(0.8), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: countInCart > 0 ? theme.colorScheme.primary : const Color(0xFF334155), + width: countInCart > 0 ? 1.5 : 1.0, ), ), - Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (widget.inventoryAlerts) - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: isLowStock - ? Colors.red.withOpacity(0.15) - : const Color(0xFF334155), - borderRadius: BorderRadius.circular(6), - ), - child: Text( - isOutOfStock ? 'Out of stock' : '$remainingStock Stock', - style: TextStyle( - color: isLowStock ? Colors.redAccent : Colors.grey, - fontSize: 8, - fontWeight: FontWeight.bold, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + prod.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 11, + ), ), - ), - ) - else - const SizedBox(), - if (countInCart > 0) - Container( - width: 20, - height: 20, - decoration: BoxDecoration( - color: theme.colorScheme.primary, - shape: BoxShape.circle, - ), - alignment: Alignment.center, - child: Text( - '$countInCart', - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 10, + const SizedBox(height: 4), + Text( + '₹${prod.price.toStringAsFixed(0)}', + style: TextStyle( + color: theme.colorScheme.primary, + fontWeight: FontWeight.w800, + fontSize: 13, + ), ), - ), - ) + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (widget.inventoryAlerts) + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: isLowStock + ? Colors.red.withOpacity(0.15) + : const Color(0xFF334155), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + isOutOfStock ? 'Out of stock' : '$remainingStock Stock', + style: TextStyle( + color: isLowStock ? Colors.redAccent : Colors.grey, + fontSize: 8, + fontWeight: FontWeight.bold, + ), + ), + ) + else + const SizedBox(), + if (countInCart > 0) + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: theme.colorScheme.primary, + shape: BoxShape.circle, + ), + alignment: Alignment.center, + child: Text( + '$countInCart', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 10, + ), + ), + ) + ], + ) ], - ) - ], + ), + ), ), ), ); @@ -655,20 +665,27 @@ class _BillingDashboardScreenState extends State { const Divider(color: Color(0xFF334155), height: 16), _buildComputationCalculations(theme), const SizedBox(height: 10), - ElevatedButton( - onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate, - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.primary, - disabledBackgroundColor: const Color(0xFF1E293B), - minimumSize: const Size.fromHeight(48), - ), - child: const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.receipt, size: 16), - SizedBox(width: 8), - Text('Generate Invoice'), - ], + InteractiveScale( + hoverScale: _billingList.isNotEmpty ? 1.03 : 1.0, + tapScale: _billingList.isNotEmpty ? 0.96 : 1.0, + child: ElevatedButton( + onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate, + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.primary, + disabledBackgroundColor: const Color(0xFF1E293B), + minimumSize: const Size.fromHeight(48), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.receipt, size: 16), + SizedBox(width: 8), + Text('Generate Invoice'), + ], + ), ), ) ] @@ -751,19 +768,26 @@ class _BillingDashboardScreenState extends State { ) ], ), - ElevatedButton( - onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate, - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - minimumSize: const Size(180, 44), - ), - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.print, size: 16), - SizedBox(width: 8), - Text('Review & Invoice', style: TextStyle(fontSize: 12)), - ], + InteractiveScale( + hoverScale: _billingList.isNotEmpty ? 1.03 : 1.0, + tapScale: _billingList.isNotEmpty ? 0.96 : 1.0, + child: ElevatedButton( + onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + minimumSize: const Size(180, 44), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.print, size: 16), + SizedBox(width: 8), + Text('Review & Invoice', style: TextStyle(fontSize: 12)), + ], + ), ), ) ], @@ -937,30 +961,40 @@ class _BillingDashboardScreenState extends State { ), ), const SizedBox(height: 16), - ElevatedButton.icon( - onPressed: () { - showTopAlert( - context, - 'Thermal Printed: $_invoiceNumber successfully!', - icon: Icons.print, - ); - }, - icon: const Icon(Icons.print, size: 16), - label: const Text('Simulate Print Receipt', style: TextStyle(fontSize: 12)), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.black, - foregroundColor: Colors.white, - minimumSize: const Size.fromHeight(42), + InteractiveScale( + child: ElevatedButton.icon( + onPressed: () { + showTopAlert( + context, + 'Thermal Printed: $_invoiceNumber successfully!', + icon: Icons.print, + ); + }, + icon: const Icon(Icons.print, size: 16), + label: const Text('Simulate Print Receipt', style: TextStyle(fontSize: 12)), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.black, + foregroundColor: Colors.white, + minimumSize: const Size.fromHeight(42), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), ), ), const SizedBox(height: 8), - ElevatedButton( - onPressed: _closeReceiptAndReset, - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.primary, - minimumSize: const Size.fromHeight(42), + InteractiveScale( + child: ElevatedButton( + onPressed: _closeReceiptAndReset, + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.primary, + minimumSize: const Size.fromHeight(42), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: const Text('New Ledger Sale', style: TextStyle(fontSize: 12)), ), - child: const Text('New Ledger Sale', style: TextStyle(fontSize: 12)), ), ], ), diff --git a/flutter_app/lib/screens/business_setup_screen.dart b/flutter_app/lib/screens/business_setup_screen.dart index 5fb5ef4..6654fd3 100644 --- a/flutter_app/lib/screens/business_setup_screen.dart +++ b/flutter_app/lib/screens/business_setup_screen.dart @@ -146,46 +146,48 @@ class _BusinessSetupScreenState extends State { Widget _buildDocChip(String label, IconData icon) { final theme = Theme.of(context); final isSelected = _selectedDocs.contains(label); - return InkWell( - onTap: () { - setState(() { - if (isSelected) { - _selectedDocs.remove(label); - } else { - _selectedDocs.add(label); - } - }); - }, - borderRadius: BorderRadius.circular(8), - child: Container( - constraints: const BoxConstraints(minHeight: 48), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: isSelected ? const Color(0xFFFAF0F0) : Colors.white, - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: isSelected ? const Color(0xFFAF101A) : theme.colorScheme.outlineVariant, - width: isSelected ? 1.5 : 1, + return InteractiveScale( + child: InkWell( + onTap: () { + setState(() { + if (isSelected) { + _selectedDocs.remove(label); + } else { + _selectedDocs.add(label); + } + }); + }, + borderRadius: BorderRadius.circular(8), + child: Container( + constraints: const BoxConstraints(minHeight: 48), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? const Color(0xFFFAF0F0) : Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isSelected ? const Color(0xFFAF101A) : theme.colorScheme.outlineVariant, + width: isSelected ? 1.5 : 1, + ), ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - icon, - size: 16, - color: isSelected ? const Color(0xFFAF101A) : Colors.grey[600], - ), - const SizedBox(width: 8), - Text( - label, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - color: isSelected ? const Color(0xFFAF101A) : Colors.grey[800], + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + size: 16, + color: isSelected ? const Color(0xFFAF101A) : Colors.grey[600], ), - ), - ], + const SizedBox(width: 8), + Text( + label, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: isSelected ? const Color(0xFFAF101A) : Colors.grey[800], + ), + ), + ], + ), ), ), ); @@ -337,31 +339,35 @@ class _BusinessSetupScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - ElevatedButton.icon( - onPressed: () => _handleImagePick(docName, ImageSource.camera), - icon: const Icon(Icons.camera_alt, size: 14), - label: const Text('Take Photo'), - style: ElevatedButton.styleFrom( - minimumSize: const Size(0, 36), - backgroundColor: const Color(0xFFAF101A), - foregroundColor: Colors.white, - elevation: 1, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8)), + InteractiveScale( + child: ElevatedButton.icon( + onPressed: () => _handleImagePick(docName, ImageSource.camera), + icon: const Icon(Icons.camera_alt, size: 14), + label: const Text('Take Photo'), + style: ElevatedButton.styleFrom( + minimumSize: const Size(0, 36), + backgroundColor: const Color(0xFFAF101A), + foregroundColor: Colors.white, + elevation: 1, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16)), + ), ), ), const SizedBox(width: 8), - OutlinedButton.icon( - onPressed: () => _handleImagePick(docName, ImageSource.gallery), - icon: const Icon(Icons.photo_library, size: 14), - label: const Text('From Gallery'), - style: OutlinedButton.styleFrom( - foregroundColor: const Color(0xFFAF101A), - side: const BorderSide(color: Color(0xFFAF101A)), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8)), + InteractiveScale( + child: OutlinedButton.icon( + onPressed: () => _handleImagePick(docName, ImageSource.gallery), + icon: const Icon(Icons.photo_library, size: 14), + label: const Text('From Gallery'), + style: OutlinedButton.styleFrom( + foregroundColor: const Color(0xFFAF101A), + side: const BorderSide(color: Color(0xFFAF101A)), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16)), + ), ), ), ], @@ -441,337 +447,407 @@ class _BusinessSetupScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Complete verification', - style: theme.textTheme.headlineLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 6), - Text( - 'Verify your business details to start selling.', - style: theme.textTheme.bodyMedium, - ), - const SizedBox(height: 16), - - Text( - 'Business/Shop Name *', - style: theme.textTheme.labelSmall?.copyWith( - color: theme.colorScheme.onSurface, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - TextField( - controller: _nameController, - decoration: const InputDecoration( - hintText: 'e.g. Sharma General Store', - ), - style: const TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 16), - - Text( - 'Business Type *', - style: theme.textTheme.labelSmall?.copyWith( - color: theme.colorScheme.onSurface, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - - GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 16, - mainAxisSpacing: 16, - childAspectRatio: 2.2, - ), - itemCount: _categories.length, - itemBuilder: (context, index) { - final cat = _categories[index]; - final isSelected = _selectedCategory == cat['id']; - - return InkWell( - onTap: () { - setState(() { - _selectedCategory = cat['id']; - }); - }, - borderRadius: BorderRadius.circular(16), - child: Container( - decoration: BoxDecoration( - color: isSelected - ? const Color(0xFFAF101A).withOpacity(0.08) - : Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: isSelected ? const Color(0xFFAF101A) : const Color(0xFFE2E2E2), - width: isSelected ? 2 : 1, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.01), - blurRadius: 4, - offset: const Offset(0, 2), - ) - ], + SlideFadeEntrance( + delay: Duration.zero, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Complete verification', + style: theme.textTheme.headlineLarge?.copyWith( + fontWeight: FontWeight.bold, ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Center( - child: Icon( - cat['icon'], - color: isSelected ? const Color(0xFFAF101A) : Colors.grey[600], - size: 26, + ), + const SizedBox(height: 6), + Text( + 'Verify your business details to start selling.', + style: theme.textTheme.bodyMedium, + ), + ], + ), + ), + const SizedBox(height: 16), + + SlideFadeEntrance( + delay: const Duration(milliseconds: 100), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Business/Shop Name *', + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + TextField( + controller: _nameController, + decoration: const InputDecoration( + hintText: 'e.g. Sharma General Store', + ), + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ), + const SizedBox(height: 16), + + SlideFadeEntrance( + delay: const Duration(milliseconds: 180), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Business Type *', + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 2.2, + ), + itemCount: _categories.length, + itemBuilder: (context, index) { + final cat = _categories[index]; + final isSelected = _selectedCategory == cat['id']; + + return InteractiveScale( + child: InkWell( + onTap: () { + setState(() { + _selectedCategory = cat['id']; + }); + }, + borderRadius: BorderRadius.circular(16), + child: Container( + decoration: BoxDecoration( + color: isSelected + ? const Color(0xFFAF101A).withOpacity(0.08) + : Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: isSelected ? const Color(0xFFAF101A) : const Color(0xFFE2E2E2), + width: isSelected ? 2 : 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.01), + blurRadius: 4, + offset: const Offset(0, 2), + ) + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center( + child: Icon( + cat['icon'], + color: isSelected ? const Color(0xFFAF101A) : Colors.grey[600], + size: 26, + ), + ), + const SizedBox(height: 6), + Center( + child: Text( + cat['name'], + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: isSelected ? const Color(0xFFAF101A) : Colors.grey[800], + ), + ), + ) + ], + ), ), ), - const SizedBox(height: 6), - Center( - child: Text( - cat['name'], - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: isSelected ? const Color(0xFFAF101A) : Colors.grey[800], - ), - ), - ) - ], - ), + ); + }, ), - ); - }, + ], + ), ), const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'GSTIN', - style: theme.textTheme.labelSmall?.copyWith( - color: theme.colorScheme.onSurface, - fontWeight: FontWeight.bold, + SlideFadeEntrance( + delay: const Duration(milliseconds: 260), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'GSTIN', + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), + ), + Text( + '(Optional)', + style: theme.textTheme.bodySmall?.copyWith( + color: Colors.grey[500], + ), + ) + ], ), - ), - Text( - '(Optional)', - style: theme.textTheme.bodySmall?.copyWith( - color: Colors.grey[500], + const SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: TextField( + controller: _gstinController, + textCapitalization: TextCapitalization.characters, + decoration: const InputDecoration( + hintText: '15-DIGIT GST NUMBER', + ), + style: const TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1.2), + ), + ), + const SizedBox(width: 12), + InteractiveScale( + child: ElevatedButton( + onPressed: () { + showTopAlert( + context, + 'GSTIN Verified Successfully!', + icon: Icons.verified_outlined, + ); + }, + style: ElevatedButton.styleFrom( + minimumSize: const Size(0, 48), + backgroundColor: theme.colorScheme.primary, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: const Text('Verify'), + ), + ), + ], ), - ) - ], + const SizedBox(height: 6), + Text( + "Auto-fetches address & legal name on verification.", + style: TextStyle(fontSize: 11, color: Colors.grey[500]), + ), + ], + ), ), - const SizedBox(height: 8), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: TextField( - controller: _gstinController, - textCapitalization: TextCapitalization.characters, - decoration: const InputDecoration( - hintText: '15-DIGIT GST NUMBER', + const SizedBox(height: 16), + + SlideFadeEntrance( + delay: const Duration(milliseconds: 340), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Shop Location *', + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.onSurface, + fontWeight: FontWeight.bold, ), - style: const TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1.2), ), - ), - const SizedBox(width: 12), - ElevatedButton( - onPressed: () { - showTopAlert( - context, - 'GSTIN Verified Successfully!', - icon: Icons.verified_outlined, - ); - }, - style: ElevatedButton.styleFrom( - minimumSize: const Size(0, 48), - backgroundColor: theme.colorScheme.primary, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), - shape: RoundedRectangleBorder( + const SizedBox(height: 8), + InteractiveScale( + child: InkWell( + onTap: () { + showTopAlert( + context, + 'Location detected: Sector 62, Noida, UP', + icon: Icons.my_location_outlined, + ); + }, borderRadius: BorderRadius.circular(16), - ), - ), - child: const Text('Verify'), - ), - ], - ), - const SizedBox(height: 6), - Text( - "Auto-fetches address & legal name on verification.", - style: TextStyle(fontSize: 11, color: Colors.grey[500]), - ), - const SizedBox(height: 16), - - Text( - 'Shop Location *', - style: theme.textTheme.labelSmall?.copyWith( - color: theme.colorScheme.onSurface, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - InkWell( - onTap: () { - showTopAlert( - context, - 'Location detected: Sector 62, Noida, UP', - icon: Icons.my_location_outlined, - ); - }, - borderRadius: BorderRadius.circular(16), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: theme.colorScheme.outlineVariant, - ), - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: theme.colorScheme.primary.withOpacity(0.1), - shape: BoxShape.circle, - ), - child: Icon( - Icons.my_location, - color: theme.colorScheme.primary, - size: 20, - ), - ), - const SizedBox(width: 14), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Detect current location', - style: theme.textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.bold, - fontSize: 15, - ), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: theme.colorScheme.outlineVariant, ), - const SizedBox(height: 2), - Text( - 'Pin shop address automatically', - style: TextStyle( - fontSize: 11, - color: Colors.grey[600], + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.my_location, + color: theme.colorScheme.primary, + size: 20, + ), ), - ), - ], + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Detect current location', + style: theme.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), + const SizedBox(height: 2), + Text( + 'Pin shop address automatically', + style: TextStyle( + fontSize: 11, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Icon( + Icons.chevron_right, + color: Colors.grey[400], + ), + ], + ), ), ), - Icon( - Icons.chevron_right, - color: Colors.grey[400], - ), - ], - ), - ), - ), - const SizedBox(height: 16), - - Text( - 'Identity / License Documents (Select all that apply)', - style: theme.textTheme.labelSmall?.copyWith( - color: theme.colorScheme.onSurface, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - _buildDocChip('GSTIN', Icons.description), - _buildDocChip('FSSAI', Icons.restaurant), - _buildDocChip('Aadhar', Icons.badge_outlined), - _buildDocChip('Udhyog Aadhar', Icons.domain), - _buildDocChip('PAN', Icons.credit_card), - ], - ), - const SizedBox(height: 8), - ..._selectedDocs.map((doc) { - IconData docIcon = Icons.description; - if (doc == 'GSTIN') docIcon = Icons.description; - if (doc == 'FSSAI') docIcon = Icons.restaurant; - if (doc == 'Aadhar') docIcon = Icons.badge_outlined; - if (doc == 'Udhyog Aadhar') docIcon = Icons.domain; - if (doc == 'PAN') docIcon = Icons.credit_card; - return _buildUploadSection(doc, docIcon); - }), - const SizedBox(height: 16), - - Text( - 'Shop Facade Photo *', - style: theme.textTheme.labelSmall?.copyWith( - color: theme.colorScheme.onSurface, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - InkWell( - onTap: () { - _showFacadePhotoOptions(); - }, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 24), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: _uploadedDocs.containsKey('facade_photo') ? Colors.green : theme.colorScheme.outlineVariant, - width: _uploadedDocs.containsKey('facade_photo') ? 2.0 : 1.0, ), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: _uploadedDocs.containsKey('facade_photo') ? Colors.green.withOpacity(0.1) : theme.colorScheme.primary.withOpacity(0.1), - shape: BoxShape.circle, - ), - child: Icon( - _uploadedDocs.containsKey('facade_photo') ? Icons.check_circle : Icons.add_a_photo_outlined, - color: _uploadedDocs.containsKey('facade_photo') ? Colors.green : theme.colorScheme.primary, - size: 24, + ], + ), + ), + const SizedBox(height: 16), + + SlideFadeEntrance( + delay: const Duration(milliseconds: 420), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Identity / License Documents (Select all that apply)', + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + _buildDocChip('GSTIN', Icons.description), + _buildDocChip('FSSAI', Icons.restaurant), + _buildDocChip('Aadhar', Icons.badge_outlined), + _buildDocChip('Udhyog Aadhar', Icons.domain), + _buildDocChip('PAN', Icons.credit_card), + ], + ), + ], + ), + ), + const SizedBox(height: 8), + + SlideFadeEntrance( + delay: const Duration(milliseconds: 500), + child: Column( + children: _selectedDocs.map((doc) { + IconData docIcon = Icons.description; + if (doc == 'GSTIN') docIcon = Icons.description; + if (doc == 'FSSAI') docIcon = Icons.restaurant; + if (doc == 'Aadhar') docIcon = Icons.badge_outlined; + if (doc == 'Udhyog Aadhar') docIcon = Icons.domain; + if (doc == 'PAN') docIcon = Icons.credit_card; + return _buildUploadSection(doc, docIcon); + }).toList(), + ), + ), + const SizedBox(height: 16), + + SlideFadeEntrance( + delay: const Duration(milliseconds: 580), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Shop Facade Photo *', + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + InteractiveScale( + child: InkWell( + onTap: () { + _showFacadePhotoOptions(); + }, + borderRadius: BorderRadius.circular(16), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 24), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: _uploadedDocs.containsKey('facade_photo') ? Colors.green : theme.colorScheme.outlineVariant, + width: _uploadedDocs.containsKey('facade_photo') ? 2.0 : 1.0, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: _uploadedDocs.containsKey('facade_photo') ? Colors.green.withOpacity(0.1) : theme.colorScheme.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + _uploadedDocs.containsKey('facade_photo') ? Icons.check_circle : Icons.add_a_photo_outlined, + color: _uploadedDocs.containsKey('facade_photo') ? Colors.green : theme.colorScheme.primary, + size: 24, + ), + ), + const SizedBox(height: 12), + Text( + _uploadedDocs.containsKey('facade_photo') ? _uploadedDocs['facade_photo']! : 'Upload front view', + style: theme.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.bold, + color: theme.colorScheme.onSurface, + ), + ), + const SizedBox(height: 4), + Text( + _uploadedDocs.containsKey('facade_photo') ? 'Facade Photo Attached • Tap to change' : 'Clear photo showing shop board', + style: TextStyle( + fontSize: 11, + color: Colors.grey[500], + ), + ), + ], + ), ), ), - const SizedBox(height: 12), - Text( - _uploadedDocs.containsKey('facade_photo') ? _uploadedDocs['facade_photo']! : 'Upload front view', - style: theme.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.bold, - color: theme.colorScheme.onSurface, - ), - ), - const SizedBox(height: 4), - Text( - _uploadedDocs.containsKey('facade_photo') ? 'Facade Photo Attached • Tap to change' : 'Clear photo showing shop board', - style: TextStyle( - fontSize: 11, - color: Colors.grey[500], - ), - ), - ], - ), + ), + ], ), ), const SizedBox(height: 12), @@ -781,30 +857,40 @@ class _BusinessSetupScreenState extends State { ), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), - child: ElevatedButton( - onPressed: _isNameValid - ? () { - Navigator.push( - context, - FadeSlidePageRoute( - child: CustomizeSetupScreen( - businessName: _nameController.text.trim(), - businessType: _selectedCategory, - gstin: _gstinController.text.trim(), - ), - ), - ); - } - : null, - style: ElevatedButton.styleFrom( - minimumSize: const Size.fromHeight(54), - backgroundColor: _isNameValid ? theme.colorScheme.primary : Colors.grey[200], - foregroundColor: _isNameValid ? Colors.white : Colors.grey[400], - elevation: _isNameValid ? 2 : 0, + SlideFadeEntrance( + delay: const Duration(milliseconds: 660), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), + child: InteractiveScale( + hoverScale: _isNameValid ? 1.03 : 1.0, + tapScale: _isNameValid ? 0.96 : 1.0, + child: ElevatedButton( + onPressed: _isNameValid + ? () { + Navigator.push( + context, + FadeSlidePageRoute( + child: CustomizeSetupScreen( + businessName: _nameController.text.trim(), + businessType: _selectedCategory, + gstin: _gstinController.text.trim(), + ), + ), + ); + } + : null, + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(54), + backgroundColor: _isNameValid ? theme.colorScheme.primary : Colors.grey[200], + foregroundColor: _isNameValid ? Colors.white : Colors.grey[400], + elevation: _isNameValid ? 2 : 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: const Text('Continue to Verification'), + ), ), - child: const Text('Continue to Verification'), ), ) ], diff --git a/flutter_app/lib/screens/customize_setup_screen.dart b/flutter_app/lib/screens/customize_setup_screen.dart index cccf4fd..6692948 100644 --- a/flutter_app/lib/screens/customize_setup_screen.dart +++ b/flutter_app/lib/screens/customize_setup_screen.dart @@ -119,49 +119,66 @@ class _CustomizeSetupScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Customize Your Setup', - style: theme.textTheme.headlineLarge?.copyWith( - fontWeight: FontWeight.bold, + SlideFadeEntrance( + delay: Duration.zero, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Customize Your Setup', + style: theme.textTheme.headlineLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + Text( + 'Turn on the features you need for your shop. You can always change these later in settings.', + style: theme.textTheme.bodyMedium, + ), + ], ), ), - const SizedBox(height: 16), - Text( - 'Turn on the features you need for your shop. You can always change these later in settings.', - style: theme.textTheme.bodyMedium, - ), - const SizedBox(height: 16), + const SizedBox(height: 20), // Toggle 1: Barcode - _buildToggleCard( - Icons.qr_code_scanner, - 'Barcode Scanner', - 'Do you use a Barcode Scanner?', - _barcode, - (val) => setState(() => _barcode = val), - theme, + SlideFadeEntrance( + delay: const Duration(milliseconds: 100), + child: _buildToggleCard( + Icons.qr_code_scanner, + 'Barcode Scanner', + 'Do you use a Barcode Scanner?', + _barcode, + (val) => setState(() => _barcode = val), + theme, + ), ), const SizedBox(height: 12), // Toggle 2: KOT - _buildToggleCard( - Icons.receipt_long, - 'KOT Management', - 'Do you need Kitchen Order Ticket management?', - _kot, - (val) => setState(() => _kot = val), - theme, + SlideFadeEntrance( + delay: const Duration(milliseconds: 180), + child: _buildToggleCard( + Icons.receipt_long, + 'KOT Management', + 'Do you need Kitchen Order Ticket management?', + _kot, + (val) => setState(() => _kot = val), + theme, + ), ), const SizedBox(height: 12), // Toggle 3: Inventory stock tracking - _buildToggleCard( - Icons.inventory_2, - 'Inventory Alerts', - 'Get notified when stock is running low.', - _inventory, - (val) => setState(() => _inventory = val), - theme, + SlideFadeEntrance( + delay: const Duration(milliseconds: 260), + child: _buildToggleCard( + Icons.inventory_2, + 'Inventory Alerts', + 'Get notified when stock is running low.', + _inventory, + (val) => setState(() => _inventory = val), + theme, + ), ), ], ), @@ -170,28 +187,36 @@ class _CustomizeSetupScreenState extends State { ), // Pinned action keys - Padding( - padding: EdgeInsets.all(padding), - child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - FadeSlidePageRoute( - child: SetupCompleteScreen( - businessName: widget.businessName, - businessType: widget.businessType, - gstin: widget.gstin, - barcodeScanner: _barcode, - kotManagement: _kot, - inventoryAlerts: _inventory, + SlideFadeEntrance( + delay: const Duration(milliseconds: 340), + child: Padding( + padding: EdgeInsets.all(padding), + child: InteractiveScale( + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + FadeSlidePageRoute( + child: SetupCompleteScreen( + businessName: widget.businessName, + businessType: widget.businessType, + gstin: widget.gstin, + barcodeScanner: _barcode, + kotManagement: _kot, + inventoryAlerts: _inventory, + ), + ), + ); + }, + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(54), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), ), ), - ); - }, - style: ElevatedButton.styleFrom( - minimumSize: const Size.fromHeight(54), + child: const Text('Save & Continue'), + ), ), - child: const Text('Save & Continue'), ), ) ], @@ -210,64 +235,67 @@ class _CustomizeSetupScreenState extends State { ValueChanged onChanged, ThemeData theme, ) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.grey[200]!), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.02), - blurRadius: 10, - offset: const Offset(0, 4), - ) - ], - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - color: Colors.grey[100], - shape: BoxShape.circle, + return InteractiveScale( + onTap: () => onChanged(!value), + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.grey[200]!), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.02), + blurRadius: 10, + offset: const Offset(0, 4), + ) + ], + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: Colors.grey[100], + shape: BoxShape.circle, + ), + child: Icon(icon, color: Colors.grey[600], size: 22), ), - child: Icon(icon, color: Colors.grey[600], size: 22), - ), - const SizedBox(width: 14), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - title, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - color: Colors.black87, + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), ), - ), - const SizedBox(height: 4), - Text( - subtitle, - style: TextStyle( - fontSize: 11, - color: Colors.grey[500], - ), - ) - ], + const SizedBox(height: 4), + Text( + subtitle, + style: TextStyle( + fontSize: 11, + color: Colors.grey[500], + ), + ) + ], + ), ), - ), - Switch( - value: value, - onChanged: onChanged, - activeColor: Colors.white, - activeTrackColor: theme.colorScheme.primary, - ), - ], + Switch( + value: value, + onChanged: onChanged, + activeColor: Colors.white, + activeTrackColor: theme.colorScheme.primary, + ), + ], + ), ), ); } diff --git a/flutter_app/lib/screens/mobile_login_screen.dart b/flutter_app/lib/screens/mobile_login_screen.dart index 5e14203..a119abc 100644 --- a/flutter_app/lib/screens/mobile_login_screen.dart +++ b/flutter_app/lib/screens/mobile_login_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:permission_handler/permission_handler.dart'; -import 'package:contacts_service/contacts_service.dart'; +import 'package:flutter_contacts/flutter_contacts.dart'; import '../theme.dart'; import '../route_transitions.dart'; import 'otp_verification_screen.dart'; @@ -16,8 +16,8 @@ class MockContact { } class MockPhone { - final String? value; - MockPhone(this.value); + final String number; + MockPhone(this.number); } class MobileLoginScreen extends StatefulWidget { @@ -103,7 +103,7 @@ class _MobileLoginScreenState extends State { ]; } else { try { - final List list = await ContactsService.getContacts(withThumbnails: false); + final List list = await FlutterContacts.getContacts(withProperties: true, withPhoto: false); return list; } catch (e) { return []; @@ -166,7 +166,7 @@ class _MobileLoginScreenState extends State { setDialogState(() { filteredContacts = allContacts.where((contact) { final name = (contact.displayName ?? '').toLowerCase(); - final phone = (contact.phones.isNotEmpty ? contact.phones.first.value ?? '' : '').toLowerCase(); + final phone = (contact.phones.isNotEmpty ? contact.phones.first.number : '').toLowerCase(); return name.contains(query.toLowerCase()) || phone.contains(query.toLowerCase()); }).toList(); }); @@ -183,7 +183,7 @@ class _MobileLoginScreenState extends State { itemBuilder: (context, index) { final contact = filteredContacts[index]; final name = contact.displayName ?? 'Unknown'; - final phone = contact.phones.isNotEmpty ? contact.phones.first.value : 'No number'; + final phone = contact.phones.isNotEmpty ? contact.phones.first.number : 'No number'; return ListTile( title: Text(name), subtitle: Text(phone), @@ -256,318 +256,335 @@ class _MobileLoginScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Step 2 of 5', - style: theme.textTheme.labelMedium?.copyWith( - fontWeight: FontWeight.bold, - color: theme.colorScheme.onSurfaceVariant, - ), - ), - const Text( - 'Mobile Login', - style: TextStyle( - fontFamily: 'Plus Jakarta Sans', - fontSize: 14, - fontWeight: FontWeight.bold, - color: Color(0xFFAF101A), - ), - ), - ], - ), - const SizedBox(height: 8), - Row( - children: List.generate(5, (index) { - return Expanded( - child: Container( - height: 6, - margin: EdgeInsets.only( - left: index == 0 ? 0 : 3.0, - right: index == 4 ? 0 : 3.0, - ), - decoration: BoxDecoration( - color: index <= 1 ? const Color(0xFFAF101A) : const Color(0xFFE2E2E2), - borderRadius: BorderRadius.circular(100), - ), - ), - ); - }), - ), - const SizedBox(height: 16), - - Text( - 'Welcome Back', - style: theme.textTheme.headlineLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - Text( - 'Enter your mobile number to sign in or create a new account.', - style: theme.textTheme.bodyMedium, - ), - const SizedBox(height: 16), - - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: const Color(0xFFFAFFD1).withOpacity(0.8), - borderRadius: BorderRadius.circular(100), - border: Border.all(color: const Color(0xFF016619).withOpacity(0.3)), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.lock, size: 14, color: Color(0xFF016619)), - const SizedBox(width: 6), - Text( - 'Secure OTP Verification', - style: theme.textTheme.labelSmall?.copyWith( - color: const Color(0xFF016619), - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - - const SizedBox(height: 16), - - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: const Color(0xFFF3F3F3), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: const Color(0xFFE4BEBA).withOpacity(0.5)), - ), + SlideFadeEntrance( + delay: Duration.zero, child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Container( - width: 40, - height: 40, - decoration: const BoxDecoration( - color: Color(0xFF005FAF), - shape: BoxShape.circle, - ), - child: const Icon( - Icons.verified_user, - color: Colors.white, - size: 24, + Text( + 'Step 2 of 5', + style: theme.textTheme.labelMedium?.copyWith( + fontWeight: FontWeight.bold, + color: theme.colorScheme.onSurfaceVariant, ), ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Verify instantly with', - style: theme.textTheme.bodySmall?.copyWith( - color: Colors.grey[600], - ), - ), - const Text( - 'Truecaller', - style: TextStyle( - fontFamily: 'Plus Jakarta Sans', - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - ], + const Text( + 'Mobile Login', + style: TextStyle( + fontFamily: 'Plus Jakarta Sans', + fontSize: 14, + fontWeight: FontWeight.bold, + color: Color(0xFFAF101A), ), ), ], ), - const SizedBox(height: 20), - const Text( - '+91 98765 43210', - style: TextStyle( - fontFamily: 'Plus Jakarta Sans', - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - const SizedBox(height: 16), - ElevatedButton.icon( - onPressed: () { - _controller.text = '9876543210'; - setState(() { - _isValid = true; - }); - showTopAlert( - context, - 'Phone verified automatically with Truecaller! Joining PozoApp...', - icon: Icons.verified_user_outlined, - ); - Navigator.push( - context, - FadeSlidePageRoute( - child: const OtpVerificationScreen( - mobileNumber: '9876543210', + const SizedBox(height: 8), + Row( + children: List.generate(5, (index) { + return Expanded( + child: Container( + height: 6, + margin: EdgeInsets.only( + left: index == 0 ? 0 : 3.0, + right: index == 4 ? 0 : 3.0, + ), + decoration: BoxDecoration( + color: index <= 1 ? const Color(0xFFAF101A) : const Color(0xFFE2E2E2), + borderRadius: BorderRadius.circular(100), ), ), ); - }, - icon: const Icon(Icons.flash_on, size: 18, color: Colors.white), - label: const Text( - '1-Tap Verification', - style: TextStyle( - fontFamily: 'Inter', - fontSize: 16, + }), + ), + const SizedBox(height: 16), + Text( + 'Welcome Back', + style: theme.textTheme.headlineLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + Text( + 'Enter your mobile number to sign in or create a new account.', + style: theme.textTheme.bodyMedium, + ), + ], + ), + ), + const SizedBox(height: 16), + + SlideFadeEntrance( + delay: const Duration(milliseconds: 100), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: const Color(0xFFFAFFD1).withOpacity(0.8), + borderRadius: BorderRadius.circular(100), + border: Border.all(color: const Color(0xFF016619).withOpacity(0.3)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.lock, size: 14, color: Color(0xFF016619)), + const SizedBox(width: 6), + Text( + 'Secure OTP Verification', + style: theme.textTheme.labelSmall?.copyWith( + color: const Color(0xFF016619), fontWeight: FontWeight.bold, ), ), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF005FAF), - foregroundColor: Colors.white, - minimumSize: const Size.fromHeight(56), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - elevation: 1, - ), - ), - const SizedBox(height: 12), - Text( - 'No OTP required. Your phone will be verified automatically.', - textAlign: TextAlign.center, - style: theme.textTheme.bodySmall?.copyWith( - color: Colors.grey[600], - height: 1.4, - ), - ), - ], - ), - ), - - SizedBox(height: gap), - - Row( - children: [ - const Expanded(child: Divider(color: Color(0xFFE4BEBA))), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - 'Or enter mobile number manually', - style: theme.textTheme.labelMedium?.copyWith( - color: Colors.grey[600], - fontWeight: FontWeight.w500, - fontSize: 12, - ), - ), + ], ), - const Expanded(child: Divider(color: Color(0xFFE4BEBA))), - ], + ), + ), + + const SizedBox(height: 16), + + SlideFadeEntrance( + delay: const Duration(milliseconds: 200), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: const Color(0xFFF3F3F3), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: const Color(0xFFE4BEBA).withOpacity(0.5)), + ), + child: Column( + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: const BoxDecoration( + color: Color(0xFF005FAF), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.verified_user, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Verify instantly with', + style: theme.textTheme.bodySmall?.copyWith( + color: Colors.grey[600], + ), + ), + const Text( + 'Truecaller', + style: TextStyle( + fontFamily: 'Plus Jakarta Sans', + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 20), + const Text( + '+91 98765 43210', + style: TextStyle( + fontFamily: 'Plus Jakarta Sans', + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + const SizedBox(height: 16), + ElevatedButton.icon( + onPressed: () { + _controller.text = '9876543210'; + setState(() { + _isValid = true; + }); + showTopAlert( + context, + 'Phone verified automatically with Truecaller! Joining PozoApp...', + icon: Icons.verified_user_outlined, + ); + Navigator.push( + context, + FadeSlidePageRoute( + child: const OtpVerificationScreen( + mobileNumber: '9876543210', + ), + ), + ); + }, + icon: const Icon(Icons.flash_on, size: 18, color: Colors.white), + label: const Text( + '1-Tap Verification', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF005FAF), + foregroundColor: Colors.white, + minimumSize: const Size.fromHeight(56), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 1, + ), + ), + const SizedBox(height: 12), + Text( + 'No OTP required. Your phone will be verified automatically.', + textAlign: TextAlign.center, + style: theme.textTheme.bodySmall?.copyWith( + color: Colors.grey[600], + height: 1.4, + ), + ), + ], + ), + ), ), SizedBox(height: gap), - Text( - 'Mobile Number', - style: theme.textTheme.labelSmall?.copyWith( - color: theme.colorScheme.onSurface, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - - Container( - height: 56, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: theme.colorScheme.outlineVariant), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.02), - blurRadius: 4, - offset: const Offset(0, 2), - ) - ], - ), - child: Row( + SlideFadeEntrance( + delay: const Duration(milliseconds: 300), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 12), - decoration: BoxDecoration( - border: Border( - right: BorderSide(color: theme.colorScheme.outlineVariant), + Row( + children: [ + const Expanded(child: Divider(color: Color(0xFFE4BEBA))), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + 'Or enter mobile number manually', + style: theme.textTheme.labelMedium?.copyWith( + color: Colors.grey[600], + fontWeight: FontWeight.w500, + fontSize: 12, + ), + ), ), + const Expanded(child: Divider(color: Color(0xFFE4BEBA))), + ], + ), + SizedBox(height: gap), + Text( + 'Mobile Number', + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Container( + height: 56, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: theme.colorScheme.outlineVariant), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.02), + blurRadius: 4, + offset: const Offset(0, 2), + ) + ], ), child: Row( children: [ Container( - width: 22, - height: 14, + padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( - border: Border.all(color: Colors.grey[300]!, width: 0.5), + border: Border( + right: BorderSide(color: theme.colorScheme.outlineVariant), + ), ), - child: Column( + child: Row( children: [ - Container(height: 4, color: const Color(0xFFFF9933)), Container( - height: 4, - color: Colors.white, - child: Center( - child: Container( - width: 2, - height: 2, - decoration: const BoxDecoration( - color: Color(0xFF000080), - shape: BoxShape.circle, + width: 22, + height: 14, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey[300]!, width: 0.5), + ), + child: Column( + children: [ + Container(height: 4, color: const Color(0xFFFF9933)), + Container( + height: 4, + color: Colors.white, + child: Center( + child: Container( + width: 2, + height: 2, + decoration: const BoxDecoration( + color: Color(0xFF000080), + shape: BoxShape.circle, + ), + ), + ), ), - ), + Container(height: 4, color: const Color(0xFF128807)), + ], ), ), - Container(height: 4, color: const Color(0xFF128807)), + const SizedBox(width: 8), + Text( + '+91', + style: theme.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.bold, + color: theme.colorScheme.onSurface, + ), + ) ], ), ), - const SizedBox(width: 8), - Text( - '+91', - style: theme.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.bold, - color: theme.colorScheme.onSurface, + Expanded( + child: TextField( + controller: _controller, + keyboardType: TextInputType.phone, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(10), + ], + decoration: const InputDecoration( + hintText: '00000 00000', + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + filled: false, + contentPadding: EdgeInsets.symmetric(horizontal: 16), + ), + style: theme.textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + color: theme.colorScheme.onSurface, + ), ), - ) + ), + IconButton( + icon: const Icon(Icons.contact_phone, color: Color(0xFFAF101A)), + onPressed: _pickContact, + ), ], ), ), - - Expanded( - child: TextField( - controller: _controller, - keyboardType: TextInputType.phone, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(10), - ], - decoration: const InputDecoration( - hintText: '00000 00000', - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - filled: false, - contentPadding: EdgeInsets.symmetric(horizontal: 16), - ), - style: theme.textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.bold, - color: theme.colorScheme.onSurface, - ), - ), - ), - IconButton( - icon: const Icon(Icons.contact_phone, color: Color(0xFFAF101A)), - onPressed: _pickContact, - ), ], ), ), @@ -577,66 +594,75 @@ class _MobileLoginScreenState extends State { ), ), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton( - onPressed: _isValid - ? () { - Navigator.push( - context, - FadeSlidePageRoute( - child: OtpVerificationScreen( - mobileNumber: _controller.text, - ), - ), - ); - } - : null, - style: ElevatedButton.styleFrom( - minimumSize: const Size.fromHeight(56), - backgroundColor: _isValid ? const Color(0xFFAF101A) : Colors.grey[200], - foregroundColor: _isValid ? Colors.white : Colors.grey[400], - elevation: _isValid ? 2 : 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: const Text('Send OTP'), - ), - SizedBox(height: gap / 2), - OutlinedButton( - onPressed: () { - showTopAlert( - context, - 'Connecting with Pozo Support... Please wait.', - icon: Icons.support_agent_outlined, - ); - }, - style: OutlinedButton.styleFrom( - minimumSize: const Size.fromHeight(56), - side: const BorderSide(color: Color(0xFFAF101A), width: 2), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.forum, color: Color(0xFFAF101A), size: 18), - const SizedBox(width: 8), - const Text( - 'Help / Talk to Support', - style: TextStyle( - color: Color(0xFFAF101A), - fontWeight: FontWeight.bold, - fontSize: 16, + SlideFadeEntrance( + delay: const Duration(milliseconds: 400), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InteractiveScale( + hoverScale: _isValid ? 1.03 : 1.0, + tapScale: _isValid ? 0.96 : 1.0, + child: ElevatedButton( + onPressed: _isValid + ? () { + Navigator.push( + context, + FadeSlidePageRoute( + child: OtpVerificationScreen( + mobileNumber: _controller.text, + ), + ), + ); + } + : null, + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(56), + backgroundColor: _isValid ? const Color(0xFFAF101A) : Colors.grey[200], + foregroundColor: _isValid ? Colors.white : Colors.grey[400], + elevation: _isValid ? 2 : 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), ), ), - ], + child: const Text('Send OTP'), + ), ), - ), - ], + SizedBox(height: gap / 2), + InteractiveScale( + child: OutlinedButton( + onPressed: () { + showTopAlert( + context, + 'Connecting with Pozo Support... Please wait.', + icon: Icons.support_agent_outlined, + ); + }, + style: OutlinedButton.styleFrom( + minimumSize: const Size.fromHeight(56), + side: const BorderSide(color: Color(0xFFAF101A), width: 2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.forum, color: Color(0xFFAF101A), size: 18), + const SizedBox(width: 8), + const Text( + 'Help / Talk to Support', + style: TextStyle( + color: Color(0xFFAF101A), + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ], + ), + ), + ), + ], + ), ), ], ), diff --git a/flutter_app/lib/screens/otp_verification_screen.dart b/flutter_app/lib/screens/otp_verification_screen.dart index d12fd76..53283b4 100644 --- a/flutter_app/lib/screens/otp_verification_screen.dart +++ b/flutter_app/lib/screens/otp_verification_screen.dart @@ -123,184 +123,212 @@ class _OtpVerificationScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Alert envelope pulse symbol - Center( - child: Container( - width: 96, - height: 96, - decoration: BoxDecoration( - color: theme.colorScheme.surfaceContainerHigh, - shape: BoxShape.circle, - ), - child: Center( - child: Container( - width: 72, - height: 72, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.06), - blurRadius: 10, - ) - ], - ), - child: Icon( - Icons.sms, - color: theme.colorScheme.primary, - size: 38, - ), + // Alert envelope pulse symbol with SlideFadeEntrance + SlideFadeEntrance( + delay: Duration.zero, + child: Center( + child: Container( + width: 96, + height: 96, + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHigh, + shape: BoxShape.circle, ), - ), - ), - ), - const SizedBox(height: 16), - Text( - 'Verifying your number', - textAlign: TextAlign.center, - style: theme.textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - "Sent code to $displayMobile", - style: theme.textTheme.bodyMedium, - ), - const SizedBox(width: 8), - GestureDetector( - onTap: () => Navigator.pop(context), - child: Text( - 'Edit', - style: TextStyle( - color: theme.colorScheme.primary, - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - ), - ], - ), - - const SizedBox(height: 16), - - // 6 Custom Inputs Fields - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(11, (index) { - if (index.isOdd) { - return const SizedBox(width: 8); - } - final fieldIndex = index ~/ 2; - return SizedBox( - width: 44, - child: TextField( - controller: _controllers[fieldIndex], - focusNode: _focusNodes[fieldIndex], - keyboardType: TextInputType.number, - textAlign: TextAlign.center, - maxLength: 1, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - decoration: InputDecoration( - counterText: '', - contentPadding: const EdgeInsets.symmetric(vertical: 12), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: theme.colorScheme.outlineVariant), + child: Center( + child: Container( + width: 72, + height: 72, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.06), + blurRadius: 10, + ) + ], ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: theme.colorScheme.primary, width: 2), + child: Icon( + Icons.sms, + color: theme.colorScheme.primary, + size: 38, ), ), - onChanged: (value) { - if (value.isNotEmpty && fieldIndex < 5) { - _focusNodes[fieldIndex + 1].requestFocus(); - } - }, ), - ); - }), - ), - - const SizedBox(height: 16), - - // Auto detect loader - Container( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - decoration: BoxDecoration( - color: theme.colorScheme.surfaceContainerLow, - borderRadius: BorderRadius.circular(12), + ), ), + ), + const SizedBox(height: 16), + SlideFadeEntrance( + delay: const Duration(milliseconds: 100), + child: Text( + 'Verifying your number', + textAlign: TextAlign.center, + style: theme.textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 16), + SlideFadeEntrance( + delay: const Duration(milliseconds: 150), child: Row( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox( - width: 14, - height: 14, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(theme.colorScheme.primary), - ), - ), - const SizedBox(width: 12), Text( - 'Auto-detecting OTP...', - style: theme.textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.bold, + "Sent code to $displayMobile", + style: theme.textTheme.bodyMedium, + ), + const SizedBox(width: 8), + InteractiveScale( + onTap: () => Navigator.pop(context), + child: Text( + 'Edit', + style: TextStyle( + color: theme.colorScheme.primary, + fontWeight: FontWeight.bold, + fontSize: 14, + ), ), ), ], ), ), - const SizedBox(height: 16), + const SizedBox(height: 20), - // Resending timer action column - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.schedule, size: 16, color: Colors.grey), - const SizedBox(width: 6), - Text( - _timeLeft > 0 - ? 'Resend code in ${_getFormattedTime()}' - : 'Resend Code via SMS', - style: theme.textTheme.bodyMedium, + // 6 Custom Inputs Fields with SlideFadeEntrance + SlideFadeEntrance( + delay: const Duration(milliseconds: 200), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(11, (index) { + if (index.isOdd) { + return const SizedBox(width: 8); + } + final fieldIndex = index ~/ 2; + return SizedBox( + width: 44, + child: TextField( + controller: _controllers[fieldIndex], + focusNode: _focusNodes[fieldIndex], + keyboardType: TextInputType.number, + textAlign: TextAlign.center, + maxLength: 1, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + decoration: InputDecoration( + counterText: '', + contentPadding: const EdgeInsets.symmetric(vertical: 12), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: theme.colorScheme.outlineVariant), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: theme.colorScheme.primary, width: 2), + ), + ), + onChanged: (value) { + if (value.isNotEmpty && fieldIndex < 5) { + _focusNodes[fieldIndex + 1].requestFocus(); + } + }, + ), + ); + }), + ), + ), + + const SizedBox(height: 24), + + // Auto detect loader card with SlideFadeEntrance + SlideFadeEntrance( + delay: const Duration(milliseconds: 250), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(16), ), - ], + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 14, + height: 14, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(theme.colorScheme.primary), + ), + ), + const SizedBox(width: 12), + Text( + 'Auto-detecting OTP...', + style: theme.textTheme.bodySmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 24), + + // Resending timer action row + SlideFadeEntrance( + delay: const Duration(milliseconds: 300), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.schedule, size: 16, color: Colors.grey), + const SizedBox(width: 6), + Text( + _timeLeft > 0 + ? 'Resend code in ${_getFormattedTime()}' + : 'Resend Code via SMS', + style: theme.textTheme.bodyMedium, + ), + ], + ), ), const SizedBox(height: 16), - // WhatsApp custom CTA button - ElevatedButton( - onPressed: () {}, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Colors.black87, - side: const BorderSide(color: Color(0xFFE2E2E2)), - elevation: 1, - minimumSize: const Size(200, 48), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: const [ - Text( + // WhatsApp custom CTA button with InteractiveScale + SlideFadeEntrance( + delay: const Duration(milliseconds: 350), + child: InteractiveScale( + child: OutlinedButton.icon( + onPressed: () { + showTopAlert( + context, + 'WhatsApp verification request sent!', + icon: Icons.chat_bubble_outline, + ); + }, + icon: const Icon(Icons.forum, color: Colors.green, size: 18), + label: const Text( 'Resend via WhatsApp', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, + color: Colors.black87, ), ), - ], + style: OutlinedButton.styleFrom( + backgroundColor: Colors.white, + side: const BorderSide(color: Color(0xFFE2E2E2)), + elevation: 1, + minimumSize: const Size(220, 48), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), ), ) ], @@ -326,28 +354,42 @@ class _OtpVerificationScreenState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _buildFooterAction(Icons.help_outline, 'Help'), - _buildFooterAction(Icons.save, 'Save Draft'), - TextButton.icon( - onPressed: () { - Navigator.push( - context, - FadeSlidePageRoute( - child: const BusinessSetupScreen(), + _buildFooterAction(Icons.help_outline, 'Help', () { + showTopAlert( + context, + 'Support agent contacted. We will call you shortly.', + icon: Icons.support_agent_outlined, + ); + }), + _buildFooterAction(Icons.save, 'Save Draft', () { + showTopAlert( + context, + 'Onboarding draft saved successfully!', + icon: Icons.save_outlined, + ); + }), + InteractiveScale( + child: TextButton.icon( + onPressed: () { + Navigator.push( + context, + FadeSlidePageRoute( + child: const BusinessSetupScreen(), + ), + ); + }, + icon: const Text( + 'Verify Manually', + style: TextStyle(fontWeight: FontWeight.bold), + ), + label: const Icon(Icons.arrow_forward, size: 16), + style: TextButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: theme.colorScheme.primary, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), ), - ); - }, - icon: const Text( - 'Verify Manually', - style: TextStyle(fontWeight: FontWeight.bold), - ), - label: const Icon(Icons.arrow_forward, size: 16), - style: TextButton.styleFrom( - foregroundColor: Colors.white, - backgroundColor: theme.colorScheme.primary, - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), ), ), ) @@ -363,21 +405,24 @@ class _OtpVerificationScreenState extends State { ); } - Widget _buildFooterAction(IconData icon, String label) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, color: Colors.grey[600], size: 22), - const SizedBox(height: 4), - Text( - label, - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: Colors.grey[600], - ), - ) - ], + Widget _buildFooterAction(IconData icon, String label, VoidCallback onTap) { + return InteractiveScale( + onTap: onTap, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, color: Colors.grey[600], size: 22), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.grey[600], + ), + ) + ], + ), ); } } diff --git a/flutter_app/lib/screens/setup_complete_screen.dart b/flutter_app/lib/screens/setup_complete_screen.dart index 5dd86a3..4e11171 100644 --- a/flutter_app/lib/screens/setup_complete_screen.dart +++ b/flutter_app/lib/screens/setup_complete_screen.dart @@ -32,7 +32,6 @@ class _SetupCompleteScreenState extends State with SingleTi late Animation _cardOpacityAnimation; late Animation _btnSlideAnimation; late Animation _btnOpacityAnimation; - double _btnScale = 1.0; @override void initState() { @@ -382,53 +381,42 @@ class _SetupCompleteScreenState extends State with SingleTi ), ); }, - child: MouseRegion( - onEnter: (_) => setState(() => _btnScale = 1.03), - onExit: (_) => setState(() => _btnScale = 1.0), - child: AnimatedScale( - scale: _btnScale, - duration: const Duration(milliseconds: 150), - child: GestureDetector( - onTapDown: (_) => setState(() => _btnScale = 0.96), - onTapUp: (_) => setState(() => _btnScale = 1.03), - onTapCancel: () => setState(() => _btnScale = 1.0), - child: ElevatedButton( - onPressed: () { - showTopAlert( - context, - 'Launching Register Dashboard... Enjoy billing!', - icon: Icons.rocket_launch_outlined, - ); - Navigator.push( - context, - FadeSlidePageRoute( - child: BillingDashboardScreen( - businessName: widget.businessName, - businessType: widget.businessType, - gstin: widget.gstin, - barcodeScanner: widget.barcodeScanner, - kotManagement: widget.kotManagement, - inventoryAlerts: widget.inventoryAlerts, - ), - ), - ); - }, - style: ElevatedButton.styleFrom( - minimumSize: const Size.fromHeight(56), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + child: InteractiveScale( + child: ElevatedButton( + onPressed: () { + showTopAlert( + context, + 'Launching Register Dashboard... Enjoy billing!', + icon: Icons.rocket_launch_outlined, + ); + Navigator.push( + context, + FadeSlidePageRoute( + child: BillingDashboardScreen( + businessName: widget.businessName, + businessType: widget.businessType, + gstin: widget.gstin, + barcodeScanner: widget.barcodeScanner, + kotManagement: widget.kotManagement, + inventoryAlerts: widget.inventoryAlerts, ), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Icon(Icons.receipt, size: 20), - SizedBox(width: 8), - Text('Create Your First Bill'), - ], - ), + ); + }, + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(56), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), ), ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon(Icons.receipt, size: 20), + SizedBox(width: 8), + Text('Create Your First Bill'), + ], + ), ), ), ), diff --git a/flutter_app/lib/screens/welcome_screen.dart b/flutter_app/lib/screens/welcome_screen.dart index f02bcce..f185777 100644 --- a/flutter_app/lib/screens/welcome_screen.dart +++ b/flutter_app/lib/screens/welcome_screen.dart @@ -17,7 +17,6 @@ class _WelcomeScreenState extends State with SingleTickerProvider late Animation _splashContainerOpacityAnimation; late Animation _contentFadeAnimation; late Animation _contentSlideAnimation; - double _btnScale = 1.0; @override void initState() { @@ -491,37 +490,25 @@ class _WelcomeScreenState extends State with SingleTickerProvider mainAxisSize: MainAxisSize.min, crossAxisAlignment: context.isMobile ? CrossAxisAlignment.center : CrossAxisAlignment.start, children: [ - MouseRegion( - onEnter: (_) => setState(() => _btnScale = 1.03), - onExit: (_) => setState(() => _btnScale = 1.0), - child: GestureDetector( - onTapDown: (_) => setState(() => _btnScale = 0.95), - onTapUp: (_) => setState(() => _btnScale = 1.03), - onTapCancel: () => setState(() => _btnScale = 1.0), - child: AnimatedScale( - scale: _btnScale, - duration: const Duration(milliseconds: 150), - curve: Curves.easeOut, - child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - FadeSlidePageRoute(child: const MobileLoginScreen()), - ); - }, - style: ElevatedButton.styleFrom( - minimumSize: const Size(260, 54), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: const [ - Text('Get Started'), - SizedBox(width: 8), - Icon(Icons.arrow_forward, size: 18), - ], - ), - ), + InteractiveScale( + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + FadeSlidePageRoute(child: const MobileLoginScreen()), + ); + }, + style: ElevatedButton.styleFrom( + minimumSize: const Size(260, 54), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: const [ + Text('Get Started'), + SizedBox(width: 8), + Icon(Icons.arrow_forward, size: 18), + ], ), ), ), diff --git a/flutter_app/lib/theme.dart b/flutter_app/lib/theme.dart index 7ef660b..6078f16 100644 --- a/flutter_app/lib/theme.dart +++ b/flutter_app/lib/theme.dart @@ -122,7 +122,7 @@ class PozoTheme { minimumSize: const Size.fromHeight(54), elevation: 2, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(16), ), textStyle: GoogleFonts.plusJakartaSans( fontSize: 16, diff --git a/flutter_app/pubspec.lock b/flutter_app/pubspec.lock index 4f4864e..53d805e 100644 --- a/flutter_app/pubspec.lock +++ b/flutter_app/pubspec.lock @@ -41,14 +41,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" - contacts_service: - dependency: "direct main" - description: - name: contacts_service - sha256: f6d5ea33b31dfcdcd2e65d8abdc836502e04ddb0f66a96aa726fa9891ea9671e - url: "https://pub.dev" - source: hosted - version: "0.6.3" cross_file: dependency: transitive description: @@ -126,6 +118,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_contacts: + dependency: "direct main" + description: + name: flutter_contacts + sha256: "388d32cd33f16640ee169570128c933b45f3259bddbfae7a100bb49e5ffea9ae" + url: "https://pub.dev" + source: hosted + version: "1.1.9+2" flutter_lints: dependency: "direct dev" description: @@ -424,14 +424,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - quiver: - dependency: transitive - description: - name: quiver - sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 - url: "https://pub.dev" - source: hosted - version: "3.2.2" sky_engine: dependency: transitive description: flutter diff --git a/flutter_app/pubspec.yaml b/flutter_app/pubspec.yaml index 672731f..4f76fa0 100644 --- a/flutter_app/pubspec.yaml +++ b/flutter_app/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: google_fonts: ^5.1.0 image_picker: ^1.0.4 permission_handler: ^11.0.1 - contacts_service: ^0.6.3 + flutter_contacts: ^1.1.9 dev_dependencies: flutter_test: