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.7 kB
// 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();
});