731 lines
27 KiB
Dart
731 lines
27 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:collection/collection.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';
|
|
import 'package:marco/model/directory/contact_model.dart';
|
|
import 'package:marco/helpers/utils/contact_picker_helper.dart';
|
|
|
|
class AddContactBottomSheet extends StatefulWidget {
|
|
final ContactModel? existingContact;
|
|
const AddContactBottomSheet({super.key, this.existingContact});
|
|
|
|
@override
|
|
State<AddContactBottomSheet> createState() => _AddContactBottomSheetState();
|
|
}
|
|
|
|
class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
|
final controller = Get.put(AddContactController());
|
|
final formKey = GlobalKey<FormState>();
|
|
|
|
final nameController = TextEditingController();
|
|
final orgController = TextEditingController();
|
|
final addressController = TextEditingController();
|
|
final descriptionController = TextEditingController();
|
|
final tagTextController = TextEditingController();
|
|
final RxBool showAdvanced = false.obs;
|
|
final RxList<TextEditingController> emailControllers =
|
|
<TextEditingController>[].obs;
|
|
final RxList<RxString> emailLabels = <RxString>[].obs;
|
|
|
|
final RxList<TextEditingController> phoneControllers =
|
|
<TextEditingController>[].obs;
|
|
final RxList<RxString> phoneLabels = <RxString>[].obs;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
controller.resetForm();
|
|
|
|
nameController.text = widget.existingContact?.name ?? '';
|
|
orgController.text = widget.existingContact?.organization ?? '';
|
|
addressController.text = widget.existingContact?.address ?? '';
|
|
descriptionController.text = widget.existingContact?.description ?? '';
|
|
tagTextController.clear();
|
|
|
|
if (widget.existingContact != null) {
|
|
emailControllers.clear();
|
|
emailLabels.clear();
|
|
for (var email in widget.existingContact!.contactEmails) {
|
|
emailControllers.add(TextEditingController(text: email.emailAddress));
|
|
emailLabels.add((email.label).obs);
|
|
}
|
|
if (emailControllers.isEmpty) {
|
|
emailControllers.add(TextEditingController());
|
|
emailLabels.add('Office'.obs);
|
|
}
|
|
|
|
phoneControllers.clear();
|
|
phoneLabels.clear();
|
|
for (var phone in widget.existingContact!.contactPhones) {
|
|
phoneControllers.add(TextEditingController(text: phone.phoneNumber));
|
|
phoneLabels.add((phone.label).obs);
|
|
}
|
|
if (phoneControllers.isEmpty) {
|
|
phoneControllers.add(TextEditingController());
|
|
phoneLabels.add('Work'.obs);
|
|
}
|
|
|
|
controller.enteredTags.assignAll(
|
|
widget.existingContact!.tags.map((tag) => tag.name).toList(),
|
|
);
|
|
|
|
ever(controller.isInitialized, (bool ready) {
|
|
if (ready) {
|
|
final projectIds = widget.existingContact!.projectIds;
|
|
final bucketId = widget.existingContact!.bucketIds.firstOrNull;
|
|
final categoryName = widget.existingContact!.contactCategory?.name;
|
|
|
|
if (categoryName != null) {
|
|
controller.selectedCategory.value = categoryName;
|
|
}
|
|
|
|
if (projectIds != null) {
|
|
final names = projectIds
|
|
.map((id) {
|
|
return controller.projectsMap.entries
|
|
.firstWhereOrNull((e) => e.value == id)
|
|
?.key;
|
|
})
|
|
.whereType<String>()
|
|
.toList();
|
|
controller.selectedProjects.assignAll(names);
|
|
}
|
|
if (bucketId != null) {
|
|
final name = controller.bucketsMap.entries
|
|
.firstWhereOrNull((e) => e.value == bucketId)
|
|
?.key;
|
|
if (name != null) {
|
|
controller.selectedBucket.value = name;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
emailControllers.add(TextEditingController());
|
|
emailLabels.add('Office'.obs);
|
|
phoneControllers.add(TextEditingController());
|
|
phoneLabels.add('Work'.obs);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
nameController.dispose();
|
|
orgController.dispose();
|
|
tagTextController.dispose();
|
|
addressController.dispose();
|
|
descriptionController.dispose();
|
|
emailControllers.forEach((e) => e.dispose());
|
|
phoneControllers.forEach((p) => p.dispose());
|
|
Get.delete<AddContactController>();
|
|
super.dispose();
|
|
}
|
|
|
|
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 _buildLabeledRow(
|
|
String label,
|
|
RxString selectedLabel,
|
|
List<String> options,
|
|
String inputLabel,
|
|
TextEditingController controller,
|
|
TextInputType inputType,
|
|
{VoidCallback? onRemove}) {
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
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),
|
|
TextFormField(
|
|
controller: controller,
|
|
keyboardType: inputType,
|
|
maxLength: inputType == TextInputType.phone ? 10 : null,
|
|
inputFormatters: inputType == TextInputType.phone
|
|
? [FilteringTextInputFormatter.digitsOnly]
|
|
: [],
|
|
decoration: _inputDecoration("Enter $inputLabel").copyWith(
|
|
counterText: "",
|
|
suffixIcon: inputType == TextInputType.phone
|
|
? IconButton(
|
|
icon: const Icon(Icons.contact_phone,
|
|
color: Colors.blue),
|
|
onPressed: () async {
|
|
final selectedPhone =
|
|
await ContactPickerHelper.pickIndianPhoneNumber(
|
|
context);
|
|
if (selectedPhone != null) {
|
|
controller.text = selectedPhone;
|
|
}
|
|
},
|
|
)
|
|
: null,
|
|
),
|
|
validator: (value) {
|
|
if (value == null || value.trim().isEmpty)
|
|
return "$inputLabel is required";
|
|
final trimmed = value.trim();
|
|
if (inputType == TextInputType.phone) {
|
|
if (!RegExp(r'^[1-9][0-9]{9}$').hasMatch(trimmed)) {
|
|
return "Enter valid phone number";
|
|
}
|
|
}
|
|
if (inputType == TextInputType.emailAddress &&
|
|
!RegExp(r'^[\w.-]+@([\w-]+\.)+[\w-]{2,4}$')
|
|
.hasMatch(trimmed)) {
|
|
return "Enter valid email";
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (onRemove != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 24),
|
|
child: IconButton(
|
|
icon: const Icon(Icons.remove_circle_outline, color: Colors.red),
|
|
onPressed: onRemove,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildEmailList() => Column(
|
|
children: List.generate(emailControllers.length, (index) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 12),
|
|
child: _buildLabeledRow(
|
|
"Email Label",
|
|
emailLabels[index],
|
|
["Office", "Personal", "Other"],
|
|
"Email",
|
|
emailControllers[index],
|
|
TextInputType.emailAddress,
|
|
onRemove: emailControllers.length > 1
|
|
? () {
|
|
emailControllers.removeAt(index);
|
|
emailLabels.removeAt(index);
|
|
}
|
|
: null,
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
|
|
Widget _buildPhoneList() => Column(
|
|
children: List.generate(phoneControllers.length, (index) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 12),
|
|
child: _buildLabeledRow(
|
|
"Phone Label",
|
|
phoneLabels[index],
|
|
["Work", "Mobile", "Other"],
|
|
"Phone",
|
|
phoneControllers[index],
|
|
TextInputType.phone,
|
|
onRemove: phoneControllers.length > 1
|
|
? () {
|
|
phoneControllers.removeAt(index);
|
|
phoneLabels.removeAt(index);
|
|
}
|
|
: null,
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
|
|
Widget _popupSelector({
|
|
required String hint,
|
|
required RxString selectedValue,
|
|
required List<String> options,
|
|
}) {
|
|
return Obx(() => GestureDetector(
|
|
onTap: () async {
|
|
final selected = await showMenu<String>(
|
|
context: context,
|
|
position: RelativeRect.fromLTRB(100, 300, 100, 0),
|
|
items: options.map((option) {
|
|
return PopupMenuItem<String>(
|
|
value: option,
|
|
child: Text(option),
|
|
);
|
|
}).toList(),
|
|
);
|
|
|
|
if (selected != null) {
|
|
selectedValue.value = selected;
|
|
}
|
|
},
|
|
child: Container(
|
|
height: 48,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade100,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.grey.shade300),
|
|
),
|
|
alignment: Alignment.centerLeft,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
selectedValue.value.isNotEmpty ? selectedValue.value : hint,
|
|
style: const TextStyle(fontSize: 14),
|
|
),
|
|
const Icon(Icons.expand_more, size: 20),
|
|
],
|
|
),
|
|
),
|
|
));
|
|
}
|
|
|
|
Widget _sectionLabel(String title) => Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
MyText.labelLarge(title, fontWeight: 600),
|
|
MySpacing.height(4),
|
|
Divider(thickness: 1, color: Colors.grey.shade200),
|
|
],
|
|
);
|
|
|
|
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()
|
|
: 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)
|
|
],
|
|
),
|
|
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();
|
|
},
|
|
);
|
|
},
|
|
),
|
|
)),
|
|
MySpacing.height(8),
|
|
Obx(() => Wrap(
|
|
spacing: 8,
|
|
children: controller.enteredTags
|
|
.map((tag) => Chip(
|
|
label: Text(tag),
|
|
onDeleted: () => controller.removeEnteredTag(tag),
|
|
))
|
|
.toList(),
|
|
)),
|
|
],
|
|
);
|
|
}
|
|
|
|
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 _buildActionButtons() {
|
|
return Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton.icon(
|
|
onPressed: () {
|
|
Get.back();
|
|
Get.delete<AddContactController>();
|
|
},
|
|
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()) {
|
|
final emails = emailControllers
|
|
.asMap()
|
|
.entries
|
|
.where((entry) => entry.value.text.trim().isNotEmpty)
|
|
.map((entry) => {
|
|
"label": emailLabels[entry.key].value,
|
|
"emailAddress": entry.value.text.trim(),
|
|
})
|
|
.toList();
|
|
|
|
final phones = phoneControllers
|
|
.asMap()
|
|
.entries
|
|
.where((entry) => entry.value.text.trim().isNotEmpty)
|
|
.map((entry) => {
|
|
"label": phoneLabels[entry.key].value,
|
|
"phoneNumber": entry.value.text.trim(),
|
|
})
|
|
.toList();
|
|
|
|
controller.submitContact(
|
|
id: widget.existingContact?.id,
|
|
name: nameController.text.trim(),
|
|
organization: orgController.text.trim(),
|
|
emails: emails,
|
|
phones: phones,
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Obx(() {
|
|
if (!controller.isInitialized.value) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
return SingleChildScrollView(
|
|
padding: MediaQuery.of(context).viewInsets,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).cardColor,
|
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 32),
|
|
child: Form(
|
|
key: formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Center(
|
|
child: MyText.titleMedium(
|
|
widget.existingContact != null
|
|
? "Edit Contact"
|
|
: "Create New Contact",
|
|
fontWeight: 700,
|
|
),
|
|
),
|
|
MySpacing.height(24),
|
|
_sectionLabel("Required Fields"),
|
|
MySpacing.height(12),
|
|
_buildTextField("Name", nameController),
|
|
MySpacing.height(16),
|
|
_buildOrganizationField(),
|
|
MySpacing.height(16),
|
|
MyText.labelMedium("Select Bucket"),
|
|
MySpacing.height(8),
|
|
_popupSelector(
|
|
hint: "Select Bucket",
|
|
selectedValue: controller.selectedBucket,
|
|
options: controller.buckets,
|
|
),
|
|
MySpacing.height(24),
|
|
|
|
// Toggle for Advanced Section
|
|
Obx(() => GestureDetector(
|
|
onTap: () => showAdvanced.toggle(),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
MyText.labelLarge("Advanced Details (Optional)",
|
|
fontWeight: 600),
|
|
Icon(showAdvanced.value
|
|
? Icons.expand_less
|
|
: Icons.expand_more),
|
|
],
|
|
),
|
|
)),
|
|
Obx(() => showAdvanced.value
|
|
? Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
MySpacing.height(24),
|
|
_sectionLabel("Contact Info"),
|
|
MySpacing.height(16),
|
|
_buildEmailList(),
|
|
TextButton.icon(
|
|
onPressed: () {
|
|
emailControllers.add(TextEditingController());
|
|
emailLabels.add('Office'.obs);
|
|
},
|
|
icon: const Icon(Icons.add),
|
|
label: const Text("Add Email"),
|
|
),
|
|
_buildPhoneList(),
|
|
TextButton.icon(
|
|
onPressed: () {
|
|
phoneControllers.add(TextEditingController());
|
|
phoneLabels.add('Work'.obs);
|
|
},
|
|
icon: const Icon(Icons.add),
|
|
label: const Text("Add Phone"),
|
|
),
|
|
MySpacing.height(24),
|
|
_sectionLabel("Other Details"),
|
|
MySpacing.height(16),
|
|
MyText.labelMedium("Category"),
|
|
MySpacing.height(8),
|
|
_popupSelector(
|
|
hint: "Select Category",
|
|
selectedValue: controller.selectedCategory,
|
|
options: controller.categories,
|
|
),
|
|
MySpacing.height(16),
|
|
MyText.labelMedium("Select Projects"),
|
|
MySpacing.height(8),
|
|
_projectSelectorUI(),
|
|
MySpacing.height(16),
|
|
MyText.labelMedium("Tags"),
|
|
MySpacing.height(8),
|
|
_tagInputSection(),
|
|
MySpacing.height(16),
|
|
_buildTextField("Address", addressController,
|
|
maxLines: 2),
|
|
MySpacing.height(16),
|
|
_buildTextField(
|
|
"Description", descriptionController,
|
|
maxLines: 2),
|
|
],
|
|
)
|
|
: SizedBox()),
|
|
|
|
MySpacing.height(24),
|
|
_buildActionButtons(),
|
|
],
|
|
),
|
|
)),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
Widget _projectSelectorUI() {
|
|
return GestureDetector(
|
|
onTap: () async {
|
|
await showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
title: const Text('Select Projects'),
|
|
content: Obx(() {
|
|
return SizedBox(
|
|
width: double.maxFinite,
|
|
child: ListView(
|
|
shrinkWrap: true,
|
|
children: controller.globalProjects.map((project) {
|
|
final isSelected =
|
|
controller.selectedProjects.contains(project);
|
|
return Theme(
|
|
data: Theme.of(context).copyWith(
|
|
unselectedWidgetColor: Colors.black,
|
|
checkboxTheme: CheckboxThemeData(
|
|
fillColor: MaterialStateProperty.resolveWith<Color>(
|
|
(states) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return Colors.white;
|
|
}
|
|
return Colors.transparent;
|
|
}),
|
|
checkColor: MaterialStateProperty.all(Colors.black),
|
|
side:
|
|
const BorderSide(color: Colors.black, width: 2),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
),
|
|
child: CheckboxListTile(
|
|
dense: true,
|
|
title: Text(project),
|
|
value: isSelected,
|
|
onChanged: (bool? selected) {
|
|
if (selected == true) {
|
|
controller.selectedProjects.add(project);
|
|
} else {
|
|
controller.selectedProjects.remove(project);
|
|
}
|
|
},
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
);
|
|
}),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Done'),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
},
|
|
child: Container(
|
|
height: 48,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade100,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.grey.shade300),
|
|
),
|
|
alignment: Alignment.centerLeft,
|
|
child: Obx(() {
|
|
final selected = controller.selectedProjects;
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
selected.isEmpty ? "Select Projects" : selected.join(', '),
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(fontSize: 14),
|
|
),
|
|
),
|
|
const Icon(Icons.expand_more, size: 20),
|
|
],
|
|
);
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
}
|