// unchanged imports import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/controller/directory/add_contact_controller.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text_style.dart'; class AddContactBottomSheet extends StatelessWidget { AddContactBottomSheet({super.key}); final controller = Get.put(AddContactController()); final formKey = GlobalKey(); final emailLabel = 'Office'.obs; final phoneLabel = 'Work'.obs; final nameController = TextEditingController(); final emailController = TextEditingController(); final phoneController = TextEditingController(); final orgController = TextEditingController(); final tagTextController = TextEditingController(); final addressController = TextEditingController(); final descriptionController = TextEditingController(); InputDecoration _inputDecoration(String hint) => InputDecoration( hintText: hint, hintStyle: MyTextStyle.bodySmall(xMuted: true), filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.blueAccent, width: 1.5), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), isDense: true, ); Widget _popupSelector({ required String hint, required RxString selectedValue, required List options, }) { return Obx(() => GestureDetector( onTap: () async { final selected = await showMenu( context: Navigator.of(Get.context!).overlay!.context, position: const RelativeRect.fromLTRB(100, 300, 100, 0), items: options .map((e) => PopupMenuItem(value: e, child: Text(e))) .toList(), ); if (selected != null) selectedValue.value = selected; }, child: AbsorbPointer( child: SizedBox( height: 48, child: TextFormField( readOnly: true, initialValue: selectedValue.value, style: const TextStyle(fontSize: 14), decoration: _inputDecoration(hint) .copyWith(suffixIcon: const Icon(Icons.expand_more)), ), ), ), )); } Widget _dropdownField({ required String label, required RxString selectedValue, required RxList options, }) { return Obx(() => SizedBox( height: 48, child: PopupMenuButton( onSelected: (value) => selectedValue.value = value, itemBuilder: (_) => options .map((item) => PopupMenuItem(value: item, child: Text(item))) .toList(), padding: EdgeInsets.zero, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), color: Colors.grey.shade100, ), alignment: Alignment.centerLeft, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( selectedValue.value.isEmpty ? label : selectedValue.value, style: const TextStyle(fontSize: 14), overflow: TextOverflow.ellipsis, ), ), const Icon(Icons.arrow_drop_down), ], ), ), ), )); } Widget _tagInputSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( height: 48, child: TextField( controller: tagTextController, onChanged: controller.filterSuggestions, onSubmitted: (value) { controller.addEnteredTag(value); tagTextController.clear(); controller.clearSuggestions(); }, decoration: _inputDecoration("Start typing to add tags"), ), ), Obx(() => controller.filteredSuggestions.isEmpty ? const SizedBox() : _buildSuggestionsList()), MySpacing.height(8), Obx(() => Wrap( spacing: 8, children: controller.enteredTags .map((tag) => Chip( label: Text(tag), onDeleted: () => controller.removeEnteredTag(tag), )) .toList(), )), ], ); } Widget _buildSuggestionsList() => Container( margin: const EdgeInsets.only(top: 4), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 4, offset: Offset(0, 2)), ], ), child: ListView.builder( shrinkWrap: true, itemCount: controller.filteredSuggestions.length, itemBuilder: (context, index) { final suggestion = controller.filteredSuggestions[index]; return ListTile( dense: true, title: Text(suggestion), onTap: () { controller.addEnteredTag(suggestion); tagTextController.clear(); controller.clearSuggestions(); }, ); }, ), ); Widget _sectionLabel(String title) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.labelLarge(title, fontWeight: 600), MySpacing.height(4), Divider(thickness: 1, color: Colors.grey.shade200), ], ); @override Widget build(BuildContext context) { final theme = Theme.of(context); return SingleChildScrollView( padding: MediaQuery.of(context).viewInsets, child: Container( decoration: BoxDecoration( color: theme.cardColor, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 12, offset: Offset(0, -2)) ], ), child: Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 32), child: Form( key: formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( child: Container( width: 40, height: 5, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(10), ), ), ), MySpacing.height(12), Center( child: MyText.titleMedium("Create New Contact", fontWeight: 700), ), MySpacing.height(24), _sectionLabel("Basic Info"), MySpacing.height(16), _buildTextField("Name", nameController), MySpacing.height(16), _buildOrganizationField(), MySpacing.height(24), _sectionLabel("Contact Info"), MySpacing.height(16), _buildLabeledRow( "Email Label", emailLabel, ["Office", "Personal", "Other"], "Email", emailController, TextInputType.emailAddress), MySpacing.height(16), _buildLabeledRow( "Phone Label", phoneLabel, ["Work", "Mobile", "Other"], "Phone", phoneController, TextInputType.phone), MySpacing.height(24), _sectionLabel("Other Details"), MySpacing.height(16), MyText.labelMedium("Category"), MySpacing.height(8), _dropdownField( label: "Select Category", selectedValue: controller.selectedCategory, options: controller.categories, ), MySpacing.height(16), MyText.labelMedium("Select Projects"), MySpacing.height(8), _dropdownField( label: "Select Project", selectedValue: controller.selectedProject, options: controller.globalProjects, ), MySpacing.height(16), MyText.labelMedium("Tags"), MySpacing.height(8), _tagInputSection(), MySpacing.height(16), MyText.labelMedium("Select Bucket"), MySpacing.height(8), _dropdownField( label: "Select Bucket", selectedValue: controller.selectedBucket, options: controller.buckets, ), MySpacing.height(16), _buildTextField("Address", addressController, maxLines: 2), MySpacing.height(16), _buildTextField("Description", descriptionController, maxLines: 2), MySpacing.height(24), _buildActionButtons(), ], ), ), ), ), ); } Widget _buildTextField(String label, TextEditingController controller, {int maxLines = 1}) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.labelMedium(label), MySpacing.height(8), TextFormField( controller: controller, maxLines: maxLines, decoration: _inputDecoration("Enter $label"), validator: (value) => (value == null || value.trim().isEmpty) ? "$label is required" : null, ), ], ); } Widget _buildOrganizationField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.labelMedium("Organization"), MySpacing.height(8), TextField( controller: orgController, onChanged: controller.filterOrganizationSuggestions, decoration: _inputDecoration("Enter organization"), ), Obx(() => controller.filteredOrgSuggestions.isEmpty ? const SizedBox() : ListView.builder( shrinkWrap: true, itemCount: controller.filteredOrgSuggestions.length, itemBuilder: (context, index) { final suggestion = controller.filteredOrgSuggestions[index]; return ListTile( dense: true, title: Text(suggestion), onTap: () { orgController.text = suggestion; controller.filteredOrgSuggestions.clear(); }, ); }, )) ], ); } Widget _buildLabeledRow( String label, RxString selectedLabel, List options, String inputLabel, TextEditingController controller, TextInputType inputType, ) { return Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.labelMedium(label), MySpacing.height(8), _popupSelector( hint: "Label", selectedValue: selectedLabel, options: options, ), ], ), ), MySpacing.width(12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.labelMedium(inputLabel), MySpacing.height(8), SizedBox( height: 48, child: TextFormField( controller: controller, keyboardType: inputType, decoration: _inputDecoration("Enter $inputLabel"), validator: (value) => (value == null || value.trim().isEmpty) ? "$inputLabel is required" : null, ), ), ], ), ), ], ); } Widget _buildActionButtons() { return Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: () => Get.back(), icon: const Icon(Icons.close, color: Colors.red), label: MyText.bodyMedium("Cancel", color: Colors.red, fontWeight: 600), style: OutlinedButton.styleFrom( side: const BorderSide(color: Colors.red), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), ), ), ), MySpacing.width(12), Expanded( child: ElevatedButton.icon( onPressed: () { if (formKey.currentState!.validate()) { controller.submitContact( name: nameController.text.trim(), organization: orgController.text.trim(), email: emailController.text.trim(), emailLabel: emailLabel.value, phone: phoneController.text.trim(), phoneLabel: phoneLabel.value, address: addressController.text.trim(), description: descriptionController.text.trim(), ); } }, icon: const Icon(Icons.check_circle_outline, color: Colors.white), label: MyText.bodyMedium("Save", color: Colors.white, fontWeight: 600), style: ElevatedButton.styleFrom( backgroundColor: Colors.indigo, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), ), ), ), ], ); } }