Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
| 3 |
+
import subprocess
|
| 4 |
+
import shutil
|
| 5 |
+
|
| 6 |
+
def build_app(app_name, url, package_name, build_format, orientation, icon_path, enable_splash, splash_text, fullscreen, encrypt_app, js_enabled, onesignal_id, firebase_path, permissions):
|
| 7 |
+
logs = "🚀 بدء تهيئة بيئة العمل وبناء المشروع...\n"
|
| 8 |
+
yield logs, None
|
| 9 |
+
|
| 10 |
+
project_dir = "/tmp/ProApp"
|
| 11 |
+
if os.path.exists(project_dir):
|
| 12 |
+
shutil.rmtree(project_dir)
|
| 13 |
+
os.makedirs(project_dir, exist_ok=True)
|
| 14 |
+
|
| 15 |
+
package_path = package_name.replace('.', '/')
|
| 16 |
+
java_dir = f"{project_dir}/app/src/main/java/{package_path}"
|
| 17 |
+
res_dir = f"{project_dir}/app/src/main/res"
|
| 18 |
+
|
| 19 |
+
for folder in ['layout', 'values', 'mipmap-xxxhdpi', 'drawable', 'anim']:
|
| 20 |
+
os.makedirs(f"{res_dir}/{folder}", exist_ok=True)
|
| 21 |
+
os.makedirs(java_dir, exist_ok=True)
|
| 22 |
+
|
| 23 |
+
# معالجة الأيقونة
|
| 24 |
+
if icon_path:
|
| 25 |
+
shutil.copy(icon_path, f"{res_dir}/mipmap-xxxhdpi/ic_launcher.png")
|
| 26 |
+
else:
|
| 27 |
+
os.system(f"touch {res_dir}/mipmap-xxxhdpi/ic_launcher.png")
|
| 28 |
+
|
| 29 |
+
# معالجة Firebase
|
| 30 |
+
use_firebase = False
|
| 31 |
+
if firebase_path:
|
| 32 |
+
try:
|
| 33 |
+
shutil.copy(firebase_path, f"{project_dir}/app/google-services.json")
|
| 34 |
+
use_firebase = True
|
| 35 |
+
logs += "✅ تم دمج ملف Firebase بنجاح\n"
|
| 36 |
+
yield logs, None
|
| 37 |
+
except:
|
| 38 |
+
logs += "⚠️ تحذير: فشل في قراءة ملف Firebase المرفوع.\n"
|
| 39 |
+
yield logs, None
|
| 40 |
+
|
| 41 |
+
# مفتاح التوقيع
|
| 42 |
+
os.system(f'keytool -genkey -v -keystore {project_dir}/app/release.keystore -alias appKey -keyalg RSA -keysize 2048 -validity 10000 -storepass 123456 -keypass 123456 -dname "CN=Pro, OU=App, O=Org, L=City, S=State, C=US" 2>/dev/null')
|
| 43 |
+
|
| 44 |
+
# كتابة إعدادات Gradle
|
| 45 |
+
with open(f"{project_dir}/settings.gradle", "w") as f:
|
| 46 |
+
f.write(f"rootProject.name = '{app_name}'\ninclude ':app'\n")
|
| 47 |
+
|
| 48 |
+
with open(f"{project_dir}/build.gradle", "w") as f:
|
| 49 |
+
f.write(f"""
|
| 50 |
+
buildscript {{
|
| 51 |
+
repositories {{ google(); mavenCentral(); gradlePluginPortal() }}
|
| 52 |
+
dependencies {{
|
| 53 |
+
classpath 'com.android.tools.build:gradle:7.4.2'
|
| 54 |
+
classpath 'gradle.plugin.com.onesignal:onesignal-gradle-plugin:[0.12.10, 0.99.99]'
|
| 55 |
+
{'classpath "com.google.gms:google-services:4.3.15"' if use_firebase else ''}
|
| 56 |
+
}}
|
| 57 |
+
}}
|
| 58 |
+
allprojects {{ repositories {{ google(); mavenCentral() }} }}
|
| 59 |
+
""")
|
| 60 |
+
|
| 61 |
+
with open(f"{project_dir}/app/build.gradle", "w") as f:
|
| 62 |
+
f.write(f"""
|
| 63 |
+
plugins {{
|
| 64 |
+
id 'com.android.application'
|
| 65 |
+
{'id "com.onesignal.androidsdk.onesignal-gradle-plugin"' if onesignal_id else ''}
|
| 66 |
+
{'id "com.google.gms.google-services"' if use_firebase else ''}
|
| 67 |
+
}}
|
| 68 |
+
android {{
|
| 69 |
+
namespace '{package_name}'
|
| 70 |
+
compileSdk 33
|
| 71 |
+
defaultConfig {{
|
| 72 |
+
applicationId "{package_name}"
|
| 73 |
+
minSdk 24
|
| 74 |
+
targetSdk 33
|
| 75 |
+
versionCode 1
|
| 76 |
+
versionName "1.0"
|
| 77 |
+
{f'manifestPlaceholders = [onesignal_app_id: "{onesignal_id}", onesignal_google_project_number: "REMOTE"]' if onesignal_id else ''}
|
| 78 |
+
}}
|
| 79 |
+
signingConfigs {{
|
| 80 |
+
release {{ storeFile file("release.keystore"); storePassword "123456"; keyAlias "appKey"; keyPassword "123456" }}
|
| 81 |
+
}}
|
| 82 |
+
buildTypes {{
|
| 83 |
+
release {{ minifyEnabled {str(encrypt_app).lower()}; signingConfig signingConfigs.release }}
|
| 84 |
+
}}
|
| 85 |
+
}}
|
| 86 |
+
dependencies {{
|
| 87 |
+
implementation 'androidx.appcompat:appcompat:1.6.1'
|
| 88 |
+
{'implementation "com.onesignal:OneSignal:[4.0.0, 4.99.99]"' if onesignal_id else ''}
|
| 89 |
+
{'implementation platform("com.google.firebase:firebase-bom:32.1.1"); implementation "com.google.firebase:firebase-messaging"' if use_firebase else ''}
|
| 90 |
+
}}
|
| 91 |
+
""")
|
| 92 |
+
|
| 93 |
+
# ملفات الموارد و XML
|
| 94 |
+
with open(f"{res_dir}/values/styles.xml", "w") as f:
|
| 95 |
+
f.write("""<resources><style name="AppTheme" parent="@android:style/Theme.NoTitleBar.Fullscreen" /></resources>""")
|
| 96 |
+
|
| 97 |
+
with open(f"{res_dir}/layout/activity_main.xml", "w") as f:
|
| 98 |
+
f.write("""<?xml version="1.0" encoding="utf-8"?>
|
| 99 |
+
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
|
| 100 |
+
<WebView android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" />
|
| 101 |
+
</RelativeLayout>""")
|
| 102 |
+
|
| 103 |
+
# كود MainActivity
|
| 104 |
+
notch_fix = "if(android.os.Build.VERSION.SDK_INT >= 28) getWindow().getAttributes().layoutInDisplayCutoutMode = android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;" if fullscreen else ""
|
| 105 |
+
fullscreen_code = "getWindow().getDecorView().setSystemUiVisibility(android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | android.view.View.SYSTEM_UI_FLAG_FULLSCREEN);" if fullscreen else ""
|
| 106 |
+
|
| 107 |
+
main_java = f"""package {package_name};
|
| 108 |
+
import android.app.Activity;
|
| 109 |
+
import android.os.Bundle;
|
| 110 |
+
import android.webkit.WebSettings;
|
| 111 |
+
import android.webkit.WebView;
|
| 112 |
+
import android.webkit.WebViewClient;
|
| 113 |
+
{'import com.onesignal.OneSignal;' if onesignal_id else ''}
|
| 114 |
+
|
| 115 |
+
public class MainActivity extends Activity {{
|
| 116 |
+
private WebView myWebView;
|
| 117 |
+
@Override protected void onCreate(Bundle savedInstanceState) {{
|
| 118 |
+
super.onCreate(savedInstanceState);
|
| 119 |
+
{notch_fix}
|
| 120 |
+
{fullscreen_code}
|
| 121 |
+
|
| 122 |
+
if (android.os.Build.VERSION.SDK_INT >= 33 && checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) != android.content.pm.PackageManager.PERMISSION_GRANTED) {{
|
| 123 |
+
requestPermissions(new String[]{{android.Manifest.permission.POST_NOTIFICATIONS}}, 101);
|
| 124 |
+
}}
|
| 125 |
+
|
| 126 |
+
{f'OneSignal.setLogLevel(OneSignal.LOG_LEVEL.VERBOSE, OneSignal.LOG_LEVEL.NONE); OneSignal.initWithContext(this); OneSignal.setAppId("{onesignal_id}");' if onesignal_id else ''}
|
| 127 |
+
|
| 128 |
+
setContentView(R.layout.activity_main);
|
| 129 |
+
myWebView = findViewById(R.id.webview);
|
| 130 |
+
|
| 131 |
+
WebSettings ws = myWebView.getSettings();
|
| 132 |
+
ws.setJavaScriptEnabled({str(js_enabled).lower()});
|
| 133 |
+
ws.setDomStorageEnabled(true);
|
| 134 |
+
|
| 135 |
+
myWebView.setWebViewClient(new WebViewClient());
|
| 136 |
+
myWebView.loadUrl("{url}");
|
| 137 |
+
}}
|
| 138 |
+
@Override public void onBackPressed() {{ if (myWebView.canGoBack()) myWebView.goBack(); else super.onBackPressed(); }}
|
| 139 |
+
}}"""
|
| 140 |
+
with open(f"{java_dir}/MainActivity.java", "w") as f: f.write(main_java)
|
| 141 |
+
|
| 142 |
+
# ملف AndroidManifest
|
| 143 |
+
perms_xml = "\n".join([f'<uses-permission android:name="android.permission.{p}" />' for p in permissions])
|
| 144 |
+
if onesignal_id or use_firebase: perms_xml += '\n<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>'
|
| 145 |
+
|
| 146 |
+
manifest_xml = f"""<?xml version="1.0" encoding="utf-8"?>
|
| 147 |
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="{package_name}">
|
| 148 |
+
<uses-permission android:name="android.permission.INTERNET" />
|
| 149 |
+
{perms_xml}
|
| 150 |
+
<application android:label="{app_name}" android:icon="@mipmap/ic_launcher" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
|
| 151 |
+
<activity android:name=".MainActivity" android:exported="true" android:screenOrientation="{orientation}" android:configChanges="orientation|keyboardHidden|screenSize">
|
| 152 |
+
<intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
|
| 153 |
+
</activity>
|
| 154 |
+
</application>
|
| 155 |
+
</manifest>"""
|
| 156 |
+
with open(f"{project_dir}/app/src/main/AndroidManifest.xml", "w") as f: f.write(manifest_xml)
|
| 157 |
+
|
| 158 |
+
logs += "⚙️ بدء عملية التجميع بواسطة Gradle (قد يستغرق دقائق)...\n"
|
| 159 |
+
yield logs, None
|
| 160 |
+
|
| 161 |
+
# تنفيذ أمر البناء
|
| 162 |
+
gradle_cmd = "gradle bundleRelease" if build_format == "aab" else "gradle assembleRelease"
|
| 163 |
+
process = subprocess.Popen(gradle_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, cwd=project_dir)
|
| 164 |
+
|
| 165 |
+
for line in process.stdout:
|
| 166 |
+
logs += line
|
| 167 |
+
yield logs, None
|
| 168 |
+
|
| 169 |
+
process.wait()
|
| 170 |
+
|
| 171 |
+
if process.returncode == 0:
|
| 172 |
+
out_path = f"{project_dir}/app/build/outputs/bundle/release/app-release.aab" if build_format == "aab" else f"{project_dir}/app/build/outputs/apk/release/app-release.apk"
|
| 173 |
+
logs += "\n✅ اكتمل البناء بنجاح! الملف جاهز للتحميل بالأسفل."
|
| 174 |
+
yield logs, out_path
|
| 175 |
+
else:
|
| 176 |
+
logs += "\n❌ فشل البناء. يرجى مراجعة سجلات الخطأ أعلاه."
|
| 177 |
+
yield logs, None
|
| 178 |
+
|
| 179 |
+
# ================== واجهة المستخدم (Gradio UI) ==================
|
| 180 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
|
| 181 |
+
gr.Markdown("<h1 style='text-align: center;'>📱 مصنع التطبيقات الشامل - الإصدار السحابي</h1>")
|
| 182 |
+
|
| 183 |
+
with gr.Tabs():
|
| 184 |
+
with gr.Tab("الأساسيات"):
|
| 185 |
+
with gr.Row():
|
| 186 |
+
app_name = gr.Textbox(label="اسم التطبيق", value="تطبيقي الجديد")
|
| 187 |
+
url = gr.Textbox(label="رابط الموقع (URL)", value="https://google.com")
|
| 188 |
+
with gr.Row():
|
| 189 |
+
package_name = gr.Textbox(label="اسم الحزمة", value="com.pro.app")
|
| 190 |
+
build_format = gr.Dropdown(choices=["apk", "aab"], value="apk", label="صيغة التصدير")
|
| 191 |
+
orientation = gr.Dropdown(choices=["unspecified", "portrait", "landscape"], value="unspecified", label="اتجاه ��لشاشة")
|
| 192 |
+
|
| 193 |
+
with gr.Tab("التصميم والأمان"):
|
| 194 |
+
with gr.Row():
|
| 195 |
+
icon_path = gr.Image(type="filepath", label="أيقونة التطبيق (اختياري)")
|
| 196 |
+
firebase_path = gr.File(type="filepath", label="ملف Firebase json (اختياري)")
|
| 197 |
+
with gr.Row():
|
| 198 |
+
enable_splash = gr.Checkbox(label="تفعيل شاشة البداية", value=False)
|
| 199 |
+
splash_text = gr.Textbox(label="نص الشاشة الافتتاحية", value="جاري التحميل...")
|
| 200 |
+
with gr.Row():
|
| 201 |
+
fullscreen = gr.Checkbox(label="وضع ملء الشاشة للألعاب", value=False)
|
| 202 |
+
encrypt_app = gr.Checkbox(label="تشفير الكود (Proguard)", value=True)
|
| 203 |
+
js_enabled = gr.Checkbox(label="دعم JavaScript كامل", value=True)
|
| 204 |
+
|
| 205 |
+
with gr.Tab("الخدمات والصلاحيات"):
|
| 206 |
+
onesignal = gr.Textbox(label="معرف OneSignal (للإشعارات)")
|
| 207 |
+
permissions = gr.CheckboxGroup(
|
| 208 |
+
choices=["CAMERA", "RECORD_AUDIO", "ACCESS_FINE_LOCATION", "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"],
|
| 209 |
+
label="الأذونات والصلاحيات"
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
build_btn = gr.Button("🚀 بدء بناء التطبيق", variant="primary", size="lg")
|
| 213 |
+
|
| 214 |
+
with gr.Row():
|
| 215 |
+
logs_out = gr.Textbox(label="شاشة السجلات المباشرة (Live Logs)", lines=15, max_lines=20)
|
| 216 |
+
file_out = gr.File(label="📥 تحميل التطبيق الجاهز")
|
| 217 |
+
|
| 218 |
+
build_btn.click(
|
| 219 |
+
fn=build_app,
|
| 220 |
+
inputs=[app_name, url, package_name, build_format, orientation, icon_path, enable_splash, splash_text, fullscreen, encrypt_app, js_enabled, onesignal, firebase_path, permissions],
|
| 221 |
+
outputs=[logs_out, file_out]
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
demo.queue()
|
| 225 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|