Poorpoor6976 commited on
Commit
f5258f1
·
verified ·
1 Parent(s): 6520ed9

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +1626 -0
app.py ADDED
@@ -0,0 +1,1626 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import json
4
+ import uuid
5
+ import time
6
+ import shutil
7
+ import base64
8
+ import threading
9
+ import subprocess
10
+ import re
11
+ from datetime import datetime
12
+ from flask import Flask, request, jsonify, render_template, send_file, Response
13
+
14
+ app = Flask(__name__)
15
+
16
+ BUILD_DIR_BASE = "/tmp/builds"
17
+ LOG_STORE = {}
18
+ BUILD_STATUS = {}
19
+ BUILD_ARTIFACTS = {}
20
+
21
+ os.makedirs(BUILD_DIR_BASE, exist_ok=True)
22
+
23
+
24
+ def analyze_logs_ai(log_text):
25
+ diagnosis_lines = []
26
+ if "Could not resolve" in log_text or "Could not find" in log_text:
27
+ diagnosis_lines.append("خطأ في التبعيات: لم يتم العثور على مكتبة مطلوبة. تأكد من أن جميع المكتبات المحددة متوفرة في مستودعات Maven/Google.")
28
+ if "FirebaseApp" in log_text or "google-services" in log_text or "com.google.gms" in log_text:
29
+ diagnosis_lines.append("خطأ في Firebase: ملف google-services.json غير صالح أو غير متوافق مع اسم الحزمة. تحقق من إعدادات Firebase الخاصة بك.")
30
+ if "AAPT" in log_text or "resource" in log_text.lower() and "not found" in log_text.lower():
31
+ diagnosis_lines.append("خطأ في الموارد: مورد XML أو صورة مفقودة. تأكد من صحة جميع ملفات الموارد.")
32
+ if "Syntax error" in log_text or "Unexpected token" in log_text or "illegal start" in log_text.lower():
33
+ diagnosis_lines.append("خطأ في بناء الجملة: يوجد خطأ نحوي في كود Java. راجع ملفات المصدر بعناية.")
34
+ if "OutOfMemoryError" in log_text or "heap" in log_text.lower():
35
+ diagnosis_lines.append("خطأ في الذاكرة: نفدت ذاكرة JVM أثناء البناء. حاول زيادة حجم الذاكرة المخصصة.")
36
+ if "SDK location not found" in log_text or "ANDROID_HOME" in log_text:
37
+ diagnosis_lines.append("خطأ في SDK: لم يتم العثور على Android SDK. تحقق من متغيرات البيئة.")
38
+ if "Manifest merger failed" in log_text:
39
+ diagnosis_lines.append("خطأ في دمج الـ Manifest: تعارض بين إعدادات AndroidManifest.xml. راجع الأذونات والإعدادات.")
40
+ if "Execution failed for task" in log_text:
41
+ task_match = re.search(r"Execution failed for task '([^']+)'", log_text)
42
+ if task_match:
43
+ diagnosis_lines.append("فشل تنفيذ المهمة: {} - تحقق من إعدادات هذه المهمة والتبعيات المرتبطة بها.".format(task_match.group(1)))
44
+ if "minSdkVersion" in log_text:
45
+ diagnosis_lines.append("خطأ في إصدار SDK الأدنى: إصدار SDK الأدنى المحدد غير متوافق مع إحدى المكتبات.")
46
+ if "Duplicate class" in log_text:
47
+ diagnosis_lines.append("خطأ تكرار: يوجد تكرار في الفئات بين المكتبات. استخدم exclude لحل التعارضات.")
48
+ if "R8" in log_text and ("error" in log_text.lower() or "failed" in log_text.lower()):
49
+ diagnosis_lines.append("خطأ في R8/ProGuard: فشل تقليص الكود. تحقق من قواعد ProGuard وتأكد من عدم حذف فئات مطلوبة.")
50
+ if not diagnosis_lines:
51
+ if "BUILD SUCCESSFUL" in log_text:
52
+ diagnosis_lines.append("تم البناء بنجاح! لا توجد أخطاء.")
53
+ else:
54
+ diagnosis_lines.append("لم يتم التعرف على خطأ محدد. راجع سجل البناء الكامل يدوياً للبحث عن المشكلة.")
55
+ return "\n".join(diagnosis_lines)
56
+
57
+
58
+ def generate_offline_html():
59
+ return """<!DOCTYPE html>
60
+ <html lang="en">
61
+ <head>
62
+ <meta charset="UTF-8">
63
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
64
+ <title>Offline</title>
65
+ <style>
66
+ *{margin:0;padding:0;box-sizing:border-box}
67
+ body{font-family:'Segoe UI',sans-serif;background:#1a1a2e;color:#e0e0e0;display:flex;justify-content:center;align-items:center;min-height:100vh;text-align:center;padding:20px}
68
+ .container{max-width:400px}
69
+ h1{font-size:2em;margin-bottom:10px;color:#e94560}
70
+ p{font-size:1.1em;color:#aaa;margin-bottom:20px}
71
+ .icon{font-size:4em;margin-bottom:20px}
72
+ button{background:#e94560;color:#fff;border:none;padding:12px 30px;border-radius:8px;font-size:1em;cursor:pointer}
73
+ button:hover{background:#c73652}
74
+ </style>
75
+ </head>
76
+ <body>
77
+ <div class="container">
78
+ <div class="icon">&#128268;</div>
79
+ <h1>No Internet Connection</h1>
80
+ <p>Please check your network settings and try again.</p>
81
+ <button onclick="location.reload()">Retry</button>
82
+ </div>
83
+ </body>
84
+ </html>"""
85
+
86
+
87
+ def generate_keystore(build_dir, dev_name, dev_email, build_id):
88
+ keystore_path = os.path.join(build_dir, "release-key.jks")
89
+ keytool_cmd = [
90
+ "keytool", "-genkeypair",
91
+ "-v",
92
+ "-keystore", keystore_path,
93
+ "-keyalg", "RSA",
94
+ "-keysize", "2048",
95
+ "-validity", "10000",
96
+ "-alias", "releasekey",
97
+ "-storepass", "android123",
98
+ "-keypass", "android123",
99
+ "-dname", "CN={}, OU=Mobile, O=Company, L=City, S=State, C=US".format(dev_name if dev_name else "Developer")
100
+ ]
101
+ subprocess.run(keytool_cmd, capture_output=True, timeout=60)
102
+
103
+ # Extract SHA-1
104
+ list_cmd = [
105
+ "keytool", "-list", "-v",
106
+ "-keystore", keystore_path,
107
+ "-alias", "releasekey",
108
+ "-storepass", "android123",
109
+ "-keypass", "android123"
110
+ ]
111
+ list_process = subprocess.run(list_cmd, capture_output=True, text=True)
112
+ sha1_match = re.search(r"SHA1:\s+([A-Fa-f0-9:]+)", list_process.stdout)
113
+ if sha1_match and build_id in LOG_STORE:
114
+ LOG_STORE[build_id].append("🟢 [IMPORTANT] Your SHA-1 Fingerprint is: {} (Copy this to Google Cloud Console)".format(sha1_match.group(1)))
115
+
116
+ return keystore_path
117
+
118
+
119
+ def create_build_gradle_root(project_dir, use_firebase, use_onesignal):
120
+ firebase_classpath = ""
121
+ if use_firebase:
122
+ firebase_classpath = " classpath 'com.google.gms:google-services:4.4.0'"
123
+ content = """buildscript {{
124
+ repositories {{
125
+ google()
126
+ mavenCentral()
127
+ }}
128
+ dependencies {{
129
+ classpath 'com.android.tools.build:gradle:7.4.2'
130
+ {firebase_cp}
131
+ }}
132
+ }}
133
+
134
+ allprojects {{
135
+ repositories {{
136
+ google()
137
+ mavenCentral()
138
+ }}
139
+ }}
140
+
141
+ task clean(type: Delete) {{
142
+ delete rootProject.buildDir
143
+ }}
144
+ """.format(firebase_cp=firebase_classpath)
145
+ with open(os.path.join(project_dir, "build.gradle"), "w") as f:
146
+ f.write(content)
147
+
148
+
149
+ def create_settings_gradle(project_dir, app_name):
150
+ content = """rootProject.name = "{app_name}"
151
+ include ':app'
152
+ """.format(app_name=app_name.replace('"', '\\"'))
153
+ with open(os.path.join(project_dir, "settings.gradle"), "w") as f:
154
+ f.write(content)
155
+
156
+
157
+ def create_gradle_properties(project_dir):
158
+ content = """org.gradle.jvmargs=-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
159
+ org.gradle.daemon=false
160
+ org.gradle.parallel=true
161
+ org.gradle.caching=true
162
+ android.useAndroidX=true
163
+ android.enableJetifier=true
164
+ """
165
+ with open(os.path.join(project_dir, "gradle.properties"), "w") as f:
166
+ f.write(content)
167
+
168
+
169
+ def create_gradle_wrapper(project_dir):
170
+ wrapper_dir = os.path.join(project_dir, "gradle", "wrapper")
171
+ os.makedirs(wrapper_dir, exist_ok=True)
172
+ props_content = """distributionBase=GRADLE_USER_HOME
173
+ distributionPath=wrapper/dists
174
+ distributionUrl=https\\://services.gradle.org/distributions/gradle-7.6-bin.zip
175
+ zipStoreBase=GRADLE_USER_HOME
176
+ zipStorePath=wrapper/dists
177
+ """
178
+ with open(os.path.join(wrapper_dir, "gradle-wrapper.properties"), "w") as f:
179
+ f.write(props_content)
180
+
181
+
182
+ def create_app_build_gradle(app_dir, config):
183
+ package_name = config.get("package_name", "com.example.webapp")
184
+ version_name = config.get("version_name", "1.0.0")
185
+ version_code = config.get("version_code", "1")
186
+ minify_enabled = "true" if config.get("proguard_enabled") else "false"
187
+
188
+ # 🔥 Use the securely validated flag from config
189
+ use_firebase = config.get("actual_firebase_enabled", False)
190
+
191
+ use_onesignal = config.get("onesignal_enabled", False)
192
+ onesignal_id = config.get("onesignal_app_id", "")
193
+ use_admob = config.get("admob_enabled", False)
194
+ google_client_id = config.get("google_web_client_id", "")
195
+
196
+ dependencies = []
197
+ dependencies.append(" implementation 'androidx.appcompat:appcompat:1.6.1'")
198
+ dependencies.append(" implementation 'androidx.webkit:webkit:1.8.0'")
199
+ dependencies.append(" implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'")
200
+ dependencies.append(" implementation 'com.google.android.material:material:1.10.0'")
201
+
202
+ if google_client_id:
203
+ dependencies.append(" implementation 'com.google.android.gms:play-services-auth:20.7.0'")
204
+
205
+ if use_firebase:
206
+ dependencies.append(" implementation platform('com.google.firebase:firebase-bom:32.7.0')")
207
+ dependencies.append(" implementation 'com.google.firebase:firebase-analytics'")
208
+ dependencies.append(" implementation 'com.google.firebase:firebase-messaging'")
209
+
210
+ if use_admob:
211
+ dependencies.append(" implementation 'com.google.android.gms:play-services-ads:22.6.0'")
212
+
213
+ deps_str = "\n".join(dependencies)
214
+
215
+ firebase_plugin = ""
216
+ if use_firebase:
217
+ firebase_plugin = "apply plugin: 'com.google.gms.google-services'"
218
+
219
+ proguard_rules = ""
220
+ if config.get("proguard_enabled"):
221
+ proguard_rules = """ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'"""
222
+
223
+ content = """plugins {{
224
+ id 'com.android.application'
225
+ }}
226
+ {firebase_plugin}
227
+
228
+ android {{
229
+ namespace '{package_name}'
230
+ compileSdk 34
231
+
232
+ defaultConfig {{
233
+ applicationId "{package_name}"
234
+ minSdk 24
235
+ targetSdk 34
236
+ versionCode {version_code}
237
+ versionName "{version_name}"
238
+ }}
239
+
240
+ signingConfigs {{
241
+ release {{
242
+ storeFile file("../release-key.jks")
243
+ storePassword "android123"
244
+ keyAlias "releasekey"
245
+ keyPassword "android123"
246
+ }}
247
+ }}
248
+
249
+ buildTypes {{
250
+ release {{
251
+ minifyEnabled {minify_enabled}
252
+ shrinkResources {minify_enabled}
253
+ {proguard_rules}
254
+ signingConfig signingConfigs.release
255
+ }}
256
+ }}
257
+
258
+ compileOptions {{
259
+ sourceCompatibility JavaVersion.VERSION_17
260
+ targetCompatibility JavaVersion.VERSION_17
261
+ }}
262
+
263
+ lint {{
264
+ abortOnError false
265
+ checkReleaseBuilds false
266
+ }}
267
+ }}
268
+
269
+ dependencies {{
270
+ {deps_str}
271
+ }}
272
+ """.format(
273
+ firebase_plugin=firebase_plugin,
274
+ package_name=package_name,
275
+ version_code=version_code,
276
+ version_name=version_name,
277
+ minify_enabled=minify_enabled,
278
+ proguard_rules=proguard_rules,
279
+ deps_str=deps_str
280
+ )
281
+ with open(os.path.join(app_dir, "build.gradle"), "w") as f:
282
+ f.write(content)
283
+
284
+
285
+ def create_proguard_rules(app_dir, config):
286
+ rules = """-keepattributes Signature
287
+ -keepattributes *Annotation*
288
+ -keep class * extends android.app.Activity
289
+ -keep class * extends android.app.Application
290
+ -keep class * extends android.app.Service
291
+ -keep class * extends android.content.BroadcastReceiver
292
+ -keep class * extends android.content.ContentProvider
293
+ -dontwarn okhttp3.**
294
+ -dontwarn okio.**
295
+ -dontwarn javax.annotation.**
296
+ -keep class com.google.android.gms.** { *; }
297
+ -keep class com.google.firebase.** { *; }
298
+ -keep class com.google.android.gms.auth.api.signin.** { *; }
299
+ -keep class androidx.** { *; }
300
+ -keepclassmembers class * {
301
+ @android.webkit.JavascriptInterface <methods>;
302
+ }
303
+ """
304
+ with open(os.path.join(app_dir, "proguard-rules.pro"), "w") as f:
305
+ f.write(rules)
306
+
307
+
308
+ def create_android_manifest(manifest_dir, config):
309
+ package_name = config.get("package_name", "com.example.webapp")
310
+ orientation = config.get("screen_orientation", "unspecified")
311
+ if orientation == "portrait":
312
+ orientation_str = "portrait"
313
+ elif orientation == "landscape":
314
+ orientation_str = "landscape"
315
+ else:
316
+ orientation_str = "unspecified"
317
+
318
+ permissions = []
319
+ permissions.append(' <uses-permission android:name="android.permission.INTERNET" />')
320
+ permissions.append(' <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />')
321
+
322
+ if config.get("perm_camera"):
323
+ permissions.append(' <uses-permission android:name="android.permission.CAMERA" />')
324
+ permissions.append(' <uses-feature android:name="android.hardware.camera" android:required="false" />')
325
+ if config.get("perm_microphone"):
326
+ permissions.append(' <uses-permission android:name="android.permission.RECORD_AUDIO" />')
327
+ if config.get("perm_location"):
328
+ permissions.append(' <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />')
329
+ permissions.append(' <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />')
330
+ if config.get("perm_storage"):
331
+ permissions.append(' <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />')
332
+ permissions.append(' <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />')
333
+ if config.get("perm_vibrate"):
334
+ permissions.append(' <uses-permission android:name="android.permission.VIBRATE" />')
335
+ if config.get("perm_contacts"):
336
+ permissions.append(' <uses-permission android:name="android.permission.READ_CONTACTS" />')
337
+ if config.get("perm_notifications"):
338
+ permissions.append(' <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />')
339
+
340
+ permissions_str = "\n".join(permissions)
341
+
342
+ fullscreen_theme = ""
343
+ if config.get("fullscreen_mode"):
344
+ fullscreen_theme = 'android:theme="@style/Theme.AppCompat.NoActionBar"'
345
+ else:
346
+ fullscreen_theme = 'android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"'
347
+
348
+ splash_activity = ""
349
+ main_activity_intent = ""
350
+ if config.get("splash_enabled"):
351
+ splash_activity = """
352
+ <activity
353
+ android:name=".SplashActivity"
354
+ android:exported="true"
355
+ android:screenOrientation="{orientation}"
356
+ {fullscreen_theme_attr}>
357
+ <intent-filter>
358
+ <action android:name="android.intent.action.MAIN" />
359
+ <category android:name="android.intent.category.LAUNCHER" />
360
+ </intent-filter>
361
+ </activity>""".format(orientation=orientation_str, fullscreen_theme_attr=fullscreen_theme)
362
+ main_activity_intent = ""
363
+ else:
364
+ main_activity_intent = """
365
+ <intent-filter>
366
+ <action android:name="android.intent.action.MAIN" />
367
+ <category android:name="android.intent.category.LAUNCHER" />
368
+ </intent-filter>"""
369
+
370
+ meta_data = ""
371
+ if config.get("admob_enabled"):
372
+ meta_data += """
373
+ <meta-data
374
+ android:name="com.google.android.gms.ads.APPLICATION_ID"
375
+ android:value="ca-app-pub-3940256099942544~3347511713" />"""
376
+
377
+ content = """<?xml version="1.0" encoding="utf-8"?>
378
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
379
+
380
+ {permissions}
381
+
382
+ <application
383
+ android:allowBackup="true"
384
+ android:icon="@mipmap/ic_launcher"
385
+ android:label="{app_name}"
386
+ android:supportsRtl="true"
387
+ android:usesCleartextTraffic="false"
388
+ android:hardwareAccelerated="true"
389
+ {fullscreen_theme}>
390
+ {splash_activity}
391
+ <activity
392
+ android:name=".MainActivity"
393
+ android:exported="true"
394
+ android:screenOrientation="{orientation}"
395
+ android:configChanges="orientation|screenSize|keyboardHidden"
396
+ android:windowSoftInputMode="adjustResize">
397
+ {main_activity_intent}
398
+ </activity>
399
+ {meta_data}
400
+ </application>
401
+
402
+ </manifest>
403
+ """.format(
404
+ permissions=permissions_str,
405
+ app_name=config.get("app_name", "WebApp").replace('"', '\\"'),
406
+ fullscreen_theme=fullscreen_theme,
407
+ splash_activity=splash_activity,
408
+ orientation=orientation_str,
409
+ main_activity_intent=main_activity_intent,
410
+ meta_data=meta_data
411
+ )
412
+ with open(os.path.join(manifest_dir, "AndroidManifest.xml"), "w") as f:
413
+ f.write(content)
414
+
415
+
416
+ def create_main_activity(java_dir, config):
417
+ package_name = config.get("package_name", "com.example.webapp")
418
+ target_url = config.get("target_url", "https://www.google.com")
419
+ enable_js = config.get("enable_javascript", True)
420
+ enable_dom = config.get("enable_dom_storage", True)
421
+ allow_file_access = config.get("allow_file_access", False)
422
+ media_autoplay = config.get("media_autoplay", False)
423
+ enable_zoom = config.get("enable_zoom", False)
424
+ custom_ua = config.get("custom_user_agent", "")
425
+ pull_to_refresh = config.get("pull_to_refresh", False)
426
+ multi_windows = config.get("multi_windows", False)
427
+ deep_link_intercept = config.get("deep_link_intercept", True)
428
+ prevent_screenshots = config.get("prevent_screenshots", False)
429
+ clear_cache_exit = config.get("clear_cache_exit", False)
430
+ clear_cookies_exit = config.get("clear_cookies_exit", False)
431
+ keep_screen_on = config.get("keep_screen_on", False)
432
+ fullscreen_mode = config.get("fullscreen_mode", False)
433
+ enforce_https = config.get("enforce_https", True)
434
+ root_detection = config.get("root_detection", False)
435
+ tamper_protection = config.get("tamper_protection", False)
436
+ status_bar_color = config.get("status_bar_color", "#1a1a2e")
437
+ bottom_nav = config.get("bottom_nav_enabled", False)
438
+ offline_mode = config.get("offline_mode", True)
439
+ mixed_content = "WebSettings.MIXED_CONTENT_NEVER_ALLOW" if enforce_https else "WebSettings.MIXED_CONTENT_ALWAYS_ALLOW"
440
+ perm_camera = config.get("perm_camera", False)
441
+ perm_microphone = config.get("perm_microphone", False)
442
+ perm_location = config.get("perm_location", False)
443
+ perm_storage = config.get("perm_storage", False)
444
+ perm_notifications = config.get("perm_notifications", False)
445
+ use_admob = config.get("admob_enabled", False)
446
+ admob_banner_id = config.get("admob_banner_id", "")
447
+ admob_interstitial_id = config.get("admob_interstitial_id", "")
448
+
449
+ # Dynamic variables
450
+ supabase_url = config.get("supabase_url", "")
451
+ supabase_anon_key = config.get("supabase_anon_key", "")
452
+ google_client_id = config.get("google_web_client_id", "")
453
+
454
+ imports = []
455
+ imports.append("package {};".format(package_name))
456
+ imports.append("")
457
+ imports.append("import android.app.Activity;")
458
+ imports.append("import android.content.Context;")
459
+ imports.append("import android.content.Intent;")
460
+ imports.append("import android.content.pm.PackageManager;")
461
+ imports.append("import android.graphics.Bitmap;")
462
+ imports.append("import android.graphics.Color;")
463
+ imports.append("import android.net.ConnectivityManager;")
464
+ imports.append("import android.net.NetworkInfo;")
465
+ imports.append("import android.net.Uri;")
466
+ imports.append("import android.os.Build;")
467
+ imports.append("import android.os.Bundle;")
468
+ imports.append("import android.view.View;")
469
+ imports.append("import android.view.Window;")
470
+ imports.append("import android.view.WindowManager;")
471
+ imports.append("import android.webkit.CookieManager;")
472
+ imports.append("import android.webkit.GeolocationPermissions;")
473
+ imports.append("import android.webkit.PermissionRequest;")
474
+ imports.append("import android.webkit.ValueCallback;")
475
+ imports.append("import android.webkit.WebChromeClient;")
476
+ imports.append("import android.webkit.WebResourceRequest;")
477
+ imports.append("import android.webkit.WebSettings;")
478
+ imports.append("import android.webkit.WebView;")
479
+ imports.append("import android.webkit.WebViewClient;")
480
+ imports.append("import android.widget.FrameLayout;")
481
+ imports.append("import android.widget.LinearLayout;")
482
+ imports.append("import android.widget.ImageButton;")
483
+ imports.append("import android.widget.ProgressBar;")
484
+ imports.append("import android.widget.Toast;")
485
+ imports.append("import androidx.appcompat.app.AppCompatActivity;")
486
+ imports.append("import androidx.core.app.ActivityCompat;")
487
+ imports.append("import androidx.core.content.ContextCompat;")
488
+ if pull_to_refresh:
489
+ imports.append("import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;")
490
+ if use_admob:
491
+ imports.append("import com.google.android.gms.ads.AdRequest;")
492
+ imports.append("import com.google.android.gms.ads.AdSize;")
493
+ imports.append("import com.google.android.gms.ads.AdView;")
494
+ imports.append("import com.google.android.gms.ads.MobileAds;")
495
+ imports.append("import com.google.android.gms.ads.interstitial.InterstitialAd;")
496
+ imports.append("import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback;")
497
+ imports.append("import com.google.android.gms.ads.LoadAdError;")
498
+
499
+ # Google Auth Imports
500
+ if google_client_id:
501
+ imports.append("import com.google.android.gms.auth.api.signin.GoogleSignIn;")
502
+ imports.append("import com.google.android.gms.auth.api.signin.GoogleSignInAccount;")
503
+ imports.append("import com.google.android.gms.auth.api.signin.GoogleSignInClient;")
504
+ imports.append("import com.google.android.gms.auth.api.signin.GoogleSignInOptions;")
505
+ imports.append("import com.google.android.gms.common.api.ApiException;")
506
+ imports.append("import com.google.android.gms.tasks.Task;")
507
+ imports.append("import android.webkit.JavascriptInterface;")
508
+
509
+ imports.append("import java.io.File;")
510
+ imports.append("import java.util.ArrayList;")
511
+ imports.append("import java.util.List;")
512
+ imports.append("")
513
+
514
+ runtime_perms_list = []
515
+ if perm_camera:
516
+ runtime_perms_list.append('"android.permission.CAMERA"')
517
+ if perm_microphone:
518
+ runtime_perms_list.append('"android.permission.RECORD_AUDIO"')
519
+ if perm_location:
520
+ runtime_perms_list.append('"android.permission.ACCESS_FINE_LOCATION"')
521
+ runtime_perms_list.append('"android.permission.ACCESS_COARSE_LOCATION"')
522
+ if perm_storage:
523
+ runtime_perms_list.append('"android.permission.READ_EXTERNAL_STORAGE"')
524
+ if perm_notifications:
525
+ runtime_perms_list.append('"android.permission.POST_NOTIFICATIONS"')
526
+
527
+ perms_array = ", ".join(runtime_perms_list)
528
+
529
+ # Google Auth Declarations
530
+ google_auth_decl = ""
531
+ if google_client_id:
532
+ google_auth_decl = """ private GoogleSignInClient mGoogleSignInClient;
533
+ private static final int RC_SIGN_IN = 9001;"""
534
+
535
+ root_detection_code = ""
536
+ if root_detection:
537
+ root_detection_code = """
538
+ if (isRootedOrEmulator()) {
539
+ Toast.makeText(this, "This app cannot run on rooted/emulated devices.", Toast.LENGTH_LONG).show();
540
+ finish();
541
+ return;
542
+ }"""
543
+
544
+ root_detection_method = ""
545
+ if root_detection:
546
+ root_detection_method = """
547
+ private boolean isRootedOrEmulator() {
548
+ String[] rootPaths = {"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su"};
549
+ for (String path : rootPaths) {
550
+ if (new File(path).exists()) return true;
551
+ }
552
+ String buildTags = android.os.Build.TAGS;
553
+ if (buildTags != null && buildTags.contains("test-keys")) return true;
554
+ if (Build.FINGERPRINT.startsWith("generic") || Build.FINGERPRINT.startsWith("unknown") || Build.MODEL.contains("google_sdk") || Build.MODEL.contains("Emulator") || Build.MODEL.contains("Android SDK built for x86") || Build.MANUFACTURER.contains("Genymotion") || Build.PRODUCT.contains("sdk_gphone") || Build.PRODUCT.contains("vbox86p") || Build.HARDWARE.contains("goldfish") || Build.HARDWARE.contains("ranchu")) return true;
555
+ return false;
556
+ }"""
557
+
558
+ tamper_method = ""
559
+ if tamper_protection:
560
+ tamper_method = """
561
+ private boolean verifyAppSignature() {
562
+ try {
563
+ android.content.pm.PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), android.content.pm.PackageManager.GET_SIGNATURES);
564
+ for (android.content.pm.Signature signature : pInfo.signatures) {
565
+ java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA");
566
+ md.update(signature.toByteArray());
567
+ String currentSignature = android.util.Base64.encodeToString(md.digest(), android.util.Base64.DEFAULT).trim();
568
+ if (currentSignature != null && currentSignature.length() > 0) {
569
+ return true;
570
+ }
571
+ }
572
+ } catch (Exception e) {
573
+ e.printStackTrace();
574
+ }
575
+ return false;
576
+ }"""
577
+
578
+ tamper_check = ""
579
+ if tamper_protection:
580
+ tamper_check = """
581
+ if (!verifyAppSignature()) {
582
+ Toast.makeText(this, "App integrity check failed.", Toast.LENGTH_LONG).show();
583
+ finish();
584
+ return;
585
+ }"""
586
+
587
+ screenshot_code = ""
588
+ if prevent_screenshots:
589
+ screenshot_code = " getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);"
590
+
591
+ keep_screen_code = ""
592
+ if keep_screen_on:
593
+ keep_screen_code = " getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);"
594
+
595
+ fullscreen_code = ""
596
+ if fullscreen_mode:
597
+ fullscreen_code = """
598
+ getWindow().getDecorView().setSystemUiVisibility(
599
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
600
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
601
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
602
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
603
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
604
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
605
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
606
+ getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
607
+ }"""
608
+
609
+ status_bar_code = """
610
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {{
611
+ Window window = getWindow();
612
+ window.setStatusBarColor(Color.parseColor("{color}"));
613
+ }}""".format(color=status_bar_color if status_bar_color else "#1a1a2e")
614
+
615
+ swipe_refresh_setup = ""
616
+ swipe_refresh_decl = ""
617
+ if pull_to_refresh:
618
+ swipe_refresh_decl = " private SwipeRefreshLayout swipeRefreshLayout;"
619
+ swipe_refresh_setup = """
620
+ swipeRefreshLayout = new SwipeRefreshLayout(this);
621
+ swipeRefreshLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
622
+ swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
623
+ @Override
624
+ public void onRefresh() {
625
+ webView.reload();
626
+ }
627
+ });"""
628
+
629
+ ua_code = ""
630
+ if custom_ua:
631
+ ua_code = ' settings.setUserAgentString("{}");'.format(custom_ua.replace('"', '\\"'))
632
+
633
+ admob_decl = ""
634
+ admob_setup = ""
635
+ admob_interstitial_setup = ""
636
+ admob_interstitial_decl = ""
637
+ if use_admob:
638
+ admob_decl = " private AdView adView;"
639
+ admob_interstitial_decl = " private InterstitialAd mInterstitialAd;"
640
+ admob_setup = """
641
+ MobileAds.initialize(this, initializationStatus -> {{}});
642
+ adView = new AdView(this);
643
+ adView.setAdSize(AdSize.BANNER);
644
+ adView.setAdUnitId("{banner_id}");
645
+ AdRequest adRequest = new AdRequest.Builder().build();
646
+ adView.loadAd(adRequest);""".format(banner_id=admob_banner_id if admob_banner_id else "ca-app-pub-3940256099942544/6300978111")
647
+ if admob_interstitial_id:
648
+ admob_interstitial_setup = """
649
+ loadInterstitialAd();"""
650
+
651
+ admob_interstitial_method = ""
652
+ if use_admob and admob_interstitial_id:
653
+ admob_interstitial_method = """
654
+ private void loadInterstitialAd() {{
655
+ AdRequest adRequest = new AdRequest.Builder().build();
656
+ InterstitialAd.load(this, "{interstitial_id}", adRequest, new InterstitialAdLoadCallback() {{
657
+ @Override
658
+ public void onAdLoaded(InterstitialAd interstitialAd) {{
659
+ mInterstitialAd = interstitialAd;
660
+ }}
661
+ @Override
662
+ public void onAdFailedToLoad(LoadAdError loadAdError) {{
663
+ mInterstitialAd = null;
664
+ }}
665
+ }});
666
+ }}
667
+
668
+ private void showInterstitialAd() {{
669
+ if (mInterstitialAd != null) {{
670
+ mInterstitialAd.show(this);
671
+ loadInterstitialAd();
672
+ }}
673
+ }}""".format(interstitial_id=admob_interstitial_id)
674
+
675
+ deep_link_code = ""
676
+ if deep_link_intercept:
677
+ deep_link_code = """
678
+ String host = Uri.parse(url).getHost();
679
+ if (host != null && (host.contains("accounts.google.com") || host.contains("facebook.com") || host.contains("wa.me") || host.contains("whatsapp.com") || host.contains("t.me") || host.contains("twitter.com") || host.contains("x.com") || host.contains("instagram.com"))) {
680
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
681
+ startActivity(intent);
682
+ return true;
683
+ }"""
684
+
685
+ offline_check_code = ""
686
+ if offline_mode:
687
+ offline_check_code = """
688
+ if (!isNetworkAvailable()) {
689
+ webView.loadUrl("file:///android_asset/offline.html");
690
+ } else {
691
+ webView.loadUrl(TARGET_URL);
692
+ }"""
693
+ else:
694
+ offline_check_code = """
695
+ webView.loadUrl(TARGET_URL);"""
696
+
697
+ offline_method = ""
698
+ if offline_mode:
699
+ offline_method = """
700
+ private boolean isNetworkAvailable() {
701
+ ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
702
+ if (cm != null) {
703
+ NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
704
+ return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
705
+ }
706
+ return false;
707
+ }"""
708
+
709
+ on_destroy_code = ""
710
+ destroy_lines = []
711
+ if clear_cache_exit:
712
+ destroy_lines.append(" if (webView != null) { webView.clearCache(true); }")
713
+ if clear_cookies_exit:
714
+ destroy_lines.append(" CookieManager.getInstance().removeAllCookies(null);")
715
+ destroy_lines.append(" CookieManager.getInstance().flush();")
716
+ if destroy_lines:
717
+ on_destroy_code = """
718
+ @Override
719
+ protected void onDestroy() {{
720
+ super.onDestroy();
721
+ {lines}
722
+ }}""".format(lines="\n".join(destroy_lines))
723
+
724
+ request_perms_code = ""
725
+ if runtime_perms_list:
726
+ request_perms_code = """
727
+ List<String> permissionsNeeded = new ArrayList<>();
728
+ String[] allPerms = new String[]{{{perms}}};
729
+ for (String perm : allPerms) {{
730
+ if (ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED) {{
731
+ permissionsNeeded.add(perm);
732
+ }}
733
+ }}
734
+ if (!permissionsNeeded.isEmpty()) {{
735
+ ActivityCompat.requestPermissions(this, permissionsNeeded.toArray(new String[0]), 1001);
736
+ }}""".format(perms=perms_array)
737
+
738
+ bottom_nav_code_layout = ""
739
+ bottom_nav_code_setup = ""
740
+ if bottom_nav:
741
+ bottom_nav_code_layout = """
742
+ LinearLayout bottomNav = new LinearLayout(this);
743
+ bottomNav.setOrientation(LinearLayout.HORIZONTAL);
744
+ bottomNav.setBackgroundColor(Color.parseColor("#1a1a2e"));
745
+ bottomNav.setGravity(android.view.Gravity.CENTER);
746
+ LinearLayout.LayoutParams navParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 130);
747
+ bottomNav.setLayoutParams(navParams);
748
+
749
+ ImageButton btnBack = new ImageButton(this);
750
+ btnBack.setImageResource(android.R.drawable.ic_media_previous);
751
+ btnBack.setBackgroundColor(Color.TRANSPARENT);
752
+ btnBack.setColorFilter(Color.WHITE);
753
+ LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
754
+ btnBack.setLayoutParams(btnParams);
755
+ btnBack.setOnClickListener(v -> { if (webView.canGoBack()) webView.goBack(); });
756
+
757
+ ImageButton btnHome = new ImageButton(this);
758
+ btnHome.setImageResource(android.R.drawable.ic_menu_revert);
759
+ btnHome.setBackgroundColor(Color.TRANSPARENT);
760
+ btnHome.setColorFilter(Color.WHITE);
761
+ btnHome.setLayoutParams(btnParams);
762
+ btnHome.setOnClickListener(v -> webView.loadUrl(TARGET_URL));
763
+
764
+ ImageButton btnRefresh = new ImageButton(this);
765
+ btnRefresh.setImageResource(android.R.drawable.ic_popup_sync);
766
+ btnRefresh.setBackgroundColor(Color.TRANSPARENT);
767
+ btnRefresh.setColorFilter(Color.WHITE);
768
+ btnRefresh.setLayoutParams(btnParams);
769
+ btnRefresh.setOnClickListener(v -> webView.reload());
770
+
771
+ bottomNav.addView(btnBack);
772
+ bottomNav.addView(btnHome);
773
+ bottomNav.addView(btnRefresh);"""
774
+
775
+ build_layout = ""
776
+ if pull_to_refresh and bottom_nav and use_admob:
777
+ build_layout = """
778
+ LinearLayout rootLayout = new LinearLayout(this);
779
+ rootLayout.setOrientation(LinearLayout.VERTICAL);
780
+ rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
781
+
782
+ FrameLayout.LayoutParams webParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 0);
783
+ LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
784
+
785
+ swipeRefreshLayout.addView(webView);
786
+ swipeRefreshLayout.setLayoutParams(webLinearParams);
787
+ rootLayout.addView(swipeRefreshLayout);
788
+ rootLayout.addView(adView);
789
+ {bottom_nav_layout}
790
+ rootLayout.addView(bottomNav);
791
+ setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout)
792
+ elif pull_to_refresh and bottom_nav:
793
+ build_layout = """
794
+ LinearLayout rootLayout = new LinearLayout(this);
795
+ rootLayout.setOrientation(LinearLayout.VERTICAL);
796
+ rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
797
+
798
+ LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
799
+
800
+ swipeRefreshLayout.addView(webView);
801
+ swipeRefreshLayout.setLayoutParams(webLinearParams);
802
+ rootLayout.addView(swipeRefreshLayout);
803
+ {bottom_nav_layout}
804
+ rootLayout.addView(bottomNav);
805
+ setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout)
806
+ elif pull_to_refresh and use_admob:
807
+ build_layout = """
808
+ LinearLayout rootLayout = new LinearLayout(this);
809
+ rootLayout.setOrientation(LinearLayout.VERTICAL);
810
+ rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
811
+
812
+ LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
813
+
814
+ swipeRefreshLayout.addView(webView);
815
+ swipeRefreshLayout.setLayoutParams(webLinearParams);
816
+ rootLayout.addView(swipeRefreshLayout);
817
+ rootLayout.addView(adView);
818
+ setContentView(rootLayout);"""
819
+ elif bottom_nav and use_admob:
820
+ build_layout = """
821
+ LinearLayout rootLayout = new LinearLayout(this);
822
+ rootLayout.setOrientation(LinearLayout.VERTICAL);
823
+ rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
824
+
825
+ LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
826
+ webView.setLayoutParams(webLinearParams);
827
+ rootLayout.addView(webView);
828
+ rootLayout.addView(adView);
829
+ {bottom_nav_layout}
830
+ rootLayout.addView(bottomNav);
831
+ setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout)
832
+ elif pull_to_refresh:
833
+ build_layout = """
834
+ swipeRefreshLayout.addView(webView);
835
+ setContentView(swipeRefreshLayout);"""
836
+ elif bottom_nav:
837
+ build_layout = """
838
+ LinearLayout rootLayout = new LinearLayout(this);
839
+ rootLayout.setOrientation(LinearLayout.VERTICAL);
840
+ rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
841
+
842
+ LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
843
+ webView.setLayoutParams(webLinearParams);
844
+ rootLayout.addView(webView);
845
+ {bottom_nav_layout}
846
+ rootLayout.addView(bottomNav);
847
+ setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout)
848
+ elif use_admob:
849
+ build_layout = """
850
+ LinearLayout rootLayout = new LinearLayout(this);
851
+ rootLayout.setOrientation(LinearLayout.VERTICAL);
852
+ rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
853
+
854
+ LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
855
+ webView.setLayoutParams(webLinearParams);
856
+ rootLayout.addView(webView);
857
+ rootLayout.addView(adView);
858
+ setContentView(rootLayout);"""
859
+ else:
860
+ build_layout = """
861
+ setContentView(webView);"""
862
+
863
+ # Variable Injection Scripts
864
+ key_injection = ""
865
+ if supabase_url or supabase_anon_key:
866
+ key_injection = """
867
+ view.evaluateJavascript("window.SUPABASE_URL = '{url}'; window.SUPABASE_ANON_KEY = '{key}';", null);""".format(
868
+ url=supabase_url.replace("'", "\\'"), key=supabase_anon_key.replace("'", "\\'")
869
+ )
870
+
871
+ swipe_page_finished = ""
872
+ if pull_to_refresh:
873
+ swipe_page_finished = """
874
+ if (swipeRefreshLayout != null) {
875
+ swipeRefreshLayout.setRefreshing(false);
876
+ }"""
877
+
878
+ # Google Auth Bridge Setup
879
+ google_auth_setup = ""
880
+ google_auth_methods = ""
881
+ google_auth_result = ""
882
+ if google_client_id:
883
+ google_auth_setup = """
884
+ GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
885
+ .requestIdToken("{client_id}")
886
+ .requestEmail()
887
+ .build();
888
+ mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
889
+ webView.addJavascriptInterface(new AndroidBridge(), "AndroidBridge");
890
+ """.format(client_id=google_client_id)
891
+
892
+ google_auth_methods = """
893
+ public class AndroidBridge {
894
+ @JavascriptInterface
895
+ public void loginWithGoogle() {
896
+ Intent signInIntent = mGoogleSignInClient.getSignInIntent();
897
+ startActivityForResult(signInIntent, RC_SIGN_IN);
898
+ }
899
+ }
900
+
901
+ private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
902
+ try {
903
+ GoogleSignInAccount account = completedTask.getResult(ApiException.class);
904
+ if (account != null) {
905
+ String idToken = account.getIdToken();
906
+ webView.post(() -> webView.evaluateJavascript("if(typeof onNativeLoginSuccess === 'function') { onNativeLoginSuccess('" + idToken + "'); }", null));
907
+ }
908
+ } catch (ApiException e) {
909
+ e.printStackTrace();
910
+ webView.post(() -> webView.evaluateJavascript("if(typeof onNativeLoginError === 'function') { onNativeLoginError('" + e.getStatusCode() + "'); }", null));
911
+ }
912
+ }"""
913
+
914
+ google_auth_result = """
915
+ if (requestCode == RC_SIGN_IN) {
916
+ Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
917
+ handleSignInResult(task);
918
+ return;
919
+ }"""
920
+
921
+ content = """{imports_str}
922
+ public class MainActivity extends AppCompatActivity {{
923
+
924
+ private WebView webView;
925
+ private static final String TARGET_URL = "{target_url}";
926
+ private ValueCallback<Uri[]> fileUploadCallback;
927
+ private static final int FILE_CHOOSER_RESULT = 1002;
928
+ {swipe_decl}
929
+ {admob_d}
930
+ {admob_interstitial_d}
931
+ {google_auth_decl_str}
932
+
933
+ @Override
934
+ protected void onCreate(Bundle savedInstanceState) {{
935
+ super.onCreate(savedInstanceState);
936
+ {screenshot_c}
937
+ {keep_screen_c}
938
+ {fullscreen_c}
939
+ {status_bar_c}
940
+ {root_det_c}
941
+ {tamper_c}
942
+
943
+ webView = new WebView(this);
944
+ webView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
945
+ webView.setBackgroundColor(Color.parseColor("#1a1a2e"));
946
+
947
+ WebSettings settings = webView.getSettings();
948
+ settings.setJavaScriptEnabled({js_enabled});
949
+ settings.setDomStorageEnabled({dom_enabled});
950
+ settings.setAllowFileAccess({file_access});
951
+ settings.setMediaPlaybackRequiresUserGesture({media_gesture});
952
+ settings.setSupportZoom({zoom_enabled});
953
+ settings.setBuiltInZoomControls({zoom_enabled});
954
+ settings.setDisplayZoomControls(false);
955
+ settings.setJavaScriptCanOpenWindowsAutomatically({multi_win});
956
+ settings.setSupportMultipleWindows({multi_win});
957
+ settings.setMixedContentMode({mixed_content});
958
+ settings.setLoadWithOverviewMode(true);
959
+ settings.setUseWideViewPort(true);
960
+ settings.setCacheMode(WebSettings.LOAD_DEFAULT);
961
+ settings.setDatabaseEnabled(true);
962
+ settings.setAllowContentAccess(true);
963
+
964
+ // Security Enhancements
965
+ settings.setGeolocationEnabled(true);
966
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {{
967
+ settings.setSafeBrowsingEnabled(true);
968
+ }}
969
+ {ua_c}
970
+
971
+ CookieManager cookieManager = CookieManager.getInstance();
972
+ cookieManager.setAcceptCookie(true);
973
+ cookieManager.setAcceptThirdPartyCookies(webView, true);
974
+
975
+ {swipe_setup}
976
+ {admob_s}
977
+ {admob_inter_s}
978
+ {request_perms}
979
+ {google_auth_setup_str}
980
+
981
+ webView.setWebViewClient(new WebViewClient() {{
982
+ @Override
983
+ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {{
984
+ String url = request.getUrl().toString();
985
+ {deep_link}
986
+ if (url.startsWith("tel:") || url.startsWith("mailto:") || url.startsWith("sms:") || url.startsWith("whatsapp:") || url.startsWith("intent:")) {{
987
+ try {{
988
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
989
+ startActivity(intent);
990
+ }} catch (Exception e) {{
991
+ e.printStackTrace();
992
+ }}
993
+ return true;
994
+ }}
995
+ return false;
996
+ }}
997
+
998
+ @Override
999
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {{
1000
+ super.onPageStarted(view, url, favicon);
1001
+ }}
1002
+
1003
+ @Override
1004
+ public void onPageFinished(WebView view, String url) {{
1005
+ super.onPageFinished(view, url);
1006
+ {swipe_finished}
1007
+ {key_injection_str}
1008
+ }}
1009
+
1010
+ @Override
1011
+ public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {{
1012
+ super.onReceivedError(view, errorCode, description, failingUrl);
1013
+ {offline_on_error}
1014
+ }}
1015
+ }});
1016
+
1017
+ webView.setWebChromeClient(new WebChromeClient() {{
1018
+ @Override
1019
+ public void onPermissionRequest(final PermissionRequest request) {{
1020
+ runOnUiThread(() -> request.grant(request.getResources()));
1021
+ }}
1022
+
1023
+ @Override
1024
+ public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {{
1025
+ callback.invoke(origin, true, false);
1026
+ }}
1027
+
1028
+ @Override
1029
+ public boolean onShowFileChooser(WebView wv, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {{
1030
+ if (fileUploadCallback != null) {{
1031
+ fileUploadCallback.onReceiveValue(null);
1032
+ }}
1033
+ fileUploadCallback = filePathCallback;
1034
+ Intent intent = fileChooserParams.createIntent();
1035
+ try {{
1036
+ startActivityForResult(intent, FILE_CHOOSER_RESULT);
1037
+ }} catch (Exception e) {{
1038
+ fileUploadCallback = null;
1039
+ return false;
1040
+ }}
1041
+ return true;
1042
+ }}
1043
+ }});
1044
+
1045
+ {build_layout_code}
1046
+
1047
+ {offline_check}
1048
+ }}
1049
+
1050
+ @Override
1051
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {{
1052
+ super.onActivityResult(requestCode, resultCode, data);
1053
+ {google_auth_res_str}
1054
+ if (requestCode == FILE_CHOOSER_RESULT) {{
1055
+ if (fileUploadCallback != null) {{
1056
+ Uri[] results = null;
1057
+ if (resultCode == Activity.RESULT_OK && data != null) {{
1058
+ String dataString = data.getDataString();
1059
+ if (dataString != null) {{
1060
+ results = new Uri[]{{Uri.parse(dataString)}};
1061
+ }}
1062
+ }}
1063
+ fileUploadCallback.onReceiveValue(results);
1064
+ fileUploadCallback = null;
1065
+ }}
1066
+ }}
1067
+ }}
1068
+
1069
+ @Override
1070
+ public void onBackPressed() {{
1071
+ if (webView.canGoBack()) {{
1072
+ webView.goBack();
1073
+ }} else {{
1074
+ super.onBackPressed();
1075
+ }}
1076
+ }}
1077
+ {offline_method_code}
1078
+ {root_method}
1079
+ {tamper_method_code}
1080
+ {admob_inter_method}
1081
+ {google_auth_methods_str}
1082
+ {on_destroy}
1083
+ }}
1084
+ """.format(
1085
+ imports_str="\n".join(imports),
1086
+ target_url=target_url.replace('"', '\\"'),
1087
+ swipe_decl=swipe_refresh_decl,
1088
+ admob_d=admob_decl,
1089
+ admob_interstitial_d=admob_interstitial_decl,
1090
+ google_auth_decl_str=google_auth_decl,
1091
+ screenshot_c=screenshot_code,
1092
+ keep_screen_c=keep_screen_code,
1093
+ fullscreen_c=fullscreen_code,
1094
+ status_bar_c=status_bar_code,
1095
+ root_det_c=root_detection_code,
1096
+ tamper_c=tamper_check,
1097
+ js_enabled="true" if enable_js else "false",
1098
+ dom_enabled="true" if enable_dom else "false",
1099
+ file_access="true" if allow_file_access else "false",
1100
+ media_gesture="false" if media_autoplay else "true",
1101
+ zoom_enabled="true" if enable_zoom else "false",
1102
+ multi_win="true" if multi_windows else "false",
1103
+ mixed_content=mixed_content,
1104
+ ua_c=ua_code,
1105
+ swipe_setup=swipe_refresh_setup,
1106
+ admob_s=admob_setup,
1107
+ admob_inter_s=admob_interstitial_setup,
1108
+ request_perms=request_perms_code,
1109
+ google_auth_setup_str=google_auth_setup,
1110
+ deep_link=deep_link_code,
1111
+ swipe_finished=swipe_page_finished,
1112
+ key_injection_str=key_injection,
1113
+ offline_on_error=' if (!isNetworkAvailable()) { view.loadUrl("file:///android_asset/offline.html"); }' if offline_mode else "",
1114
+ build_layout_code=build_layout,
1115
+ offline_check=offline_check_code,
1116
+ offline_method_code=offline_method,
1117
+ root_method=root_detection_method,
1118
+ tamper_method_code=tamper_method,
1119
+ admob_inter_method=admob_interstitial_method,
1120
+ google_auth_methods_str=google_auth_methods,
1121
+ google_auth_res_str=google_auth_result,
1122
+ on_destroy=on_destroy_code
1123
+ )
1124
+
1125
+ with open(os.path.join(java_dir, "MainActivity.java"), "w") as f:
1126
+ f.write(content)
1127
+
1128
+
1129
+ def create_splash_activity(java_dir, config):
1130
+ package_name = config.get("package_name", "com.example.webapp")
1131
+ splash_text = config.get("splash_text", "Loading...")
1132
+ splash_text_color = config.get("splash_text_color", "#FFFFFF")
1133
+ splash_bg_color = config.get("splash_bg_color", "#1a1a2e")
1134
+ has_splash_image = config.get("has_splash_image", False)
1135
+
1136
+ image_code = ""
1137
+ if has_splash_image:
1138
+ image_code = """
1139
+ ImageView splashImage = new ImageView(this);
1140
+ try {
1141
+ java.io.InputStream is = getAssets().open("splash_media");
1142
+ android.graphics.drawable.Drawable d = android.graphics.drawable.Drawable.createFromStream(is, null);
1143
+ splashImage.setImageDrawable(d);
1144
+ splashImage.setScaleType(ImageView.ScaleType.FIT_CENTER);
1145
+ LinearLayout.LayoutParams imgParams = new LinearLayout.LayoutParams(600, 600);
1146
+ imgParams.gravity = android.view.Gravity.CENTER;
1147
+ splashImage.setLayoutParams(imgParams);
1148
+ layout.addView(splashImage);
1149
+ is.close();
1150
+ } catch (Exception e) {
1151
+ e.printStackTrace();
1152
+ }"""
1153
+
1154
+ content = """package {package_name};
1155
+
1156
+ import android.content.Intent;
1157
+ import android.graphics.Color;
1158
+ import android.os.Bundle;
1159
+ import android.os.Handler;
1160
+ import android.view.Gravity;
1161
+ import android.view.WindowManager;
1162
+ import android.widget.ImageView;
1163
+ import android.widget.LinearLayout;
1164
+ import android.widget.TextView;
1165
+ import androidx.appcompat.app.AppCompatActivity;
1166
+
1167
+ public class SplashActivity extends AppCompatActivity {{
1168
+
1169
+ @Override
1170
+ protected void onCreate(Bundle savedInstanceState) {{
1171
+ super.onCreate(savedInstanceState);
1172
+
1173
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
1174
+
1175
+ LinearLayout layout = new LinearLayout(this);
1176
+ layout.setOrientation(LinearLayout.VERTICAL);
1177
+ layout.setGravity(Gravity.CENTER);
1178
+ layout.setBackgroundColor(Color.parseColor("{bg_color}"));
1179
+ layout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
1180
+ {image_code}
1181
+
1182
+ TextView textView = new TextView(this);
1183
+ textView.setText("{splash_text}");
1184
+ textView.setTextColor(Color.parseColor("{text_color}"));
1185
+ textView.setTextSize(24);
1186
+ textView.setGravity(Gravity.CENTER);
1187
+ LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
1188
+ textParams.topMargin = 40;
1189
+ textView.setLayoutParams(textParams);
1190
+ layout.addView(textView);
1191
+
1192
+ setContentView(layout);
1193
+
1194
+ new Handler().postDelayed(new Runnable() {{
1195
+ @Override
1196
+ public void run() {{
1197
+ Intent intent = new Intent(SplashActivity.this, MainActivity.class);
1198
+ startActivity(intent);
1199
+ finish();
1200
+ }}
1201
+ }}, 3000);
1202
+ }}
1203
+ }}
1204
+ """.format(
1205
+ package_name=package_name,
1206
+ bg_color=splash_bg_color if splash_bg_color else "#1a1a2e",
1207
+ image_code=image_code,
1208
+ splash_text=splash_text.replace('"', '\\"') if splash_text else "Loading...",
1209
+ text_color=splash_text_color if splash_text_color else "#FFFFFF"
1210
+ )
1211
+
1212
+ with open(os.path.join(java_dir, "SplashActivity.java"), "w") as f:
1213
+ f.write(content)
1214
+
1215
+
1216
+ def save_app_icon(res_dir, icon_b64):
1217
+ sizes = {
1218
+ "mipmap-mdpi": 48,
1219
+ "mipmap-hdpi": 72,
1220
+ "mipmap-xhdpi": 96,
1221
+ "mipmap-xxhdpi": 144,
1222
+ "mipmap-xxxhdpi": 192
1223
+ }
1224
+ try:
1225
+ if "," in icon_b64:
1226
+ icon_b64 = icon_b64.split(",")[1]
1227
+ icon_bytes = base64.b64decode(icon_b64)
1228
+ for folder, size in sizes.items():
1229
+ mipmap_dir = os.path.join(res_dir, folder)
1230
+ os.makedirs(mipmap_dir, exist_ok=True)
1231
+ icon_path = os.path.join(mipmap_dir, "ic_launcher.png")
1232
+ with open(icon_path, "wb") as f:
1233
+ f.write(icon_bytes)
1234
+ except Exception as e:
1235
+ for folder, size in sizes.items():
1236
+ mipmap_dir = os.path.join(res_dir, folder)
1237
+ os.makedirs(mipmap_dir, exist_ok=True)
1238
+ create_default_icon(os.path.join(mipmap_dir, "ic_launcher.png"))
1239
+
1240
+
1241
+ def create_default_icon(path):
1242
+ import struct
1243
+ import zlib
1244
+
1245
+ width = 48
1246
+ height = 48
1247
+
1248
+ def create_png(w, h, r, g, b):
1249
+ def chunk(chunk_type, data):
1250
+ c = chunk_type + data
1251
+ crc = zlib.crc32(c) & 0xFFFFFFFF
1252
+ return struct.pack(">I", len(data)) + c + struct.pack(">I", crc)
1253
+
1254
+ header = b'\x89PNG\r\n\x1a\n'
1255
+ ihdr = chunk(b'IHDR', struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0))
1256
+ raw_data = b''
1257
+ for y in range(h):
1258
+ raw_data += b'\x00'
1259
+ for x in range(w):
1260
+ raw_data += bytes([r, g, b])
1261
+ compressed = zlib.compress(raw_data)
1262
+ idat = chunk(b'IDAT', compressed)
1263
+ iend = chunk(b'IEND', b'')
1264
+ return header + ihdr + idat + iend
1265
+
1266
+ png_data = create_png(width, height, 0x4e, 0x54, 0xc8)
1267
+ with open(path, "wb") as f:
1268
+ f.write(png_data)
1269
+
1270
+
1271
+ def setup_project(build_id, config):
1272
+ project_dir = os.path.join(BUILD_DIR_BASE, build_id, "project")
1273
+ os.makedirs(project_dir, exist_ok=True)
1274
+
1275
+ package_name = config.get("package_name", "com.example.webapp")
1276
+ package_path = package_name.replace(".", "/")
1277
+
1278
+ app_dir = os.path.join(project_dir, "app")
1279
+ src_main = os.path.join(app_dir, "src", "main")
1280
+ java_dir = os.path.join(src_main, "java", package_path)
1281
+ res_dir = os.path.join(src_main, "res")
1282
+ assets_dir = os.path.join(src_main, "assets")
1283
+
1284
+ os.makedirs(java_dir, exist_ok=True)
1285
+ os.makedirs(res_dir, exist_ok=True)
1286
+ os.makedirs(assets_dir, exist_ok=True)
1287
+ os.makedirs(os.path.join(res_dir, "values"), exist_ok=True)
1288
+
1289
+ # 🔥 FIREBASE VALIDATION LOGIC 🔥
1290
+ use_firebase = config.get("firebase_enabled", False)
1291
+ use_onesignal = config.get("onesignal_enabled", False)
1292
+ firebase_json = config.get("firebase_json", "")
1293
+
1294
+ actual_firebase_enabled = False
1295
+
1296
+ if use_firebase and firebase_json.strip():
1297
+ try:
1298
+ # Check if it's valid JSON AND contains the crucial "project_info" key
1299
+ parsed = json.loads(firebase_json)
1300
+ if "project_info" in parsed:
1301
+ # Only write the file and enable the plugin if it's perfectly valid
1302
+ os.makedirs(app_dir, exist_ok=True)
1303
+ with open(os.path.join(app_dir, "google-services.json"), "w") as f:
1304
+ json.dump(parsed, f, indent=2)
1305
+ actual_firebase_enabled = True
1306
+ else:
1307
+ LOG_STORE[build_id].append("⚠️ WARNING: Firebase JSON is missing 'project_info'. Firebase integration skipped to prevent build failure.")
1308
+ except json.JSONDecodeError:
1309
+ LOG_STORE[build_id].append("⚠️ WARNING: Firebase JSON is invalid. Firebase integration skipped to prevent build failure.")
1310
+ elif use_firebase:
1311
+ LOG_STORE[build_id].append("⚠️ WARNING: Firebase was enabled but no JSON was provided. Integration skipped.")
1312
+
1313
+ # Save actual state to config so create_app_build_gradle knows what to do
1314
+ config["actual_firebase_enabled"] = actual_firebase_enabled
1315
+
1316
+ create_build_gradle_root(project_dir, actual_firebase_enabled, use_onesignal)
1317
+ create_settings_gradle(project_dir, config.get("app_name", "WebApp"))
1318
+ create_gradle_properties(project_dir)
1319
+ create_gradle_wrapper(project_dir)
1320
+
1321
+ create_app_build_gradle(app_dir, config)
1322
+ create_proguard_rules(app_dir, config)
1323
+
1324
+ create_android_manifest(src_main, config)
1325
+
1326
+ create_main_activity(java_dir, config)
1327
+
1328
+ if config.get("splash_enabled"):
1329
+ create_splash_activity(java_dir, config)
1330
+
1331
+ icon_b64 = config.get("app_icon_b64", "")
1332
+ if icon_b64:
1333
+ save_app_icon(res_dir, icon_b64)
1334
+ else:
1335
+ for folder in ["mipmap-mdpi", "mipmap-hdpi", "mipmap-xhdpi", "mipmap-xxhdpi", "mipmap-xxxhdpi"]:
1336
+ mipmap_dir = os.path.join(res_dir, folder)
1337
+ os.makedirs(mipmap_dir, exist_ok=True)
1338
+ create_default_icon(os.path.join(mipmap_dir, "ic_launcher.png"))
1339
+
1340
+ splash_media_b64 = config.get("splash_media_b64", "")
1341
+ if splash_media_b64 and config.get("splash_enabled"):
1342
+ try:
1343
+ if "," in splash_media_b64:
1344
+ splash_media_b64 = splash_media_b64.split(",")[1]
1345
+ media_bytes = base64.b64decode(splash_media_b64)
1346
+ with open(os.path.join(assets_dir, "splash_media"), "wb") as f:
1347
+ f.write(media_bytes)
1348
+ config["has_splash_image"] = True
1349
+ create_splash_activity(java_dir, config)
1350
+ except Exception as e:
1351
+ config["has_splash_image"] = False
1352
+
1353
+ if config.get("offline_mode", True):
1354
+ with open(os.path.join(assets_dir, "offline.html"), "w") as f:
1355
+ f.write(generate_offline_html())
1356
+
1357
+ strings_xml = """<?xml version="1.0" encoding="utf-8"?>
1358
+ <resources>
1359
+ <string name="app_name">{app_name}</string>
1360
+ </resources>
1361
+ """.format(app_name=config.get("app_name", "WebApp").replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;"))
1362
+ with open(os.path.join(res_dir, "values", "strings.xml"), "w") as f:
1363
+ f.write(strings_xml)
1364
+
1365
+ colors_xml = """<?xml version="1.0" encoding="utf-8"?>
1366
+ <resources>
1367
+ <color name="colorPrimary">{status_color}</color>
1368
+ <color name="colorPrimaryDark">{status_color}</color>
1369
+ <color name="colorAccent">#e94560</color>
1370
+ </resources>
1371
+ """.format(status_color=config.get("status_bar_color", "#1a1a2e"))
1372
+ with open(os.path.join(res_dir, "values", "colors.xml"), "w") as f:
1373
+ f.write(colors_xml)
1374
+
1375
+ styles_xml = """<?xml version="1.0" encoding="utf-8"?>
1376
+ <resources>
1377
+ <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
1378
+ <item name="colorPrimary">@color/colorPrimary</item>
1379
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
1380
+ <item name="colorAccent">@color/colorAccent</item>
1381
+ </style>
1382
+ </resources>
1383
+ """
1384
+ with open(os.path.join(res_dir, "values", "styles.xml"), "w") as f:
1385
+ f.write(styles_xml)
1386
+
1387
+ generate_keystore(os.path.join(BUILD_DIR_BASE, build_id, "project"), config.get("dev_name", "Developer"), config.get("dev_email", "dev@example.com"), build_id)
1388
+
1389
+ local_props = "sdk.dir={}\n".format(os.environ.get("ANDROID_SDK_ROOT", "/opt/android-sdk"))
1390
+ with open(os.path.join(project_dir, "local.properties"), "w") as f:
1391
+ f.write(local_props)
1392
+
1393
+ return project_dir
1394
+
1395
+
1396
+ def run_build(build_id, config):
1397
+ LOG_STORE[build_id] = []
1398
+ BUILD_STATUS[build_id] = "running"
1399
+
1400
+ def log(msg):
1401
+ timestamp = datetime.now().strftime("%H:%M:%S")
1402
+ LOG_STORE[build_id].append("[{}] {}".format(timestamp, msg))
1403
+
1404
+ try:
1405
+ log("=== Build Started ===")
1406
+ log("App Name: {}".format(config.get("app_name", "WebApp")))
1407
+ log("Package: {}".format(config.get("package_name", "com.example.webapp")))
1408
+ log("Build Format: {}".format(config.get("build_format", "apk").upper()))
1409
+ log("Setting up project structure...")
1410
+
1411
+ project_dir = setup_project(build_id, config)
1412
+
1413
+ log("Project structure created successfully.")
1414
+ log("Starting Gradle build...")
1415
+
1416
+ build_format = config.get("build_format", "apk").lower()
1417
+ if build_format == "aab":
1418
+ gradle_task = "bundleRelease"
1419
+ else:
1420
+ gradle_task = "assembleRelease"
1421
+
1422
+ gradle_bin = os.path.join(os.environ.get("GRADLE_HOME", "/opt/gradle/gradle-7.6"), "bin", "gradle")
1423
+
1424
+ cmd = [
1425
+ gradle_bin,
1426
+ gradle_task,
1427
+ "--parallel",
1428
+ "--build-cache",
1429
+ "--no-daemon",
1430
+ "--stacktrace"
1431
+ ]
1432
+
1433
+ log("Running: {}".format(" ".join(cmd)))
1434
+
1435
+ env = os.environ.copy()
1436
+ env["ANDROID_SDK_ROOT"] = os.environ.get("ANDROID_SDK_ROOT", "/opt/android-sdk")
1437
+ env["ANDROID_HOME"] = os.environ.get("ANDROID_HOME", "/opt/android-sdk")
1438
+ env["JAVA_HOME"] = os.environ.get("JAVA_HOME", "/usr/lib/jvm/java-17-openjdk-amd64")
1439
+
1440
+ process = subprocess.Popen(
1441
+ cmd,
1442
+ cwd=project_dir,
1443
+ stdout=subprocess.PIPE,
1444
+ stderr=subprocess.STDOUT,
1445
+ text=True,
1446
+ env=env,
1447
+ bufsize=1
1448
+ )
1449
+
1450
+ for line in iter(process.stdout.readline, ""):
1451
+ stripped = line.rstrip()
1452
+ if stripped:
1453
+ log(stripped)
1454
+
1455
+ process.wait()
1456
+
1457
+ if process.returncode == 0:
1458
+ log("=== BUILD SUCCESSFUL ===")
1459
+
1460
+ if build_format == "aab":
1461
+ output_path = os.path.join(project_dir, "app", "build", "outputs", "bundle", "release", "app-release.aab")
1462
+ else:
1463
+ output_path = os.path.join(project_dir, "app", "build", "outputs", "apk", "release", "app-release.apk")
1464
+ if not os.path.exists(output_path):
1465
+ output_path = os.path.join(project_dir, "app", "build", "outputs", "apk", "release", "app-release-unsigned.apk")
1466
+
1467
+ if os.path.exists(output_path):
1468
+ artifact_name = "{}.{}".format(config.get("app_name", "app").replace(" ", "_"), "aab" if build_format == "aab" else "apk")
1469
+ final_path = os.path.join(BUILD_DIR_BASE, build_id, artifact_name)
1470
+ shutil.copy2(output_path, final_path)
1471
+ BUILD_ARTIFACTS[build_id] = final_path
1472
+ log("Artifact saved: {}".format(artifact_name))
1473
+ BUILD_STATUS[build_id] = "success"
1474
+ else:
1475
+ log("ERROR: Build output file not found at expected path.")
1476
+ log("Searching for output files...")
1477
+ for root, dirs, files in os.walk(os.path.join(project_dir, "app", "build", "outputs")):
1478
+ for fname in files:
1479
+ log(" Found: {}".format(os.path.join(root, fname)))
1480
+ BUILD_STATUS[build_id] = "failed"
1481
+ else:
1482
+ log("=== BUILD FAILED (exit code: {}) ===".format(process.returncode))
1483
+ BUILD_STATUS[build_id] = "failed"
1484
+
1485
+ full_log = "\n".join(LOG_STORE[build_id])
1486
+ ai_analysis = analyze_logs_ai(full_log)
1487
+ log("")
1488
+ log("=== AI Log Analysis (Arabic) ===")
1489
+ for analysis_line in ai_analysis.split("\n"):
1490
+ log(analysis_line)
1491
+
1492
+ except Exception as e:
1493
+ log("CRITICAL ERROR: {}".format(str(e)))
1494
+ import traceback
1495
+ for tb_line in traceback.format_exc().split("\n"):
1496
+ log(tb_line)
1497
+ BUILD_STATUS[build_id] = "failed"
1498
+
1499
+
1500
+ @app.route("/")
1501
+ def index():
1502
+ return render_template("index.html")
1503
+
1504
+
1505
+ @app.route("/build", methods=["POST"])
1506
+ def start_build():
1507
+ try:
1508
+ data = request.form.to_dict()
1509
+
1510
+ config = {}
1511
+ config["app_name"] = data.get("app_name", "MyApp")
1512
+ config["package_name"] = data.get("package_name", "com.example.myapp")
1513
+ config["target_url"] = data.get("target_url", "https://www.google.com")
1514
+ config["build_format"] = data.get("build_format", "apk")
1515
+ config["version_name"] = data.get("version_name", "1.0.0")
1516
+ config["version_code"] = data.get("version_code", "1")
1517
+ config["screen_orientation"] = data.get("screen_orientation", "unspecified")
1518
+ config["dev_name"] = data.get("dev_name", "Developer")
1519
+ config["dev_email"] = data.get("dev_email", "dev@example.com")
1520
+
1521
+ # Key injections variables
1522
+ config["google_web_client_id"] = data.get("google_web_client_id", "")
1523
+ config["supabase_url"] = data.get("supabase_url", "")
1524
+ config["supabase_anon_key"] = data.get("supabase_anon_key", "")
1525
+
1526
+ config["splash_enabled"] = data.get("splash_enabled") == "on"
1527
+ config["splash_text"] = data.get("splash_text", "Loading...")
1528
+ config["splash_text_color"] = data.get("splash_text_color", "#FFFFFF")
1529
+ config["splash_bg_color"] = data.get("splash_bg_color", "#1a1a2e")
1530
+ config["status_bar_color"] = data.get("status_bar_color", "#1a1a2e")
1531
+ config["bottom_nav_enabled"] = data.get("bottom_nav_enabled") == "on"
1532
+
1533
+ config["enable_javascript"] = data.get("enable_javascript") == "on"
1534
+ config["enable_dom_storage"] = data.get("enable_dom_storage") == "on"
1535
+ config["allow_file_access"] = data.get("allow_file_access") == "on"
1536
+ config["media_autoplay"] = data.get("media_autoplay") == "on"
1537
+ config["enable_zoom"] = data.get("enable_zoom") == "on"
1538
+ config["custom_user_agent"] = data.get("custom_user_agent", "")
1539
+ config["pull_to_refresh"] = data.get("pull_to_refresh") == "on"
1540
+ config["multi_windows"] = data.get("multi_windows") == "on"
1541
+ config["deep_link_intercept"] = data.get("deep_link_intercept") == "on"
1542
+
1543
+ config["proguard_enabled"] = data.get("proguard_enabled") == "on"
1544
+ config["root_detection"] = data.get("root_detection") == "on"
1545
+ config["prevent_screenshots"] = data.get("prevent_screenshots") == "on"
1546
+ config["clear_cache_exit"] = data.get("clear_cache_exit") == "on"
1547
+ config["clear_cookies_exit"] = data.get("clear_cookies_exit") == "on"
1548
+ config["keep_screen_on"] = data.get("keep_screen_on") == "on"
1549
+ config["fullscreen_mode"] = data.get("fullscreen_mode") == "on"
1550
+ config["enforce_https"] = data.get("enforce_https") == "on"
1551
+ config["tamper_protection"] = data.get("tamper_protection") == "on"
1552
+
1553
+ config["firebase_enabled"] = data.get("firebase_enabled") == "on"
1554
+ config["firebase_json"] = data.get("firebase_json", "")
1555
+ config["onesignal_enabled"] = bool(data.get("onesignal_app_id", "").strip())
1556
+ config["onesignal_app_id"] = data.get("onesignal_app_id", "")
1557
+ config["admob_enabled"] = data.get("admob_enabled") == "on"
1558
+ config["admob_banner_id"] = data.get("admob_banner_id", "")
1559
+ config["admob_interstitial_id"] = data.get("admob_interstitial_id", "")
1560
+
1561
+ config["perm_camera"] = data.get("perm_camera") == "on"
1562
+ config["perm_microphone"] = data.get("perm_microphone") == "on"
1563
+ config["perm_location"] = data.get("perm_location") == "on"
1564
+ config["perm_storage"] = data.get("perm_storage") == "on"
1565
+ config["perm_vibrate"] = data.get("perm_vibrate") == "on"
1566
+ config["perm_contacts"] = data.get("perm_contacts") == "on"
1567
+ config["perm_notifications"] = data.get("perm_notifications") == "on"
1568
+
1569
+ config["offline_mode"] = data.get("offline_mode") == "on"
1570
+
1571
+ config["app_icon_b64"] = data.get("app_icon_b64", "")
1572
+ config["splash_media_b64"] = data.get("splash_media_b64", "")
1573
+ config["has_splash_image"] = bool(data.get("splash_media_b64", "").strip())
1574
+
1575
+ build_id = str(uuid.uuid4())[:12]
1576
+ os.makedirs(os.path.join(BUILD_DIR_BASE, build_id), exist_ok=True)
1577
+
1578
+ thread = threading.Thread(target=run_build, args=(build_id, config), daemon=True)
1579
+ thread.start()
1580
+
1581
+ return jsonify({"status": "started", "build_id": build_id})
1582
+ except Exception as e:
1583
+ return jsonify({"status": "error", "message": str(e)}), 500
1584
+
1585
+
1586
+ @app.route("/logs/<build_id>")
1587
+ def get_logs(build_id):
1588
+ def generate():
1589
+ last_index = 0
1590
+ while True:
1591
+ logs = LOG_STORE.get(build_id, [])
1592
+ status = BUILD_STATUS.get(build_id, "unknown")
1593
+ if last_index < len(logs):
1594
+ new_logs = logs[last_index:]
1595
+ last_index = len(logs)
1596
+ for log_line in new_logs:
1597
+ yield "data: {}\n\n".format(json.dumps({"type": "log", "message": log_line}))
1598
+ if status in ("success", "failed"):
1599
+ yield "data: {}\n\n".format(json.dumps({"type": "status", "status": status, "build_id": build_id}))
1600
+ break
1601
+ time.sleep(0.5)
1602
+
1603
+ return Response(generate(), mimetype="text/event-stream", headers={
1604
+ "Cache-Control": "no-cache",
1605
+ "X-Accel-Buffering": "no",
1606
+ "Connection": "keep-alive"
1607
+ })
1608
+
1609
+
1610
+ @app.route("/download/<build_id>")
1611
+ def download_artifact(build_id):
1612
+ artifact_path = BUILD_ARTIFACTS.get(build_id)
1613
+ if artifact_path and os.path.exists(artifact_path):
1614
+ return send_file(artifact_path, as_attachment=True, download_name=os.path.basename(artifact_path))
1615
+ else:
1616
+ return jsonify({"error": "Artifact not found"}), 404
1617
+
1618
+
1619
+ @app.route("/status/<build_id>")
1620
+ def get_status(build_id):
1621
+ status = BUILD_STATUS.get(build_id, "unknown")
1622
+ return jsonify({"status": status, "build_id": build_id})
1623
+
1624
+
1625
+ if __name__ == "__main__":
1626
+ app.run(host="0.0.0.0", port=7860, debug=False, threaded=True)