408 lines
16 KiB
PHP
408 lines
16 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Chatterly</title>
|
|
<meta name="description" content="Built with Flatlogic Generator">
|
|
<meta name="keywords" content="chatterly, chat app, messaging, instant messaging, mobile chat, group chat, media sharing, real-time communication, Built with Flatlogic Generator">
|
|
<meta property="og:title" content="Chatterly">
|
|
<meta property="og:description" content="Built with Flatlogic Generator">
|
|
<meta property="og:image" content="">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:image" content="">
|
|
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
</head>
|
|
<body>
|
|
|
|
<div class="mobile-chat-container">
|
|
<header class="chat-header">
|
|
<div class="d-flex align-items-center">
|
|
<a href="#" class="text-white me-3"><i class="bi bi-arrow-left"></i></a>
|
|
<img src="https://i.pravatar.cc/40?u=jane" alt="Avatar" class="avatar">
|
|
<div class="ms-3">
|
|
<h1 class="h6 mb-0">Jane Doe</h1>
|
|
<p class="small text-white-50 mb-0">online</p>
|
|
</div>
|
|
</div>
|
|
<div class="actions">
|
|
<a href="#" class="text-white"><i class="bi bi-camera-video"></i></a>
|
|
<a href="#" class="text-white ms-3"><i class="bi bi-telephone"></i></a>
|
|
<a href="#" class="text-white ms-3" id="settings-button"><i class="bi bi-gear"></i></a>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="chat-body">
|
|
<div class="message-day-divider">
|
|
<span>Today</span>
|
|
</div>
|
|
|
|
<div class="message received">
|
|
<div class="message-bubble">
|
|
<p class="mb-0">Hey! How's it going?</p>
|
|
<span class="message-time">10:00 AM</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="message sent">
|
|
<div class="message-bubble">
|
|
<p class="mb-0">Pretty good! Just working on a new project. You?</p>
|
|
<span class="message-time">10:01 AM</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="message received">
|
|
<div class="message-bubble">
|
|
<p class="mb-0">Same here. This is a mockup of the chat interface.</p>
|
|
<span class="message-time">10:02 AM</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="message received">
|
|
<div class="message-bubble">
|
|
<p class="mb-0">What do you think?</p>
|
|
<span class="message-time">10:02 AM</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="message sent">
|
|
<div class="message-bubble">
|
|
<p class="mb-0">Looks great! The dark mode is really nice.</p>
|
|
<span class="message-time">10:03 AM</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="message sent">
|
|
<div class="message-bubble">
|
|
<p class="mb-0">And the primary color #0D6EFD for my messages looks cool.</p>
|
|
<span class="message-time">10:03 AM</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="message sent">
|
|
<div class="message-bubble voice-message">
|
|
<i class="bi bi-play-circle-fill"></i>
|
|
<div class="voice-progress"></div>
|
|
<span class="message-time">10:05 AM</span>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<div class="chat-footer">
|
|
<textarea id="message-input" class="form-control" placeholder="Type a message..."></textarea>
|
|
<button id="emoji-btn" class="btn btn-secondary"><i class="bi bi-emoji-smile"></i></button>
|
|
<button id="attachment-btn" class="btn btn-secondary"><i class="bi bi-paperclip"></i></button>
|
|
<input type="file" id="attachment-input" style="display: none;" accept="image/*,video/*">
|
|
<button id="mic-btn" class="btn btn-primary mic-button"><i class="bi bi-mic-fill"></i></button>
|
|
<button id="send-button" class="btn btn-primary send-button" style="display: none;"><i class="bi bi-send-fill"></i></button>
|
|
</div>
|
|
|
|
<div class="theme-panel" id="theme-panel">
|
|
<h5>Theme</h5>
|
|
<button class="btn btn-sm btn-light" id="light-theme-button">Light</button>
|
|
<button class="btn btn-sm btn-dark" id="dark-theme-button">Dark</button>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
// Theme switcher logic
|
|
const settingsButton = document.getElementById('settings-button');
|
|
const themePanel = document.getElementById('theme-panel');
|
|
const lightThemeButton = document.getElementById('light-theme-button');
|
|
const darkThemeButton = document.getElementById('dark-theme-button');
|
|
|
|
settingsButton.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
themePanel.classList.toggle('show');
|
|
});
|
|
|
|
function setTheme(theme) {
|
|
document.body.dataset.theme = theme;
|
|
localStorage.setItem('theme', theme);
|
|
themePanel.classList.remove('show');
|
|
}
|
|
|
|
lightThemeButton.addEventListener('click', () => setTheme('light'));
|
|
darkThemeButton.addEventListener('click', () => setTheme('dark'));
|
|
|
|
// Apply saved theme on load
|
|
const savedTheme = localStorage.getItem('theme') || 'dark';
|
|
document.body.dataset.theme = savedTheme;
|
|
|
|
// Hide theme panel if clicked outside
|
|
document.addEventListener('click', (e) => {
|
|
if (!themePanel.contains(e.target) && !settingsButton.contains(e.target)) {
|
|
themePanel.classList.remove('show');
|
|
}
|
|
});
|
|
|
|
|
|
// Existing scripts for message input and voice recording
|
|
const messageInput = document.getElementById('message-input');
|
|
const sendButton = document.getElementById('send-button');
|
|
const micButton = document.getElementById('mic-btn');
|
|
|
|
messageInput.addEventListener('input', () => {
|
|
if (messageInput.value.trim() !== '') {
|
|
sendButton.style.display = 'block';
|
|
micButton.style.display = 'none';
|
|
} else {
|
|
sendButton.style.display = 'none';
|
|
micButton.style.display = 'block';
|
|
}
|
|
});
|
|
|
|
const chatBody = document.querySelector('.chat-body');
|
|
let mediaRecorder;
|
|
let audioChunks = [];
|
|
let voiceMessages = []; // To store voice message blobs
|
|
|
|
micButton.addEventListener('mousedown', async () => {
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
alert('Your browser does not support audio recording.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
mediaRecorder = new MediaRecorder(stream);
|
|
mediaRecorder.start();
|
|
audioChunks = [];
|
|
|
|
mediaRecorder.addEventListener('dataavailable', event => {
|
|
audioChunks.push(event.data);
|
|
});
|
|
|
|
micButton.classList.add('recording');
|
|
|
|
} catch (err) {
|
|
console.error('Error accessing microphone:', err);
|
|
alert('Could not access your microphone. Please check your browser permissions.');
|
|
}
|
|
});
|
|
|
|
micButton.addEventListener('mouseup', () => {
|
|
if (mediaRecorder && mediaRecorder.state === 'recording') {
|
|
mediaRecorder.stop();
|
|
micButton.classList.remove('recording');
|
|
|
|
mediaRecorder.addEventListener('stop', () => {
|
|
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
|
const audioUrl = URL.createObjectURL(audioBlob);
|
|
const voiceMessageId = voiceMessages.push(audioBlob) - 1;
|
|
|
|
const messageTime = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
|
|
const voiceMessageHTML = `
|
|
<div class="message sent">
|
|
<div class="message-bubble voice-message" data-voice-id="${voiceMessageId}">
|
|
<audio src="${audioUrl}" style="display:none;"></audio>
|
|
<div class="voice-message-controls">
|
|
<button class="play-button btn"><i class="bi bi-play-circle-fill"></i></button>
|
|
<div class="voice-progress-container">
|
|
<div class="voice-progress"></div>
|
|
</div>
|
|
</div>
|
|
<button class="share-button btn"><i class="bi bi-share-fill"></i></button>
|
|
<span class="message-time">${messageTime}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
chatBody.insertAdjacentHTML('beforeend', voiceMessageHTML);
|
|
});
|
|
}
|
|
});
|
|
|
|
chatBody.addEventListener('click', async (e) => {
|
|
const playButton = e.target.closest('.play-button');
|
|
const shareButton = e.target.closest('.share-button');
|
|
|
|
if (playButton) {
|
|
const voiceMessage = playButton.closest('.voice-message');
|
|
const audio = voiceMessage.querySelector('audio');
|
|
const playIcon = playButton.querySelector('i');
|
|
|
|
if (audio.paused) {
|
|
audio.play();
|
|
playIcon.classList.remove('bi-play-circle-fill');
|
|
playIcon.classList.add('bi-pause-circle-fill');
|
|
} else {
|
|
audio.pause();
|
|
playIcon.classList.remove('bi-pause-circle-fill');
|
|
playIcon.classList.add('bi-play-circle-fill');
|
|
}
|
|
|
|
audio.addEventListener('ended', () => {
|
|
playIcon.classList.remove('bi-pause-circle-fill');
|
|
playIcon.classList.add('bi-play-circle-fill');
|
|
voiceMessage.querySelector('.voice-progress').style.width = `0%`;
|
|
});
|
|
|
|
audio.addEventListener('timeupdate', () => {
|
|
const progress = (audio.currentTime / audio.duration) * 100;
|
|
voiceMessage.querySelector('.voice-progress').style.width = `${progress}%`;
|
|
});
|
|
}
|
|
|
|
if (shareButton) {
|
|
const voiceMessage = shareButton.closest('.voice-message');
|
|
const voiceId = voiceMessage.dataset.voiceId;
|
|
const audioBlob = voiceMessages[voiceId];
|
|
|
|
if (!audioBlob) {
|
|
alert('Could not find the audio to share.');
|
|
return;
|
|
}
|
|
|
|
const audioFile = new File([audioBlob], `chatterly-voice-${new Date().toISOString()}.webm`, {
|
|
type: audioBlob.type,
|
|
});
|
|
|
|
if (navigator.canShare && navigator.canShare({ files: [audioFile] })) {
|
|
try {
|
|
await navigator.share({
|
|
files: [audioFile],
|
|
title: 'Voice Message',
|
|
text: 'Listen to my voice message from Chatterly.',
|
|
});
|
|
} catch (error) {
|
|
console.error('Error sharing:', error);
|
|
}
|
|
} else {
|
|
// Fallback for browsers that don't support sharing files
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(audioBlob);
|
|
link.download = `chatterly-voice-${new Date().toISOString()}.webm`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Emoji panel logic
|
|
const emojiBtn = document.getElementById('emoji-btn');
|
|
const emojiPanel = document.getElementById('emoji-panel');
|
|
const emojiGrid = document.getElementById('emojis');
|
|
const stickerGrid = document.getElementById('stickers');
|
|
const categoryBtns = document.querySelectorAll('.category-btn');
|
|
|
|
emojiBtn.addEventListener('click', () => {
|
|
emojiPanel.classList.toggle('show');
|
|
});
|
|
|
|
const emojis = ['π', 'π', 'π', 'π€', 'π', 'β€οΈ', 'π', 'π₯', 'π―', 'π', 'π', 'π’', 'π‘', 'π€―', 'π΄', 'π'];
|
|
emojis.forEach(emoji => {
|
|
const emojiEl = document.createElement('span');
|
|
emojiEl.textContent = emoji;
|
|
emojiGrid.appendChild(emojiEl);
|
|
});
|
|
|
|
const stickers = [
|
|
'https://picsum.photos/id/10/80/80',
|
|
'https://picsum.photos/id/20/80/80',
|
|
'https://picsum.photos/id/30/80/80',
|
|
'https://picsum.photos/id/40/80/80',
|
|
'https://picsum.photos/id/50/80/80',
|
|
'https://picsum.photos/id/60/80/80',
|
|
];
|
|
stickers.forEach(stickerUrl => {
|
|
const stickerEl = document.createElement('img');
|
|
stickerEl.src = stickerUrl;
|
|
stickerGrid.appendChild(stickerEl);
|
|
});
|
|
|
|
categoryBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
categoryBtns.forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
if (btn.dataset.category === 'emojis') {
|
|
emojiGrid.classList.remove('hidden');
|
|
stickerGrid.classList.add('hidden');
|
|
} else {
|
|
emojiGrid.classList.add('hidden');
|
|
stickerGrid.classList.remove('hidden');
|
|
}
|
|
});
|
|
});
|
|
|
|
function sendMessage(content) {
|
|
const messageTime = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
const message = `
|
|
<div class="message sent">
|
|
<div class="message-bubble">
|
|
${content}
|
|
<span class="message-time">${messageTime}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
chatBody.insertAdjacentHTML('beforeend', message);
|
|
chatBody.scrollTop = chatBody.scrollHeight;
|
|
emojiPanel.classList.remove('show');
|
|
}
|
|
|
|
emojiGrid.addEventListener('click', e => {
|
|
if (e.target.tagName === 'SPAN') {
|
|
sendMessage(`<p class="mb-0">${e.target.textContent}</p>`);
|
|
}
|
|
});
|
|
|
|
stickerGrid.addEventListener('click', e => {
|
|
if (e.target.tagName === 'IMG') {
|
|
sendMessage(`<img src="${e.target.src}" style="max-width: 100px; height: auto; display: block;">`);
|
|
}
|
|
});
|
|
|
|
</script>
|
|
<script>
|
|
const mediaBtn = document.querySelector('.media-btn');
|
|
const mediaInput = document.getElementById('media-input');
|
|
const chatMessages = document.querySelector('.chat-messages');
|
|
|
|
if (mediaBtn) {
|
|
mediaBtn.addEventListener('click', () => {
|
|
if (mediaInput) {
|
|
mediaInput.click();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (mediaInput) {
|
|
mediaInput.addEventListener('change', (event) => {
|
|
const file = event.target.files[0];
|
|
if (file && file.type.startsWith('image/')) {
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
const imageSrc = e.target.result;
|
|
createImageMessage(imageSrc);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
});
|
|
}
|
|
|
|
function createImageMessage(imageSrc) {
|
|
const messageId = 'msg-' + new Date().getTime();
|
|
const messageHTML = `
|
|
<div class="message-bubble sent" id="${messageId}">
|
|
<div class="message-content image-message">
|
|
<img src="${imageSrc}" alt="Image message">
|
|
</div>
|
|
<div class="message-timestamp">${new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</div>
|
|
</div>
|
|
`;
|
|
if (chatMessages) {
|
|
chatMessages.insertAdjacentHTML('beforeend', messageHTML);
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|