feat: Enhance contact detail screen; implement reactive contact updates and improve note handling

This commit is contained in:
Vaibhav Surve 2025-09-18 16:18:01 +05:30
parent e6f028d129
commit 47666c7897
2 changed files with 43 additions and 34 deletions

View File

@ -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(),

View File

@ -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,