const firebaseConfig = {
apiKey: "AIzaSyC2jdXkwJGqHHmJDWzm1aVZL75OujQvUvI",
authDomain: "computero-213b5.firebaseapp.com",
databaseURL: "https://computero-213b5-default-rtdb.firebaseio.com",
projectId: "computero-213b5",
storageBucket: "computero-213b5.appspot.com",
messagingSenderId: "300084057509",
appId: "1:300084057509:web:cc9d92f5f70a92b7b4044c"
};
firebase.initializeApp(firebaseConfig);
// GIF API Key
const GIPHY_API_KEY = 'EyJHZFDCFSFSE5g7VXSAsKBzDCDBqKHr';
let gifPickerVisible = false;
let emojiPickerVisible = false;
// Call related variables
let localStream;
let remoteStream;
let peerConnection;
let callTimer;
let callStartTime;
let currentCallType = null; // 'voice' or 'video'
let isCaller = false;
let callChannel;
let callRequestRef;
let callStatusRef;
let currentCallId = null;
let incomingCallListener;
let currentIncomingCallId = null;
let iceCandidatesRef;
let answerRef;
let offerRef;
// Group chat variables
let selectedGroupId = null;
let currentGroupAvatar = null;
// Heartbeat interval for status updates
let heartbeatInterval;
// Flag to track the unread message separator
let unreadSeparatorAdded = false;
function initUserProfile(user) {
const userNameElement = document.getElementById('user-profile-name');
const sanitizedEmail = sanitizeEmail(user.email);
// Load profile name from Firebase
firebase.database().ref(`usernames/${sanitizedEmail}`).on('value', snapshot => {
const username = snapshot.val();
userNameElement.textContent = username || user.email.split('@')[0].replace(/\./g, ' ');
});
// Load profile photo from Firebase Database
firebase.database().ref(`profile_photos/${sanitizedEmail}`).on('value', snapshot => {
const photoData = snapshot.val();
if (photoData) {
document.getElementById('user-profile-photo').src = photoData;
} else {
document.getElementById('user-profile-photo').src = 'profile.png';
}
});
// Load dark mode preference
loadDarkModePreference();
// Set up profile photo upload
document.getElementById('profile-photo-upload').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
if (!file.type.match('image.*')) {
alert('Please select an image file');
return;
}
if (file.size > 5 * 1024 * 1024) {
alert('File size should be less than 5MB');
return;
}
// Compress and upload the image
compressImage(file, {
quality: 0.7,
maxWidth: 400,
maxHeight: 400,
success: (compressedFile) => {
uploadProfilePhotoToDatabase(compressedFile, user);
},
error: (err) => {
console.error('Error compressing image:', err);
alert('Error processing image');
}
});
});
}
function uploadProfilePhotoToDatabase(file, user) {
const reader = new FileReader();
const currentUser = firebase.auth().currentUser;
reader.onload = function(event) {
const base64String = event.target.result;
const sanitizedEmail = sanitizeEmail(user.email);
// Save to Firebase Database under user's own reference
firebase.database().ref(`profile_photos/${sanitizedEmail}`).set(base64String)
.then(() => {
// Update local profile photo
document.getElementById('user-profile-photo').src = base64String;
// Update profile photo in all friends' views
updateProfilePhotoForAllFriends(currentUser.uid, base64String);
// If we're in a chat with someone, update our avatar in the chat
if (selectedUser || selectedGroupId) {
const messages = document.querySelectorAll('.message.sender');
messages.forEach(message => {
const avatar = message.parentElement.querySelector('img');
if (avatar) {
avatar.src = base64String;
}
});
}
})
.catch(error => {
console.error('Error saving profile photo:', error);
alert('Failed to save profile photo');
});
};
reader.onerror = function() {
console.error('Error reading file');
alert('Error reading image file');
};
reader.readAsDataURL(file);
}
function updateProfilePhotoForAllFriends(userId, photoData) {
const friendsRef = firebase.database().ref(`friends/${userId}`);
friendsRef.once('value').then(snapshot => {
snapshot.forEach(childSnapshot => {
const friendEmail = childSnapshot.val();
const sanitizedFriendEmail = sanitizeEmail(friendEmail);
// Update the photo reference for each friend
firebase.database().ref(`profile_photos/${sanitizedFriendEmail}_contacts/${sanitizeEmail(currentUser.email)}`).set(photoData);
});
});
}
// Image preview function
function showImagePreview(imageSrc) {
const previewContainer = document.getElementById('image-preview-container');
const previewImage = document.getElementById('image-preview');
previewImage.src = imageSrc;
previewContainer.style.display = 'flex';
}
function closeImagePreview() {
document.getElementById('image-preview-container').style.display = 'none';
}
// Video preview functions
function showVideoPreview(videoSrc) {
const previewContainer = document.getElementById('video-preview-container');
const videoPlayer = document.getElementById('video-preview-player');
videoPlayer.src = videoSrc;
previewContainer.style.display = 'flex';
}
function closeVideoPreview() {
const videoPlayer = document.getElementById('video-preview-player');
videoPlayer.pause();
videoPlayer.src = '';
document.getElementById('video-preview-container').style.display = 'none';
}
// NEW: Centralized function to handle file uploads from both the input and drag-and-drop
function handleFileUpload(file) {
if (!file) return;
// Check if a chat is selected
if (!selectedUser && !selectedGroupId) {
alert('Please select a chat to send a file.');
return;
}
// Check if file is an image or video
if (!file.type.match('image.*') && !file.type.match('video.*')) {
alert('Please select an image or video file');
return;
}
// Check file size (max 10MB for videos, 5MB for images)
const maxSize = file.type.match('video.*') ? 10 : 5;
if (file.size > maxSize * 1024 * 1024) {
alert(`File size should be less than ${maxSize}MB`);
return;
}
// Show upload progress
const progressBar = document.getElementById('upload-progress-bar');
const progressContainer = document.getElementById('upload-progress');
progressContainer.style.display = 'block';
progressBar.style.width = '0%';
if (file.type.match('image.*')) {
// Handle image upload
compressImage(file, {
quality: 0.7,
maxWidth: 1024,
maxHeight: 1024,
success: (compressedFile) => {
uploadMedia(compressedFile, 'image', progressBar, progressContainer);
},
error: (err) => {
console.error('Error compressing image:', err);
alert('Error processing image');
progressContainer.style.display = 'none';
}
});
} else if (file.type.match('video.*')) {
// Handle video upload
uploadMedia(file, 'video', progressBar, progressContainer);
}
}
// UPDATED: File upload handling now uses the new centralized function
document.getElementById('file-upload').addEventListener('change', function(e) {
const file = e.target.files[0];
handleFileUpload(file);
});
function compressImage(file, options) {
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.src = event.target.result;
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Calculate new dimensions
let width = img.width;
let height = img.height;
if (width > options.maxWidth) {
height *= options.maxWidth / width;
width = options.maxWidth;
}
if (height > options.maxHeight) {
width *= options.maxHeight / height;
height = options.maxHeight;
}
// Draw image on canvas
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
// Convert to blob with specified quality
canvas.toBlob(function(blob) {
const compressedFile = new File([blob], file.name, {
type: 'image/jpeg',
lastModified: Date.now()
});
options.success(compressedFile);
}, 'image/jpeg', options.quality);
};
img.onerror = function() {
options.error('Image load error');
};
};
reader.onerror = function() {
options.error('File read error');
};
reader.readAsDataURL(file);
}
function uploadMedia(file, type, progressBar, progressContainer) {
const reader = new FileReader();
reader.onload = function(event) {
const base64String = event.target.result.split(',')[1];
const currentUser = firebase.auth().currentUser;
if (!currentUser || (!selectedUser && !selectedGroupId)) {
alert('You must be signed in and select a user/group to send files');
progressContainer.style.display = 'none';
return;
}
const currentUserId = sanitizeEmail(currentUser.email);
// Create message data with actual media content
const messageData = {
sender: currentUser.email,
timestamp: Date.now(),
fileType: type,
fileName: file.name
};
if (type === 'image') {
messageData.image = base64String;
} else if (type === 'video') {
messageData.video = base64String;
}
if (selectedUser) {
// Direct message
messageData.receiver = selectedUser;
const sanitizedUser = sanitizeEmail(selectedUser);
// Simulate upload progress
let progress = 0;
const progressInterval = setInterval(() => {
progress += 5;
if (progress >= 100) {
clearInterval(progressInterval);
progressBar.style.width = '100%';
setTimeout(() => {
const senderRef = firebase.database().ref(`messages/${currentUserId}/${sanitizedUser}`).push();
const receiverRef = firebase.database().ref(`messages/${sanitizedUser}/${currentUserId}`).push();
senderRef.set(messageData);
receiverRef.set(messageData);
if (selectedUser !== currentUser.email) {
const unreadRef = firebase.database().ref(`unreadMessages/${sanitizedUser}/${currentUserId}`);
unreadRef.transaction((currentCount) => (currentCount || 0) + 1);
}
progressContainer.style.display = 'none';
document.getElementById('file-upload').value = '';
setTimeout(scrollToBottom); // MODIFIED: Added setTimeout
}, 300);
} else {
progressBar.style.width = progress + '%';
}
}, 50);
} else if (selectedGroupId) {
// Group message
messageData.groupId = selectedGroupId;
// Simulate upload progress
let progress = 0;
const progressInterval = setInterval(() => {
progress += 5;
if (progress >= 100) {
clearInterval(progressInterval);
progressBar.style.width = '100%';
setTimeout(() => {
// Save message to group
firebase.database().ref(`groupMessages/${selectedGroupId}`).push(messageData);
// Update last message timestamp for group
firebase.database().ref(`groups/${selectedGroupId}/lastMessageTimestamp`).set(Date.now());
// Update unread count for all group members except sender
firebase.database().ref(`groups/${selectedGroupId}/members`).once('value').then(snapshot => {
snapshot.forEach(memberSnapshot => {
const memberEmail = memberSnapshot.val();
if (memberEmail !== currentUser.email) {
const sanitizedMember = sanitizeEmail(memberEmail);
firebase.database().ref(`unreadGroupMessages/${sanitizedMember}/${selectedGroupId}`).transaction((currentCount) => (currentCount || 0) + 1);
}
});
});
progressContainer.style.display = 'none';
document.getElementById('file-upload').value = '';
setTimeout(scrollToBottom); // MODIFIED: Added setTimeout
}, 300);
} else {
progressBar.style.width = progress + '%';
}
}, 50);
}
};
reader.onerror = function() {
alert('Error reading file');
progressContainer.style.display = 'none';
};
reader.readAsDataURL(file);
}
function cleanupCallResources() {
console.log('Cleaning up call resources...');
// Stop all media tracks
if (localStream) {
localStream.getTracks().forEach(track => {
track.stop();
track.enabled = false;
});
localStream = null;
}
if (remoteStream) {
remoteStream.getTracks().forEach(track => {
track.stop();
track.enabled = false;
});
remoteStream = null;
}
// Close peer connection
if (peerConnection) {
// Remove all event listeners first
peerConnection.onicecandidate = null;
peerConnection.ontrack = null;
peerConnection.oniceconnectionstatechange = null;
peerConnection.onicegatheringstatechange = null;
peerConnection.onsignalingstatechange = null;
peerConnection.onnegotiationneeded = null;
peerConnection.onconnectionstatechange = null;
// Close the connection
peerConnection.close();
peerConnection = null;
}
// Clear the call timer
if (callTimer) {
clearInterval(callTimer);
callTimer = null;
}
// Remove all Firebase listeners
if (callStatusRef) {
callStatusRef.off();
callStatusRef = null;
}
if (iceCandidatesRef) {
iceCandidatesRef.off();
iceCandidatesRef = null;
}
if (answerRef) {
answerRef.off();
answerRef = null;
}
if (offerRef) {
offerRef.off();
offerRef = null;
}
// Reset video elements
const localVideo = document.getElementById('local-video');
const remoteVideo = document.getElementById('remote-video');
if (localVideo) {
localVideo.srcObject = null;
localVideo.pause();
}
if (remoteVideo) {
remoteVideo.srcObject = null;
remoteVideo.pause();
}
// Reset call data
currentCallType = null;
isCaller = false;
callChannel = null;
callRequestRef = null;
currentCallId = null;
}
function addFriend() {
const friendEmail = prompt("Enter your friend's email:");
if (!friendEmail) return alert("Friend email cannot be empty.");
const currentUser = firebase.auth().currentUser;
if (!currentUser) return alert("You must be signed in to add friends.");
const friendsRef = firebase.database().ref(`friends/${currentUser.uid}`);
friendsRef.push(friendEmail).then(() => {
loadFriends();
}).catch(error => alert("Error: " + error.message));
}
// 🚀 Optimized loadFriends() with delayed last message and group loading
function loadFriends() {
const currentUser = firebase.auth().currentUser;
if (!currentUser) return;
const friendsRef = firebase.database().ref(`friends/${currentUser.uid}`);
const groupsRef = firebase.database().ref(`groupMembers/${sanitizeEmail(currentUser.email)}`);
const usersContainer = document.getElementById('users');
usersContainer.innerHTML = '';
const friendsData = {};
const groupsData = {};
// 🧑 Load Friends
friendsRef.on('value', snapshot => {
snapshot.forEach(childSnapshot => {
const friendEmail = childSnapshot.val();
const sanitizedUser = sanitizeEmail(friendEmail);
const currentUserId = sanitizeEmail(currentUser.email);
if (!friendsData[friendEmail]) {
friendsData[friendEmail] = {
email: friendEmail,
sanitizedUser,
currentUserId,
lastMessage: '',
lastTimestamp: 0,
unreadCount: 0,
button: null
};
const button = document.createElement('button');
button.classList.add('friend-button');
button.setAttribute('data-email', friendEmail);
button.onclick = () => startChat(friendEmail);
const profileLogoContainer = document.createElement('div');
profileLogoContainer.classList.add('profile-logo-container');
const profileLogo = document.createElement('img');
profileLogo.src = 'profile.png';
profileLogo.classList.add('profile-logo');
profileLogoContainer.appendChild(profileLogo);
const statusDot = document.createElement('div');
statusDot.classList.add('status-dot');
profileLogoContainer.appendChild(statusDot);
button.appendChild(profileLogoContainer);
const friendText = document.createElement('div');
friendText.classList.add('friend-text');
const nameSpan = document.createElement('span');
nameSpan.classList.add('friend-name');
nameSpan.textContent = friendEmail.split('@')[0].replace(/\./g, ' ');
friendText.appendChild(nameSpan);
firebase.database().ref(`usernames/${sanitizedUser}`).on('value', snap => {
nameSpan.textContent = snap.exists() ? snap.val() : friendEmail.split('@')[0].replace(/\./g, ' ');
});
const lastMessageSpan = document.createElement('span');
lastMessageSpan.classList.add('last-message');
friendText.appendChild(lastMessageSpan);
button.appendChild(friendText);
friendsData[friendEmail].button = button;
usersContainer.appendChild(button);
// Load profile photo
firebase.database().ref(`profile_photos/${sanitizedUser}`).on('value', photoSnap => {
if (photoSnap.exists()) {
profileLogo.src = photoSnap.val();
} else {
firebase.database().ref(`profile_photos/${currentUserId}_contacts/${sanitizedUser}`).on('value', contactPhotoSnap => {
profileLogo.src = contactPhotoSnap.exists() ? contactPhotoSnap.val() : 'profile.png';
});
}
});
// ✅ Delay loading last message
setTimeout(() => {
const messagesRef = firebase.database().ref(`messages/${currentUserId}/${sanitizedUser}`);
messagesRef.orderByChild('timestamp').limitToLast(1).once('value').then(snap => {
const lastMessage = Object.values(snap.val() || {})[0];
if (!lastMessage) return;
let messageText = lastMessage.gif ? '🎁 GIF' : lastMessage.image ? '📷 Image' : lastMessage.video ? '🎥 Video' : lastMessage.text;
friendsData[friendEmail].lastMessage = messageText;
friendsData[friendEmail].lastTimestamp = lastMessage.timestamp;
const lastMessageSpan = friendsData[friendEmail].button.querySelector('.last-message');
lastMessageSpan.textContent = messageText;
sortAndDisplayFriends(friendsData, groupsData, usersContainer);
});
}, 300);
// Unread count
firebase.database().ref(`unreadMessages/${currentUserId}/${sanitizedUser}`).on('value', unreadSnap => {
const count = unreadSnap.val() || 0;
friendsData[friendEmail].unreadCount = count;
const unreadDot = friendsData[friendEmail].button.querySelector('.unread-dot');
const shouldShow = count > 0 && selectedUser !== friendEmail;
if (shouldShow) {
if (!unreadDot) {
const dot = document.createElement('span');
dot.classList.add('unread-dot');
dot.textContent = count;
friendsData[friendEmail].button.appendChild(dot);
} else {
unreadDot.textContent = count;
}
} else if (unreadDot) {
unreadDot.remove();
}
});
const statusRef = firebase.database().ref(`status/${sanitizedUser}`);
const statusUpdatedRef = firebase.database().ref(`statusLastUpdated/${sanitizedUser}`);
function updateStatusDot() {
Promise.all([
statusRef.once('value'),
statusUpdatedRef.once('value')
]).then(([statusSnap, tsSnap]) => {
const isOnline = statusSnap.val() === 'online' && (Date.now() - tsSnap.val()) < 30000;
const dot = friendsData[friendEmail].button.querySelector('.status-dot');
if (dot) {
dot.classList.toggle('online', isOnline);
dot.classList.toggle('offline', !isOnline);
}
});
}
// Listen for changes to both status and timestamp
statusRef.on('value', updateStatusDot);
statusUpdatedRef.on('value', updateStatusDot);
}
});
});
// 👥 Load Groups (Optimized)
groupsRef.on('value', snapshot => {
snapshot.forEach(childSnapshot => {
const groupId = childSnapshot.key;
firebase.database().ref(`groups/${groupId}`).once('value').then(groupSnap => {
const groupData = groupSnap.val();
if (!groupsData[groupId]) {
groupsData[groupId] = {
id: groupId,
name: groupData.name,
avatar: groupData.avatar,
lastMessage: '',
lastTimestamp: groupData.lastMessageTimestamp || 0,
unreadCount: 0,
button: null
};
const button = document.createElement('button');
button.classList.add('friend-button');
button.setAttribute('data-group-id', groupId);
button.onclick = () => startGroupChat(groupId);
const groupLogoContainer = document.createElement('div');
groupLogoContainer.classList.add('profile-logo-container');
const groupLogo = document.createElement('div');
groupLogo.classList.add('group-logo');
if (groupData.avatar) {
groupLogo.style.backgroundImage = `url(${groupData.avatar})`;
groupLogo.style.backgroundSize = 'cover';
groupLogo.style.backgroundPosition = 'center';
} else {
const initials = groupData.name.split(' ').map(w => w[0]).join('').substring(0, 2);
groupLogo.textContent = initials.toUpperCase();
}
groupLogoContainer.appendChild(groupLogo);
button.appendChild(groupLogoContainer);
const groupText = document.createElement('div');
groupText.classList.add('friend-text');
const nameSpan = document.createElement('span');
nameSpan.classList.add('friend-name');
nameSpan.textContent = groupData.name;
const lastMessageSpan = document.createElement('span');
lastMessageSpan.classList.add('last-message');
groupText.appendChild(nameSpan);
groupText.appendChild(lastMessageSpan);
button.appendChild(groupText);
groupsData[groupId].button = button;
usersContainer.appendChild(button);
// ✅ Delay message fetch
setTimeout(() => {
firebase.database().ref(`groupMessages/${groupId}`).orderByChild('timestamp').limitToLast(1).once('child_added').then(msgSnap => {
if (!msgSnap.exists()) return;
const msg = msgSnap.val();
const text = msg.gif ? '🎁 GIF' : msg.image ? '📷 Image' : msg.video ? '🎥 Video' : msg.text;
groupsData[groupId].lastMessage = text;
groupsData[groupId].lastTimestamp = msg.timestamp;
const msgSpan = groupsData[groupId].button.querySelector('.last-message');
msgSpan.textContent = text;
sortAndDisplayFriends(friendsData, groupsData, usersContainer);
});
}, 300);
// Unread
const unreadRef = firebase.database().ref(`unreadGroupMessages/${sanitizeEmail(currentUser.email)}/${groupId}`);
unreadRef.on('value', unreadSnap => {
const count = unreadSnap.val() || 0;
groupsData[groupId].unreadCount = count;
const unreadDot = groupsData[groupId].button.querySelector('.unread-dot');
const shouldShow = count > 0 && selectedGroupId !== groupId;
if (shouldShow) {
if (!unreadDot) {
const dot = document.createElement('span');
dot.classList.add('unread-dot');
dot.textContent = count;
groupsData[groupId].button.appendChild(dot);
} else {
unreadDot.textContent = count;
}
} else if (unreadDot) {
unreadDot.remove();
}
});
}
});
});
});
}
function sortAndDisplayFriends(friendsData, groupsData, usersContainer) {
// Combine friends and groups into one array
const allChats = [
...Object.values(friendsData),
...Object.values(groupsData)
];
// Sort by last message timestamp (newest first)
const sortedChats = allChats.sort((a, b) => b.lastTimestamp - a.lastTimestamp);
// Clear the container and re-add sorted chats
usersContainer.innerHTML = '';
sortedChats.forEach(chat => {
usersContainer.appendChild(chat.button);
// Ensure the button's last message is up to date
const lastMessageSpan = chat.button.querySelector('.last-message');
if (lastMessageSpan) {
lastMessageSpan.textContent = chat.lastMessage;
}
});
}
let selectedUser = null;
function listenForTypingStatus() {
const currentUser = firebase.auth().currentUser;
if (!currentUser || (!selectedUser && !selectedGroupId)) return;
const currentUserId = sanitizeEmail(currentUser.email);
if (window.currentTypingStatusRef) {
window.currentTypingStatusRef.off();
}
const typingIndicator = document.getElementById('typing-indicator');
if (selectedUser) {
// ✅ Listen for the OTHER user's typing status about YOU
window.currentTypingStatusRef = firebase.database().ref(`typingStatus/${sanitizeEmail(selectedUser)}/${currentUserId}`);
window.currentTypingStatusRef.on('value', async (snapshot) => {
const isTyping = snapshot.val();
// ✅ Only show if the OTHER user is typing (not you)
if (isTyping) {
const name = await formatName(selectedUser);
typingIndicator.textContent = `${name} is typing...`;
} else {
typingIndicator.textContent = '';
}
});
} else if (selectedGroupId) {
// Group chat logic
window.currentTypingStatusRef = firebase.database().ref(`typingStatusGroup/${selectedGroupId}`);
window.currentTypingStatusRef.on('value', async (snapshot) => {
if (!snapshot.exists()) {
typingIndicator.textContent = '';
return;
}
const typingUsers = [];
snapshot.forEach(userSnapshot => {
const userId = userSnapshot.key;
const isTyping = userSnapshot.val();
if (isTyping && userId !== currentUserId) {
typingUsers.push(userId);
}
});
if (typingUsers.length === 0) {
typingIndicator.textContent = '';
return;
}
const names = [];
for (const userId of typingUsers) {
const name = await formatName(userId.replace(/_/g, '.'));
names.push(name);
}
if (names.length === 1) {
typingIndicator.textContent = `${names[0]} is typing...`;
} else if (names.length === 2) {
typingIndicator.textContent = `${names[0]} and ${names[1]} are typing...`;
} else {
typingIndicator.textContent = `${names[0]}, ${names[1]} and others are typing...`;
}
});
}
}
function startChat(friendEmail) {
// Reset group selection
selectedGroupId = null;
// Remove active class from all buttons
document.querySelectorAll('.friend-button').forEach(btn => {
btn.classList.remove('active');
});
// Add active class to selected button
const friendButton = document.querySelector(`button[data-email="${friendEmail}"]`);
if (friendButton) {
friendButton.classList.add('active');
}
selectedUser = friendEmail;
const chatBox = document.getElementById("chat");
const messageInput = document.getElementById("message");
if (selectedUser) {
// Create chat header with call buttons
const chatHeader = document.getElementById('chat-header');
chatHeader.innerHTML = '';
// Add profile photo to chat header
const profilePhoto = document.createElement('img');
profilePhoto.style.width = '32px';
profilePhoto.style.height = '32px';
profilePhoto.style.borderRadius = '50%';
profilePhoto.style.marginRight = '8px';
// Load profile photo
const sanitizedUser = sanitizeEmail(selectedUser);
firebase.database().ref(`profile_photos/${sanitizedUser}`).once('value').then(snapshot => {
profilePhoto.src = snapshot.exists() ? snapshot.val() : 'profile.png';
});
chatHeader.appendChild(profilePhoto);
// Add name element (will be updated from Firebase)
const nameElement = document.createElement('h2');
nameElement.textContent = selectedUser.split('@')[0].replace(/\./g, ' '); // Default to email prefix
chatHeader.appendChild(nameElement);
// Add listener for username changes in Firebase
firebase.database().ref(`usernames/${sanitizedUser}`).on('value', snapshot => {
if (snapshot.exists()) {
nameElement.textContent = snapshot.val(); // Update with custom name if available
} else {
nameElement.textContent = selectedUser.split('@')[0].replace(/\./g, ' '); // Fallback to email prefix
}
});
// Add call buttons
const headerActions = document.createElement('div');
headerActions.id = 'chat-header-actions';
const voiceCallBtn = document.createElement('button');
voiceCallBtn.className = 'call-header-btn';
voiceCallBtn.id = 'voice-call-btn';
voiceCallBtn.title = 'Voice Call';
voiceCallBtn.innerHTML = '';
voiceCallBtn.addEventListener('click', () => initiateCall('voice'));
const videoCallBtn = document.createElement('button');
videoCallBtn.className = 'call-header-btn';
videoCallBtn.id = 'video-call-btn';
videoCallBtn.title = 'Video Call';
videoCallBtn.innerHTML = '';
videoCallBtn.addEventListener('click', () => initiateCall('video'));
headerActions.appendChild(voiceCallBtn);
headerActions.appendChild(videoCallBtn);
chatHeader.appendChild(headerActions);
chatBox.style.display = "flex";
messageInput.style.display = "block";
// This single call now handles loading, scrolling, and marking as read.
loadMessages();
// Start listening for typing status
listenForTypingStatus();
}
}
function startGroupChat(groupId) {
// Reset direct message selection
selectedUser = null;
// Remove active class from all buttons
document.querySelectorAll('.friend-button').forEach(btn => {
btn.classList.remove('active');
});
// Add active class to selected group button
const groupButton = document.querySelector(`button[data-group-id="${groupId}"]`);
if (groupButton) {
groupButton.classList.add('active');
}
selectedGroupId = groupId;
const chatBox = document.getElementById("chat");
const messageInput = document.getElementById("message");
if (selectedGroupId) {
// Create group chat header
const chatHeader = document.getElementById('chat-header');
chatHeader.innerHTML = '';
// Get group info
firebase.database().ref(`groups/${selectedGroupId}`).once('value').then(snapshot => {
const groupData = snapshot.val();
// Add group avatar to header
const groupAvatar = document.createElement('div');
groupAvatar.style.width = '32px';
groupAvatar.style.height = '32px';
groupAvatar.style.borderRadius = '50%';
groupAvatar.style.marginRight = '8px';
groupAvatar.style.display = 'flex';
groupAvatar.style.alignItems = 'center';
groupAvatar.style.justifyContent = 'center';
groupAvatar.style.fontWeight = 'bold';
groupAvatar.style.backgroundColor = 'var(--group-chat)';
groupAvatar.style.color = 'white';
if (groupData.avatar) {
groupAvatar.style.backgroundImage = `url(${groupData.avatar})`;
groupAvatar.style.backgroundSize = 'cover';
groupAvatar.style.backgroundPosition = 'center';
} else {
// Use initials if no avatar
const initials = groupData.name.split(' ').map(word => word[0]).join('').substring(0, 2);
groupAvatar.textContent = initials.toUpperCase();
}
// Add click handler to show group info
groupAvatar.style.cursor = 'pointer';
groupAvatar.addEventListener('click', () => showGroupInfoModal(selectedGroupId));
chatHeader.appendChild(groupAvatar);
// Add group name
const nameElement = document.createElement('h2');
nameElement.textContent = groupData.name;
nameElement.style.cursor = 'pointer';
nameElement.addEventListener('click', () => showGroupInfoModal(selectedGroupId));
chatHeader.appendChild(nameElement);
// Add group info button
const infoBtn = document.createElement('button');
infoBtn.className = 'call-header-btn';
infoBtn.title = 'Group Info';
infoBtn.innerHTML = '';
infoBtn.addEventListener('click', () => showGroupInfoModal(selectedGroupId));
const headerActions = document.createElement('div');
headerActions.id = 'chat-header-actions';
headerActions.appendChild(infoBtn);
chatHeader.appendChild(headerActions);
});
chatBox.style.display = "flex";
messageInput.style.display = "block";
// This single call now handles loading, scrolling, and marking as read.
loadGroupMessages();
// Start listening for typing status
listenForTypingStatus();
}
}
async function loadMessages() {
if (!selectedUser) return;
const currentUser = firebase.auth().currentUser;
if (!currentUser) return;
const currentUserId = sanitizeEmail(currentUser.email);
const sanitizedUser = sanitizeEmail(selectedUser);
const messagesRef = firebase.database().ref(`messages/${currentUserId}/${sanitizedUser}`);
const unreadRef = firebase.database().ref(`unreadMessages/${currentUserId}/${sanitizedUser}`);
// Detach all old listeners to prevent duplication
messagesRef.off();
const messagesDiv = document.getElementById('messages');
messagesDiv.innerHTML = '';
lastDisplayedDate = null;
unreadSeparatorAdded = false;
// FIX #1: Get unread count and immediately reset it to 0 when opening the chat.
const unreadSnapshot = await unreadRef.once('value');
const unreadCount = unreadSnapshot.val() || 0;
if (unreadCount > 0) {
unreadRef.set(0);
}
// Then, fetch all messages for the chat.
const messagesSnapshot = await messagesRef.orderByChild('timestamp').once('value');
let unreadSeparator = null;
if (messagesSnapshot.exists()) {
const allMessages = [];
messagesSnapshot.forEach(child => {
allMessages.push({key: child.key, ...child.val()});
});
const totalMessages = allMessages.length;
// Iterate and display all messages
allMessages.forEach((message, index) => {
if (unreadCount > 0 && !unreadSeparatorAdded && index === totalMessages - unreadCount) {
unreadSeparator = document.createElement('div');
unreadSeparator.classList.add('unread-separator');
unreadSeparator.innerHTML = 'Unread';
messagesDiv.appendChild(unreadSeparator);
unreadSeparatorAdded = true;
}
// Display the message itself
const isSender = message.sender === currentUser.email;
displayMessage(message, isSender, message.timestamp);
});
}
// Scroll to the separator if it was added, otherwise to the bottom.
if (unreadSeparator) {
unreadSeparator.scrollIntoView({ behavior: 'auto', block: 'center' });
} else {
setTimeout(scrollToBottom); // MODIFIED: Added setTimeout for initial load
}
// Now, attach a listener for *new* messages that arrive after this initial load.
messagesRef.orderByChild('timestamp').startAt(Date.now()).on('child_added', snapshot => {
const message = snapshot.val();
if ((message.sender === currentUser.email && message.receiver === selectedUser) || (message.sender === selectedUser && message.receiver === currentUser.email)) {
// FIX #2: When a new message arrives in the active chat, reset the count again.
if (message.sender === selectedUser) {
unreadRef.set(0);
}
if (document.querySelector('.unread-separator')) {
document.querySelector('.unread-separator').remove();
}
const isSender = message.sender === currentUser.email;
displayMessage(message, isSender, message.timestamp);
setTimeout(scrollToBottom); // MODIFIED: Added setTimeout for new messages
}
});
}
async function loadGroupMessages() {
if (!selectedGroupId) return;
const currentUser = firebase.auth().currentUser;
if (!currentUser) return;
const currentUserId = sanitizeEmail(currentUser.email);
const messagesRef = firebase.database().ref(`groupMessages/${selectedGroupId}`);
const unreadRef = firebase.database().ref(`unreadGroupMessages/${currentUserId}/${selectedGroupId}`);
messagesRef.off();
const messagesDiv = document.getElementById('messages');
messagesDiv.innerHTML = '';
lastDisplayedDate = null;
unreadSeparatorAdded = false;
// FIX #1: Get unread count and immediately reset it to 0 for the group.
const unreadSnapshot = await unreadRef.once('value');
const unreadCount = unreadSnapshot.val() || 0;
if (unreadCount > 0) {
unreadRef.set(0);
}
// Fetch all messages
const messagesSnapshot = await messagesRef.orderByChild('timestamp').once('value');
let unreadSeparator = null;
if (messagesSnapshot.exists()) {
const allMessages = [];
messagesSnapshot.forEach(child => {
allMessages.push(child.val());
});
const totalMessages = allMessages.length;
// Display messages and separator
allMessages.forEach((message, index) => {
if (unreadCount > 0 && !unreadSeparatorAdded && index === totalMessages - unreadCount) {
unreadSeparator = document.createElement('div');
unreadSeparator.classList.add('unread-separator');
unreadSeparator.innerHTML = 'Unread';
messagesDiv.appendChild(unreadSeparator);
unreadSeparatorAdded = true;
}
if (message.groupId === selectedGroupId) {
const isSender = message.sender === currentUser.email;
displayGroupMessage(message, isSender, message.timestamp);
}
});
}
// Scroll to position
if (unreadSeparator) {
unreadSeparator.scrollIntoView({ behavior: 'auto', block: 'center' });
} else {
setTimeout(scrollToBottom); // MODIFIED: Added setTimeout for initial load
}
// Listen for new messages
messagesRef.orderByChild('timestamp').startAt(Date.now()).on('child_added', snapshot => {
const message = snapshot.val();
if (message.groupId === selectedGroupId) {
// FIX #2: When a new message arrives in the active group, reset the count.
if (message.sender !== currentUser.email) {
unreadRef.set(0);
}
if (document.querySelector('.unread-separator')) {
document.querySelector('.unread-separator').remove();
}
const isSender = message.sender === currentUser.email;
displayGroupMessage(message, isSender, message.timestamp);
updateSidebarGroupMessage(selectedGroupId, message.text || (message.image ? '📷 Image' : '🎥 Video'), message.timestamp);
setTimeout(scrollToBottom); // MODIFIED: Added setTimeout for new messages
}
});
}
function scrollToBottom() {
const messagesDiv = document.getElementById('messages');
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
function sanitizeEmail(email) {
return email.replace(/\./g, '_');
}
function sendMessage() {
const messageInput = document.getElementById('message');
let messageText = messageInput.value.trim();
if (!messageText && !selectedGroupId && !selectedUser) return;
const currentUser = firebase.auth().currentUser;
if (!currentUser) return alert("You must be signed in to send messages.");
const currentUserId = sanitizeEmail(currentUser.email);
// Update user's online status and timestamp when sending a message
firebase.database().ref(`status/${currentUserId}`).set('online');
firebase.database().ref(`statusLastUpdated/${currentUserId}`).set(Date.now());
if (selectedUser) {
// Direct message
const sanitizedUser = sanitizeEmail(selectedUser);
const messageData = {
sender: currentUser.email,
receiver: selectedUser,
text: messageText.replace(/\n/g, '
'), // Replace newlines with
tags
timestamp: Date.now()
};
const senderRef = firebase.database().ref(`messages/${currentUserId}/${sanitizedUser}`).push();
const receiverRef = firebase.database().ref(`messages/${sanitizedUser}/${currentUserId}`).push();
senderRef.set(messageData);
receiverRef.set(messageData);
if (selectedUser !== currentUser.email) {
const unreadRef = firebase.database().ref(`unreadMessages/${sanitizedUser}/${currentUserId}`);
unreadRef.transaction((currentCount) => (currentCount || 0) + 1);
}
// Update sidebar with first line only, no
tags
updateSidebarMessage(selectedUser, messageText.split('\n')[0], Date.now());
} else if (selectedGroupId) {
// Group message
const messageData = {
sender: currentUser.email,
text: messageText.replace(/\n/g, '
'), // Replace newlines with
tags
timestamp: Date.now(),
groupId: selectedGroupId
};
// Save message to group
firebase.database().ref(`groupMessages/${selectedGroupId}`).push(messageData);
// Update last message timestamp for group
firebase.database().ref(`groups/${selectedGroupId}/lastMessageTimestamp`).set(Date.now());
// Update unread count for all group members except sender
firebase.database().ref(`groups/${selectedGroupId}/members`).once('value').then(snapshot => {
snapshot.forEach(memberSnapshot => {
const memberEmail = memberSnapshot.val();
if (memberEmail !== currentUser.email) {
const sanitizedMember = sanitizeEmail(memberEmail);
firebase.database().ref(`unreadGroupMessages/${sanitizedMember}/${selectedGroupId}`).transaction((currentCount) => (currentCount || 0) + 1);
}
});
});
// Update sidebar with first line only, no
tags
updateSidebarGroupMessage(selectedGroupId, messageText.split('\n')[0], Date.now());
}
messageInput.value = "";
// Reset textarea height after sending
messageInput.style.height = 'auto';
messageInput.style.overflowY = 'hidden';
updateTypingStatus(false);
}
function sendGif(gifUrl) {
if (!selectedUser && !selectedGroupId) return;
const currentUser = firebase.auth().currentUser;
if (!currentUser) return alert("You must be signed in to send messages.");
const currentUserId = sanitizeEmail(currentUser.email);
const messageData = {
sender: currentUser.email,
timestamp: Date.now(),
gif: gifUrl
};
if (selectedUser) {
messageData.receiver = selectedUser;
const sanitizedUser = sanitizeEmail(selectedUser);
const senderRef = firebase.database().ref(`messages/${currentUserId}/${sanitizedUser}`).push();
const receiverRef = firebase.database().ref(`messages/${sanitizedUser}/${currentUserId}`).push();
senderRef.set(messageData);
receiverRef.set(messageData);
if (selectedUser !== currentUser.email) {
const unreadRef = firebase.database().ref(`unreadMessages/${sanitizedUser}/${currentUserId}`);
unreadRef.transaction((currentCount) => (currentCount || 0) + 1);
}
updateSidebarMessage(selectedUser, '🎁 GIF', Date.now());
setTimeout(scrollToBottom); // MODIFIED: Added setTimeout
} else if (selectedGroupId) {
messageData.groupId = selectedGroupId;
firebase.database().ref(`groupMessages/${selectedGroupId}`).push(messageData);
firebase.database().ref(`groups/${selectedGroupId}/lastMessageTimestamp`).set(Date.now());
firebase.database().ref(`groups/${selectedGroupId}/members`).once('value').then(snapshot => {
snapshot.forEach(memberSnapshot => {
const memberEmail = memberSnapshot.val();
if (memberEmail !== currentUser.email) {
const sanitizedMember = sanitizeEmail(memberEmail);
firebase.database().ref(`unreadGroupMessages/${sanitizedMember}/${selectedGroupId}`).transaction((currentCount) => (currentCount || 0) + 1);
}
});
});
updateSidebarGroupMessage(selectedGroupId, '🎁 GIF', Date.now());
setTimeout(scrollToBottom); // MODIFIED: Added setTimeout
}
}
// REMOVED: Old keypress listener
// document.getElementById('message').addEventListener('keypress', ...);
function formatTimestamp(timestamp) {
const date = new Date(timestamp);
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
function formatDate(timestamp) {
const date = new Date(timestamp);
return date.toLocaleDateString([], { year: 'numeric', month: 'long', day: 'numeric' });
}
function displayDaySeparator(date) {
const messagesDiv = document.getElementById('messages');
const separator = document.createElement('div');
separator.classList.add('day-separator');
separator.textContent = date;
messagesDiv.appendChild(separator);
}
let lastDisplayedDate = null;
function isSingleEmoji(text) {
if (!text) return false;
return /^[\p{Extended_Pictographic}]$/u.test(text.trim());
}
function displayMessage(message, isSender, timestamp) {
const messagesDiv = document.getElementById('messages');
const messageDate = formatDate(timestamp);
if (messageDate !== lastDisplayedDate) {
displayDaySeparator(messageDate);
lastDisplayedDate = messageDate;
}
// Create message container
const messageContainer = document.createElement('div');
messageContainer.style.display = 'flex';
messageContainer.style.gap = '8px';
messageContainer.style.alignItems = 'flex-start';
messageContainer.style.marginBottom = '16px';
messageContainer.style.justifyContent = isSender ? 'flex-end' : 'flex-start';
if (!isSender) {
const senderPhoto = document.createElement('img');
Object.assign(senderPhoto.style, {
width: '32px',
height: '32px',
borderRadius: '50%',
objectFit: 'cover',
marginTop: '4px'
});
firebase.database().ref(`profile_photos/${sanitizeEmail(message.sender)}`).once('value').then(snapshot => {
senderPhoto.src = snapshot.exists() ? snapshot.val() : 'profile.png';
});
messageContainer.appendChild(senderPhoto);
}
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', isSender ? 'sender' : 'receiver');
if (message.text && isSingleEmoji(message.text)) {
messageDiv.classList.add('emoji-only');
messageDiv.textContent = message.text;
const timestampDiv = document.createElement('div');
timestampDiv.classList.add('message-timestamp');
const date = new Date(message.timestamp);
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
timestampDiv.textContent = `${hours}:${minutes}`;
messageDiv.appendChild(timestampDiv);
messageContainer.appendChild(messageDiv);
messagesDiv.appendChild(messageContainer);
return;
}
if (message.image) {
const img = document.createElement('img');
img.src = `data:image/jpeg;base64,${message.image}`;
img.classList.add('message-image');
img.onclick = () => showImagePreview(img.src);
messageDiv.appendChild(img);
if (message.fileType === 'video') {
const fileType = document.createElement('div');
fileType.classList.add('file-type-indicator');
fileType.textContent = 'Video thumbnail';
messageDiv.appendChild(fileType);
}
} else if (message.video) {
// Handle video message (existing code)
const videoContainer = document.createElement('div');
videoContainer.classList.add('message-video');
videoContainer.onclick = () => showVideoPreview(`data:video/mp4;base64,${message.video}`);
const thumbnail = document.createElement('div');
Object.assign(thumbnail.style, {
width: '100%',
height: '200px',
backgroundColor: '#333',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: 'white'
});
thumbnail.textContent = 'Video Message';
videoContainer.appendChild(thumbnail);
const playIcon = document.createElement('i');
playIcon.classList.add('fas', 'fa-play', 'video-play-icon');
videoContainer.appendChild(playIcon);
messageDiv.appendChild(videoContainer);
const fileType = document.createElement('div');
fileType.classList.add('file-type-indicator');
fileType.textContent = message.fileName || 'Click to play video';
messageDiv.appendChild(fileType);
} else if (message.gif) {
const gifContainer = document.createElement('div');
gifContainer.classList.add('message-content-gif');
const gifImg = document.createElement('img');
gifImg.src = message.gif;
gifContainer.appendChild(gifImg);
messageDiv.appendChild(gifContainer);
} else {
const textContainer = document.createElement('div');
// Replace
tags with actual line breaks
const messageText = message.text.replace(/
/g, '\n');
// Check for URLs in the message (existing code)
const urlRegex = /(https?:\/\/[^\s]+)/g;
const urls = messageText.match(urlRegex);
let textWithoutUrls = messageText;
// Inside displayMessage function
if (urls) {
const previewContainer = document.createElement('div');
previewContainer.classList.add('link-preview-container');
urls.forEach(url => {
textWithoutUrls = textWithoutUrls.replace(url, '');
const previewWrapper = document.createElement('a');
previewWrapper.href = url;
previewWrapper.target = '_blank';
previewWrapper.classList.add('link-preview-wrapper');
previewWrapper.style.textDecoration = 'none';
const preview = document.createElement('div');
preview.classList.add('link-preview');
preview.innerHTML = `
No friends available to add to this group.
'; } document.getElementById('add-members-modal').style.display = 'flex'; }); }); } function showEditGroupModal() { if (!selectedGroupId) return; firebase.database().ref(`groups/${selectedGroupId}`).once('value').then(snapshot => { const groupData = snapshot.val(); document.getElementById('edit-group-name').value = groupData.name; const groupAvatar = document.getElementById('edit-group-avatar'); if (groupData.avatar) { groupAvatar.style.backgroundImage = `url(${groupData.avatar})`; groupAvatar.style.backgroundSize = 'cover'; groupAvatar.style.backgroundPosition = 'center'; groupAvatar.innerHTML = ''; currentGroupAvatar = groupData.avatar; } else { const initials = groupData.name.split(' ').map(word => word[0]).join('').substring(0, 2); groupAvatar.textContent = initials.toUpperCase(); groupAvatar.style.backgroundImage = 'none'; currentGroupAvatar = null; } document.getElementById('edit-group-avatar-input').addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; if (!file.type.match('image.*')) { alert('Please select an image file'); return; } if (file.size > 5 * 1024 * 1024) { alert('File size should be less than 5MB'); return; } const reader = new FileReader(); reader.onload = function(event) { const groupAvatar = document.getElementById('edit-group-avatar'); groupAvatar.style.backgroundImage = `url(${event.target.result})`; groupAvatar.style.backgroundSize = 'cover'; groupAvatar.style.backgroundPosition = 'center'; groupAvatar.innerHTML = ''; currentGroupAvatar = event.target.result; }; reader.readAsDataURL(file); }); document.getElementById('edit-group-modal').style.display = 'flex'; }); } function updateGroupInfo() { if (!selectedGroupId) return; const groupName = document.getElementById('edit-group-name').value.trim(); if (!groupName) { alert('Please enter a group name'); return; } const updates = { name: groupName }; if (currentGroupAvatar) { updates.avatar = currentGroupAvatar; } firebase.database().ref(`groups/${selectedGroupId}`).update(updates) .then(() => { alert('Group updated successfully!'); closeModal('edit-group-modal'); if (selectedGroupId) { const chatHeader = document.getElementById('chat-header'); const nameElement = chatHeader.querySelector('h2'); if (nameElement) { nameElement.textContent = groupName; } const groupAvatar = chatHeader.querySelector('div'); if (groupAvatar && currentGroupAvatar) { groupAvatar.style.backgroundImage = `url(${currentGroupAvatar})`; groupAvatar.textContent = ''; } } loadFriends(); }) .catch(error => { console.error('Error updating group:', error); alert('Failed to update group'); }); } function leaveGroup() { const currentUser = firebase.auth().currentUser; if (!currentUser || !selectedGroupId) return; if (!confirm('Are you sure you want to leave this group?')) { return; } const sanitizedUser = sanitizeEmail(currentUser.email); firebase.database().ref(`groups/${selectedGroupId}/members/${sanitizedUser}`).remove() .then(() => { return firebase.database().ref(`groupMembers/${sanitizedUser}/${selectedGroupId}`).remove(); }) .then(() => { alert('You have left the group'); closeModal('group-info-modal'); if (selectedGroupId) { document.getElementById('chat').style.display = 'none'; selectedGroupId = null; } loadFriends(); }) .catch(error => { console.error('Error leaving group:', error); alert('Failed to leave group'); }); } function closeModal(modalId) { document.getElementById(modalId).style.display = 'none'; } // Heartbeat function to keep status fresh function startHeartbeat() { const user = firebase.auth().currentUser; if (!user) return; const userId = sanitizeEmail(user.email); const userStatusRef = firebase.database().ref(`status/${userId}`); const userStatusTimestampRef = firebase.database().ref(`statusLastUpdated/${userId}`); firebase.database().ref(".info/connected").on("value", function(snapshot) { if (snapshot.val() === true) { userStatusRef.onDisconnect().set('offline'); userStatusTimestampRef.onDisconnect().remove(); } }); heartbeatInterval = setInterval(() => { userStatusRef.set('online'); userStatusTimestampRef.set(Date.now()); }, 20000); window.addEventListener('beforeunload', function () { updateTypingStatus(false); userStatusRef.set('offline'); userStatusTimestampRef.remove(); }); } firebase.auth().onAuthStateChanged(user => { if (user) { const sanitizedEmail = user.email.replace(/[.#$\[\]]/g, '_'); firebase.database().ref('userEmails/' + sanitizedEmail).set(user.uid); console.log("User logged in:", user.email); initUserProfile(user); loadFriends(); setupIncomingCallListener(); loadDarkModePreference(); const userStatusRef = firebase.database().ref(`status/${sanitizeEmail(user.email)}`); const userStatusTimestampRef = firebase.database().ref(`statusLastUpdated/${sanitizeEmail(user.email)}`); userStatusRef.set('online'); userStatusTimestampRef.set(Date.now()); userStatusRef.onDisconnect().set('offline'); userStatusTimestampRef.onDisconnect().remove(); startHeartbeat(); } else { if (heartbeatInterval) { clearInterval(heartbeatInterval); } console.log("No user logged in. Redirecting to login..."); window.location.href = "https://www.computeromaster.rf.gd/Login"; } }); firebase.auth().onAuthStateChanged(user => { if (!user) { if (window.currentTypingStatusRef) { window.currentTypingStatusRef.off(); } } }); window.addEventListener('beforeunload', function() { if (window.currentTypingStatusRef) { window.currentTypingStatusRef.off(); } updateTypingStatus(false); }); function toggleDarkMode() { const body = document.body; const isDarkMode = body.classList.toggle('dark-mode'); const darkModeText = document.getElementById('dark-mode-text'); darkModeText.textContent = isDarkMode ? 'Light Mode' : 'Dark Mode'; saveDarkModePreference(isDarkMode); } function saveDarkModePreference(isDarkMode) { const user = firebase.auth().currentUser; if (!user) return; const userId = sanitizeEmail(user.email); firebase.database().ref(`user_preferences/${userId}/darkMode`).set(isDarkMode) .catch(error => { console.error('Error saving dark mode preference:', error); }); } function loadDarkModePreference() { const user = firebase.auth().currentUser; if (!user) return; const userId = sanitizeEmail(user.email); firebase.database().ref(`user_preferences/${userId}/darkMode`).once('value') .then(snapshot => { const isDarkMode = snapshot.val(); if (isDarkMode) { document.body.classList.add('dark-mode'); if(document.getElementById('dark-mode-text')) { document.getElementById('dark-mode-text').textContent = 'Light Mode'; } } }) .catch(error => { console.error('Error loading dark mode preference:', error); }); } function setupSearch() { const searchInput = document.getElementById('chat-search'); searchInput.addEventListener('input', function() { const searchTerm = this.value.toLowerCase(); const chatButtons = document.querySelectorAll('.friend-button'); chatButtons.forEach(button => { const name = button.querySelector('.friend-name').textContent.toLowerCase(); const lastMessage = button.querySelector('.last-message').textContent.toLowerCase(); if (name.includes(searchTerm) || lastMessage.includes(searchTerm)) { button.style.display = 'flex'; } else { button.style.display = 'none'; } }); }); } document.addEventListener('DOMContentLoaded', function() { setupCallButtons(); setupSearch(); // --- Picker Logic --- const gifPickerBtn = document.getElementById('gif-picker-btn'); const gifPicker = document.getElementById('gif-picker'); const gifSearchInput = document.getElementById('gif-search'); const gifResults = document.getElementById('gif-results'); const emojiPickerBtn = document.getElementById('emoji-picker-btn'); const emojiPickerContainer = document.getElementById('emoji-picker-container'); const emojiPicker = document.querySelector('emoji-picker'); const messageTextarea = document.getElementById('message'); // --- Event Listeners --- gifPickerBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleEmojiPicker(false); // Close emoji picker if open toggleGifPicker(); }); emojiPickerBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleGifPicker(false); // Close gif picker if open toggleEmojiPicker(); }); emojiPicker.addEventListener('emoji-click', event => { messageTextarea.value += event.detail.unicode; messageTextarea.focus(); autoResizeTextarea(); // Resize after adding emoji }); gifSearchInput.addEventListener('input', (e) => { const query = gifSearchInput.value.trim(); if (query.length > 1) { searchGifs(query); } }); document.addEventListener('click', (e) => { if (gifPickerVisible && !gifPicker.contains(e.target) && e.target !== gifPickerBtn) { toggleGifPicker(false); } if (emojiPickerVisible && !emojiPickerContainer.contains(e.target) && e.target !== emojiPickerBtn) { toggleEmojiPicker(false); } }); // --- Textarea Auto-Resize and Enter Key Logic --- const autoResizeTextarea = () => { messageTextarea.style.height = 'auto'; // Temporarily shrink to get correct scrollHeight const computedStyle = getComputedStyle(messageTextarea); const maxHeight = parseInt(computedStyle.maxHeight, 10); const scrollHeight = messageTextarea.scrollHeight; if (scrollHeight > maxHeight) { messageTextarea.style.height = `${maxHeight}px`; messageTextarea.style.overflowY = 'auto'; // Show scrollbar } else { messageTextarea.style.height = `${scrollHeight}px`; messageTextarea.style.overflowY = 'hidden'; // Hide scrollbar } }; messageTextarea.addEventListener('input', autoResizeTextarea); messageTextarea.addEventListener('keydown', function(e) { // If Enter is pressed WITHOUT Shift key, send message if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); // Prevent new line in textarea sendMessage(); } // If Shift+Enter is pressed, the default action (new line) is allowed. }); // --- Toggle Functions --- function toggleGifPicker(forceShow) { gifPickerVisible = forceShow !== undefined ? forceShow : !gifPickerVisible; if (gifPickerVisible) { gifPicker.style.display = 'flex'; if (gifResults.innerHTML.trim() === '') { fetchTrendingGifs(); } } else { gifPicker.style.display = 'none'; } } function toggleEmojiPicker(forceShow) { emojiPickerVisible = forceShow !== undefined ? forceShow : !emojiPickerVisible; emojiPickerContainer.style.display = emojiPickerVisible ? 'block' : 'none'; if (emojiPickerVisible) { // Focus the search bar inside the emoji picker's shadow DOM setTimeout(() => emojiPicker.shadowRoot.querySelector('input[type="search"]').focus(), 100); } } // --- GIF Fetching Functions --- async function fetchTrendingGifs() { gifResults.innerHTML = 'Loading GIFs...
'; try { const response = await fetch(`https://api.giphy.com/v1/gifs/search?api_key=${GIPHY_API_KEY}&q=computer&limit=30&rating=g`); const data = await response.json(); displayGifs(data.data); } catch (error) { console.error('Error fetching trending GIFs:', error); gifResults.innerHTML = 'Could not load GIFs.
'; } } async function searchGifs(query) { gifResults.innerHTML = `Searching for "${query}"...
`; try { const response = await fetch(`https://api.giphy.com/v1/gifs/search?api_key=${GIPHY_API_KEY}&q=${encodeURIComponent(query)}&limit=30&rating=g`); const data = await response.json(); displayGifs(data.data); } catch (error) { console.error('Error searching GIFs:', error); gifResults.innerHTML = 'Could not load GIFs.
'; } } function displayGifs(gifs) { gifResults.innerHTML = ''; if (gifs.length === 0) { gifResults.innerHTML = 'No results found.
'; return; } gifs.forEach(gif => { const img = document.createElement('img'); img.src = gif.images.fixed_height_small.url; img.alt = gif.title; img.addEventListener('click', () => { sendGif(gif.images.fixed_height.url); toggleGifPicker(false); }); gifResults.appendChild(img); }); } // --- NEW: Drag and Drop Functionality --- const chatArea = document.getElementById('chat'); // Create the overlay div dynamically const overlay = document.createElement('div'); overlay.id = 'drag-drop-overlay'; Object.assign(overlay.style, { position: 'absolute', top: '0', left: '0', width: '100%', height: '100%', backgroundColor: 'rgba(0, 123, 255, 0.7)', color: 'white', display: 'none', // Initially hidden justifyContent: 'center', alignItems: 'center', fontSize: '24px', fontWeight: 'bold', zIndex: '1000', pointerEvents: 'none', // Allows events to pass through to elements underneath border: '2px dashed white', borderRadius: '8px' }); overlay.textContent = 'Drop File to Upload'; // Ensure the chat area can contain the absolutely positioned overlay chatArea.style.position = 'relative'; chatArea.appendChild(overlay); chatArea.addEventListener('dragenter', (e) => { e.preventDefault(); e.stopPropagation(); // Only show the overlay if a chat is selected if (selectedUser || selectedGroupId) { overlay.style.display = 'flex'; } }); chatArea.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); }); chatArea.addEventListener('dragleave', (e) => { e.preventDefault(); e.stopPropagation(); // This check prevents the overlay from flickering when dragging over child elements if (e.relatedTarget === null || !chatArea.contains(e.relatedTarget)) { overlay.style.display = 'none'; } }); chatArea.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); overlay.style.display = 'none'; const files = e.dataTransfer.files; if (files.length > 0) { // Use the centralized handler to process the dropped file handleFileUpload(files[0]); } }); }); document.querySelectorAll('.settings-tab').forEach(tab => { tab.addEventListener('click', function() { document.querySelectorAll('.settings-tab').forEach(t => t.classList.remove('active')); this.classList.add('active'); document.querySelectorAll('.settings-content').forEach(content => { content.style.display = 'none'; }); const tabName = this.getAttribute('data-tab'); document.getElementById(`${tabName}-tab-content`).style.display = 'block'; }); }); function loadCurrentUsername() { const user = firebase.auth().currentUser; if (!user) return; const userId = sanitizeEmail(user.email); firebase.database().ref(`usernames/${userId}`).once('value').then(snapshot => { const username = snapshot.val(); document.getElementById('username-input').value = username || ''; }); } function updateUsername() { const user = firebase.auth().currentUser; if (!user) return; const newUsername = document.getElementById('username-input').value.trim(); if (!newUsername) return; const userId = sanitizeEmail(user.email); firebase.database().ref(`usernames/${userId}`).set(newUsername) .then(() => { document.getElementById('user-profile-name').textContent = newUsername; updateNameInAllFriendLists(user.email, newUsername); updateChatHeaderName(user.email, newUsername); updateMessageSenderNames(user.email, newUsername); alert('Username updated successfully!'); }) .catch(error => { console.error('Error updating username:', error); alert('Failed to update username'); }); } function updateAllUsernameReferences(newUsername) { document.getElementById('user-profile-name').textContent = newUsername; document.querySelectorAll('.friend-name').forEach(element => { if (element.textContent === formatName(firebase.auth().currentUser.email)) { element.textContent = newUsername; } }); const chatHeader = document.getElementById('chat-header'); if (chatHeader && chatHeader.querySelector('h2').textContent === formatName(firebase.auth().currentUser.email)) { chatHeader.querySelector('h2').textContent = newUsername; } document.querySelectorAll('.sender-name').forEach(element => { if (element.textContent === formatName(firebase.auth().currentUser.email)) { element.textContent = newUsername; } }); } function loadFriendsListForSettings() { const user = firebase.auth().currentUser; if (!user) return; const friendsList = document.querySelector('.friends-list'); if (!friendsList) return; friendsList.innerHTML = ''; firebase.database().ref(`friends/${user.uid}`).once('value').then(snapshot => { snapshot.forEach(childSnapshot => { const friendEmail = childSnapshot.val(); const sanitizedEmail = sanitizeEmail(friendEmail); const friendItem = document.createElement('div'); friendItem.classList.add('friend-item'); const friendInfo = document.createElement('div'); friendInfo.classList.add('friend-info'); const friendAvatar = document.createElement('img'); friendAvatar.classList.add('friend-avatar'); friendAvatar.src = 'profile.png'; firebase.database().ref(`profile_photos/${sanitizedEmail}`).once('value').then(photoSnapshot => { if (photoSnapshot.exists()) { friendAvatar.src = photoSnapshot.val(); } }); const friendName = document.createElement('div'); friendName.classList.add('friend-name'); formatName(friendEmail).then(name => { friendName.textContent = name; }); const unfriendBtn = document.createElement('button'); unfriendBtn.classList.add('unfriend-btn'); unfriendBtn.textContent = 'Unfriend'; unfriendBtn.onclick = () => removeFriend(friendEmail, childSnapshot.key); friendInfo.appendChild(friendAvatar); friendInfo.appendChild(friendName); friendItem.appendChild(friendInfo); friendItem.appendChild(unfriendBtn); friendsList.appendChild(friendItem); }); }); } function removeFriend(friendEmail) { const currentUser = firebase.auth().currentUser; if (!currentUser) { alert("You must be signed in."); return; } const confirmUnfriend = confirm(`Are you sure you want to remove ${friendEmail} from your friends list?`); if (!confirmUnfriend) return; const currentUserId = currentUser.uid; const currentUserEmail = currentUser.email; const sanitizedCurrentEmail = currentUserEmail.replace(/[.#$[\]]/g, '_'); const sanitizedFriendEmail = friendEmail.replace(/[.#$[\]]/g, '_'); const db = firebase.database(); db.ref(`friends/${currentUserId}`).once('value').then(snapshot => { snapshot.forEach(child => { if (child.val() === friendEmail) { db.ref(`friends/${currentUserId}/${child.key}`).remove(); } }); }); db.ref(`userEmails/${sanitizedFriendEmail}`).once('value').then(friendSnap => { const friendUid = friendSnap.val(); if (!friendUid) return; const removeFromFriendList = db.ref(`friends/${friendUid}`).once('value').then(snapshot => { snapshot.forEach(child => { if (child.val() === currentUserEmail) { db.ref(`friends/${friendUid}/${child.key}`).remove(); } }); }); const path1 = `${sanitizedCurrentEmail}/${sanitizedFriendEmail}`; const path2 = `${sanitizedFriendEmail}/${sanitizedCurrentEmail}`; const deleteMessages1 = db.ref(`messages/${path1}`).remove(); const deleteMessages2 = db.ref(`messages/${path2}`).remove(); const deleteUnread1 = db.ref(`unreadMessages/${path1}`).remove(); const deleteUnread2 = db.ref(`unreadMessages/${path2}`).remove(); Promise.all([ removeFromFriendList, deleteMessages1, deleteMessages2, deleteUnread1, deleteUnread2 ]).then(() => { const friendElement = document.querySelector(`.friend-button[data-email="${friendEmail}"]`); if (friendElement) friendElement.remove(); if (typeof loadFriends === 'function') { loadFriends(); } alert(`${friendEmail} removed successfully.`); }); }); } function getUserIdByEmail(email) { return new Promise((resolve, reject) => { firebase.database().ref('users').orderByChild('email').equalTo(email).once('value') .then(snapshot => { if (snapshot.exists()) { resolve(Object.keys(snapshot.val())[0]); } else { resolve(null); } }) .catch(error => { reject(error); }); }); } function formatName(email) { if (!email) return Promise.resolve(''); const sanitizedEmail = sanitizeEmail(email); return new Promise((resolve) => { firebase.database().ref(`usernames/${sanitizedEmail}`).once('value').then(snapshot => { resolve(snapshot.exists() ? snapshot.val() : email.split('@')[0].replace(/\./g, ' ')); }).catch(() => { resolve(email.split('@')[0].replace(/\./g, ' ')); }); }); } document.getElementById('avatar-upload').addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; if (!file.type.match('image.*')) { alert('Please select an image file'); return; } if (file.size > 5 * 1024 * 1024) { alert('File size should be less than 5MB'); return; } const user = firebase.auth().currentUser; if (!user) return; compressImage(file, { quality: 0.7, maxWidth: 400, maxHeight: 400, success: (compressedFile) => { uploadProfilePhotoToDatabase(compressedFile, user); }, error: (err) => { console.error('Error compressing image:', err); alert('Error processing image'); } }); }); document.getElementById('dark-mode-toggle').addEventListener('change', function() { document.body.classList.toggle('dark-mode', this.checked); saveDarkModePreference(this.checked); }); function getUserIdByEmail(email) { return new Promise((resolve, reject) => { firebase.database().ref('users').orderByChild('email').equalTo(email).once('value') .then(snapshot => { if (snapshot.exists()) { resolve(Object.keys(snapshot.val())[0]); } else { resolve(null); } }) .catch(error => { reject(error); }); }); } function toggleSettings() { const container = document.getElementById('user-profile-container'); const settingsIcon = document.getElementById('settings-icon'); container.classList.toggle('extended'); if (container.classList.contains('extended')) { settingsIcon.style.display = 'none'; loadCurrentUsername(); loadFriendsListForSettings(); } else { settingsIcon.style.display = 'flex'; } } document.addEventListener('click', function(e) { const container = document.getElementById('user-profile-container'); const settingsIcon = document.getElementById('settings-icon'); if (!container.contains(e.target) && e.target !== settingsIcon && !settingsIcon.contains(e.target)) { if(container.classList.contains('extended')) { container.classList.remove('extended'); settingsIcon.style.display = 'flex'; } } }); function updateNameInAllFriendLists(userEmail, newName) { const friendButtons = document.querySelectorAll('.friend-button'); friendButtons.forEach(button => { if (button.getAttribute('data-email') === userEmail) { const nameElement = button.querySelector('.friend-name'); if (nameElement) { nameElement.textContent = newName; } } }); } function updateChatHeaderName(userEmail, newName) { const chatHeader = document.getElementById('chat-header'); if (chatHeader && selectedUser === userEmail) { const nameElement = chatHeader.querySelector('h2'); if (nameElement) { nameElement.textContent = newName; } } } function updateMessageSenderNames(userEmail, newName) { const messages = document.querySelectorAll('.message-sender-name'); messages.forEach(senderName => { formatName(userEmail).then(formattedName => { if (senderName.textContent === formattedName) { senderName.textContent = newName; } }); }); } function updateSidebarMessage(userEmail, messageText, timestamp) { const button = document.querySelector(`button[data-email="${userEmail}"]`); if (button) { const lastMessageSpan = button.querySelector('.last-message'); if (lastMessageSpan) { // Remove