Complete 3.3.2: Touch Interaction Optimization

- Verified 44px minimum touch targets already in place
- Added swipe gesture support for mobile chat toggling
- Implemented pull-to-refresh functionality for mobile
- Added double-tap gesture for fullscreen toggle on video area
- All touch interactions are mobile-only to avoid desktop conflicts
This commit is contained in:
VinnyNC 2025-09-29 21:12:50 -04:00
parent 68b97e946b
commit a7fa21d3fa
2 changed files with 179 additions and 3 deletions

View file

@ -195,6 +195,163 @@
}
}
// Touch gesture handling for mobile interactions
let touchStartX = 0;
let touchStartY = 0;
let touchStartTime = 0;
let isSwipeGesture = false;
function handleTouchStart(event) {
touchStartX = event.touches[0].clientX;
touchStartY = event.touches[0].clientY;
touchStartTime = Date.now();
isSwipeGesture = false;
}
function handleTouchMove(event) {
if (!event.touches || event.touches.length === 0) return;
const touchCurrentX = event.touches[0].clientX;
const touchCurrentY = event.touches[0].clientY;
const deltaX = touchCurrentX - touchStartX;
const deltaY = touchCurrentY - touchStartY;
// Determine if this is a horizontal swipe (more horizontal than vertical movement)
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
isSwipeGesture = true;
}
}
function handleTouchEnd(event) {
if (!touchStartTime) return;
const touchEndTime = Date.now();
const touchDuration = touchEndTime - touchStartTime;
if (touchDuration < 1000 && isSwipeGesture) { // Swipe gesture within 1 second
const touchEndX = event.changedTouches[0].clientX;
const deltaX = touchEndX - touchStartX;
const minSwipeDistance = 75; // Minimum swipe distance in pixels
if (Math.abs(deltaX) > minSwipeDistance) {
const isMobile = window.innerWidth <= AppConfig.ui.mobileBreakpoint;
if (isMobile) {
// On mobile, swipe left to hide chat, right to show chat
if (deltaX < 0) {
// Swipe left - hide chat
if (!AppState.chatCollapsed) {
toggleChat();
}
} else {
// Swipe right - show chat
if (AppState.chatCollapsed) {
toggleChat();
}
}
}
}
}
// Reset touch tracking
touchStartX = 0;
touchStartY = 0;
touchStartTime = 0;
isSwipeGesture = false;
}
// Pull-to-refresh functionality for mobile
let pullStartY = 0;
let pullDistance = 0;
let isPulling = false;
const pullThreshold = 80; // Minimum pull distance to trigger refresh
function handlePullToRefreshTouchStart(event) {
if (window.innerWidth > AppConfig.ui.mobileBreakpoint) return; // Only on mobile
pullStartY = event.touches[0].clientY;
pullDistance = 0;
isPulling = true;
}
function handlePullToRefreshTouchMove(event) {
if (!isPulling || window.innerWidth > AppConfig.ui.mobileBreakpoint) return;
const currentY = event.touches[0].clientY;
pullDistance = currentY - pullStartY;
// Only allow pull down from top of page
if (window.scrollY === 0 && pullDistance > 0) {
event.preventDefault(); // Prevent default scrolling behavior
const pullIndicator = document.getElementById('pullRefreshIndicator');
if (pullIndicator) {
const opacity = Math.min(pullDistance / pullThreshold, 1);
pullIndicator.style.opacity = opacity;
pullIndicator.style.transform = `translateY(${Math.min(pullDistance * 0.5, 40)}px)`;
}
} else {
pullDistance = 0;
}
}
function handlePullToRefreshTouchEnd() {
if (!isPulling) return;
isPulling = false;
if (pullDistance > pullThreshold && window.scrollY === 0) {
// Trigger refresh
performPullToRefresh();
}
// Reset pull indicator
const pullIndicator = document.getElementById('pullRefreshIndicator');
if (pullIndicator) {
pullIndicator.style.opacity = '0';
pullIndicator.style.transform = 'translateY(0px)';
}
pullStartY = 0;
pullDistance = 0;
}
function performPullToRefresh() {
showToast('Refreshing...', 0); // Show loading message
// Trigger the refresh by reloading data
if (window.ChatSystem && typeof window.ChatSystem.refresh === 'function') {
window.ChatSystem.refresh().then(() => {
showToast('Content refreshed', 2000);
}).catch(() => {
showToast('Refresh failed', 2000);
});
} else {
// Fallback: refresh the page after a short delay
setTimeout(() => {
window.location.reload();
}, 500);
}
}
// Double-tap handler for video area (fullscreen toggle)
let lastTapTime = 0;
const doubleTapDelay = 300; // milliseconds
function handleVideoDoubleTap(event) {
const currentTime = Date.now();
const timeSinceLastTap = currentTime - lastTapTime;
if (timeSinceLastTap < doubleTapDelay && timeSinceLastTap > 0) {
// Double tap detected - toggle fullscreen
event.preventDefault();
toggleFullscreen();
lastTapTime = 0;
} else {
lastTapTime = currentTime;
}
}
// Page visibility API handler
function handleVisibilityChange() {
if (!document.hidden && !AppState.chatCollapsed) {
@ -268,6 +425,23 @@
// Page visibility for notification clearing
DOMUtils.addEvent(document, 'visibilitychange', handleVisibilityChange);
// Touch gesture support for mobile
const videoSection = document.getElementById('videoSection');
if (videoSection) {
// Swipe gestures on video area for chat toggle
DOMUtils.addEvent(videoSection, 'touchstart', handleTouchStart, { passive: true });
DOMUtils.addEvent(videoSection, 'touchmove', handleTouchMove, { passive: true });
DOMUtils.addEvent(videoSection, 'touchend', handleTouchEnd, { passive: true });
// Double-tap on video for fullscreen
DOMUtils.addEvent(videoSection, 'touchend', handleVideoDoubleTap);
}
// Pull-to-refresh on the whole document (only on mobile)
DOMUtils.addEvent(document, 'touchstart', handlePullToRefreshTouchStart, { passive: true });
DOMUtils.addEvent(document, 'touchmove', handlePullToRefreshTouchMove, { passive: false });
DOMUtils.addEvent(document, 'touchend', handlePullToRefreshTouchEnd, { passive: true });
AppLogger.log('UI controls event listeners initialized');
}