// ArchDroid Launcher Core Logic class ArchLauncher { constructor() { this.apps = [ { name: 'Terminal', icon: 'terminal', package: 'com.termux', category: 'dev' }, { name: 'Files', icon: 'folder', package: 'com.android.documentsui', category: 'system' }, { name: 'Settings', icon: 'settings', package: 'com.android.settings', category: 'system' }, { name: 'Firefox', icon: 'globe', package: 'org.mozilla.firefox', category: 'internet' }, { name: 'Spotify', icon: 'music', package: 'com.spotify.music', category: 'media' }, { name: 'Gallery', icon: 'image', package: 'com.android.gallery3d', category: 'media' }, { name: 'Calendar', icon: 'calendar', package: 'com.google.android.calendar', category: 'productivity' }, { name: 'Email', icon: 'mail', package: 'com.google.android.gm', category: 'internet' }, { name: 'Maps', icon: 'map', package: 'com.google.android.apps.maps', category: 'internet' }, { name: 'Calculator', icon: 'hash', package: 'com.google.android.calculator', category: 'productivity' }, { name: 'Discord', icon: 'message-circle', package: 'com.discord', category: 'social' }, { name: 'GitHub', icon: 'github', package: 'com.github.android', category: 'dev' }, { name: 'Code', icon: 'code', package: 'com.microsoft.vscode', category: 'dev' }, { name: 'SSH', icon: 'server', package: 'com.termux.api', category: 'dev' }, { name: 'Monitor', icon: 'activity', package: 'com.samsung.android.app.usagestats', category: 'system' }, { name: 'Weather', icon: 'cloud', package: 'com.sec.android.daemonapp', category: 'system' } ]; this.favorites = ['Terminal', 'Firefox', 'Settings', 'Files']; this.serverEndpoint = localStorage.getItem('serverEndpoint') || 'http://192.168.1.100:3000'; this.touchStartY = 0; this.init(); } init() { this.setupGestures(); this.updateUptime(); this.checkServerStatus(); this.renderFavorites(); this.setupTime(); // Update uptime every minute setInterval(() => this.updateUptime(), 60000); // Check server every 30 seconds setInterval(() => this.checkServerStatus(), 30000); // Handle app drawer toggle document.addEventListener('toggle-app-drawer', () => this.toggleAppDrawer()); document.addEventListener('toggle-server-panel', () => this.toggleServerPanel()); // Handle app launch document.addEventListener('launch-app', (e) => this.launchApp(e.detail)); } setupTime() { const updateTime = () => { const now = new Date(); const timeStr = now.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' }); document.querySelectorAll('.clock-time').forEach(el => el.textContent = timeStr); }; updateTime(); setInterval(updateTime, 1000); } updateUptime() { // Simulate uptime since last boot (stored in session) const startTime = sessionStorage.getItem('bootTime') || Date.now(); if (!sessionStorage.getItem('bootTime')) { sessionStorage.setItem('bootTime', startTime); } const diff = Date.now() - parseInt(startTime); const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const el = document.getElementById('uptime'); if (el) el.textContent = `${days} days, ${hours} hrs`; } async checkServerStatus() { const statusEl = document.getElementById('server-status'); if (!statusEl) return; try { // Try to fetch from home server with timeout const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); const response = await fetch(`${this.serverEndpoint}/health`, { signal: controller.signal, mode: 'no-cors' // For private servers without CORS }).catch(() => null); clearTimeout(timeout); if (response || !response) { // With no-cors we can't read response, assume online if no error statusEl.textContent = '● online'; statusEl.className = 'text-green-400'; statusEl.classList.remove('animate-pulse'); window.dispatchEvent(new CustomEvent('server-status', { detail: 'online' })); } } catch (e) { statusEl.textContent = '● offline'; statusEl.className = 'text-red-400'; window.dispatchEvent(new CustomEvent('server-status', { detail: 'offline' })); } } renderFavorites() { const grid = document.getElementById('favorites-grid'); if (!grid) return; const favoriteApps = this.apps.filter(app => this.favorites.includes(app.name)); favoriteApps.forEach(app => { const icon = document.createElement('arch-app-icon'); icon.setAttribute('name', app.name); icon.setAttribute('icon', app.icon); icon.setAttribute('package', app.package); grid.appendChild(icon); }); } setupGestures() { const main = document.getElementById('main-screen'); const drawer = document.getElementById('app-drawer'); if (!main || !drawer) return; // Swipe up to open drawer main.addEventListener('touchstart', (e) => { this.touchStartY = e.touches[0].clientY; }, { passive: true }); main.addEventListener('touchend', (e) => { const touchEndY = e.changedTouches[0].clientY; const diff = this.touchStartY - touchEndY; // Swipe up from bottom area (> 100px from bottom) if (diff > 50 && this.touchStartY > window.innerHeight - 150) { this.openAppDrawer(); } }, { passive: true }); // Swipe down to close drawer drawer.addEventListener('touchstart', (e) => { this.touchStartY = e.touches[0].clientY; }, { passive: true }); drawer.addEventListener('touchend', (e) => { const touchEndY = e.changedTouches[0].clientY; const diff = touchEndY - this.touchStartY; if (diff > 50) { this.closeAppDrawer(); } }, { passive: true }); } openAppDrawer() { const drawer = document.getElementById('app-drawer'); if (drawer) { drawer.classList.remove('translate-y-full'); drawer.classList.add('translate-y-0'); } } closeAppDrawer() { const drawer = document.getElementById('app-drawer'); if (drawer) { drawer.classList.add('translate-y-full'); drawer.classList.remove('translate-y-0'); } } toggleAppDrawer() { const drawer = document.getElementById('app-drawer'); if (drawer.classList.contains('translate-y-full')) { this.openAppDrawer(); } else { this.closeAppDrawer(); } } toggleServerPanel() { const panel = document.getElementById('server-panel'); if (panel) { if (panel.classList.contains('-translate-x-full')) { panel.classList.remove('-translate-x-full'); panel.classList.add('translate-x-0'); } else { panel.classList.add('-translate-x-full'); panel.classList.remove('translate-x-0'); } } } launchApp(appData) { console.log(`Launching ${appData.name} (${appData.package})...`); // Visual feedback const event = new CustomEvent('app-launching', { detail: appData }); document.dispatchEvent(event); // In a real Android WebView, this would call Android.launchApp(package) // For now, show a terminal-style toast this.showToast(`> launching ${appData.name.toLowerCase()}...`); // Simulate app opening delay setTimeout(() => { // Try to open actual app if on Android via intent if (window.Android && window.Android.launchApp) { window.Android.launchApp(appData.package); } }, 300); } showToast(message) { const existing = document.querySelector('.arch-toast'); if (existing) existing.remove(); const toast = document.createElement('div'); toast.className = 'arch-toast fixed top-24 left-1/2 -translate-x-1/2 bg-arch-muted/90 text-arch-primary px-4 py-2 rounded border border-arch-primary/50 text-xs font-mono z-50 animate-pulse'; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => toast.remove(), 2000); } getAllApps() { return this.apps; } } // Initialize const launcher = new ArchLauncher(); // Prevent default touch behaviors for app-like feel document.addEventListener('touchmove', function(e) { if (e.target.closest('.scrollable')) return; // Allow scrolling in specific containers only }, { passive: false }); // Prevent zoom document.addEventListener('gesturestart', function(e) { e.preventDefault(); });