import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/controller/directory/directory_controller.dart'; import 'package:marco/controller/project_controller.dart'; import 'package:marco/helpers/widgets/my_card.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/utils/my_shadow.dart'; import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/helpers/widgets/my_custom_skeleton.dart'; import 'package:marco/model/directory/directory_filter_bottom_sheet.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:marco/helpers/utils/launcher_utils.dart'; import 'package:marco/view/directory/contact_detail_screen.dart'; import 'package:marco/model/directory/add_contact_bottom_sheet.dart'; class DirectoryMainScreen extends StatelessWidget { DirectoryMainScreen({super.key}); final DirectoryController controller = Get.put(DirectoryController()); final TextEditingController searchController = TextEditingController(); Future _refreshDirectory() async { try { await controller.fetchContacts(); } catch (e, stackTrace) { debugPrint('Error refreshing directory data: ${e.toString()}'); debugPrintStack(stackTrace: stackTrace); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F5F5), appBar: PreferredSize( preferredSize: const Size.fromHeight(72), child: AppBar( backgroundColor: const Color(0xFFF5F5F5), elevation: 0.5, automaticallyImplyLeading: false, titleSpacing: 0, title: Padding( padding: MySpacing.xy(16, 0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black, size: 20), onPressed: () => Get.offNamed('/dashboard'), ), MySpacing.width(8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ MyText.titleLarge( 'Directory', fontWeight: 700, color: Colors.black, ), MySpacing.height(2), GetBuilder( builder: (projectController) { final projectName = projectController.selectedProject?.name ?? 'Select Project'; return Row( children: [ const Icon(Icons.work_outline, size: 14, color: Colors.grey), MySpacing.width(4), Expanded( child: MyText.bodySmall( projectName, fontWeight: 600, overflow: TextOverflow.ellipsis, color: Colors.grey[700], ), ), ], ); }, ), ], ), ), ], ), ), ), ), floatingActionButton: FloatingActionButton( backgroundColor: Colors.indigo, onPressed: () async { final result = await Get.bottomSheet( AddContactBottomSheet(), isScrollControlled: true, backgroundColor: Colors.transparent, ); if (result == true) { controller.fetchContacts(); } }, child: const Icon(Icons.add, color: Colors.white), ), body: SafeArea( child: Column( children: [ // Search + Filter + Toggle Padding( padding: MySpacing.xy(8, 10), child: Row( children: [ // Compact Search Field Expanded( child: SizedBox( height: 42, child: TextField( controller: searchController, onChanged: (value) { controller.searchQuery.value = value; controller.applyFilters(); }, decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric(horizontal: 12), prefixIcon: const Icon(Icons.search, size: 20, color: Colors.grey), hintText: 'Search contacts...', filled: true, fillColor: Colors.white, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.grey.shade300), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.grey.shade300), ), ), ), ), ), MySpacing.width(8), Tooltip( message: 'Refresh Data', child: InkWell( borderRadius: BorderRadius.circular(24), onTap: _refreshDirectory, child: MouseRegion( cursor: SystemMouseCursors.click, child: const Padding( padding: EdgeInsets.all(0), child: Icon( Icons.refresh, color: Colors.green, size: 28, ), ), ), ), ), MySpacing.width(8), // Filter Icon with optional red dot Obx(() { final isFilterActive = controller.hasActiveFilters(); return Stack( children: [ Container( height: 38, width: 38, decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(10), ), child: IconButton( icon: Icon(Icons.filter_alt_outlined, size: 20, color: isFilterActive ? Colors.indigo : Colors.black87), onPressed: () { showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(20)), ), builder: (_) => const DirectoryFilterBottomSheet(), ); }, ), ), if (isFilterActive) Positioned( top: 6, right: 6, child: Container( height: 8, width: 8, decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), ), ), ], ); }), MySpacing.width(10), // 3-dot Popup Menu with Toggle Container( height: 38, width: 38, decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(10), ), child: PopupMenuButton( padding: EdgeInsets.zero, icon: const Icon(Icons.more_vert, size: 20, color: Colors.black87), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), itemBuilder: (context) => [ PopupMenuItem( value: 0, enabled: false, child: Obx(() => Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ MyText.bodySmall('Show Inactive', fontWeight: 600), Switch.adaptive( value: !controller.isActive.value, activeColor: Colors.indigo, onChanged: (val) { controller.isActive.value = !val; controller.fetchContacts(active: !val); Navigator.pop(context); }, ), ], )), ), ], ), ), ], ), ), // Contacts List Expanded( child: Obx(() { if (controller.isLoading.value) { return ListView.separated( itemCount: 10, separatorBuilder: (_, __) => MySpacing.height(12), itemBuilder: (_, __) => SkeletonLoaders.contactSkeletonCard(), ); } if (controller.filteredContacts.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.contact_page_outlined, size: 60, color: Colors.grey), const SizedBox(height: 12), MyText.bodyMedium('No contacts found.', fontWeight: 500), ], ), ); } return ListView.separated( padding: MySpacing.only( left: 8, right: 8, top: 4, bottom: 80, ), itemCount: controller.filteredContacts.length, separatorBuilder: (_, __) => MySpacing.height(12), itemBuilder: (_, index) { final contact = controller.filteredContacts[index]; final phone = contact.contactPhones.isNotEmpty ? contact.contactPhones.first.phoneNumber : '-'; final email = contact.contactEmails.isNotEmpty ? contact.contactEmails.first.emailAddress : '-'; final nameParts = contact.name.trim().split(" "); final firstName = nameParts.first; final lastName = nameParts.length > 1 ? nameParts.last : ""; final tags = contact.tags.map((tag) => tag.name).toList(); return MyCard.bordered( margin: MySpacing.only(bottom: 2), paddingAll: 8, borderRadiusAll: 8, shadow: MyShadow( elevation: 1.5, position: MyShadowPosition.bottom, ), onTap: () { Get.to(() => ContactDetailScreen(contact: contact)); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Avatar( firstName: firstName, lastName: lastName, size: 31, ), MySpacing.width(12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.titleSmall( contact.name, fontWeight: 700, color: Colors.black87, ), MyText.bodySmall( contact.organization, fontWeight: 500, ), ], ), ), GestureDetector( onTap: () { Get.to(() => ContactDetailScreen(contact: contact)); }, child: const Icon(Icons.arrow_forward_ios, color: Colors.black, size: 15), ), MySpacing.width(4), ], ), const Divider(), if (contact.contactEmails.isNotEmpty || contact.contactPhones.isNotEmpty) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Email Row if (contact.contactEmails.isNotEmpty) Padding( padding: const EdgeInsets.only(bottom: 5), child: Row( children: [ GestureDetector( onTap: () => LauncherUtils.launchEmail(email), child: const Padding( padding: EdgeInsets.only(right: 8.0), child: Icon(Icons.email_outlined, color: Colors.blue, size: 25), ), ), Expanded( child: GestureDetector( onTap: () => LauncherUtils.launchEmail( email), onLongPress: () => LauncherUtils.copyToClipboard( email, typeLabel: 'Email'), child: MyText.bodyMedium( email, maxLines: 1, overflow: TextOverflow.ellipsis, color: Colors.blue, fontWeight: 600, textAlign: TextAlign.start, decoration: TextDecoration.underline, ), ), ), ], ), ), // Phone Row with icons at the end if (contact.contactPhones.isNotEmpty) Row( children: [ // Phone Icon Padding( padding: const EdgeInsets.only(right: 6.0), child: GestureDetector( onTap: () => LauncherUtils.launchPhone(phone), child: const Icon( Icons.phone_outlined, color: Colors.blue, size: 25), ), ), // Phone number text Expanded( child: GestureDetector( onTap: () => LauncherUtils.launchPhone(phone), onLongPress: () => LauncherUtils.copyToClipboard( phone, typeLabel: 'Phone number'), child: MyText.bodyMedium( phone, maxLines: 1, overflow: TextOverflow.ellipsis, color: Colors.blue, fontWeight: 600, textAlign: TextAlign.start, decoration: TextDecoration.underline, ), ), ), // WhatsApp Icon Padding( padding: const EdgeInsets.only(right: 6.0), child: GestureDetector( onTap: () => LauncherUtils.launchWhatsApp( phone), child: const FaIcon( FontAwesomeIcons.whatsapp, color: Colors.green, size: 25), ), ), ], ), ], ), MySpacing.height(8), // Tags Section if (tags.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 2), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ SingleChildScrollView( scrollDirection: Axis.horizontal, reverse: true, // ensures scroll starts from right child: Row( children: tags.map((name) { return Container( margin: const EdgeInsets.only(left: 6), child: TextButton( onPressed: () {}, style: TextButton.styleFrom( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4, ), backgroundColor: const Color.fromARGB( 255, 179, 207, 246), tapTargetSize: MaterialTapTargetSize .shrinkWrap, minimumSize: Size.zero, visualDensity: VisualDensity.standard, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), ), ), child: Text( name, style: const TextStyle( fontSize: 10, color: Color.fromARGB( 255, 0, 0, 0), height: 1.2, ), ), ), ); }).toList(), ), ), ], ), ), ], ), ); }, ); }), ), ], ), ), ); } }