import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../Notifiers/HomeScreenNotifier.dart'; import '../../Notifiers/hrmProvider/contact_provider.dart'; import '../../Utils/app_colors.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../Utils/custom_snackbar.dart'; class ContactListScreen extends StatefulWidget { const ContactListScreen({super.key}); @override State createState() => _ContactListScreenState(); } class _ContactListScreenState extends State { final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _requestContactsPermission(); final homeProv = Provider.of(context, listen: false); Future.microtask(() { final provider = Provider.of(context, listen: false); provider.fetchContactList(homeProv.session, homeProv.empId, 1); }); }); } Future _requestContactsPermission() async { final status = await Permission.contacts.status; if (status.isPermanentlyDenied) { // Show dialog and send user to settings await openAppSettings(); return; } if (!status.isGranted) { await Permission.contacts.request(); } } void _addToContact(String name, String phoneNumber) async { if (phoneNumber.isEmpty) { CustomSnackBar.showError( context: context, message: "No phone number available", ); return; } try { // Show loading snackbar CustomSnackBar.showLoading( context: context, message: "Opening contact form for $name...", ); // var permissionStatus = await Permission.contacts.request(); // // Check and request contact permission // // var permissionStatus = await Permission.contacts.status; // if (permissionStatus.isDenied) { // final requestedStatus = await Permission.contacts.request(); // if (!requestedStatus.isGranted) { // CustomSnackBar.hide(context); // CustomSnackBar.showError( // context: context, // message: "Contact permission is required", // ); // return; // } // } // if (permissionStatus.isPermanentlyDenied) { // CustomSnackBar.hide(context); // _showPermissionDialog(); // return; // } // Check current status var status = await Permission.contacts.status; // Only request if not already granted if (!await status.isGranted) { status = await Permission.contacts.request(); } // Handle denied or permanently denied if (await status.isDenied) { CustomSnackBar.hide(context); CustomSnackBar.showError( context: context, message: "Contact permission is required", ); return; } if (await status.isPermanentlyDenied) { CustomSnackBar.hide(context); _showPermissionDialog(); return; } // Clean phone number final cleanPhoneNumber = _cleanPhoneNumber(phoneNumber); // Instead of inserting contact, open system form final newContact = Contact() ..name = Name(first: name) ..phones = [Phone(cleanPhoneNumber)]; // This opens the phone’s native “Add contact” screen (prefilled) await FlutterContacts.openExternalInsert(newContact); // Hide loading CustomSnackBar.hide(context); // Optional confirmation message CustomSnackBar.showSuccess( context: context, message: "Contact form opened for $name", ); } catch (e) { debugPrint("❌ Error opening contact form: $e"); CustomSnackBar.hide(context); CustomSnackBar.showError( context: context, message: "Failed to open contact form", ); } } // Helper function to clean phone numbers String _cleanPhoneNumber(String phoneNumber) { // Remove all non-digit characters except + String cleaned = phoneNumber.replaceAll(RegExp(r'[^\d+]'), ''); // Remove country code if it's +91 (India) and number is 10 digits if (cleaned.startsWith('+91') && cleaned.length == 13) { cleaned = cleaned.substring(3); } else if (cleaned.startsWith('91') && cleaned.length == 12) { cleaned = cleaned.substring(2); } // Remove leading 0 if present if (cleaned.startsWith('0') && cleaned.length == 11) { cleaned = cleaned.substring(1); } return cleaned; } void _showPermissionDialog() { showDialog( context: context, builder: (BuildContext context) => AlertDialog( title: const Text("Permission Required"), content: const Text( "Contact permission is permanently denied. Please enable it in app settings to add contacts.", ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text("Cancel"), ), TextButton( onPressed: () { Navigator.pop(context); openAppSettings(); }, child: const Text("Open Settings"), ), ], ), ); } void _openWhatsApp(String phoneNumber) async { if (phoneNumber.isEmpty) { CustomSnackBar.showError( context: context, message: "No phone number available", ); return; } final whatsappUrl = "https://wa.me/$phoneNumber"; final Uri whatsappUri = Uri.parse(whatsappUrl); if (await canLaunchUrl(whatsappUri)) { await launchUrl(whatsappUri); } else { CustomSnackBar.showError( context: context, message: "Could not open WhatsApp", ); } } @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; final bool isSmallScreen = screenWidth < 360; final bool isTablet = screenWidth > 768; return Scaffold( backgroundColor: AppColors.scaffold_bg_color, appBar: AppBar( automaticallyImplyLeading: false, backgroundColor: Colors.white, title: Row( children: [ InkResponse( onTap: () => Navigator.pop(context, true), child: SvgPicture.asset( "assets/svg/appbar_back_button.svg", height: isSmallScreen ? 22 : isTablet ? 28 : 25, ), ), const SizedBox(width: 10), Text( "Contacts", style: TextStyle( fontSize: isSmallScreen ? 16 : isTablet ? 20 : 18, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), ), body: Consumer( builder: (context, provider, _) { if (provider.isLoading) { return const Center(child: CircularProgressIndicator()); } if (provider.contactList.isEmpty) { return const Center( child: Text( "No contacts found", style: TextStyle( color: Colors.black54, fontSize: 16, fontFamily: "Plus Jakarta Sans", ), ), ); } final filteredContacts = provider.contactList .where( (c) => (c.name ?? '').toLowerCase().contains( _searchController.text.toLowerCase(), ), ) .toList(); return Padding( padding: EdgeInsets.symmetric(horizontal: isSmallScreen ? 10 : 16), child: Column( children: [ const SizedBox(height: 12), Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: TextField( controller: _searchController, onChanged: (_) => setState(() {}), decoration: const InputDecoration( hintText: "Search", hintStyle: TextStyle(color: Colors.grey), prefixIcon: Icon(Icons.search, color: Colors.grey), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 14), ), ), ), const SizedBox(height: 12), Expanded( child: ListView.builder( itemCount: filteredContacts.length, itemBuilder: (context, index) { final contact = filteredContacts[index]; final canSwipe = contact.mobileNumber != null && contact.mobileNumber!.trim().length == 10; return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Slidable( key: Key( contact.mobileNumber?.toString() ?? '${contact.name}_$index', ), // Left swipe (Add to contact) startActionPane: canSwipe ? ActionPane( motion: const ScrollMotion(), extentRatio: 0.4, // Width percentage dragDismissible: false, children: [ // This will automatically take full height of the list item SlidableAction( onPressed: (_) async { _addToContact( contact.name ?? 'Contact', contact.mobileNumber ?? '', ); }, backgroundColor: const Color( 0xFFE5FFE5, ), foregroundColor: const Color( 0xFF38903c, ), icon: Icons.contact_page, label: 'Add to Contact', autoClose: true, ), ], ) : null, // Right swipe (WhatsApp) endActionPane: canSwipe ? ActionPane( motion: const ScrollMotion(), extentRatio: 0.4, // Width percentage dragDismissible: false, children: [ SlidableAction( onPressed: (_) async { _openWhatsApp( contact.mobileNumber ?? '', ); }, backgroundColor: const Color( 0xFFE9FFE8, ), foregroundColor: const Color( 0xFF4CB443, ), icon: Icons.chat, label: 'WhatsApp', autoClose: true, ), ], ) : null, // The main content - this determines the height child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(1), ), padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 10, ), child: Row( children: [ // 👤 Avatar Container( height: 42, width: 42, decoration: const BoxDecoration( color: Color(0xFFE6F6FF), shape: BoxShape.circle, ), clipBehavior: Clip.antiAlias, child: (contact.profileImage != null && contact .profileImage! .isNotEmpty) ? Image.network( contact.profileImage!, fit: BoxFit.cover, errorBuilder: ( context, error, stackTrace, ) { return const Icon( Icons.person_outline, color: Color(0xFF2d2d2d), ); }, loadingBuilder: ( context, child, loadingProgress, ) { if (loadingProgress == null) return child; return const Center( child: CircularProgressIndicator( strokeWidth: 2, ), ); }, ) : const Icon( Icons.person_outline, color: Color(0xFF2d2d2d), ), ), const SizedBox(width: 12), // 📝 Info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( contact.name ?? "-", style: const TextStyle( fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, fontSize: 14, color: Colors.black87, ), ), const SizedBox(height: 3), Text( "${contact.designation ?? ''}\n${contact.branchName ?? ''}", style: TextStyle( fontFamily: "JakartaRegular", fontSize: 12, fontWeight: FontWeight.w400, color: Colors.grey.shade600, height: 1.4, ), ), ], ), ), // Call if (contact.mobileNumber != null && contact.mobileNumber!.trim().length == 10) IconButton( icon: const Icon( Icons.call, color: Colors.green, size: 24, ), onPressed: () async { final phone = contact.mobileNumber!.trim(); final Uri callUri = Uri( scheme: 'tel', path: phone, ); if (await canLaunchUrl(callUri)) { await launchUrl(callUri); } else { debugPrint( "❌ Could not launch dialer for $phone", ); CustomSnackBar.showError( context: context, message: "Unable to open dialer", ); } }, ), ], ), ), ), ), ); }, ), ), ], ), ); }, ), ); } }