Skip to main content

💬 Parent Chat

This document covers the comprehensive chat system for parents in the Com DEALL mobile application, including real-time messaging, media sharing, therapist communication, and chat management.

🎯 Overview

The parent chat system enables real-time communication between parents and therapists, supporting text messages, media sharing, and group conversations. The system includes message status tracking, online presence, and notification management.

🏗️ Architecture

Chat System Components

Parent Chat System
├── Chat List & Contacts
├── Real-time Messaging
├── Media Sharing
├── Message Status
├── Online Presence
└── Notifications

Data Flow

Message Input → Socket.IO → Server → Database → Real-time Sync → UI Update
↓ ↓ ↓ ↓ ↓ ↓
Parent Types → WebSocket → Processing → Storage → Live Update → Message Display

📱 Mobile Implementation

Parent Chat Screen

// ParentChat.tsx - Main chat interface
const ParentChat = ({ navigation }: Props) => {
const { activeChildId, setActiveChildId } = useChildContext();
const [messageType, setMessageType] = useState<MessageType>('ALL');
const [viewContacts, setViewContacts] = useState(false);

const { parentId, userId } = useSelector(selectUserData);

const { data, loading, error, refetch } = useGetChildrenForAssessmentQuery({
fetchPolicy: 'cache-and-network',
variables: {
parent_id: parentId,
},
});

const filteredChildren = useMemo(
() =>
data?.child.map(c => {
return {
name: c.name,
age: c.age,
profile_image: {
path: c.profile_image?.path,
},
id: c.id,
};
}) ?? [],
[data?.child],
);

const selectedChild = useMemo(
() => filteredChildren.find(c => c.id === activeChildId),
[filteredChildren, activeChildId],
);

const {
data: therapists,
refetch: refetchTherapist,
loading: loadingTherapist,
} = useGetChildsTherapistsQuery({
variables: { child_id: selectedChild?.id },
skip: !selectedChild?.id,
});

const { data: chatEligibility } = useCheckChatEligibilitySubscription({
variables: { child_id: selectedChild?.id, chat_feature: 'chat' },
fetchPolicy: 'network-only',
skip: !selectedChild?.id,
});

const { data: allChats, loading: loadingAllChats } = useChatAvailableSubscription({
variables: { child_id: selectedChild?.id },
fetchPolicy: 'network-only',
skip: !selectedChild?.id,
});

const { data: totalUnread } = useUnreadCountTotalSubscription({
variables: { child_id: selectedChild?.id },
fetchPolicy: 'network-only',
skip: !selectedChild?.id,
});

// Chat data processing with comprehensive filtering and eligibility checks
const chatsAvailable = (allChats?.chat_messages.length ?? 0) >= 1;
const isChildPremium = (chatEligibility?.child_subscription.length ?? 0) > 0;

const toggleView = () => setViewContacts(!viewContacts);

// Chat eligibility and availability logic
const chatEligibilityData = useMemo(() => {
return {
isEligible: isChildPremium,
hasActiveChats: chatsAvailable,
totalUnread: totalUnread?.chat_messages?.length ?? 0
};
}, [isChildPremium, chatsAvailable, totalUnread]);
};

Chat List Component

// ChatList.tsx - Chat conversations list
const ChatList = ({ data, toggleView, messageType, clearFilter, onRefresh, refreshing }) => {
const { userId } = useSelector(selectUserData);
const { data: totalCount } = useUnreadCountTotalSubscription({
variables: { my_id: userId },
});

// Chat list data processing with comprehensive contact management
const combinedContacts = data?.child_therapists?.map(therapist => {
const therapistData = {
name: therapist?.therapist?.user?.name ?? '',
id: therapist?.therapist?.id ?? '',
user: { id: therapist?.therapist?.user?.id ?? '' },
profile_picture: therapist?.therapist?.user?.profile_picture?.path ?? '',
type: 'therapist',
};

return therapistData;
});

// Chat navigation logic with comprehensive user data
const handleChatNavigation = (contact: any) => {
navigation.navigate('parent/chat-screen', {
user: {
childId: selectedChild?.id,
name: contact.name,
img: contact.profile_picture,
userId: contact.user.id,
userType: 'PARENT',
recipientType: 'THERAPIST',
}
});
};
};

Chat Screen

// ChatScreen.tsx - Individual chat interface
const ChatScreen = ({ navigation, route }: ScreenProps) => {
const { userId } = useSelector(selectUserData);
const [messages, setMessages] = useState([]);
const [isUploading, setIsUploading] = useState(false);
const [isOnline, setIsOnline] = useState(false);

const { data: eligibilityData } = useCheckChatEligibilitySubscription({
variables: {
child_id: route?.params?.user?.childId,
chat_feature: 'chat',
},
fetchPolicy: 'network-only',
});

const isChildPremium = (eligibilityData?.child_subscription.length ?? 0) > 0;

const [markAsRead] = useMarkAsReadMutation();
useEffect(() => {
markAsRead({
variables: {
sender_id: route?.params?.user?.userId,
my_user_id: userId,
child_id: route?.params?.user?.childId ?? '',
},
});
}, [markAsRead, route?.params?.user?.childId, route?.params?.user?.userId, userId]);

const { loading } = useGetChatBetweenUsersQuery({
variables: {
sender_id: route?.params?.user?.userId,
my_user_id: userId,
child_id: route?.params?.user?.childId ?? '',
},
onCompleted: (data) => {
const formattedMessages = data?.chat_messages?.map(msg => ({
_id: msg.id,
text: msg.message,
createdAt: new Date(msg.created_at),
user: {
_id: msg.sender_id,
name: msg.sender_id === userId ? 'You' : route?.params?.user?.name,
avatar: msg.sender_id === userId ? null : route?.params?.user?.img,
},
image: msg.media?.path,
video: msg.media?.type === 'video' ? msg.media?.path : null,
audio: msg.media?.type === 'audio' ? msg.media?.path : null,
document: msg.media?.type === 'document' ? msg.media?.path : null,
}));
setMessages(formattedMessages || []);
},
});

// Message sending logic with comprehensive validation and premium checks
const onSend = useCallback((messages = []) => {
if (!isChildPremium) {
showUnsubscribedToast();
return;
}

const message = messages[0];
const messageData = {
message: message.text,
sender_id: userId,
recipient_id: route?.params?.user?.userId,
child_id: route?.params?.user?.childId,
media_id: message.media_id,
};

socket.emit('send_message', messageData);
setMessages(previousMessages => GiftedChat.append(previousMessages, messages));
}, [userId, route?.params?.user?.userId, route?.params?.user?.childId, isChildPremium]);

// Message formatting logic with comprehensive media support
const formatMessages = (chatMessages: any[]) => {
return chatMessages?.map(msg => ({
_id: msg.id,
text: msg.message,
createdAt: new Date(msg.created_at),
user: {
_id: msg.sender_id,
name: msg.sender_id === userId ? 'You' : route?.params?.user?.name,
avatar: msg.sender_id === userId ? null : route?.params?.user?.img,
},
image: msg.media?.path,
video: msg.media?.type === 'video' ? msg.media?.path : null,
audio: msg.media?.type === 'audio' ? msg.media?.path : null,
document: msg.media?.type === 'document' ? msg.media?.path : null,
}));
};
};

📱 Media Sharing

Media Upload

// Media sharing functionality
const useMediaSharing = () => {
const [isUploading, setIsUploading] = useState(false);

const pickImage = async () => {
try {
const result = await ImagePicker.launchImageLibrary({
mediaType: 'photo',
quality: 0.8,
maxWidth: 1024,
maxHeight: 1024,
});

if (result.assets?.[0]) {
await uploadMedia(result.assets[0]);
}
} catch (error) {
console.error('Error picking image:', error);
}
};

const pickVideo = async () => {
try {
const result = await ImagePicker.launchImageLibrary({
mediaType: 'video',
quality: 0.8,
});

if (result.assets?.[0]) {
await uploadMedia(result.assets[0]);
}
} catch (error) {
console.error('Error picking video:', error);
}
};

const pickDocument = async () => {
try {
const result = await DocumentPicker.pick({
type: [DocumentPicker.types.pdf, DocumentPicker.types.doc, DocumentPicker.types.docx],
});

if (result[0]) {
await uploadMedia(result[0]);
}
} catch (error) {
console.error('Error picking document:', error);
}
};

const uploadMedia = async (media: any) => {
setIsUploading(true);
try {
const formData = new FormData();
formData.append('files', {
name: media.fileName || 'media',
uri: media.uri,
type: media.type,
});

const response = await axios.post(
process.env.EXPO_PUBLIC_MEDIA_UPLOAD_PUBLIC!,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${mmkvStorage.get('accessToken')}`,
},
}
);

return response.data.data[0];
} catch (error) {
console.error('Error uploading media:', error);
throw error;
} finally {
setIsUploading(false);
}
};

return {
pickImage,
pickVideo,
pickDocument,
uploadMedia,
isUploading,
};
};

Media Preview

// MediaPreview.tsx - Media preview component
const MediaPreview = ({ media, onClose }) => {
// Media preview logic with comprehensive type handling
const renderMedia = () => {
switch (media.type) {
case 'image':
return {
type: 'image',
source: media.path,
resizeMode: 'contain'
};
case 'video':
return {
type: 'video',
source: media.path,
useNativeControls: true,
resizeMode: 'contain'
};
case 'document':
return {
type: 'document',
path: media.path,
name: media.name,
icon: 'PDF_ICON'
};
default:
return null;
}
};

const handleClose = () => {
onClose();
};
};

🔄 Real-time Communication

Socket.IO Integration

// socket.ts - Socket.IO configuration
import { io } from 'socket.io-client';

const socket = io(process.env.EXPO_PUBLIC_SOCKET_URL!, {
auth: {
token: mmkvStorage.get('accessToken'),
},
transports: ['websocket'],
});

socket.on('connect', () => {
console.log('Connected to chat server');
});

socket.on('disconnect', () => {
console.log('Disconnected from chat server');
});

socket.on('message_received', (message) => {
// Handle incoming message
handleIncomingMessage(message);
});

socket.on('typing_start', (data) => {
// Handle typing indicator
handleTypingStart(data);
});

socket.on('typing_stop', (data) => {
// Handle typing stop
handleTypingStop(data);
});

export { socket };

Message Status Tracking

// Message status management
const useMessageStatus = () => {
const [messageStatus, setMessageStatus] = useState({});

const updateMessageStatus = (messageId: string, status: 'sent' | 'delivered' | 'read') => {
setMessageStatus(prev => ({
...prev,
[messageId]: status
}));
};

const markAsRead = (messageId: string) => {
updateMessageStatus(messageId, 'read');
};

const markAsDelivered = (messageId: string) => {
updateMessageStatus(messageId, 'delivered');
};

return {
messageStatus,
updateMessageStatus,
markAsRead,
markAsDelivered,
};
};

📱 Mobile-Specific Features

Touch Interactions

// Chat touch interactions
const useChatGestures = () => {
const handleMessageLongPress = (message: Message) => {
// Long press to show message options
showMessageOptions(message);
};

const handleMessageSwipe = (message: Message, direction: 'left' | 'right') => {
// Swipe to reply or forward
if (direction === 'left') {
showReplyOptions(message);
} else {
showForwardOptions(message);
}
};

const handleMediaPress = (media: Media) => {
// Press to preview media
showMediaPreview(media);
};

return {
handleMessageLongPress,
handleMessageSwipe,
handleMediaPress,
};
};

Offline Chat

// Offline chat capabilities
const useOfflineChat = () => {
const [offlineMessages, setOfflineMessages] = useState([]);

const saveOfflineMessage = (message: Message) => {
const offlineMessage = {
...message,
id: `offline_${Date.now()}`,
isOffline: true,
createdAt: new Date().toISOString()
};

setOfflineMessages(prev => [...prev, offlineMessage]);
mmkvStorage.set('offline_messages', JSON.stringify([...offlineMessages, offlineMessage]));
};

const syncOfflineMessages = async () => {
if (offlineMessages.length === 0) return;

try {
for (const message of offlineMessages) {
await sendMessage(message);
}

setOfflineMessages([]);
mmkvStorage.delete('offline_messages');

toast.show({ text: 'Offline messages synced successfully' });
} catch (error) {
toast.show({ text: 'Failed to sync offline messages' });
}
};

return {
saveOfflineMessage,
syncOfflineMessages,
offlineMessages,
};
};

🎨 UI Components

Chat Preview Card

// ChatPreviewCard.tsx
const ChatPreviewCard = ({
contact,
onPress
}) => {
const { colors } = useTheme<Theme>();

// Chat preview card logic with comprehensive data display
const handleCardPress = () => {
onPress(contact);
};

// Contact data processing for display
const contactData = useMemo(() => {
return {
name: contact.name,
profilePicture: contact.profile_picture,
lastMessage: contact.lastMessage,
lastMessageTime: format(new Date(contact.lastMessageTime), 'HH:mm'),
unreadCount: contact.unreadCount,
hasUnread: contact.unreadCount > 0
};
}, [contact]);
};

🔧 GraphQL Integration

Chat Queries

# Get chat users
query GetChildrenForAssessment($parent_id: String!) {
child(
where: { parent_id: { _eq: $parent_id } }
order_by: { created_at: desc }
) {
id
name
profile_image { path }
child_therapists {
therapist {
id
user { id name profile_picture { path } }
}
}
}
}

# Get chat messages
query GetChatBetweenUsers($sender_id: String!, $my_user_id: String!, $child_id: String!) {
chat_messages(
where: {
_or: [
{ sender_id: { _eq: $sender_id }, recipient_id: { _eq: $my_user_id } }
{ sender_id: { _eq: $my_user_id }, recipient_id: { _eq: $sender_id } }
]
child_id: { _eq: $child_id }
}
order_by: { created_at: asc }
) {
id
message
sender_id
recipient_id
child_id
created_at
media {
id
path
type
}
}
}

Chat Mutations

# Send message
mutation SendMessage($input: chat_messages_insert_input!) {
insert_chat_messages_one(object: $input) {
id
message
sender_id
recipient_id
child_id
created_at
}
}

# Mark as read
mutation MarkAsRead($sender_id: String!, $my_user_id: String!, $child_id: String!) {
markAsRead(sender_id: $sender_id, my_user_id: $my_user_id, child_id: $child_id) {
success
}
}

🎯 Best Practices

Chat Management

  • Real-time Updates: Provide live message synchronization
  • Message Status: Track message delivery and read status
  • Media Sharing: Support various media types
  • Offline Support: Queue messages when offline

User Experience

  • Visual Feedback: Clear message status indicators
  • Touch Interactions: Intuitive chat gestures
  • Media Preview: Easy media viewing and sharing
  • Notifications: Timely message notifications

Performance

  • Message Caching: Cache chat history locally
  • Lazy Loading: Load messages on demand
  • Optimistic Updates: Update UI before server confirmation
  • Background Sync: Sync messages in background

🎯 Summary

The parent chat system provides comprehensive functionality for real-time communication with therapists, including media sharing, message status tracking, and offline capabilities. The mobile implementation includes touch-optimized interfaces, real-time synchronization, and seamless media handling for a complete chat experience.