proti0070 commited on
Commit
22be007
·
verified ·
1 Parent(s): a6119c0

Upload 7 files

Browse files
Files changed (6) hide show
  1. .hf-space.yaml +8 -0
  2. index.html +266 -0
  3. package.json +21 -0
  4. script.js +648 -0
  5. server.js +473 -0
  6. style.css +265 -0
.hf-space.yaml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Android Studio Web - Real Build with Android 10 Preview
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
index.html ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="bn">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Android Studio Web - Real Build + Android 10 Preview</title>
7
+
8
+ <!-- Tailwind CSS -->
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+
11
+ <!-- Icons -->
12
+ <script src="https://unpkg.com/lucide@latest"></script>
13
+
14
+ <!-- Fonts -->
15
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
16
+
17
+ <!-- xterm.js for terminal -->
18
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css" />
19
+
20
+ <!-- Custom CSS -->
21
+ <link rel="stylesheet" href="style.css">
22
+ </head>
23
+ <body class="bg-[#2B2B2B] text-gray-300 h-screen flex flex-col overflow-hidden">
24
+ <!-- Top Toolbar -->
25
+ <div class="h-12 bg-[#3C3F41] border-b border-[#4E4E4E] flex items-center px-4 justify-between select-none">
26
+ <div class="flex items-center space-x-4">
27
+ <div class="flex items-center space-x-2">
28
+ <i data-lucide="smartphone" class="w-5 h-5 text-[#3DDC84]"></i>
29
+ <span class="font-semibold text-white text-sm">Android Studio Web</span>
30
+ <span class="text-xs text-gray-500">Real Build + Android 10</span>
31
+ </div>
32
+
33
+ <!-- Project Dropdown -->
34
+ <div class="relative">
35
+ <button onclick="toggleProjectDropdown()" class="flex items-center space-x-1 bg-[#2B2B2B] px-3 py-1 rounded text-sm">
36
+ <i data-lucide="folder" class="w-4 h-4 text-yellow-500"></i>
37
+ <span id="current-project">Select Project</span>
38
+ <i data-lucide="chevron-down" class="w-4 h-4"></i>
39
+ </button>
40
+ <div id="project-dropdown" class="absolute top-full left-0 mt-1 bg-[#3C3F41] border border-[#4E4E4E] rounded shadow-lg hidden z-50">
41
+ <div class="p-2 min-w-[200px]" id="project-list">
42
+ <!-- Projects will load here -->
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- File Operations -->
48
+ <div class="h-4 w-px bg-gray-600"></div>
49
+ <div class="flex items-center space-x-1">
50
+ <button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="createNewFile()" title="New File">
51
+ <i data-lucide="file-plus" class="w-4 h-4"></i>
52
+ </button>
53
+ <button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="saveCurrentFile()" title="Save (Ctrl+S)">
54
+ <i data-lucide="save" class="w-4 h-4"></i>
55
+ </button>
56
+ <button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="refreshFiles()" title="Refresh">
57
+ <i data-lucide="refresh-cw" class="w-4 h-4"></i>
58
+ </button>
59
+ </div>
60
+
61
+ <!-- Build & Run -->
62
+ <div class="h-4 w-px bg-gray-600"></div>
63
+ <div class="flex items-center space-x-1">
64
+ <button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="startBuild()" title="Build APK">
65
+ <i data-lucide="play" class="w-4 h-4 text-[#3DDC84]"></i>
66
+ </button>
67
+ <button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="downloadAPK()" title="Download APK">
68
+ <i data-lucide="download" class="w-4 h-4 text-blue-400"></i>
69
+ </button>
70
+ <button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="installOnPreview()" title="Install on Preview">
71
+ <i data-lucide="smartphone" class="w-4 h-4 text-green-400"></i>
72
+ </button>
73
+ </div>
74
+ </div>
75
+
76
+ <!-- Right side -->
77
+ <div class="flex items-center space-x-3">
78
+ <div class="flex items-center space-x-2 px-3 py-1 bg-[#2B2B2B] rounded-full text-xs">
79
+ <i data-lucide="git-branch" class="w-3 h-3"></i>
80
+ <span>main</span>
81
+ </div>
82
+ <button class="p-1.5 hover:bg-gray-600 rounded" onclick="openSettings()">
83
+ <i data-lucide="settings" class="w-4 h-4"></i>
84
+ </button>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Main Content -->
89
+ <div class="flex-1 flex overflow-hidden">
90
+ <!-- Left Sidebar - Project Files -->
91
+ <div class="w-64 bg-[#3C3F41] border-r border-[#4E4E4E] flex flex-col">
92
+ <!-- Project Header -->
93
+ <div class="p-3 border-b border-[#4E4E4E]">
94
+ <div class="flex items-center justify-between mb-2">
95
+ <span class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Project</span>
96
+ <button onclick="createNewProject()" class="hover:bg-gray-600 p-1 rounded" title="New Project">
97
+ <i data-lucide="folder-plus" class="w-4 h-4"></i>
98
+ </button>
99
+ </div>
100
+ <div class="flex items-center space-x-2 text-sm">
101
+ <i data-lucide="folder" class="w-4 h-4 text-yellow-600"></i>
102
+ <span class="font-medium text-white" id="project-name-display">No Project</span>
103
+ </div>
104
+ </div>
105
+
106
+ <!-- File Tree -->
107
+ <div class="flex-1 overflow-y-auto py-2" id="file-tree">
108
+ <div class="px-3 py-1 text-xs text-gray-500">Open a project to see files</div>
109
+ </div>
110
+
111
+ <!-- Bottom Tools -->
112
+ <div class="border-t border-[#4E4E4E] p-2">
113
+ <div class="flex items-center justify-around">
114
+ <button class="p-2 hover:bg-gray-600 rounded text-gray-400 hover:text-white" title="Project Structure">
115
+ <i data-lucide="structure" class="w-4 h-4"></i>
116
+ </button>
117
+ <button class="p-2 hover:bg-gray-600 rounded text-gray-400 hover:text-white" title="Search">
118
+ <i data-lucide="search" class="w-4 h-4"></i>
119
+ </button>
120
+ <button class="p-2 hover:bg-gray-600 rounded text-gray-400 hover:text-white" title="Version Control">
121
+ <i data-lucide="git-commit" class="w-4 h-4"></i>
122
+ </button>
123
+ <button class="p-2 hover:bg-gray-600 rounded text-gray-400 hover:text-white" title="Logcat">
124
+ <i data-lucide="terminal" class="w-4 h-4"></i>
125
+ </button>
126
+ </div>
127
+ </div>
128
+ </div>
129
+
130
+ <!-- Center - Editor Area -->
131
+ <div class="flex-1 flex flex-col bg-[#2B2B2B]">
132
+ <!-- Editor Tabs -->
133
+ <div class="flex bg-[#3C3F41] border-b border-[#4E4E4E] overflow-x-auto" id="editor-tabs">
134
+ <div class="px-4 py-2 flex items-center space-x-2 text-sm text-gray-400">
135
+ <i data-lucide="file" class="w-4 h-4"></i>
136
+ <span>No file open</span>
137
+ </div>
138
+ </div>
139
+
140
+ <!-- Editor with Line Numbers -->
141
+ <div class="flex-1 flex overflow-hidden">
142
+ <div class="line-numbers code-font text-sm py-4 select-none bg-[#313335]" id="line-numbers">
143
+ <div class="leading-6">1</div>
144
+ </div>
145
+ <div class="flex-1 editor-content code-font text-sm p-4 overflow-auto"
146
+ id="code-editor"
147
+ contenteditable="true"
148
+ spellcheck="false"
149
+ oninput="updateLineNumbers()"
150
+ onscroll="syncScroll()">
151
+ {/* Open a file to start editing */}
152
+ </div>
153
+ </div>
154
+
155
+ <!-- Bottom Panel with Terminal & Build Output -->
156
+ <div class="h-48 bg-[#2B2B2B] border-t border-[#4E4E4E] flex flex-col">
157
+ <!-- Panel Tabs -->
158
+ <div class="flex items-center border-b border-[#4E4E4E] bg-[#3C3F41]">
159
+ <button class="px-4 py-2 text-sm text-white border-b-2 border-[#3DDC84] panel-tab" onclick="switchPanel('terminal')">
160
+ <div class="flex items-center space-x-2">
161
+ <i data-lucide="terminal" class="w-4 h-4"></i>
162
+ <span>Terminal</span>
163
+ </div>
164
+ </button>
165
+ <button class="px-4 py-2 text-sm text-gray-400 hover:text-white panel-tab" onclick="switchPanel('build')">
166
+ <div class="flex items-center space-x-2">
167
+ <i data-lucide="package" class="w-4 h-4"></i>
168
+ <span>Build Output</span>
169
+ </div>
170
+ </button>
171
+ </div>
172
+
173
+ <!-- Terminal Panel -->
174
+ <div id="terminal-panel" class="flex-1 bg-black"></div>
175
+
176
+ <!-- Build Output Panel (hidden by default) -->
177
+ <div id="build-panel" class="flex-1 p-3 code-font text-sm overflow-auto hidden">
178
+ <div class="text-gray-400">▶ Click Build button to compile your app</div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+
183
+ <!-- Right Panel - Android 10 Preview -->
184
+ <div class="w-80 bg-[#2B2B2B] border-l border-[#4E4E4E] flex flex-col">
185
+ <div class="h-10 border-b border-[#4E4E4E] flex items-center px-4 justify-between bg-[#3C3F41]">
186
+ <span class="text-sm font-medium text-white">Android 10 Preview</span>
187
+ <div class="flex items-center space-x-2">
188
+ <button class="p-1 hover:bg-gray-600 rounded" onclick="rotatePreview()" title="Rotate">
189
+ <i data-lucide="rotate-ccw" class="w-4 h-4"></i>
190
+ </button>
191
+ <button class="p-1 hover:bg-gray-600 rounded" onclick="toggleTheme()" title="Toggle Theme">
192
+ <i data-lucide="moon" class="w-4 h-4"></i>
193
+ </button>
194
+ </div>
195
+ </div>
196
+
197
+ <!-- Android 10 Phone Frame -->
198
+ <div class="flex-1 p-4 flex items-center justify-center bg-[#1E1E1E]">
199
+ <div class="phone-frame android-10" id="phone-preview">
200
+ <!-- Status Bar (Android 10 style) -->
201
+ <div class="status-bar" id="status-bar">
202
+ <div class="time">9:41</div>
203
+ <div class="status-icons">
204
+ <i data-lucide="signal" class="w-3 h-3"></i>
205
+ <i data-lucide="wifi" class="w-3 h-3"></i>
206
+ <i data-lucide="battery-full" class="w-3 h-3"></i>
207
+ </div>
208
+ </div>
209
+
210
+ <!-- App Content -->
211
+ <div class="app-screen" id="app-screen">
212
+ <div class="logo-container">
213
+ <img id="preview-logo" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 24 24' fill='%233DDC84'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z'/%3E%3C/svg%3E" alt="App Logo">
214
+ <h3 id="preview-app-name">My Android App</h3>
215
+ <p class="app-version">Android 10 (API 29)</p>
216
+ </div>
217
+ </div>
218
+
219
+ <!-- Navigation Bar (Android 10 gesture) -->
220
+ <div class="nav-bar" id="nav-bar">
221
+ <div class="nav-handle"></div>
222
+ </div>
223
+ </div>
224
+ </div>
225
+
226
+ <!-- Preview Controls -->
227
+ <div class="h-16 border-t border-[#4E4E4E] bg-[#3C3F41] flex items-center justify-center space-x-4">
228
+ <button class="p-2 rounded-full hover:bg-gray-600" onclick="simulateBack()">
229
+ <i data-lucide="arrow-left" class="w-5 h-5"></i>
230
+ </button>
231
+ <button class="p-2 rounded-full hover:bg-gray-600" onclick="simulateHome()">
232
+ <i data-lucide="circle" class="w-5 h-5"></i>
233
+ </button>
234
+ <button class="p-2 rounded-full hover:bg-gray-600" onclick="simulateRecent()">
235
+ <i data-lucide="square" class="w-5 h-5"></i>
236
+ </button>
237
+ <button class="p-2 rounded-full hover:bg-gray-600" onclick="uploadLogo()">
238
+ <i data-lucide="image" class="w-5 h-5"></i>
239
+ </button>
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ <!-- Status Bar -->
245
+ <div class="h-6 bg-[#3DDC84] text-black text-xs flex items-center px-3 justify-between">
246
+ <div class="flex items-center space-x-4">
247
+ <span id="status-message" class="font-medium">Ready</span>
248
+ <span id="build-status" class="hidden md:inline">● Gradle sync</span>
249
+ </div>
250
+ <div class="flex items-center space-x-4">
251
+ <span class="flex items-center space-x-1">
252
+ <i data-lucide="code" class="w-3 h-3"></i>
253
+ <span>Android 10 (API 29)</span>
254
+ </span>
255
+ <span class="flex items-center space-x-1 cursor-pointer hover:underline">
256
+ <i data-lucide="download" class="w-3 h-3"></i>
257
+ <span>Download APK</span>
258
+ </span>
259
+ </div>
260
+ </div>
261
+
262
+ <!-- Scripts -->
263
+ <script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script>
264
+ <script src="script.js"></script>
265
+ </body>
266
+ </html>
package.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "android-studio-web-real",
3
+ "version": "1.0.0",
4
+ "description": "Real Android Studio Web with Android 10 Preview",
5
+ "main": "server.js",
6
+ "scripts": {
7
+ "start": "node server.js",
8
+ "dev": "nodemon server.js"
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.18.2",
12
+ "ws": "^8.14.2",
13
+ "node-pty": "^1.0.0",
14
+ "multer": "^1.4.5-lts.1",
15
+ "adm-zip": "^0.5.10",
16
+ "archiver": "^6.0.1"
17
+ },
18
+ "engines": {
19
+ "node": ">=18.0.0"
20
+ }
21
+ }
script.js ADDED
@@ -0,0 +1,648 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Android Studio Web - Complete JavaScript with Real Build
2
+
3
+ // ==================== Global Variables ====================
4
+ let currentProject = null;
5
+ let currentFile = null;
6
+ let currentFilePath = null;
7
+ let terminal = null;
8
+ let ws = null;
9
+ let activeBuildId = null;
10
+ let files = [];
11
+ let projects = [];
12
+
13
+ // ==================== Initialize on Load ====================
14
+ document.addEventListener('DOMContentLoaded', function() {
15
+ // Load Lucide icons
16
+ lucide.createIcons();
17
+
18
+ // Initialize terminal
19
+ initTerminal();
20
+
21
+ // Load projects
22
+ loadProjects();
23
+
24
+ // Setup event listeners
25
+ setupEventListeners();
26
+
27
+ // Update status
28
+ updateStatus('Ready', 'success');
29
+ });
30
+
31
+ // ==================== Terminal Functions ====================
32
+ function initTerminal() {
33
+ const terminalContainer = document.getElementById('terminal-panel');
34
+
35
+ terminal = new Terminal({
36
+ theme: {
37
+ background: '#1E1E1E',
38
+ foreground: '#A9B7C6',
39
+ cursor: '#3DDC84',
40
+ selection: '#214283'
41
+ },
42
+ fontFamily: 'JetBrains Mono, monospace',
43
+ fontSize: 12,
44
+ cursorBlink: true,
45
+ scrollback: 5000
46
+ });
47
+
48
+ terminal.open(terminalContainer);
49
+
50
+ // Connect WebSocket
51
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
52
+ const wsUrl = `${protocol}//${window.location.host}`;
53
+
54
+ ws = new WebSocket(wsUrl);
55
+
56
+ ws.onopen = () => {
57
+ terminal.write('\x1b[32mAndroid Studio Web Terminal\x1b[0m\r\n');
58
+ terminal.write('\x1b[32mProject workspace: /workspace\x1b[0m\r\n');
59
+ terminal.write('$ ');
60
+ };
61
+
62
+ ws.onmessage = (event) => {
63
+ const data = JSON.parse(event.data);
64
+ if (data.type === 'data') {
65
+ terminal.write(data.data);
66
+ }
67
+ };
68
+
69
+ ws.onerror = (error) => {
70
+ terminal.write('\x1b[31mTerminal connection error\x1b[0m\r\n');
71
+ };
72
+
73
+ terminal.onData((data) => {
74
+ if (ws && ws.readyState === WebSocket.OPEN) {
75
+ ws.send(JSON.stringify({ type: 'input', data }));
76
+ }
77
+ });
78
+
79
+ terminal.onResize((size) => {
80
+ if (ws && ws.readyState === WebSocket.OPEN) {
81
+ ws.send(JSON.stringify({ type: 'resize', data: size }));
82
+ }
83
+ });
84
+ }
85
+
86
+ // ==================== Project Functions ====================
87
+ async function loadProjects() {
88
+ try {
89
+ const response = await fetch('/api/projects');
90
+ const data = await response.json();
91
+ projects = data.projects || [];
92
+ renderProjectList();
93
+ } catch (error) {
94
+ console.error('Failed to load projects:', error);
95
+ }
96
+ }
97
+
98
+ function renderProjectList() {
99
+ const projectList = document.getElementById('project-list');
100
+ if (!projectList) return;
101
+
102
+ if (projects.length === 0) {
103
+ projectList.innerHTML = '<div class="p-2 text-gray-400">No projects found</div>';
104
+ return;
105
+ }
106
+
107
+ let html = '';
108
+ projects.forEach(project => {
109
+ html += `
110
+ <div class="project-item" onclick="openProject('${project}')">
111
+ <i data-lucide="folder" class="w-4 h-4 text-yellow-500"></i>
112
+ <span>${project}</span>
113
+ </div>
114
+ `;
115
+ });
116
+
117
+ projectList.innerHTML = html;
118
+ lucide.createIcons();
119
+ }
120
+
121
+ function toggleProjectDropdown() {
122
+ const dropdown = document.getElementById('project-dropdown');
123
+ dropdown.classList.toggle('hidden');
124
+ }
125
+
126
+ async function createNewProject() {
127
+ const name = prompt('Enter project name:');
128
+ if (!name) return;
129
+
130
+ updateStatus('Creating project...', 'building');
131
+
132
+ try {
133
+ const response = await fetch('/api/project/create', {
134
+ method: 'POST',
135
+ headers: { 'Content-Type': 'application/json' },
136
+ body: JSON.stringify({ name })
137
+ });
138
+
139
+ const data = await response.json();
140
+
141
+ if (data.success) {
142
+ await loadProjects();
143
+ await openProject(name);
144
+ updateStatus('Project created', 'success');
145
+ } else {
146
+ updateStatus('Failed to create project', 'error');
147
+ }
148
+ } catch (error) {
149
+ updateStatus('Error creating project', 'error');
150
+ }
151
+
152
+ toggleProjectDropdown();
153
+ }
154
+
155
+ async function openProject(projectName) {
156
+ currentProject = projectName;
157
+ document.getElementById('current-project').textContent = projectName;
158
+ document.getElementById('project-name-display').textContent = projectName;
159
+ document.getElementById('preview-app-name').textContent = projectName;
160
+
161
+ toggleProjectDropdown();
162
+ await loadProjectFiles();
163
+ }
164
+
165
+ async function loadProjectFiles() {
166
+ if (!currentProject) return;
167
+
168
+ try {
169
+ const response = await fetch(`/api/files/${currentProject}/`);
170
+ const data = await response.json();
171
+
172
+ if (data.type === 'directory') {
173
+ files = data.files || [];
174
+ renderFileTree();
175
+ }
176
+ } catch (error) {
177
+ console.error('Failed to load files:', error);
178
+ }
179
+ }
180
+
181
+ function renderFileTree() {
182
+ const treeElement = document.getElementById('file-tree');
183
+
184
+ if (files.length === 0) {
185
+ treeElement.innerHTML = '<div class="px-3 py-1 text-xs text-gray-500">No files found</div>';
186
+ return;
187
+ }
188
+
189
+ let html = '<div class="px-3 py-1 text-xs text-gray-500 uppercase">app/src/main</div>';
190
+
191
+ // Simple file tree - you can make this recursive for full structure
192
+ files.forEach(file => {
193
+ if (file.includes('.')) {
194
+ const icon = file.endsWith('.kt') ? 'file-code' :
195
+ file.endsWith('.xml') ? 'file-text' : 'file';
196
+ const color = file.endsWith('.kt') ? 'text-blue-300' :
197
+ file.endsWith('.xml') ? 'text-orange-300' : 'text-gray-400';
198
+
199
+ html += `
200
+ <div class="file-item px-3 py-1.5 flex items-center space-x-2 cursor-pointer text-sm"
201
+ onclick="openFile('${file}')">
202
+ <i data-lucide="${icon}" class="w-4 h-4 ${color}"></i>
203
+ <span>${file}</span>
204
+ </div>
205
+ `;
206
+ }
207
+ });
208
+
209
+ treeElement.innerHTML = html;
210
+ lucide.createIcons();
211
+ }
212
+
213
+ // ==================== File Functions ====================
214
+ async function openFile(filename) {
215
+ if (!currentProject) {
216
+ alert('Please open a project first');
217
+ return;
218
+ }
219
+
220
+ currentFile = filename;
221
+ currentFilePath = filename;
222
+
223
+ try {
224
+ const response = await fetch(`/api/files/${currentProject}/${filename}`);
225
+ const data = await response.json();
226
+
227
+ if (data.type === 'file') {
228
+ document.getElementById('code-editor').innerHTML = data.content;
229
+ updateLineNumbers();
230
+ addTab(filename);
231
+ }
232
+ } catch (error) {
233
+ console.error('Failed to open file:', error);
234
+ }
235
+
236
+ // Update active state
237
+ document.querySelectorAll('.file-item').forEach(el => {
238
+ el.classList.remove('active');
239
+ if (el.textContent.trim() === filename) {
240
+ el.classList.add('active');
241
+ }
242
+ });
243
+ }
244
+
245
+ function addTab(filename) {
246
+ const tabs = document.getElementById('editor-tabs');
247
+
248
+ // Check if tab already exists
249
+ const existingTab = Array.from(tabs.children).find(
250
+ tab => tab.dataset.file === filename
251
+ );
252
+
253
+ if (existingTab) return;
254
+
255
+ const icon = filename.endsWith('.kt') ? 'file-code' :
256
+ filename.endsWith('.xml') ? 'file-text' : 'file';
257
+ const color = filename.endsWith('.kt') ? 'text-blue-300' :
258
+ filename.endsWith('.xml') ? 'text-orange-300' : 'text-gray-400';
259
+
260
+ const tabHtml = `
261
+ <div class="px-4 py-2 flex items-center space-x-2 text-sm text-white bg-[#2B2B2B] border-t-2 border-[#3DDC84] cursor-pointer"
262
+ data-file="${filename}"
263
+ onclick="switchToTab('${filename}')">
264
+ <i data-lucide="${icon}" class="w-4 h-4 ${color}"></i>
265
+ <span>${filename}</span>
266
+ <i data-lucide="x" class="w-3 h-3 hover:text-red-400 cursor-pointer ml-2"
267
+ onclick="closeTab(event, '${filename}')"></i>
268
+ </div>
269
+ `;
270
+
271
+ tabs.insertAdjacentHTML('beforeend', tabHtml);
272
+ lucide.createIcons();
273
+ }
274
+
275
+ function switchToTab(filename) {
276
+ openFile(filename);
277
+ }
278
+
279
+ function closeTab(event, filename) {
280
+ event.stopPropagation();
281
+ const tab = event.target.closest('[data-file]');
282
+ if (tab) {
283
+ tab.remove();
284
+ if (currentFile === filename) {
285
+ const firstTab = document.querySelector('[data-file]');
286
+ if (firstTab) {
287
+ openFile(firstTab.dataset.file);
288
+ } else {
289
+ currentFile = null;
290
+ document.getElementById('code-editor').innerHTML = '// No file open';
291
+ updateLineNumbers();
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ async function saveCurrentFile() {
298
+ if (!currentProject || !currentFile) {
299
+ alert('No file open');
300
+ return;
301
+ }
302
+
303
+ const content = document.getElementById('code-editor').innerText;
304
+
305
+ try {
306
+ const response = await fetch(`/api/file/${currentProject}/${currentFile}`, {
307
+ method: 'POST',
308
+ headers: { 'Content-Type': 'application/json' },
309
+ body: JSON.stringify({ content })
310
+ });
311
+
312
+ const data = await response.json();
313
+
314
+ if (data.success) {
315
+ updateStatus('File saved', 'success');
316
+ }
317
+ } catch (error) {
318
+ updateStatus('Save failed', 'error');
319
+ }
320
+ }
321
+
322
+ function createNewFile() {
323
+ if (!currentProject) {
324
+ alert('Please open a project first');
325
+ return;
326
+ }
327
+
328
+ const filename = prompt('Enter file name (e.g., NewFile.kt):');
329
+ if (filename) {
330
+ currentFile = filename;
331
+ document.getElementById('code-editor').innerHTML = getDefaultContent(filename);
332
+ updateLineNumbers();
333
+ addTab(filename);
334
+ saveCurrentFile();
335
+ }
336
+ }
337
+
338
+ function getDefaultContent(filename) {
339
+ if (filename.endsWith('.kt')) {
340
+ return `package com.example.app
341
+
342
+ import android.os.Bundle
343
+ import androidx.appcompat.app.AppCompatActivity
344
+
345
+ class MainActivity : AppCompatActivity() {
346
+ override fun onCreate(savedInstanceState: Bundle?) {
347
+ super.onCreate(savedInstanceState)
348
+ setContentView(R.layout.activity_main)
349
+ }
350
+ }`;
351
+ } else if (filename.endsWith('.xml')) {
352
+ return `<?xml version="1.0" encoding="utf-8"?>
353
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
354
+ android:layout_width="match_parent"
355
+ android:layout_height="match_parent"
356
+ android:orientation="vertical">
357
+
358
+ <TextView
359
+ android:layout_width="wrap_content"
360
+ android:layout_height="wrap_content"
361
+ android:text="Hello World!" />
362
+
363
+ </LinearLayout>`;
364
+ }
365
+ return '';
366
+ }
367
+
368
+ function refreshFiles() {
369
+ if (currentProject) {
370
+ loadProjectFiles();
371
+ }
372
+ }
373
+
374
+ // ==================== Build Functions ====================
375
+ async function startBuild() {
376
+ if (!currentProject) {
377
+ alert('Please open a project first');
378
+ return;
379
+ }
380
+
381
+ switchPanel('build');
382
+ const buildPanel = document.getElementById('build-panel');
383
+ buildPanel.innerHTML = '<div class="building">🔨 Building APK...</div>';
384
+ updateStatus('Building...', 'building');
385
+
386
+ try {
387
+ const response = await fetch(`/api/build/${currentProject}`, {
388
+ method: 'POST'
389
+ });
390
+
391
+ const data = await response.json();
392
+ activeBuildId = data.buildId;
393
+
394
+ // Poll for build status
395
+ checkBuildStatus(activeBuildId);
396
+
397
+ } catch (error) {
398
+ buildPanel.innerHTML = '<div class="build-error">❌ Build failed to start</div>';
399
+ updateStatus('Build failed', 'error');
400
+ }
401
+ }
402
+
403
+ async function checkBuildStatus(buildId) {
404
+ const buildPanel = document.getElementById('build-panel');
405
+
406
+ try {
407
+ const response = await fetch(`/api/build/${buildId}`);
408
+ const data = await response.json();
409
+
410
+ if (data.output) {
411
+ let html = data.output.replace(/\n/g, '<br>')
412
+ .replace(/SUCCESS/g, '<span class="build-success">SUCCESS</span>')
413
+ .replace(/FAILED/g, '<span class="build-error">FAILED</span>')
414
+ .replace(/warning/g, '<span class="build-warning">warning</span>');
415
+
416
+ buildPanel.innerHTML = html;
417
+ }
418
+
419
+ if (data.success !== undefined) {
420
+ if (data.success) {
421
+ buildPanel.innerHTML += '<br><br><div class="build-success">✅ Build successful! APK ready.</div>';
422
+ updateStatus('Build successful', 'success');
423
+ } else {
424
+ buildPanel.innerHTML += '<br><br><div class="build-error">❌ Build failed</div>';
425
+ updateStatus('Build failed', 'error');
426
+ }
427
+ } else {
428
+ // Still building, check again
429
+ setTimeout(() => checkBuildStatus(buildId), 1000);
430
+ }
431
+
432
+ } catch (error) {
433
+ buildPanel.innerHTML = '<div class="build-error">Error checking build status</div>';
434
+ }
435
+ }
436
+
437
+ async function downloadAPK() {
438
+ if (!currentProject) {
439
+ alert('Please open a project first');
440
+ return;
441
+ }
442
+
443
+ window.location.href = `/api/apk/${currentProject}`;
444
+ }
445
+
446
+ // ==================== Preview Functions ====================
447
+ async function uploadLogo() {
448
+ if (!currentProject) {
449
+ alert('Please open a project first');
450
+ return;
451
+ }
452
+
453
+ const input = document.createElement('input');
454
+ input.type = 'file';
455
+ input.accept = 'image/*';
456
+
457
+ input.onchange = async (e) => {
458
+ const file = e.target.files[0];
459
+ const formData = new FormData();
460
+ formData.append('logo', file);
461
+
462
+ updateStatus('Uploading logo...', 'building');
463
+
464
+ try {
465
+ const response = await fetch(`/api/logo/upload/${currentProject}`, {
466
+ method: 'POST',
467
+ body: formData
468
+ });
469
+
470
+ const data = await response.json();
471
+
472
+ if (data.success) {
473
+ // Update preview logo
474
+ const reader = new FileReader();
475
+ reader.onload = (e) => {
476
+ document.getElementById('preview-logo').src = e.target.result;
477
+ };
478
+ reader.readAsDataURL(file);
479
+
480
+ updateStatus('Logo updated', 'success');
481
+ }
482
+ } catch (error) {
483
+ updateStatus('Logo upload failed', 'error');
484
+ }
485
+ };
486
+
487
+ input.click();
488
+ }
489
+
490
+ function installOnPreview() {
491
+ const appScreen = document.getElementById('app-screen');
492
+ const logo = document.getElementById('preview-logo');
493
+ const appName = document.getElementById('preview-app-name');
494
+
495
+ // Simulate app installation
496
+ updateStatus('Installing on Android 10...', 'building');
497
+
498
+ setTimeout(() => {
499
+ appScreen.innerHTML = `
500
+ <div class="logo-container">
501
+ <img id="preview-logo" src="${logo.src}" alt="App Logo" style="width: 96px; height: 96px; border-radius: 20px;">
502
+ <h3 id="preview-app-name" style="font-size: 20px; font-weight: 600; color: #333; margin-top: 16px;">${appName.textContent}</h3>
503
+ <p style="font-size: 12px; color: #666; margin-top: 4px;">Running on Android 10</p>
504
+ <div style="margin-top: 20px; padding: 8px 16px; background: #3DDC84; color: white; border-radius: 20px; font-size: 12px;">
505
+ App installed
506
+ </div>
507
+ </div>
508
+ `;
509
+ updateStatus('App installed on preview', 'success');
510
+ }, 2000);
511
+ }
512
+
513
+ function rotatePreview() {
514
+ const phone = document.getElementById('phone-preview');
515
+ phone.classList.toggle('rotated');
516
+ }
517
+
518
+ function toggleTheme() {
519
+ const statusBar = document.getElementById('status-bar');
520
+ const appScreen = document.getElementById('app-screen');
521
+ const navBar = document.getElementById('nav-bar');
522
+
523
+ statusBar.classList.toggle('light');
524
+ appScreen.classList.toggle('dark');
525
+ navBar.classList.toggle('light');
526
+ }
527
+
528
+ function simulateBack() {
529
+ updateStatus('Back button pressed', 'info');
530
+ }
531
+
532
+ function simulateHome() {
533
+ updateStatus('Home button pressed', 'info');
534
+ // Reset to home screen
535
+ const appScreen = document.getElementById('app-screen');
536
+ appScreen.innerHTML = `
537
+ <div class="logo-container">
538
+ <img id="preview-logo" src="${document.getElementById('preview-logo').src}" alt="App Logo" style="width: 96px; height: 96px; border-radius: 20px;">
539
+ <h3 id="preview-app-name" style="font-size: 20px; font-weight: 600; color: #333; margin-top: 16px;">${document.getElementById('preview-app-name').textContent}</h3>
540
+ <p class="app-version">Android 10 (API 29)</p>
541
+ </div>
542
+ `;
543
+ }
544
+
545
+ function simulateRecent() {
546
+ updateStatus('Recent apps button pressed', 'info');
547
+ }
548
+
549
+ // ==================== UI Functions ====================
550
+ function updateLineNumbers() {
551
+ const editor = document.getElementById('code-editor');
552
+ const lines = editor.innerText.split('\n').length;
553
+ const lineNumbers = document.getElementById('line-numbers');
554
+
555
+ let html = '';
556
+ for (let i = 1; i <= lines; i++) {
557
+ html += `<div class="leading-6">${i}</div>`;
558
+ }
559
+ lineNumbers.innerHTML = html;
560
+ }
561
+
562
+ function syncScroll() {
563
+ const editor = document.getElementById('code-editor');
564
+ const lineNumbers = document.getElementById('line-numbers');
565
+ lineNumbers.scrollTop = editor.scrollTop;
566
+ }
567
+
568
+ function switchPanel(panel) {
569
+ // Hide all panels
570
+ document.getElementById('terminal-panel').classList.add('hidden');
571
+ document.getElementById('build-panel').classList.add('hidden');
572
+
573
+ // Show selected panel
574
+ document.getElementById(`${panel}-panel`).classList.remove('hidden');
575
+
576
+ // Update tab styles
577
+ document.querySelectorAll('.panel-tab').forEach(tab => {
578
+ tab.classList.remove('border-[#3DDC84]', 'text-white');
579
+ tab.classList.add('text-gray-400');
580
+ });
581
+
582
+ event.currentTarget.classList.add('border-[#3DDC84]', 'text-white');
583
+ event.currentTarget.classList.remove('text-gray-400');
584
+ }
585
+
586
+ function updateStatus(message, type) {
587
+ const statusEl = document.getElementById('status-message');
588
+ statusEl.textContent = message;
589
+
590
+ const colors = {
591
+ success: '#3DDC84',
592
+ error: '#CF6679',
593
+ building: '#FBC02D',
594
+ info: '#64B5F6'
595
+ };
596
+
597
+ statusEl.style.color = colors[type] || '#000000';
598
+ }
599
+
600
+ function openSettings() {
601
+ alert('Settings dialog would open here');
602
+ }
603
+
604
+ function setupEventListeners() {
605
+ // Keyboard shortcuts
606
+ document.addEventListener('keydown', (e) => {
607
+ if (e.ctrlKey && e.key === 's') {
608
+ e.preventDefault();
609
+ saveCurrentFile();
610
+ }
611
+
612
+ if (e.key === 'F5') {
613
+ e.preventDefault();
614
+ startBuild();
615
+ }
616
+ });
617
+
618
+ // Click outside to close dropdown
619
+ document.addEventListener('click', (e) => {
620
+ if (!e.target.closest('.relative')) {
621
+ document.getElementById('project-dropdown').classList.add('hidden');
622
+ }
623
+ });
624
+ }
625
+
626
+ // Export functions for HTML
627
+ window.toggleProjectDropdown = toggleProjectDropdown;
628
+ window.createNewProject = createNewProject;
629
+ window.openProject = openProject;
630
+ window.openFile = openFile;
631
+ window.saveCurrentFile = saveCurrentFile;
632
+ window.createNewFile = createNewFile;
633
+ window.refreshFiles = refreshFiles;
634
+ window.startBuild = startBuild;
635
+ window.downloadAPK = downloadAPK;
636
+ window.installOnPreview = installOnPreview;
637
+ window.rotatePreview = rotatePreview;
638
+ window.toggleTheme = toggleTheme;
639
+ window.uploadLogo = uploadLogo;
640
+ window.simulateBack = simulateBack;
641
+ window.simulateHome = simulateHome;
642
+ window.simulateRecent = simulateRecent;
643
+ window.switchPanel = switchPanel;
644
+ window.updateLineNumbers = updateLineNumbers;
645
+ window.syncScroll = syncScroll;
646
+ window.openSettings = openSettings;
647
+ window.switchToTab = switchToTab;
648
+ window.closeTab = closeTab;
server.js ADDED
@@ -0,0 +1,473 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const http = require('http');
3
+ const WebSocket = require('ws');
4
+ const { exec, spawn } = require('child_process');
5
+ const fs = require('fs').promises;
6
+ const path = require('path');
7
+ const multer = require('multer');
8
+ const AdmZip = require('adm-zip');
9
+ const archiver = require('archiver');
10
+
11
+ const app = express();
12
+ const server = http.createServer(app);
13
+ const wss = new WebSocket.Server({ server });
14
+ const upload = multer({ dest: 'uploads/' });
15
+
16
+ // Store active builds and terminals
17
+ const activeBuilds = new Map();
18
+ const activeTerminals = new Map();
19
+
20
+ // Middleware
21
+ app.use(express.json({ limit: '50mb' }));
22
+ app.use(express.urlencoded({ extended: true, limit: '50mb' }));
23
+
24
+ // Serve all static files from root
25
+ app.use(express.static(__dirname));
26
+
27
+ // ==================== API Routes ====================
28
+
29
+ // 1. Home page
30
+ app.get('/', (req, res) => {
31
+ res.sendFile(path.join(__dirname, 'index.html'));
32
+ });
33
+
34
+ // 2. Get all projects
35
+ app.get('/api/projects', async (req, res) => {
36
+ try {
37
+ const projects = await fs.readdir('/workspace');
38
+ res.json({ projects });
39
+ } catch (error) {
40
+ res.json({ projects: [] });
41
+ }
42
+ });
43
+
44
+ // 3. Create new project
45
+ app.post('/api/project/create', async (req, res) => {
46
+ const { name, template = 'empty' } = req.body;
47
+ const projectPath = path.join('/workspace', name);
48
+
49
+ try {
50
+ await fs.mkdir(projectPath, { recursive: true });
51
+ await createAndroidProject(projectPath, name);
52
+ res.json({ success: true, message: 'Project created', path: projectPath });
53
+ } catch (error) {
54
+ res.status(500).json({ error: error.message });
55
+ }
56
+ });
57
+
58
+ // 4. Get project files
59
+ app.get('/api/files/:project/*', async (req, res) => {
60
+ const { project, '0': filePath } = req.params;
61
+ const fullPath = path.join('/workspace', project, filePath || '');
62
+
63
+ try {
64
+ const stat = await fs.stat(fullPath);
65
+
66
+ if (stat.isDirectory()) {
67
+ const files = await fs.readdir(fullPath);
68
+ res.json({ type: 'directory', files });
69
+ } else {
70
+ const content = await fs.readFile(fullPath, 'utf-8');
71
+ res.json({ type: 'file', content });
72
+ }
73
+ } catch (error) {
74
+ res.status(404).json({ error: 'Not found' });
75
+ }
76
+ });
77
+
78
+ // 5. Save file
79
+ app.post('/api/file/:project/*', async (req, res) => {
80
+ const { project, '0': filePath } = req.params;
81
+ const { content } = req.body;
82
+ const fullPath = path.join('/workspace', project, filePath);
83
+
84
+ try {
85
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
86
+ await fs.writeFile(fullPath, content);
87
+ res.json({ success: true });
88
+ } catch (error) {
89
+ res.status(500).json({ error: error.message });
90
+ }
91
+ });
92
+
93
+ // 6. Build project
94
+ app.post('/api/build/:project', (req, res) => {
95
+ const { project } = req.params;
96
+ const projectPath = path.join('/workspace', project);
97
+ const buildId = Date.now().toString();
98
+
99
+ const buildProcess = spawn('./gradlew', ['assembleDebug'], {
100
+ cwd: projectPath,
101
+ env: {
102
+ ...process.env,
103
+ ANDROID_HOME: '/android-sdk',
104
+ JAVA_HOME: '/usr/lib/jvm/java-17-openjdk-amd64'
105
+ }
106
+ });
107
+
108
+ let output = '';
109
+
110
+ buildProcess.stdout.on('data', (data) => {
111
+ output += data.toString();
112
+ });
113
+
114
+ buildProcess.stderr.on('data', (data) => {
115
+ output += data.toString();
116
+ });
117
+
118
+ buildProcess.on('close', (code) => {
119
+ const success = code === 0;
120
+ const apkPath = path.join(projectPath, 'app/build/outputs/apk/debug/app-debug.apk');
121
+
122
+ activeBuilds.set(buildId, {
123
+ success,
124
+ output,
125
+ apkPath: success ? apkPath : null,
126
+ project
127
+ });
128
+
129
+ res.json({
130
+ buildId,
131
+ success,
132
+ output,
133
+ message: success ? 'Build successful!' : 'Build failed'
134
+ });
135
+ });
136
+ });
137
+
138
+ // 7. Get build status
139
+ app.get('/api/build/:buildId', (req, res) => {
140
+ const { buildId } = req.params;
141
+ const build = activeBuilds.get(buildId);
142
+
143
+ if (build) {
144
+ res.json(build);
145
+ } else {
146
+ res.status(404).json({ error: 'Build not found' });
147
+ }
148
+ });
149
+
150
+ // 8. Download APK
151
+ app.get('/api/apk/:project', async (req, res) => {
152
+ const { project } = req.params;
153
+ const apkPath = path.join('/workspace', project, 'app/build/outputs/apk/debug/app-debug.apk');
154
+
155
+ try {
156
+ await fs.access(apkPath);
157
+ res.download(apkPath, `${project}.apk`);
158
+ } catch (error) {
159
+ res.status(404).json({ error: 'APK not found. Build first!' });
160
+ }
161
+ });
162
+
163
+ // 9. Upload logo
164
+ app.post('/api/logo/upload/:project', upload.single('logo'), async (req, res) => {
165
+ const { project } = req.params;
166
+ const logoPath = req.file.path;
167
+
168
+ try {
169
+ // Convert to Android icon sizes and copy to project
170
+ const resDir = path.join('/workspace', project, 'app/src/main/res');
171
+
172
+ // Create mipmap directories
173
+ const sizes = {
174
+ 'mipmap-hdpi': 72,
175
+ 'mipmap-mdpi': 48,
176
+ 'mipmap-xhdpi': 96,
177
+ 'mipmap-xxhdpi': 144,
178
+ 'mipmap-xxxhdpi': 192
179
+ };
180
+
181
+ for (const [dir, size] of Object.entries(sizes)) {
182
+ const destDir = path.join(resDir, dir);
183
+ await fs.mkdir(destDir, { recursive: true });
184
+
185
+ // Copy and rename to ic_launcher.png
186
+ const destPath = path.join(destDir, 'ic_launcher.png');
187
+ await fs.copyFile(logoPath, destPath);
188
+ }
189
+
190
+ res.json({ success: true, message: 'Logo updated successfully' });
191
+ } catch (error) {
192
+ res.status(500).json({ error: error.message });
193
+ }
194
+ });
195
+
196
+ // 10. Terminal WebSocket
197
+ wss.on('connection', (ws) => {
198
+ console.log('Terminal connected');
199
+
200
+ const { spawn } = require('node-pty');
201
+ const shell = spawn('bash', [], {
202
+ name: 'xterm-color',
203
+ cols: 80,
204
+ rows: 24,
205
+ cwd: '/workspace',
206
+ env: process.env
207
+ });
208
+
209
+ const terminalId = Date.now().toString();
210
+ activeTerminals.set(terminalId, shell);
211
+
212
+ shell.on('data', (data) => {
213
+ ws.send(JSON.stringify({ type: 'data', data: data.toString() }));
214
+ });
215
+
216
+ ws.on('message', (msg) => {
217
+ try {
218
+ const { type, data } = JSON.parse(msg);
219
+ if (type === 'input') {
220
+ shell.write(data);
221
+ } else if (type === 'resize') {
222
+ shell.resize(data.cols, data.rows);
223
+ }
224
+ } catch (e) {
225
+ console.error('Terminal error:', e);
226
+ }
227
+ });
228
+
229
+ ws.on('close', () => {
230
+ shell.kill();
231
+ activeTerminals.delete(terminalId);
232
+ });
233
+ });
234
+
235
+ // ==================== Helper Functions ====================
236
+
237
+ async function createAndroidProject(projectPath, name) {
238
+ const packageName = `com.${name.toLowerCase().replace(/[^a-z]/g, '')}.app`;
239
+
240
+ // Create directory structure
241
+ const mainPath = path.join(projectPath, 'app/src/main');
242
+ const javaPath = path.join(mainPath, 'java', ...packageName.split('.'));
243
+ const resPath = path.join(mainPath, 'res');
244
+ const layoutPath = path.join(resPath, 'layout');
245
+ const drawablePath = path.join(resPath, 'drawable');
246
+ const valuesPath = path.join(resPath, 'values');
247
+ const mipmapPath = path.join(resPath, 'mipmap-hdpi');
248
+
249
+ await fs.mkdir(javaPath, { recursive: true });
250
+ await fs.mkdir(layoutPath, { recursive: true });
251
+ await fs.mkdir(drawablePath, { recursive: true });
252
+ await fs.mkdir(valuesPath, { recursive: true });
253
+ await fs.mkdir(mipmapPath, { recursive: true });
254
+
255
+ // 1. settings.gradle
256
+ await fs.writeFile(path.join(projectPath, 'settings.gradle'),
257
+ `rootProject.name = "${name}"\ninclude ':app'`);
258
+
259
+ // 2. build.gradle (project)
260
+ await fs.writeFile(path.join(projectPath, 'build.gradle'), `
261
+ // Top-level build file
262
+ buildscript {
263
+ ext.kotlin_version = '1.9.0'
264
+ repositories {
265
+ google()
266
+ mavenCentral()
267
+ }
268
+ dependencies {
269
+ classpath 'com.android.tools.build:gradle:8.0.0'
270
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
271
+ }
272
+ }
273
+
274
+ allprojects {
275
+ repositories {
276
+ google()
277
+ mavenCentral()
278
+ }
279
+ }
280
+
281
+ task clean(type: Delete) {
282
+ delete rootProject.buildDir
283
+ }
284
+ `);
285
+
286
+ // 3. gradle.properties
287
+ await fs.writeFile(path.join(projectPath, 'gradle.properties'), `
288
+ org.gradle.jvmargs=-Xmx2048m
289
+ android.useAndroidX=true
290
+ kotlin.code.style=official
291
+ `);
292
+
293
+ // 4. gradlew wrapper
294
+ const gradlew = `#!/usr/bin/env bash
295
+ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
296
+ $DIR/gradlew "$@"
297
+ `;
298
+ await fs.writeFile(path.join(projectPath, 'gradlew'), gradlew);
299
+ await fs.chmod(path.join(projectPath, 'gradlew'), 0o755);
300
+
301
+ // 5. app/build.gradle
302
+ await fs.writeFile(path.join(projectPath, 'app/build.gradle'), `
303
+ plugins {
304
+ id 'com.android.application'
305
+ id 'org.jetbrains.kotlin.android'
306
+ }
307
+
308
+ android {
309
+ compileSdk 34
310
+
311
+ defaultConfig {
312
+ applicationId "${packageName}"
313
+ minSdk 29
314
+ targetSdk 34
315
+ versionCode 1
316
+ versionName "1.0"
317
+ }
318
+
319
+ buildTypes {
320
+ release {
321
+ minifyEnabled false
322
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
323
+ }
324
+ }
325
+
326
+ compileOptions {
327
+ sourceCompatibility JavaVersion.VERSION_17
328
+ targetCompatibility JavaVersion.VERSION_17
329
+ }
330
+
331
+ kotlinOptions {
332
+ jvmTarget = '17'
333
+ }
334
+ }
335
+
336
+ dependencies {
337
+ implementation 'androidx.core:core-ktx:1.12.0'
338
+ implementation 'androidx.appcompat:appcompat:1.6.1'
339
+ implementation 'com.google.android.material:material:1.11.0'
340
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
341
+ }
342
+ `);
343
+
344
+ // 6. AndroidManifest.xml
345
+ await fs.writeFile(path.join(mainPath, 'AndroidManifest.xml'), `
346
+ <?xml version="1.0" encoding="utf-8"?>
347
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
348
+ package="${packageName}">
349
+
350
+ <application
351
+ android:allowBackup="true"
352
+ android:icon="@mipmap/ic_launcher"
353
+ android:label="${name}"
354
+ android:theme="@style/Theme.${name}">
355
+ <activity
356
+ android:name=".MainActivity"
357
+ android:exported="true">
358
+ <intent-filter>
359
+ <action android:name="android.intent.action.MAIN" />
360
+ <category android:name="android.intent.category.LAUNCHER" />
361
+ </intent-filter>
362
+ </activity>
363
+ </application>
364
+
365
+ </manifest>
366
+ `);
367
+
368
+ // 7. MainActivity.kt
369
+ await fs.writeFile(path.join(javaPath, 'MainActivity.kt'), `
370
+ package ${packageName}
371
+
372
+ import android.os.Bundle
373
+ import androidx.appcompat.app.AppCompatActivity
374
+
375
+ class MainActivity : AppCompatActivity() {
376
+ override fun onCreate(savedInstanceState: Bundle?) {
377
+ super.onCreate(savedInstanceState)
378
+ setContentView(R.layout.activity_main)
379
+
380
+ // Your app starts here
381
+ supportActionBar?.title = "${name}"
382
+ }
383
+ }
384
+ `);
385
+
386
+ // 8. activity_main.xml
387
+ await fs.writeFile(path.join(layoutPath, 'activity_main.xml'), `
388
+ <?xml version="1.0" encoding="utf-8"?>
389
+ <LinearLayout
390
+ xmlns:android="http://schemas.android.com/apk/res/android"
391
+ android:layout_width="match_parent"
392
+ android:layout_height="match_parent"
393
+ android:orientation="vertical"
394
+ android:gravity="center"
395
+ android:padding="20dp">
396
+
397
+ <ImageView
398
+ android:id="@+id/app_logo"
399
+ android:layout_width="120dp"
400
+ android:layout_height="120dp"
401
+ android:src="@drawable/ic_launcher_foreground"
402
+ android:layout_marginBottom="20dp" />
403
+
404
+ <TextView
405
+ android:layout_width="wrap_content"
406
+ android:layout_height="wrap_content"
407
+ android:text="${name}"
408
+ android:textSize="28sp"
409
+ android:textStyle="bold"
410
+ android:textColor="#3DDC84"
411
+ android:layout_marginBottom="8dp" />
412
+
413
+ <TextView
414
+ android:layout_width="wrap_content"
415
+ android:layout_height="wrap_content"
416
+ android:text="Android 10 (API 29)"
417
+ android:textSize="16sp"
418
+ android:textColor="#666666"
419
+ android:layout_marginBottom="24dp" />
420
+
421
+ <TextView
422
+ android:layout_width="wrap_content"
423
+ android:layout_height="wrap_content"
424
+ android:text="Welcome to your Android app!"
425
+ android:textSize="14sp"
426
+ android:textColor="#333333" />
427
+
428
+ </LinearLayout>
429
+ `);
430
+
431
+ // 9. strings.xml
432
+ await fs.writeFile(path.join(valuesPath, 'strings.xml'), `
433
+ <resources>
434
+ <string name="app_name">${name}</string>
435
+ </resources>
436
+ `);
437
+
438
+ // 10. colors.xml
439
+ await fs.writeFile(path.join(valuesPath, 'colors.xml'), `
440
+ <?xml version="1.0" encoding="utf-8"?>
441
+ <resources>
442
+ <color name="purple_200">#FFBB86FC</color>
443
+ <color name="purple_500">#FF6200EE</color>
444
+ <color name="purple_700">#FF3700B3</color>
445
+ <color name="teal_200">#FF03DAC5</color>
446
+ <color name="teal_700">#FF018786</color>
447
+ <color name="black">#FF000000</color>
448
+ <color name="white">#FFFFFFFF</color>
449
+ </resources>
450
+ `);
451
+
452
+ // 11. themes.xml
453
+ await fs.writeFile(path.join(valuesPath, 'themes.xml'), `
454
+ <resources xmlns:tools="http://schemas.android.com/tools">
455
+ <style name="Theme.${name}" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
456
+ <item name="colorPrimary">@color/purple_500</item>
457
+ <item name="colorPrimaryVariant">@color/purple_700</item>
458
+ <item name="colorOnPrimary">@color/white</item>
459
+ <item name="colorSecondary">@color/teal_200</item>
460
+ <item name="colorSecondaryVariant">@color/teal_700</item>
461
+ <item name="colorOnSecondary">@color/black</item>
462
+ <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
463
+ </style>
464
+ </resources>
465
+ `);
466
+ }
467
+
468
+ // Start server
469
+ server.listen(7860, () => {
470
+ console.log('🚀 Server running on http://localhost:7860');
471
+ console.log('📱 Android 10 Preview Ready');
472
+ console.log('🔧 Build System Active');
473
+ });
style.css ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Android Studio Web - Complete Styles with Android 10 Preview */
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ transition: background-color 0.2s ease, border-color 0.2s ease;
8
+ }
9
+
10
+ ::selection {
11
+ background-color: #214283;
12
+ color: white;
13
+ }
14
+
15
+ body {
16
+ font-family: 'Inter', sans-serif;
17
+ overflow: hidden;
18
+ }
19
+
20
+ .code-font {
21
+ font-family: 'JetBrains Mono', monospace;
22
+ }
23
+
24
+ /* Custom Scrollbar */
25
+ ::-webkit-scrollbar {
26
+ width: 8px;
27
+ height: 8px;
28
+ }
29
+
30
+ ::-webkit-scrollbar-track {
31
+ background: #2B2B2B;
32
+ }
33
+
34
+ ::-webkit-scrollbar-thumb {
35
+ background: #4E4E4E;
36
+ border-radius: 4px;
37
+ }
38
+
39
+ ::-webkit-scrollbar-thumb:hover {
40
+ background: #5E5E5E;
41
+ }
42
+
43
+ ::-webkit-scrollbar-corner {
44
+ background: #2B2B2B;
45
+ }
46
+
47
+ /* Editor Styles */
48
+ .line-numbers {
49
+ background-color: #313335;
50
+ color: #808080;
51
+ text-align: right;
52
+ padding-right: 12px;
53
+ user-select: none;
54
+ min-width: 40px;
55
+ line-height: 1.5rem;
56
+ font-size: 12px;
57
+ }
58
+
59
+ .editor-content {
60
+ background-color: #1E1E1E;
61
+ color: #A9B7C6;
62
+ outline: none;
63
+ white-space: pre;
64
+ tab-size: 4;
65
+ font-size: 12px;
66
+ line-height: 1.5rem;
67
+ }
68
+
69
+ /* File Tree */
70
+ .file-item {
71
+ cursor: pointer;
72
+ transition: background-color 0.15s ease;
73
+ font-size: 13px;
74
+ }
75
+
76
+ .file-item:hover {
77
+ background-color: #4E4E4E;
78
+ }
79
+
80
+ .file-item.active {
81
+ background-color: #4E4E4E;
82
+ color: white;
83
+ }
84
+
85
+ /* Android 10 Phone Frame */
86
+ .phone-frame {
87
+ width: 280px;
88
+ height: 560px;
89
+ background: #121212;
90
+ border-radius: 40px;
91
+ position: relative;
92
+ overflow: hidden;
93
+ box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
94
+ transition: transform 0.3s ease;
95
+ }
96
+
97
+ .phone-frame.rotated {
98
+ transform: rotate(90deg);
99
+ width: 560px;
100
+ height: 280px;
101
+ }
102
+
103
+ /* Android 10 Status Bar */
104
+ .status-bar {
105
+ display: flex;
106
+ justify-content: space-between;
107
+ padding: 12px 20px;
108
+ background: #121212;
109
+ color: white;
110
+ font-size: 12px;
111
+ font-weight: 500;
112
+ }
113
+
114
+ .status-bar.light {
115
+ background: #FFFFFF;
116
+ color: black;
117
+ }
118
+
119
+ .status-bar .status-icons {
120
+ display: flex;
121
+ gap: 4px;
122
+ }
123
+
124
+ /* App Screen */
125
+ .app-screen {
126
+ height: calc(100% - 80px);
127
+ padding: 20px;
128
+ display: flex;
129
+ flex-direction: column;
130
+ align-items: center;
131
+ justify-content: center;
132
+ background: #F5F5F5;
133
+ }
134
+
135
+ .app-screen.dark {
136
+ background: #1E1E1E;
137
+ }
138
+
139
+ .logo-container {
140
+ text-align: center;
141
+ }
142
+
143
+ #preview-logo {
144
+ width: 96px;
145
+ height: 96px;
146
+ border-radius: 20px;
147
+ margin-bottom: 16px;
148
+ object-fit: cover;
149
+ }
150
+
151
+ #preview-app-name {
152
+ font-size: 20px;
153
+ font-weight: 600;
154
+ color: #333;
155
+ margin-bottom: 4px;
156
+ }
157
+
158
+ .dark #preview-app-name {
159
+ color: white;
160
+ }
161
+
162
+ .app-version {
163
+ font-size: 12px;
164
+ color: #666;
165
+ }
166
+
167
+ .dark .app-version {
168
+ color: #999;
169
+ }
170
+
171
+ /* Android 10 Navigation */
172
+ .nav-bar {
173
+ position: absolute;
174
+ bottom: 0;
175
+ left: 0;
176
+ right: 0;
177
+ height: 40px;
178
+ display: flex;
179
+ justify-content: center;
180
+ align-items: center;
181
+ background: #121212;
182
+ }
183
+
184
+ .nav-bar.light {
185
+ background: #FFFFFF;
186
+ }
187
+
188
+ .nav-handle {
189
+ width: 120px;
190
+ height: 5px;
191
+ background: #4E4E4E;
192
+ border-radius: 3px;
193
+ }
194
+
195
+ /* Terminal */
196
+ .xterm {
197
+ padding: 8px;
198
+ height: 100%;
199
+ }
200
+
201
+ .xterm-viewport {
202
+ overflow-y: auto !important;
203
+ }
204
+
205
+ /* Build Output */
206
+ .build-success {
207
+ color: #3DDC84;
208
+ }
209
+
210
+ .build-error {
211
+ color: #CF6679;
212
+ }
213
+
214
+ .build-warning {
215
+ color: #FBC02D;
216
+ }
217
+
218
+ .build-info {
219
+ color: #64B5F6;
220
+ }
221
+
222
+ /* Animations */
223
+ @keyframes pulse {
224
+ 0%, 100% { opacity: 1; }
225
+ 50% { opacity: 0.5; }
226
+ }
227
+
228
+ .building {
229
+ animation: pulse 1.5s infinite;
230
+ }
231
+
232
+ /* Project Dropdown */
233
+ #project-dropdown {
234
+ max-height: 300px;
235
+ overflow-y: auto;
236
+ }
237
+
238
+ .project-item {
239
+ padding: 8px 12px;
240
+ cursor: pointer;
241
+ border-radius: 4px;
242
+ display: flex;
243
+ align-items: center;
244
+ gap: 8px;
245
+ }
246
+
247
+ .project-item:hover {
248
+ background: #4E4E4E;
249
+ }
250
+
251
+ /* Responsive */
252
+ @media (max-width: 1024px) {
253
+ .w-64 {
254
+ width: 200px;
255
+ }
256
+ .w-80 {
257
+ width: 240px;
258
+ }
259
+ }
260
+
261
+ @media (max-width: 768px) {
262
+ .w-64, .w-80 {
263
+ display: none;
264
+ }
265
+ }