import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/cm_module/cm_detail_provider.dart'; import 'package:test_sa/new_views/common_widgets/custom_badge.dart'; import 'chat_page.dart'; import 'chat_provider.dart'; class ChatWidget extends StatefulWidget { final int moduleId; final int requestId; final String? assigneeEmployeeNumber; final String? myLoginUserID; final String? contactEmployeeINumber; final bool isReadOnly; final bool isShow; final String title; const ChatWidget({ Key? key, this.isReadOnly = false, this.title = "Chat", required this.moduleId, required this.requestId, this.assigneeEmployeeNumber, this.myLoginUserID, this.contactEmployeeINumber, this.isShow = false, }) : super(key: key); @override State createState() => _ChatWidgetState(); } class _ChatWidgetState extends State { @override void initState() { super.initState(); if (!widget.isShow) return; WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; _prepareChatSession(); }); } @override void didUpdateWidget(covariant ChatWidget oldWidget) { super.didUpdateWidget(oldWidget); // Re-prepare when the widget is pointed at a different request. if (widget.isShow && (oldWidget.requestId != widget.requestId || oldWidget.moduleId != widget.moduleId)) { _prepareChatSession(); } } /// Fetches the chat token + participants, unless the exact same conversation /// is already loaded in the (globally shared) [ChatProvider]. /// /// NOTE: `reset()` is intentionally NOT called here. `ChatProvider` is a /// singleton, so resetting on every mount used to drop a live SignalR socket /// and made the "already loaded" guard below dead code (it always re-fetched). /// `getUserAutoLoginTokenSilent()` performs its own reset internally. void _prepareChatSession() { final ChatProvider cProvider = context.read(); final String sessionKey = ChatProvider.buildSessionKey(widget.moduleId, widget.requestId); if (cProvider.chatLoginTokenLoading) return; if (cProvider.chatLoginResponse != null && cProvider.sessionKey == sessionKey) return; final String? myEmployeeId = widget.myLoginUserID ?? context.userProvider.user?.username; final String? receiver = _resolveReceiver(); if (myEmployeeId == null || myEmployeeId.isEmpty || receiver == null || receiver.isEmpty) { // Not enough information to open a conversation (e.g. no assignee / // contact person yet) - keep the icon idle instead of crashing on a // null assertion. return; } cProvider.getUserAutoLoginTokenSilent( widget.moduleId, widget.requestId, widget.title, myEmployeeId, receiver, isMounted: mounted, ); } /// Nurse -> chats the assigned engineer. Engineer -> chats the contact person. String? _resolveReceiver() { final workOrder = context.read().currentWorkOrder?.data; if (context.userProvider.isNurse) { return widget.assigneeEmployeeNumber ?? workOrder?.assignedEmployee?.employeeId; } if (context.userProvider.isEngineer) { if (widget.contactEmployeeINumber != null && widget.contactEmployeeINumber!.isNotEmpty) { return widget.contactEmployeeINumber; } final contacts = workOrder?.workOrderContactPerson; if (contacts != null && contacts.isNotEmpty) { return contacts.first.employeeId; } return workOrder?.workOrderCreatedBy?.employeeId; } return null; } void _openChat() { Navigator.push( context, CupertinoPageRoute( builder: (_) => ChatPage( moduleId: widget.moduleId, requestId: widget.requestId, title: widget.title, readOnly: widget.isReadOnly, assigneeEmployeeNumber: widget.assigneeEmployeeNumber, contactEmployeeINumber: widget.contactEmployeeINumber, myLoginUserID: widget.myLoginUserID, ), ), ); } @override Widget build(BuildContext context) { if (!widget.isShow) return const SizedBox(); // Selector (instead of Consumer) so the badge does not rebuild on every // incoming message / typing event pushed through ChatProvider. return Selector( selector: (_, provider) => _ChatBadgeState( isLoading: provider.chatLoginTokenLoading, unreadCount: provider.chatParticipantModel?.unreadCount ?? 0, ), builder: (_, state, __) => CustomBadge2( value: state.effectiveUnread, top: 2, right: 2, minSize: 18, child: IconButton( icon: const Icon(Icons.chat_bubble), onPressed: _openChat, ).toShimmer(context: context, isShow: state.isLoading, radius: 30, height: 30, width: 30), ), ); } } class _ChatBadgeState { final bool isLoading; final int unreadCount; const _ChatBadgeState({required this.isLoading, required this.unreadCount}); int get effectiveUnread => isLoading ? 0 : unreadCount; @override bool operator ==(Object other) => identical(this, other) || (other is _ChatBadgeState && other.isLoading == isLoading && other.effectiveUnread == effectiveUnread); @override int get hashCode => Object.hash(isLoading, effectiveUnread); }