From a7fa21d3fafe3ecefc30ae0c0950ba0b69a49c11 Mon Sep 17 00:00:00 2001 From: VinnyNC Date: Mon, 29 Sep 2025 21:12:50 -0400 Subject: [PATCH] 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 --- UI_UPDATE.MD | 8 +- assets/js/ui-controls.js | 174 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 3 deletions(-) diff --git a/UI_UPDATE.MD b/UI_UPDATE.MD index 42df371..f9d6626 100644 --- a/UI_UPDATE.MD +++ b/UI_UPDATE.MD @@ -346,9 +346,11 @@ Always come back and update UI_UPDATE.MD once complete with task and task item. **Notes:** Implemented comprehensive mobile-first responsive design. Updated layout.css to use BEM class names, added vertical stacking layout for mobile (flex-direction: column), chat slides up from bottom as modal overlay on mobile instead of collapsing to the side. Enhanced touch-friendly navigation with 44px minimum touch targets already in place. Updated ui-controls.js to handle mobile chat overlay behavior. Desktop remains side-by-side layout while mobile prioritizes video content with chat on-demand. #### 3.3.2: Touch Interaction Optimization -- [ ] Ensure 44px minimum touch targets -- [ ] Implement swipe gesture support -- [ ] Add mobile-specific interactive elements +- [x] Ensure 44px minimum touch targets +- [x] Implement swipe gesture support +- [x] Add mobile-specific interactive elements + +**Notes:** Implemented comprehensive touch interaction enhancements for mobile devices. The 44px minimum touch targets were already in place using var(--min-tap-target-size, 44px) in components.css. Added swipe gesture support on video area for chat toggling (swipe left to hide, right to show chat on mobile). Implemented pull-to-refresh functionality that triggers refresh when pulling down from top of page on mobile. Added double-tap gesture on video area to toggle fullscreen. All touch interactions are mobile-only to avoid conflicts on desktop. #### 3.3.3: Mobile Navigation - [ ] Create bottom navigation pattern diff --git a/assets/js/ui-controls.js b/assets/js/ui-controls.js index 6280ea1..84d815f 100644 --- a/assets/js/ui-controls.js +++ b/assets/js/ui-controls.js @@ -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'); }