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

@ -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

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');
}