diff --git a/src/templates/chat.html b/src/templates/chat.html index 556470f..f323017 100644 --- a/src/templates/chat.html +++ b/src/templates/chat.html @@ -148,15 +148,31 @@ if (diff > 150) { // Keyboard is likely open if (messagesWrapper) { - messagesWrapper.style.paddingBottom = '100px'; - // Scroll to bottom when keyboard opens + messagesWrapper.style.paddingBottom = '120px'; // More padding for mobile keyboard + // Auto-scroll when keyboard opens, with multiple attempts for reliability + userScrolled = false; // Reset scroll state when keyboard opens setTimeout(() => { - messagesWrapper.scrollTop = messagesWrapper.scrollHeight; - }, 100); + scrollToBottom(false); + }, 200); + // iOS often needs multiple scroll attempts + if (isIOS) { + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight; + }, 400); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight; + }, 600); + } } } else { if (messagesWrapper) { messagesWrapper.style.paddingBottom = '80px'; + // Scroll to bottom when keyboard closes too + setTimeout(() => { + if (!userScrolled) { + scrollToBottom(false); + } + }, 200); } } } @@ -168,31 +184,66 @@ window.addEventListener('resize', adjustForKeyboard); } - // Focus handling for textarea + // Mobile-optimized focus handling for textarea document.addEventListener('DOMContentLoaded', function() { const messageInput = document.getElementById('messageInput'); if (messageInput) { messageInput.addEventListener('focus', function() { - setTimeout(adjustForKeyboard, 300); + // Reset scroll state when focusing input + userScrolled = false; + // Multiple timeout attempts for different mobile browsers + setTimeout(adjustForKeyboard, 200); + setTimeout(adjustForKeyboard, 400); + if (isMobile) { + setTimeout(adjustForKeyboard, 600); + setTimeout(() => { + const messagesWrapper = document.getElementById('messagesWrapper'); + if (messagesWrapper) { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight; + } + }, 800); + } }); messageInput.addEventListener('blur', function() { - setTimeout(adjustForKeyboard, 300); + setTimeout(adjustForKeyboard, 200); + setTimeout(adjustForKeyboard, 400); + }); + + // Handle input events for better mobile experience + messageInput.addEventListener('input', function() { + if (isMobile && !userScrolled) { + setTimeout(() => { + scrollToBottom(false); + }, 100); + } }); } }); - // Auto-scroll functionality for new messages + // Mobile-optimized auto-scroll functionality + let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768; + let isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent); + function scrollToBottom(smooth = true) { const messagesWrapper = document.getElementById('messagesWrapper'); if (messagesWrapper) { - if (smooth) { - messagesWrapper.scrollTo({ - top: messagesWrapper.scrollHeight, - behavior: 'smooth' - }); - } else { + // On mobile, especially iOS, direct scrollTop assignment is more reliable + if (isMobile) { messagesWrapper.scrollTop = messagesWrapper.scrollHeight; + // Double-check scroll position after a short delay for mobile + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight; + }, 100); + } else { + if (smooth) { + messagesWrapper.scrollTo({ + top: messagesWrapper.scrollHeight, + behavior: 'smooth' + }); + } else { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight; + } } } } @@ -202,39 +253,80 @@ const messagesWrapper = document.getElementById('messagesWrapper'); if (!messagesWrapper) return true; - const threshold = 100; // pixels from bottom + // Larger threshold for mobile to account for touch scrolling momentum + const threshold = isMobile ? 150 : 100; return messagesWrapper.scrollHeight - messagesWrapper.scrollTop - messagesWrapper.clientHeight < threshold; } - // Store the last scroll position to detect if user manually scrolled + // Mobile scroll state management let lastScrollTop = 0; let userScrolled = false; + let scrollTimeout; + let touchStartY = 0; + let isScrolling = false; - // Detect user scroll + // Detect user scroll with mobile optimizations document.addEventListener('DOMContentLoaded', function() { const messagesWrapper = document.getElementById('messagesWrapper'); if (messagesWrapper) { + + // Handle touch events for better mobile detection + messagesWrapper.addEventListener('touchstart', function(e) { + touchStartY = e.touches[0].clientY; + isScrolling = false; + clearTimeout(scrollTimeout); + }, { passive: true }); + + messagesWrapper.addEventListener('touchmove', function(e) { + if (!isScrolling) { + isScrolling = true; + const touchY = e.touches[0].clientY; + const deltaY = touchStartY - touchY; + + // If user is swiping up (scrolling up), mark as user scrolled + if (deltaY < -10 && !isNearBottom()) { + userScrolled = true; + } + } + }, { passive: true }); + + messagesWrapper.addEventListener('touchend', function() { + // Check scroll position after touch ends with delay for momentum scrolling + setTimeout(() => { + if (isNearBottom()) { + userScrolled = false; + } + isScrolling = false; + }, 300); + }, { passive: true }); + + // Regular scroll event with debouncing for mobile messagesWrapper.addEventListener('scroll', function() { - const currentScrollTop = messagesWrapper.scrollTop; - const maxScroll = messagesWrapper.scrollHeight - messagesWrapper.clientHeight; - - // If user scrolled up manually, don't auto-scroll for new messages - if (currentScrollTop < lastScrollTop && currentScrollTop < maxScroll - 50) { - userScrolled = true; - } - - // If user scrolled to near bottom, resume auto-scrolling - if (isNearBottom()) { - userScrolled = false; - } - - lastScrollTop = currentScrollTop; - }); + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(() => { + const currentScrollTop = messagesWrapper.scrollTop; + const maxScroll = messagesWrapper.scrollHeight - messagesWrapper.clientHeight; + + // More lenient detection for mobile + const upScrollThreshold = isMobile ? 30 : 50; + + // If user scrolled up manually, don't auto-scroll for new messages + if (currentScrollTop < lastScrollTop && currentScrollTop < maxScroll - upScrollThreshold) { + userScrolled = true; + } + + // If user scrolled to near bottom, resume auto-scrolling + if (isNearBottom()) { + userScrolled = false; + } + + lastScrollTop = currentScrollTop; + }, isMobile ? 150 : 50); // Longer debounce for mobile + }, { passive: true }); } }); - // Override the message adding function to include auto-scroll - // You'll need to call this function whenever a new message is added + // Mobile-optimized message adding function function addMessageWithAutoScroll(messageElement) { const messagesContainer = document.getElementById('messagesContainer'); if (messagesContainer && messageElement) { @@ -242,23 +334,34 @@ // Only auto-scroll if user hasn't manually scrolled up if (!userScrolled) { - // Small delay to ensure DOM is updated + // Longer delay for mobile to ensure proper rendering + const delay = isMobile ? 200 : 50; setTimeout(() => { - scrollToBottom(true); - }, 50); + scrollToBottom(false); // Always instant scroll on mobile + }, delay); } } } - // Function to force scroll to bottom (useful for when joining a room) + // Force scroll with mobile optimizations function forceScrollToBottom() { userScrolled = false; + const delay = isMobile ? 300 : 100; setTimeout(() => { scrollToBottom(false); - }, 100); + // iOS double-check + if (isIOS) { + setTimeout(() => { + const messagesWrapper = document.getElementById('messagesWrapper'); + if (messagesWrapper) { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight; + } + }, 100); + } + }, delay); } - // Observer to watch for new messages being added to the DOM + // Mobile-optimized mutation observer document.addEventListener('DOMContentLoaded', function() { const messagesContainer = document.getElementById('messagesContainer'); if (messagesContainer) { @@ -273,9 +376,10 @@ ); if (hasNewMessage && !userScrolled) { + const delay = isMobile ? 250 : 50; setTimeout(() => { - scrollToBottom(true); - }, 50); + scrollToBottom(false); // Instant scroll for reliability + }, delay); } } });