// Android Studio Web - Complete JavaScript with Real Build // ==================== Global Variables ==================== let currentProject = null; let currentFile = null; let currentFilePath = null; let terminal = null; let ws = null; let activeBuildId = null; let files = []; let projects = []; // ==================== Initialize on Load ==================== document.addEventListener('DOMContentLoaded', function() { // Load Lucide icons lucide.createIcons(); // Initialize terminal initTerminal(); // Load projects loadProjects(); // Setup event listeners setupEventListeners(); // Update status updateStatus('Ready', 'success'); }); // ==================== Terminal Functions ==================== function initTerminal() { const terminalContainer = document.getElementById('terminal-panel'); terminal = new Terminal({ theme: { background: '#1E1E1E', foreground: '#A9B7C6', cursor: '#3DDC84', selection: '#214283' }, fontFamily: 'JetBrains Mono, monospace', fontSize: 12, cursorBlink: true, scrollback: 5000 }); terminal.open(terminalContainer); // Connect WebSocket const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}`; ws = new WebSocket(wsUrl); ws.onopen = () => { terminal.write('\x1b[32mAndroid Studio Web Terminal\x1b[0m\r\n'); terminal.write('\x1b[32mProject workspace: /workspace\x1b[0m\r\n'); terminal.write('$ '); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'data') { terminal.write(data.data); } }; ws.onerror = (error) => { terminal.write('\x1b[31mTerminal connection error\x1b[0m\r\n'); }; terminal.onData((data) => { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'input', data })); } }); terminal.onResize((size) => { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'resize', data: size })); } }); } // ==================== Project Functions ==================== async function loadProjects() { try { const response = await fetch('/api/projects'); const data = await response.json(); projects = data.projects || []; renderProjectList(); } catch (error) { console.error('Failed to load projects:', error); } } function renderProjectList() { const projectList = document.getElementById('project-list'); if (!projectList) return; if (projects.length === 0) { projectList.innerHTML = '
No projects found
'; return; } let html = ''; projects.forEach(project => { html += `
${project}
`; }); projectList.innerHTML = html; lucide.createIcons(); } function toggleProjectDropdown() { const dropdown = document.getElementById('project-dropdown'); dropdown.classList.toggle('hidden'); } async function createNewProject() { const name = prompt('Enter project name:'); if (!name) return; updateStatus('Creating project...', 'building'); try { const response = await fetch('/api/project/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }) }); const data = await response.json(); if (data.success) { await loadProjects(); await openProject(name); updateStatus('Project created', 'success'); } else { updateStatus('Failed to create project', 'error'); } } catch (error) { updateStatus('Error creating project', 'error'); } toggleProjectDropdown(); } async function openProject(projectName) { currentProject = projectName; document.getElementById('current-project').textContent = projectName; document.getElementById('project-name-display').textContent = projectName; document.getElementById('preview-app-name').textContent = projectName; toggleProjectDropdown(); await loadProjectFiles(); } async function loadProjectFiles() { if (!currentProject) return; try { const response = await fetch(`/api/files/${currentProject}/`); const data = await response.json(); if (data.type === 'directory') { files = data.files || []; renderFileTree(); } } catch (error) { console.error('Failed to load files:', error); } } function renderFileTree() { const treeElement = document.getElementById('file-tree'); if (files.length === 0) { treeElement.innerHTML = '
No files found
'; return; } let html = '
app/src/main
'; // Simple file tree - you can make this recursive for full structure files.forEach(file => { if (file.includes('.')) { const icon = file.endsWith('.kt') ? 'file-code' : file.endsWith('.xml') ? 'file-text' : 'file'; const color = file.endsWith('.kt') ? 'text-blue-300' : file.endsWith('.xml') ? 'text-orange-300' : 'text-gray-400'; html += `
${file}
`; } }); treeElement.innerHTML = html; lucide.createIcons(); } // ==================== File Functions ==================== async function openFile(filename) { if (!currentProject) { alert('Please open a project first'); return; } currentFile = filename; currentFilePath = filename; try { const response = await fetch(`/api/files/${currentProject}/${filename}`); const data = await response.json(); if (data.type === 'file') { document.getElementById('code-editor').innerHTML = data.content; updateLineNumbers(); addTab(filename); } } catch (error) { console.error('Failed to open file:', error); } // Update active state document.querySelectorAll('.file-item').forEach(el => { el.classList.remove('active'); if (el.textContent.trim() === filename) { el.classList.add('active'); } }); } function addTab(filename) { const tabs = document.getElementById('editor-tabs'); // Check if tab already exists const existingTab = Array.from(tabs.children).find( tab => tab.dataset.file === filename ); if (existingTab) return; const icon = filename.endsWith('.kt') ? 'file-code' : filename.endsWith('.xml') ? 'file-text' : 'file'; const color = filename.endsWith('.kt') ? 'text-blue-300' : filename.endsWith('.xml') ? 'text-orange-300' : 'text-gray-400'; const tabHtml = `
${filename}
`; tabs.insertAdjacentHTML('beforeend', tabHtml); lucide.createIcons(); } function switchToTab(filename) { openFile(filename); } function closeTab(event, filename) { event.stopPropagation(); const tab = event.target.closest('[data-file]'); if (tab) { tab.remove(); if (currentFile === filename) { const firstTab = document.querySelector('[data-file]'); if (firstTab) { openFile(firstTab.dataset.file); } else { currentFile = null; document.getElementById('code-editor').innerHTML = '// No file open'; updateLineNumbers(); } } } } async function saveCurrentFile() { if (!currentProject || !currentFile) { alert('No file open'); return; } const content = document.getElementById('code-editor').innerText; try { const response = await fetch(`/api/file/${currentProject}/${currentFile}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }) }); const data = await response.json(); if (data.success) { updateStatus('File saved', 'success'); } } catch (error) { updateStatus('Save failed', 'error'); } } function createNewFile() { if (!currentProject) { alert('Please open a project first'); return; } const filename = prompt('Enter file name (e.g., NewFile.kt):'); if (filename) { currentFile = filename; document.getElementById('code-editor').innerHTML = getDefaultContent(filename); updateLineNumbers(); addTab(filename); saveCurrentFile(); } } function getDefaultContent(filename) { if (filename.endsWith('.kt')) { return `package com.example.app import android.os.Bundle import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }`; } else if (filename.endsWith('.xml')) { return ` `; } return ''; } function refreshFiles() { if (currentProject) { loadProjectFiles(); } } // ==================== Build Functions ==================== async function startBuild() { if (!currentProject) { alert('Please open a project first'); return; } switchPanel('build'); const buildPanel = document.getElementById('build-panel'); buildPanel.innerHTML = '
🔨 Building APK...
'; updateStatus('Building...', 'building'); try { const response = await fetch(`/api/build/${currentProject}`, { method: 'POST' }); const data = await response.json(); activeBuildId = data.buildId; // Poll for build status checkBuildStatus(activeBuildId); } catch (error) { buildPanel.innerHTML = '
❌ Build failed to start
'; updateStatus('Build failed', 'error'); } } async function checkBuildStatus(buildId) { const buildPanel = document.getElementById('build-panel'); try { const response = await fetch(`/api/build/${buildId}`); const data = await response.json(); if (data.output) { let html = data.output.replace(/\n/g, '
') .replace(/SUCCESS/g, 'SUCCESS') .replace(/FAILED/g, 'FAILED') .replace(/warning/g, 'warning'); buildPanel.innerHTML = html; } if (data.success !== undefined) { if (data.success) { buildPanel.innerHTML += '

✅ Build successful! APK ready.
'; updateStatus('Build successful', 'success'); } else { buildPanel.innerHTML += '

❌ Build failed
'; updateStatus('Build failed', 'error'); } } else { // Still building, check again setTimeout(() => checkBuildStatus(buildId), 1000); } } catch (error) { buildPanel.innerHTML = '
Error checking build status
'; } } async function downloadAPK() { if (!currentProject) { alert('Please open a project first'); return; } window.location.href = `/api/apk/${currentProject}`; } // ==================== Preview Functions ==================== async function uploadLogo() { if (!currentProject) { alert('Please open a project first'); return; } const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; input.onchange = async (e) => { const file = e.target.files[0]; const formData = new FormData(); formData.append('logo', file); updateStatus('Uploading logo...', 'building'); try { const response = await fetch(`/api/logo/upload/${currentProject}`, { method: 'POST', body: formData }); const data = await response.json(); if (data.success) { // Update preview logo const reader = new FileReader(); reader.onload = (e) => { document.getElementById('preview-logo').src = e.target.result; }; reader.readAsDataURL(file); updateStatus('Logo updated', 'success'); } } catch (error) { updateStatus('Logo upload failed', 'error'); } }; input.click(); } function installOnPreview() { const appScreen = document.getElementById('app-screen'); const logo = document.getElementById('preview-logo'); const appName = document.getElementById('preview-app-name'); // Simulate app installation updateStatus('Installing on Android 10...', 'building'); setTimeout(() => { appScreen.innerHTML = `

${appName.textContent}

Running on Android 10

App installed
`; updateStatus('App installed on preview', 'success'); }, 2000); } function rotatePreview() { const phone = document.getElementById('phone-preview'); phone.classList.toggle('rotated'); } function toggleTheme() { const statusBar = document.getElementById('status-bar'); const appScreen = document.getElementById('app-screen'); const navBar = document.getElementById('nav-bar'); statusBar.classList.toggle('light'); appScreen.classList.toggle('dark'); navBar.classList.toggle('light'); } function simulateBack() { updateStatus('Back button pressed', 'info'); } function simulateHome() { updateStatus('Home button pressed', 'info'); // Reset to home screen const appScreen = document.getElementById('app-screen'); appScreen.innerHTML = `

${document.getElementById('preview-app-name').textContent}

Android 10 (API 29)

`; } function simulateRecent() { updateStatus('Recent apps button pressed', 'info'); } // ==================== UI Functions ==================== function updateLineNumbers() { const editor = document.getElementById('code-editor'); const lines = editor.innerText.split('\n').length; const lineNumbers = document.getElementById('line-numbers'); let html = ''; for (let i = 1; i <= lines; i++) { html += `
${i}
`; } lineNumbers.innerHTML = html; } function syncScroll() { const editor = document.getElementById('code-editor'); const lineNumbers = document.getElementById('line-numbers'); lineNumbers.scrollTop = editor.scrollTop; } function switchPanel(panel) { // Hide all panels document.getElementById('terminal-panel').classList.add('hidden'); document.getElementById('build-panel').classList.add('hidden'); // Show selected panel document.getElementById(`${panel}-panel`).classList.remove('hidden'); // Update tab styles document.querySelectorAll('.panel-tab').forEach(tab => { tab.classList.remove('border-[#3DDC84]', 'text-white'); tab.classList.add('text-gray-400'); }); event.currentTarget.classList.add('border-[#3DDC84]', 'text-white'); event.currentTarget.classList.remove('text-gray-400'); } function updateStatus(message, type) { const statusEl = document.getElementById('status-message'); statusEl.textContent = message; const colors = { success: '#3DDC84', error: '#CF6679', building: '#FBC02D', info: '#64B5F6' }; statusEl.style.color = colors[type] || '#000000'; } function openSettings() { alert('Settings dialog would open here'); } function setupEventListeners() { // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 's') { e.preventDefault(); saveCurrentFile(); } if (e.key === 'F5') { e.preventDefault(); startBuild(); } }); // Click outside to close dropdown document.addEventListener('click', (e) => { if (!e.target.closest('.relative')) { document.getElementById('project-dropdown').classList.add('hidden'); } }); } // Export functions for HTML window.toggleProjectDropdown = toggleProjectDropdown; window.createNewProject = createNewProject; window.openProject = openProject; window.openFile = openFile; window.saveCurrentFile = saveCurrentFile; window.createNewFile = createNewFile; window.refreshFiles = refreshFiles; window.startBuild = startBuild; window.downloadAPK = downloadAPK; window.installOnPreview = installOnPreview; window.rotatePreview = rotatePreview; window.toggleTheme = toggleTheme; window.uploadLogo = uploadLogo; window.simulateBack = simulateBack; window.simulateHome = simulateHome; window.simulateRecent = simulateRecent; window.switchPanel = switchPanel; window.updateLineNumbers = updateLineNumbers; window.syncScroll = syncScroll; window.openSettings = openSettings; window.switchToTab = switchToTab; window.closeTab = closeTab;