feat: Enhance contact detail screen; implement reactive contact updates and improve note handling
This commit is contained in:
parent
e6f028d129
commit
47666c7897
@ -270,36 +270,41 @@ class NotificationActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ---------------------- DIRECTORY HANDLERS ----------------------
|
||||||
/// ---------------------- DIRECTORY HANDLERS ----------------------
|
/// ---------------------- DIRECTORY HANDLERS ----------------------
|
||||||
static void _handleContactModified(Map<String, dynamic> data) {
|
static void _handleContactModified(Map<String, dynamic> data) {
|
||||||
_safeControllerUpdate<DirectoryController>(
|
final contactId = data['ContactId'];
|
||||||
onFound: (controller) => controller.fetchContacts(),
|
|
||||||
notFoundMessage: '⚠️ DirectoryController not found, cannot refresh.',
|
|
||||||
successMessage: '✅ Directory contacts refreshed from notification.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _handleContactNoteModified(Map<String, dynamic> data) {
|
|
||||||
final contactId = data['contactId'];
|
|
||||||
|
|
||||||
|
// Always refresh the contact list
|
||||||
_safeControllerUpdate<DirectoryController>(
|
_safeControllerUpdate<DirectoryController>(
|
||||||
onFound: (controller) {
|
onFound: (controller) {
|
||||||
|
controller.fetchContacts();
|
||||||
|
// If a specific contact is provided, refresh its notes as well
|
||||||
if (contactId != null) {
|
if (contactId != null) {
|
||||||
controller.fetchCommentsForContact(contactId);
|
controller.fetchCommentsForContact(contactId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
notFoundMessage:
|
notFoundMessage:
|
||||||
'⚠️ DirectoryController not found, cannot refresh notes.',
|
'⚠️ DirectoryController not found, cannot refresh contacts.',
|
||||||
successMessage: '✅ Directory comments refreshed from notification.',
|
successMessage:
|
||||||
|
'✅ Directory contacts (and notes if applicable) refreshed from notification.',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Refresh notes globally as well
|
||||||
_safeControllerUpdate<NotesController>(
|
_safeControllerUpdate<NotesController>(
|
||||||
onFound: (controller) => controller.fetchNotes(),
|
onFound: (controller) => controller.fetchNotes(),
|
||||||
notFoundMessage: '⚠️ NotesController not found, cannot refresh.',
|
notFoundMessage: '⚠️ NotesController not found, cannot refresh notes.',
|
||||||
successMessage: '✅ Notes refreshed from notification.',
|
successMessage: '✅ Notes refreshed from notification.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _handleContactNoteModified(Map<String, dynamic> data) {
|
||||||
|
final contactId = data['ContactId'];
|
||||||
|
|
||||||
|
// Refresh both contacts and notes when a note is modified
|
||||||
|
_handleContactModified(data);
|
||||||
|
}
|
||||||
|
|
||||||
static void _handleBucketModified(Map<String, dynamic> data) {
|
static void _handleBucketModified(Map<String, dynamic> data) {
|
||||||
_safeControllerUpdate<DirectoryController>(
|
_safeControllerUpdate<DirectoryController>(
|
||||||
onFound: (controller) => controller.fetchBuckets(),
|
onFound: (controller) => controller.fetchBuckets(),
|
||||||
|
@ -63,6 +63,7 @@ String _convertDeltaToHtml(dynamic delta) {
|
|||||||
class ContactDetailScreen extends StatefulWidget {
|
class ContactDetailScreen extends StatefulWidget {
|
||||||
final ContactModel contact;
|
final ContactModel contact;
|
||||||
const ContactDetailScreen({super.key, required this.contact});
|
const ContactDetailScreen({super.key, required this.contact});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ContactDetailScreen> createState() => _ContactDetailScreenState();
|
State<ContactDetailScreen> createState() => _ContactDetailScreenState();
|
||||||
}
|
}
|
||||||
@ -70,16 +71,25 @@ class ContactDetailScreen extends StatefulWidget {
|
|||||||
class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
||||||
late final DirectoryController directoryController;
|
late final DirectoryController directoryController;
|
||||||
late final ProjectController projectController;
|
late final ProjectController projectController;
|
||||||
late ContactModel contact;
|
|
||||||
|
late Rx<ContactModel> contactRx;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
directoryController = Get.find<DirectoryController>();
|
directoryController = Get.find<DirectoryController>();
|
||||||
projectController = Get.find<ProjectController>();
|
projectController = Get.find<ProjectController>();
|
||||||
contact = widget.contact;
|
contactRx = widget.contact.obs;
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
directoryController.fetchCommentsForContact(contact.id);
|
directoryController.fetchCommentsForContact(contactRx.value.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen to controller's allContacts and update contact if changed
|
||||||
|
ever(directoryController.allContacts, (_) {
|
||||||
|
final updated = directoryController.allContacts
|
||||||
|
.firstWhereOrNull((c) => c.id == contactRx.value.id);
|
||||||
|
if (updated != null) contactRx.value = updated;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,12 +104,12 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildSubHeader(),
|
Obx(() => _buildSubHeader(contactRx.value)),
|
||||||
const Divider(height: 1, thickness: 0.5, color: Colors.grey),
|
const Divider(height: 1, thickness: 0.5, color: Colors.grey),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TabBarView(children: [
|
child: TabBarView(children: [
|
||||||
_buildDetailsTab(),
|
Obx(() => _buildDetailsTab(contactRx.value)),
|
||||||
_buildCommentsTab(context),
|
_buildCommentsTab(),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -135,9 +145,9 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
MyText.titleLarge('Contact Profile',
|
MyText.titleLarge('Contact Profile',
|
||||||
fontWeight: 700, color: Colors.black),
|
fontWeight: 700, color: Colors.black),
|
||||||
MySpacing.height(2),
|
MySpacing.height(2),
|
||||||
GetBuilder<ProjectController>(
|
GetBuilder<ProjectController>(builder: (p) {
|
||||||
builder: (p) => ProjectLabel(p.selectedProject?.name),
|
return ProjectLabel(p.selectedProject?.name);
|
||||||
),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -147,7 +157,7 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSubHeader() {
|
Widget _buildSubHeader(ContactModel contact) {
|
||||||
final firstName = contact.name.split(" ").first;
|
final firstName = contact.name.split(" ").first;
|
||||||
final lastName =
|
final lastName =
|
||||||
contact.name.split(" ").length > 1 ? contact.name.split(" ").last : "";
|
contact.name.split(" ").length > 1 ? contact.name.split(" ").last : "";
|
||||||
@ -196,7 +206,7 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDetailsTab() {
|
Widget _buildDetailsTab(ContactModel contact) {
|
||||||
final tags = contact.tags.map((e) => e.name).join(", ");
|
final tags = contact.tags.map((e) => e.name).join(", ");
|
||||||
final bucketNames = contact.bucketIds
|
final bucketNames = contact.bucketIds
|
||||||
.map((id) => directoryController.contactBuckets
|
.map((id) => directoryController.contactBuckets
|
||||||
@ -249,7 +259,6 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
MySpacing.height(12),
|
MySpacing.height(12),
|
||||||
// BASIC INFO CARD
|
|
||||||
_infoCard("Basic Info", [
|
_infoCard("Basic Info", [
|
||||||
multiRows(
|
multiRows(
|
||||||
items:
|
items:
|
||||||
@ -273,20 +282,17 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
),
|
),
|
||||||
_iconInfoRow(Icons.location_on, "Address", contact.address),
|
_iconInfoRow(Icons.location_on, "Address", contact.address),
|
||||||
]),
|
]),
|
||||||
// ORGANIZATION CARD
|
|
||||||
_infoCard("Organization", [
|
_infoCard("Organization", [
|
||||||
_iconInfoRow(
|
_iconInfoRow(
|
||||||
Icons.business, "Organization", contact.organization),
|
Icons.business, "Organization", contact.organization),
|
||||||
_iconInfoRow(Icons.category, "Category", category),
|
_iconInfoRow(Icons.category, "Category", category),
|
||||||
]),
|
]),
|
||||||
// META INFO CARD
|
|
||||||
_infoCard("Meta Info", [
|
_infoCard("Meta Info", [
|
||||||
_iconInfoRow(Icons.label, "Tags", tags.isNotEmpty ? tags : "-"),
|
_iconInfoRow(Icons.label, "Tags", tags.isNotEmpty ? tags : "-"),
|
||||||
_iconInfoRow(Icons.folder_shared, "Contact Buckets",
|
_iconInfoRow(Icons.folder_shared, "Contact Buckets",
|
||||||
bucketNames.isNotEmpty ? bucketNames : "-"),
|
bucketNames.isNotEmpty ? bucketNames : "-"),
|
||||||
_iconInfoRow(Icons.work_outline, "Projects", projectNames),
|
_iconInfoRow(Icons.work_outline, "Projects", projectNames),
|
||||||
]),
|
]),
|
||||||
// DESCRIPTION CARD
|
|
||||||
_infoCard("Description", [
|
_infoCard("Description", [
|
||||||
MySpacing.height(6),
|
MySpacing.height(6),
|
||||||
Align(
|
Align(
|
||||||
@ -318,7 +324,7 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
final updated = directoryController.allContacts
|
final updated = directoryController.allContacts
|
||||||
.firstWhereOrNull((c) => c.id == contact.id);
|
.firstWhereOrNull((c) => c.id == contact.id);
|
||||||
if (updated != null) {
|
if (updated != null) {
|
||||||
setState(() => contact = updated);
|
contactRx.value = updated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -331,9 +337,9 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCommentsTab(BuildContext context) {
|
Widget _buildCommentsTab() {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
final contactId = contact.id;
|
final contactId = contactRx.value.id;
|
||||||
if (!directoryController.contactCommentsMap.containsKey(contactId)) {
|
if (!directoryController.contactCommentsMap.containsKey(contactId)) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
@ -355,7 +361,7 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: MediaQuery.of(context).size.height * 0.6,
|
height: Get.height * 0.6,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: MyText.bodyLarge(
|
child: MyText.bodyLarge(
|
||||||
"No comments yet.",
|
"No comments yet.",
|
||||||
@ -375,7 +381,7 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
itemBuilder: (_, index) => _buildCommentItem(
|
itemBuilder: (_, index) => _buildCommentItem(
|
||||||
comments[index],
|
comments[index],
|
||||||
editingId,
|
editingId,
|
||||||
contact.id,
|
contactId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -438,7 +444,6 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Header Row
|
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -474,7 +479,6 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Comment Content
|
|
||||||
if (isEditing && quillController != null)
|
if (isEditing && quillController != null)
|
||||||
CommentEditorCard(
|
CommentEditorCard(
|
||||||
controller: quillController,
|
controller: quillController,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user