import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:permission_handler/permission_handler.dart'; import 'package:image_picker/image_picker.dart'; import 'package:geolocator/geolocator.dart'; import 'package:geocoding/geocoding.dart'; import 'dart:ui'; import 'dart:io'; import '../theme.dart'; import '../route_transitions.dart'; import '../feedback_helper.dart'; import 'customize_setup_screen.dart'; class BusinessSetupScreen extends StatefulWidget { const BusinessSetupScreen({super.key}); @override State createState() => _BusinessSetupScreenState(); } class _BusinessSetupScreenState extends State { final TextEditingController _nameController = TextEditingController(); final TextEditingController _gstinController = TextEditingController(); final TextEditingController _addressController = TextEditingController(); final TextEditingController _fssaiController = TextEditingController(); final TextEditingController _udhyogController = TextEditingController(); final TextEditingController _panController = TextEditingController(); final TextEditingController _aadharController = TextEditingController(); String _selectedCategory = 'supermarket'; bool _isNameValid = false; bool _isGstinValid = false; bool _hasGstinInput = false; final Set _selectedDocs = {'GSTIN', 'FSSAI'}; final Map _uploadedDocs = {}; @override void initState() { super.initState(); _nameController.addListener(() { setState(() { _isNameValid = _nameController.text.trim().isNotEmpty; }); }); _gstinController.addListener(_validateGstin); } void _validateGstin() { final text = _gstinController.text.trim(); if (text.isEmpty) { setState(() { _hasGstinInput = false; _isGstinValid = false; }); return; } final RegExp gstinRegex = RegExp(r'^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[0-9]{1}Z[A-Z0-9]{1}$'); setState(() { _hasGstinInput = true; _isGstinValid = gstinRegex.hasMatch(text); }); } @override void dispose() { _nameController.dispose(); _gstinController.dispose(); _addressController.dispose(); _fssaiController.dispose(); _udhyogController.dispose(); _panController.dispose(); _aadharController.dispose(); super.dispose(); } TextEditingController _getControllerForDoc(String docName) { switch (docName) { case 'GSTIN': return _gstinController; case 'FSSAI': return _fssaiController; case 'Udhyog Aadhar': return _udhyogController; case 'PAN': return _panController; case 'Aadhar': return _aadharController; default: return TextEditingController(); } } final List> _categories = [ {'id': 'supermarket', 'name': 'Supermarket', 'icon': Icons.shopping_cart}, {'id': 'restaurant', 'name': 'Restaurant', 'icon': Icons.restaurant}, {'id': 'clothing', 'name': 'Clothing', 'icon': Icons.checkroom}, {'id': 'kirana', 'name': 'Kirana', 'icon': Icons.local_mall}, {'id': 'grocers', 'name': 'Grocers', 'icon': Icons.storefront}, {'id': 'others', 'name': 'Others', 'icon': Icons.grid_view}, ]; Future _handleImagePick(String docName, ImageSource source) async { AppFeedback.click(); final permission = source == ImageSource.camera ? Permission.camera : Permission.photos; var status = await permission.status; if (!mounted) return; if (status.isPermanentlyDenied) { _showSettingsDialog(source == ImageSource.camera ? 'Camera' : 'Photos'); return; } if (status.isDenied && !kIsWeb) { final String sourceName = source == ImageSource.camera ? 'Camera' : 'Photos/Gallery'; final bool? proceed = await showDialog( context: context, builder: (context) => AlertDialog( title: Text('$sourceName Permission Required'), content: Text( 'PozoApp requires $sourceName permission to upload document verification files and store facade images.', ), actions: [ TextButton( onPressed: () { AppFeedback.click(); Navigator.pop(context, false); }, child: const Text('Cancel'), ), TextButton( onPressed: () { AppFeedback.click(); Navigator.pop(context, true); }, child: const Text('Continue'), ), ], ), ); if (!mounted) return; if (proceed != true) return; status = await permission.request(); if (!mounted) return; if (status.isPermanentlyDenied) { _showSettingsDialog(sourceName); return; } } if (status.isGranted || kIsWeb) { try { final picker = ImagePicker(); final XFile? file = await picker.pickImage(source: source); if (!mounted) return; if (file != null) { setState(() { _uploadedDocs[docName] = file; }); showTopAlert( context, 'Successfully loaded $docName!', icon: Icons.check_circle_outline, ); } } catch (e) { if (!mounted) return; showTopAlert( context, 'Failed to pick image: $e', isError: true, ); } } else { showTopAlert( context, 'Permission denied. Cannot load document image.', isError: true, ); } } void _showSettingsDialog(String permissionName) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('$permissionName Permission Disabled'), content: Text('Please enable $permissionName access in system settings to proceed.'), actions: [ TextButton( onPressed: () { AppFeedback.click(); Navigator.pop(context); }, child: const Text('Cancel'), ), TextButton( onPressed: () { AppFeedback.click(); Navigator.pop(context); openAppSettings(); }, child: const Text('Open Settings'), ), ], ), ); } Future _fetchLocationAndAddress() async { AppFeedback.click(); bool serviceEnabled; LocationPermission permission; serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { if (mounted) { showTopAlert(context, 'Location services are disabled.', isError: true); } return; } permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { if (mounted) { showTopAlert(context, 'Location permissions are denied', isError: true); } return; } } if (permission == LocationPermission.deniedForever) { if (mounted) { _showSettingsDialog('Location'); } return; } if (mounted) { showTopAlert(context, 'Fetching location...', icon: Icons.gps_fixed); } try { Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); if (kIsWeb) { // Reverse geocoding might have CORS issues on web without a backend proxy or proper keys depending on provider, // Mocking for web demonstration unless specifically supported setState(() { _addressController.text = 'Web Mock Address: Lat ${position.latitude.toStringAsFixed(4)}, Lng ${position.longitude.toStringAsFixed(4)}\nCity, State, Country'; }); if (mounted) { showTopAlert(context, 'Location detected (Web Mock)', icon: Icons.my_location_outlined); } } else { List placemarks = await placemarkFromCoordinates( position.latitude, position.longitude); if (placemarks.isNotEmpty) { Placemark place = placemarks[0]; List parts = []; if (place.name != null && place.name!.isNotEmpty) parts.add(place.name!); if (place.subLocality != null && place.subLocality!.isNotEmpty) parts.add(place.subLocality!); if (place.locality != null && place.locality!.isNotEmpty) parts.add(place.locality!); if (place.administrativeArea != null && place.administrativeArea!.isNotEmpty) parts.add(place.administrativeArea!); if (place.postalCode != null && place.postalCode!.isNotEmpty) parts.add(place.postalCode!); setState(() { _addressController.text = parts.join(', '); }); if (mounted) { showTopAlert(context, 'Address auto-filled successfully!', icon: Icons.my_location_outlined); } } } } catch (e) { if (mounted) { showTopAlert(context, 'Failed to fetch address: $e', isError: true); } } } void _showFacadePhotoOptions() { showModalBottomSheet( context: context, builder: (context) { return SafeArea( child: Wrap( children: [ ListTile( leading: const Icon(Icons.camera_alt, color: Color(0xFFAF101A)), title: const Text('Take Photo'), onTap: () { AppFeedback.click(); Navigator.pop(context); _handleImagePick('facade_photo', ImageSource.camera); }, ), ListTile( leading: const Icon(Icons.photo_library, color: Color(0xFFAF101A)), title: const Text('Choose from Gallery'), onTap: () { AppFeedback.click(); Navigator.pop(context); _handleImagePick('facade_photo', ImageSource.gallery); }, ), ], ), ); }, ); } Widget _buildDocChip(String label, IconData icon) { final theme = Theme.of(context); final isSelected = _selectedDocs.contains(label); return InteractiveScale( child: InkWell( onTap: () { AppFeedback.lightHaptic(); if (!isSelected) { _selectedDocs.add(label); _showFacadePhotoOptionsForDoc(label); } else { setState(() { _selectedDocs.remove(label); _uploadedDocs.remove(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], ), ), ], ), ), ), ); } void _showFacadePhotoOptionsForDoc(String docName) { showModalBottomSheet( context: context, builder: (context) { return SafeArea( child: Wrap( children: [ ListTile( leading: const Icon(Icons.camera_alt, color: Color(0xFFAF101A)), title: const Text('Take Photo'), onTap: () { AppFeedback.click(); Navigator.pop(context); _handleImagePick(docName, ImageSource.camera); }, ), ListTile( leading: const Icon(Icons.photo_library, color: Color(0xFFAF101A)), title: const Text('Choose from Gallery'), onTap: () { AppFeedback.click(); Navigator.pop(context); _handleImagePick(docName, ImageSource.gallery); }, ), ], ), ); }, ); } Widget _buildUploadSection(String docName, IconData icon) { final theme = Theme.of(context); final isUploaded = _uploadedDocs.containsKey(docName); return Container( margin: const EdgeInsets.only(top: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: theme.colorScheme.outlineVariant, width: 1, ), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon(icon, color: const Color(0xFFAF101A), size: 18), const SizedBox(width: 8), Text( '$docName Document', style: theme.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.onSurface, ), ), ], ), IconButton( icon: const Icon(Icons.close, size: 18, color: Colors.grey), onPressed: () { AppFeedback.click(); setState(() { _selectedDocs.remove(docName); }); }, padding: EdgeInsets.zero, constraints: const BoxConstraints(), ) ], ), const SizedBox(height: 12), Text( 'Enter $docName Number (Optional if photo uploaded)', style: theme.textTheme.bodySmall?.copyWith( fontWeight: FontWeight.bold, color: Colors.grey[700], ), ), const SizedBox(height: 6), TextField( controller: _getControllerForDoc(docName), textCapitalization: TextCapitalization.characters, decoration: InputDecoration( hintText: 'e.g. Enter $docName number', prefixIcon: Icon(icon, size: 16, color: Colors.grey[500]), ), style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 16), Row( children: [ Expanded(child: Divider(color: Colors.grey[300])), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( 'OR UPLOAD PHOTO', style: TextStyle( fontSize: 10, fontWeight: FontWeight.bold, color: Colors.grey[500], letterSpacing: 0.5, ), ), ), Expanded(child: Divider(color: Colors.grey[300])), ], ), const SizedBox(height: 12), if (isUploaded) ...[ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: Row( children: [ Container( width: 50, height: 50, decoration: BoxDecoration( color: Colors.red[50], borderRadius: BorderRadius.circular(8), ), clipBehavior: Clip.antiAlias, child: kIsWeb ? Image.network( _uploadedDocs[docName]!.path, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => const Icon(Icons.broken_image, color: Color(0xFFAF101A)), ) : Image.file( File(_uploadedDocs[docName]!.path), fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => const Icon(Icons.broken_image, color: Color(0xFFAF101A)), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _uploadedDocs[docName]?.name ?? 'Attached_document.jpg', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis, ), ), const SizedBox(height: 2), const Text( 'Attached • Ready', style: TextStyle( fontSize: 11, color: Colors.grey, ), ), ], ), ), IconButton( icon: const Icon(Icons.delete_outline, color: Colors.red), onPressed: () { AppFeedback.click(); setState(() { _uploadedDocs.remove(docName); }); showTopAlert( context, '$docName attachment removed.', icon: Icons.delete_outline, ); }, ), ], ), ), ] else ...[ CustomPaint( painter: DashedBorderPainter( color: const Color(0xFFAF101A), radius: 16, ), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(16), ), child: Column( children: [ const Icon( Icons.cloud_upload_outlined, color: Color(0xFFAF101A), size: 32, ), const SizedBox(height: 8), Text( 'Upload your $docName Proof', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: Colors.grey, ), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ 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), 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)), ), ), ), ], ), ], ), ), ), ], ], ), ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final padding = context.responsivePadding; return Scaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back, color: Color(0xFFBA1A20)), onPressed: () { AppFeedback.click(); Navigator.pop(context); }, ), title: Text( 'PozoApp', style: theme.textTheme.headlineMedium?.copyWith( color: theme.colorScheme.primary, fontWeight: FontWeight.bold, ), ), actions: [ Padding( padding: EdgeInsets.symmetric(horizontal: padding), child: Center( child: Text( 'Step 3 of 5', style: theme.textTheme.labelMedium?.copyWith( fontWeight: FontWeight.bold, ), ), ), ) ], bottom: PreferredSize( preferredSize: const Size.fromHeight(6), child: Row( children: List.generate(5, (index) { return Expanded( child: AnimatedContainer( duration: const Duration(milliseconds: 350), height: 6, margin: const EdgeInsets.symmetric(horizontal: 2.0), decoration: BoxDecoration( color: index <= 2 ? theme.colorScheme.primary : theme.colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(3), ), ), ); }), ), ), ), body: SafeArea( child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 600), child: Column( children: [ Expanded( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SlideFadeEntrance( delay: Duration.zero, 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), 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: () { AppFeedback.select(); 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: 16), 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], ), ) ], ), const SizedBox(height: 8), Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: TextField( controller: _gstinController, textCapitalization: TextCapitalization.characters, decoration: InputDecoration( hintText: '15-DIGIT GST NUMBER', suffixIcon: _hasGstinInput ? Icon( _isGstinValid ? Icons.check_circle : Icons.error_outline, color: _isGstinValid ? Colors.green : Colors.red, ) : null, ), style: const TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1.2), ), ), const SizedBox(width: 12), InteractiveScale( child: ElevatedButton( onPressed: () { if (_isGstinValid) { AppFeedback.success(); showTopAlert( context, 'GSTIN Verified Successfully!', icon: Icons.verified_outlined, ); } else { AppFeedback.error(); showTopAlert( context, 'Invalid GSTIN Format. Check 15-digit code.', isError: true, ); } }, 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: 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, ), ), const SizedBox(height: 8), InteractiveScale( child: InkWell( onTap: _fetchLocationAndAddress, 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, ), ), 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], ), ], ), ), ), ), const SizedBox(height: 16), Text( 'Shop Address *', style: theme.textTheme.labelSmall?.copyWith( color: theme.colorScheme.onSurface, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), TextField( controller: _addressController, maxLines: 3, decoration: const InputDecoration( hintText: 'Enter complete shop address', ), style: const TextStyle(fontWeight: FontWeight.bold), ), ], ), ), 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']!.name : '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), ], ), ), ), ), 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 ? () { AppFeedback.click(); 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'), ), ), ), ) ], ), ), ), ), ); } } class DashedBorderPainter extends CustomPainter { final Color color; final double strokeWidth; final double gap; final double radius; DashedBorderPainter({ required this.color, this.strokeWidth = 1.5, this.gap = 4.0, this.radius = 16.0, }); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = color ..strokeWidth = strokeWidth ..style = PaintingStyle.stroke; final RRect rrect = RRect.fromRectAndRadius( Rect.fromLTWH(0, 0, size.width, size.height), Radius.circular(radius), ); try { final Path path = Path()..addRRect(rrect); final Path dashPath = Path(); double distance = 0.0; bool draw = true; for (final PathMetric metric in path.computeMetrics()) { while (distance < metric.length) { final double len = draw ? gap * 1.5 : gap; if (draw) { dashPath.addPath( metric.extractPath(distance, (distance + len).clamp(0.0, metric.length)), Offset.zero, ); } distance += len; draw = !draw; } } canvas.drawPath(dashPath, paint); } catch (e) { canvas.drawRRect(rrect, paint); } } @override bool shouldRepaint(covariant DashedBorderPainter oldDelegate) { return oldDelegate.color != color || oldDelegate.strokeWidth != strokeWidth || oldDelegate.gap != gap || oldDelegate.radius != radius; } }