archdroid-parallel-workers / js /gesture-controller.js
jonin925's picture
lets deploy parallel workers to speed up the development process. make the app fetch current settings, app data from android to display in the application drawer. the navigation gestures and other daily use settings like the ones in notification bar should comply with current android settings. help me build an apk ready pwa stack that can be tested on samsung s23 ultra
3a08226 verified
Raw
History Blame Contribute Delete
9.94 kB
// Gesture Controller - Android 14 compliant gesture handling
class GestureController {
constructor() {
this.minSwipeDistance = 60;
this.edgeSwipeWidth = 40;
this.gestureArea = document.documentElement;
this.isTracking = false;
this.startY = 0;
this.startX = 0;
this.currentGesture = null;
this.vibrationEnabled = true;
this.init();
}
init() {
this.setupTouchHandlers();
this.setupAndroidGestures();
this.listenForSettings();
}
setupTouchHandlers() {
// Use passive listeners for performance
document.addEventListener('touchstart', (e) => this.onTouchStart(e), { passive: true });
document.addEventListener('touchmove', (e) => this.onTouchMove(e), { passive: true });
document.addEventListener('touchend', (e) => this.onTouchEnd(e), { passive: true });
document.addEventListener('touchcancel', (e) => this.onTouchCancel(e), { passive: true });
// Prevent default only for specific gestures
document.addEventListener('touchmove', (e) => {
if (this.isTracking && this.shouldPreventScroll()) {
e.preventDefault();
}
}, { passive: false });
}
setupAndroidGestures() {
// Register with Android system if available
if (window.Android?.GestureManager?.registerCallback) {
window.Android.GestureManager.registerCallback((gesture) => {
this.handleAndroidGesture(gesture);
});
}
}
listenForSettings() {
window.addEventListener('settings-updated', (e) => {
if (e.detail.settings) {
const settings = e.detail.settings;
this.vibrationEnabled = settings['haptic_feedback'] !== '0';
}
});
}
onTouchStart(e) {
const touch = e.touches[0];
this.startX = touch.clientX;
this.startY = touch.clientY;
this.startTime = Date.now();
this.isTracking = true;
this.currentGesture = null;
// Detect gesture zone
const screenHeight = window.innerHeight;
const screenWidth = window.innerWidth;
this.gestureType = this.detectGestureZone(this.startX, this.startY, screenWidth, screenHeight);
// Send to worker for processing
if (window.workerBridge) {
window.workerBridge.processGesture('TOUCH_START', {
x: this.startX,
y: this.startY,
time: this.startTime
});
}
}
onTouchMove(e) {
if (!this.isTracking) return;
const touch = e.touches[0];
const deltaX = touch.clientX - this.startX;
const deltaY = touch.clientY - this.startY;
// Send to worker
if (window.workerBridge) {
window.workerBridge.processGesture('TOUCH_MOVE', {
x: touch.clientX,
y: touch.clientY,
time: Date.now()
});
}
// Live gesture detection for UI feedback
this.updateGesturePreview(deltaX, deltaY);
}
onTouchEnd(e) {
if (!this.isTracking) return;
const touch = e.changedTouches[0];
const deltaX = touch.clientX - this.startX;
const deltaY = touch.clientY - this.startY;
const deltaTime = Date.now() - this.startTime;
this.isTracking = false;
// Send to worker
if (window.workerBridge) {
window.workerBridge.processGesture('TOUCH_END', {
x: touch.clientX,
y: touch.clientY,
time: Date.now()
});
}
// Local gesture detection for immediate response
const gesture = this.detectGesture(deltaX, deltaY, deltaTime);
if (gesture) {
this.executeGesture(gesture);
}
this.hideGesturePreview();
}
onTouchCancel(e) {
this.isTracking = false;
this.hideGesturePreview();
}
detectGestureZone(x, y, width, height) {
// Android 14 gesture zones
if (y < 50) {
if (x < width * 0.4) return 'STATUS_NOTIFICATIONS';
if (x > width * 0.6) return 'STATUS_QUICK_SETTINGS';
return 'STATUS_CLOCK';
}
if (y > height - 20 && x > width * 0.2 && x < width * 0.8) {
return 'HOME_INDICATOR';
}
if (x < this.edgeSwipeWidth) return 'EDGE_BACK';
if (x > width - this.edgeSwipeWidth) return 'EDGE_FORWARD';
return 'CONTENT';
}
detectGesture(deltaX, deltaY, deltaTime) {
const absX = Math.abs(deltaX);
const absY = Math.abs(deltaY);
const velocityX = absX / deltaTime;
const velocityY = absY / deltaTime;
const minVelocity = 0.3; // px/ms
// Home gesture (bottom swipe up)
if (this.gestureType === 'HOME_INDICATOR' && deltaY < -this.minSwipeDistance) {
return { type: 'HOME', intensity: Math.min(absY / 200, 1) };
}
// Back gesture (edge swipe)
if (this.gestureType === 'EDGE_BACK' && deltaX > this.minSwipeDistance) {
return { type: 'BACK', edge: 'left' };
}
// Quick settings (top-right swipe down)
if (this.gestureType === 'STATUS_QUICK_SETTINGS' && deltaY > this.minSwipeDistance) {
return { type: 'QUICK_SETTINGS' };
}
// Notifications (top-left swipe down)
if (this.gestureType === 'STATUS_NOTIFICATIONS' && deltaY > this.minSwipeDistance) {
return { type: 'NOTIFICATIONS' };
}
// App drawer (home screen swipe up)
if (absY > absX && deltaY < -this.minSwipeDistance && velocityY > minVelocity) {
if (document.getElementById('main-screen')?.contains(document.elementFromPoint(this.startX, this.startY))) {
return { type: 'APP_DRAWER' };
}
}
// General swipes
if (absX > absY && absX > this.minSwipeDistance && velocityX > minVelocity) {
return { type: deltaX > 0 ? 'SWIPE_RIGHT' : 'SWIPE_LEFT' };
}
return null;
}
executeGesture(gesture) {
if (this.vibrationEnabled) {
this.hapticFeedback(gesture.type);
}
switch (gesture.type) {
case 'HOME':
this.goHome();
break;
case 'BACK':
this.goBack();
break;
case 'QUICK_SETTINGS':
case 'APP_DRAWER':
this.togglePanel('settings-panel');
break;
case 'NOTIFICATIONS':
this.togglePanel('notification-shade');
break;
}
// Dispatch event for components
window.dispatchEvent(new CustomEvent('gesture-executed', { detail: gesture }));
}
goHome() {
// Close all panels
['server-panel', 'settings-panel', 'notification-shade', 'app-drawer'].forEach(id => {
const el = document.getElementById(id);
if (el) {
el.classList.add('-translate-y-full', '-translate-x-full', 'translate-y-full');
el.classList.remove('translate-y-0', 'translate-x-0', 'translate-y-full');
}
});
// Reset main screen
const main = document.getElementById('main-screen');
if (main) {
main.style.transform = '';
}
}
goBack() {
// Close topmost panel or go back in history
const panels = ['notification-shade', 'settings-panel', 'server-panel', 'app-drawer'];
for (const id of panels) {
const el = document.getElementById(id);
if (el && this.isPanelOpen(el)) {
this.closePanel(el);
return;
}
}
// Fallback to browser back
if (history.length > 1) {
history.back();
}
}
togglePanel(id) {
const el = document.getElementById(id);
if (!el) return;
if (this.isPanelOpen(el)) {
this.closePanel(el);
} else {
this.openPanel(el);
}
}
openPanel(el) {
const id = el.id;
if (id === 'notification-shade') {
el.classList.remove('-translate-y-full');
el.classList.add('translate-y-0');
} else if (id === 'server-panel') {
el.classList.remove('-translate-x-full');
el.classList.add('translate-x-0');
} else {
el.classList.remove('translate-y-full');
el.classList.add('translate-y-0');
}
}
closePanel(el) {
const id = el.id;
if (id === 'notification-shade') {
el.classList.add('-translate-y-full');
el.classList.remove('translate-y-0');
} else if (id === 'server-panel') {
el.classList.add('-translate-x-full');
el.classList.remove('translate-x-0');
} else {
el.classList.add('translate-y-full');
el.classList.remove('translate-y-0');
}
}
isPanelOpen(el) {
return !el.classList.contains('-translate-y-full') &&
!el.classList.contains('-translate-x-full') &&
!el.classList.contains('translate-y-full');
}
hapticFeedback(type) {
const patterns = {
'HOME': [0, 20, 10, 20],
'BACK': [0, 15],
'QUICK_SETTINGS': [0, 10, 40, 10],
'NOTIFICATIONS': [0, 10, 40, 10],
'default': [0, 20]
};
if (navigator.vibrate) {
navigator.vibrate(patterns[type] || patterns.default);
}
// Also notify Android
if (window.Android?.Haptic?.perform) {
window.Android.Haptic.perform(type.toLowerCase());
}
}
updateGesturePreview(deltaX, deltaY) {
// Visual feedback during gesture
const indicator = document.getElementById('gesture-indicator');
if (!indicator) return;
const progress = Math.min(Math.abs(deltaY) / 200, 1);
indicator.style.opacity = progress;
indicator.style.transform = `translateY(${Math.min(deltaY * 0.3, 50)}px)`;
}
hideGesturePreview() {
const indicator = document.getElementById('gesture-indicator');
if (indicator) {
indicator.style.opacity = '0';
}
}
shouldPreventScroll() {
return ['HOME_INDICATOR', 'STATUS_NOTIFICATIONS', 'STATUS_QUICK_SETTINGS'].includes(this.gestureType);
}
handleAndroidGesture(gesture) {
// Handle gestures from Android system
this.executeGesture({ type: gesture.type });
}
}
// Initialize when DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.gestureController = new GestureController();
});
} else {
window.gestureController = new GestureController();
}