feat(apps): سقالة rider_new و driver_new على Cubit — المرحلة 1

بناء من الصفر على باك إند NestJS، بقرار المالك 2026-08-04 الذي يعكس قرارَي
نقل GetX (2026-07-21) واعتماد باك إند PHP (2026-07-27). لا يُورَّث سطر دارت
من apps/*-archive-cubit، وسيرو مرجع بصري وسلوكي لا مصدر نسخ.

الوثائق:
- docs/37: الخطة الكاملة بستّ مراحل وبواباتها
- docs/38: عقد الـAPI من 37 controller، معظمه متحقَّق حيّاً من السيرفر المنشور
- docs/39: جرد 202 شاشة في تطبيقَي سيرو، مصنّفة داخل/خارج النطاق

الطبقة الأصلية منقولة من *-archive-cubit وحدها لأنها هوية النشر:
- rider_new  → com.mobileapp.store.ride · shorebird 496cb3ac
- driver_new → com.sefer_driver (أندرويد) · com.sefer.driver (iOS) · 68cc9345
- أُصلح تعارض: هدف RunnerTests في driver_new كان يحمل bundle الراكب

الأصول مصدرها *-archive-cubit لا سيرو: أصول الأرشيف مجموعة أشمل (كل صور
سيرو + صور تريبز) وخطوطها هي خطوط docs/26. استُكمل السائق بعشرة ملفات
ناقصة من سيرو (شعارات مزوّدي الدفع + صوتان).

lib/ مكتوب من الصفر (11 ملف لكل تطبيق):
- AppConfig بأعلام const — أساس نموذج lite/pro/max
- TokenStore على التخزين الآمن، يقرأ exp محليّاً بلا حزمة خارجية
- AuthInterceptor بتجديد استباقي وطلقة واحدة — عمر التوكن 15 دقيقة فقط
- ApiClient و ApiException يفهم مصفوفة message في NestJS

flutter analyze نظيف في التطبيقين. البناء الفعلي لم يُجرَّب بعد: بوابة
المرحلة 1 تتطلّب البناء على السيرفر، والوصول إليه غير متاح حالياً.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-08-04 21:21:26 +03:00
co-authored by Claude Opus 5
parent 1243e5a3b2
commit 69da4abc01
379 changed files with 15686 additions and 0 deletions
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
+176
View File
@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.sefer_driver"> <!-- تأكد من أن الباكيج هنا صحيح حسب مشروعك -->
<!-- الأذونات (Permissions) -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- أذونات الخدمات الأمامية (ضرورية لأندرويد 14+) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.QUICKBOOT_POWERON" />
<uses-permission android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
<!-- أذونات إضافية لتطابق Intaleq -->
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.PICTURE_IN_PICTURE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<!-- تم إضافة android:name لجعل التطبيق يعمل كـ Flutter Application بشكل صحيح مثل Intaleq -->
<application
android:name="${applicationName}"
android:label="Tripz Driver"
android:icon="@mipmap/launcher_icon"
android:allowBackup="false"
android:fullBackupContent="false"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="false"
android:theme="@style/LaunchTheme"
android:enableOnBackInvokedCallback="true">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTask"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Deep Link المضاف مثل Intaleq -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tripzdriver" />
</intent-filter>
</activity>
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${mapsApiKey}" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- تم تعطيل Impeller مؤقتاً كما في Intaleq لتجنب مشاكل الريندر -->
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false" />
<!-- ✅ الخدمة الأساسية للخلفية مع تحديد النوع (حل الكراش) -->
<service
android:name="id.flutter.flutter_background_service.BackgroundService"
android:foregroundServiceType="location"
android:enabled="true"
android:exported="true" />
<!-- الخدمات الأخرى -->
<service
android:name=".MyFirebaseMessagingService"
android:exported="false" />
<service
android:name=".LocationUpdatesService"
android:exported="false"
android:foregroundServiceType="location" />
<service
android:name="com.google.firebase.messaging.FirebaseMessagingService"
android:exported="false"
tools:replace="android:exported">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name="com.phan_tech.flutter_overlay_apps.OverlayService"
android:exported="false" />
<service
android:name="flutter.overlay.window.flutter_overlay_window.OverlayService"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="explanation_for_special_use" />
</service>
<receiver
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.sefer_driver" />
</intent-filter>
</receiver>
<service
android:name="com.dsaved.bubblehead.bubble.BubbleHeadService"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="intent.bring.app.to.foreground" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
<receiver
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver"
android:exported="false" />
<receiver
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
<receiver
android:name=".YourBroadcastReceiver"
android:exported="false" />
</application>
</manifest>
+11
View File
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.10.2) # 3.10.2 is fine, but no need to go as high as 3.31.5
project(sefer_driver) # Good
# Add your C++ source file(s) to create a SHARED library.
add_library(native-lib SHARED native-lib.cpp)
# Find the Android log library.
find_library(log-lib log)
# Link your library against the log library. This is essential for debugging.
target_link_libraries(native-lib ${log-lib})
+167
View File
@@ -0,0 +1,167 @@
#include <jni.h>
#include <string>
#include <fstream>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <android/log.h>
#include <pthread.h>
#include <dlfcn.h>
#include <link.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ptrace.h> // Add this line
#define LOG_TAG "NativeLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// Function to check for common root binaries
bool isRooted() {
std::string paths[] = {
"/system/app/Superuser.apk",
"/system/xbin/su",
"/system/bin/su",
"/system/bin/magisk",
"/system/xbin/magisk",
"/sbin/magisk"
};
for (const auto &path : paths) {
std::ifstream file(path);
if (file.good()) {
return true;
}
}
return false;
}
// Function to check for the presence of files or directories commonly associated with Frida.
bool checkFridaFiles() {
std::string fridaFiles[] = {
"/data/local/tmp/re.frida.server", //Common Frida server path
"/data/local/tmp/frida-server",
"/usr/lib/libfrida-gadget.so", // Frida gadget (injected library)
"/usr/lib64/libfrida-gadget.so",
"/data/local/re.frida.server",
};
for (const auto &path : fridaFiles) {
if (access(path.c_str(), F_OK) != -1) {
LOGE("Frida file detected: %s", path.c_str());
return true;
}
}
return false;
}
// Checks for open ports commonly used by Frida. This is less reliable, as ports can be changed.
bool checkFridaPorts() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
return false; // Couldn't create socket, not a strong indicator.
}
sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(27042); // Default Frida port
inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr);
if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) != -1) {
LOGE("Frida default port (27042) is open.");
close(sock);
return true;
}
close(sock);
return false;
}
// Check the maps file of the current process for any suspicious entries.
bool checkMaps() {
std::ifstream mapsFile("/proc/self/maps");
std::string line;
if (mapsFile.is_open()) {
while (std::getline(mapsFile, line)) {
// Look for lines that indicate injected libraries, especially Frida.
if (line.find("frida") != std::string::npos ||
line.find("gum-js-") != std::string::npos) { // Gum is Frida's JavaScript engine
LOGE("Suspicious entry in /proc/self/maps: %s", line.c_str());
return true;
}
}
mapsFile.close();
} else
{
LOGE("Could not open /proc/self/maps");
return false;
}
return false;
}
// Check loaded modules.
bool checkLoadedModules() {
bool found = false;
dl_iterate_phdr([](struct dl_phdr_info *info, size_t size, void *data) {
bool *found_ptr = static_cast<bool *>(data);
if (std::string(info->dlpi_name).find("frida") != std::string::npos) {
LOGE("Frida module detected: %s", info->dlpi_name);
*found_ptr = true;
return 1; // Stop iterating
}
return 0; // Continue iterating
}, &found);
return found;
}
// This is a simple ptrace check. More sophisticated checks are possible (and necessary for robust detection).
//bool checkPtrace() {
// // Attempt to ptrace ourselves. If another process is already tracing us, this will fail.
// if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
// LOGE("ptrace failed. Debugger or tracer detected.");
// return true; // Likely being traced
// }
// // Detach. If attached, need to detach to not interfere.
// ptrace(PTRACE_DETACH, 0, 0, 0);
// return false;
//}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_sefer_1driver_RootDetection_isNativeRooted(JNIEnv *env, jobject /* this */) {
if (isRooted()) {
return JNI_TRUE;
}
if (checkFridaFiles()) {
return JNI_TRUE;
}
if (checkFridaPorts()) {
return JNI_TRUE;
}
if (checkMaps()) {
return JNI_TRUE;
}
if (checkLoadedModules()) {
return JNI_TRUE;
}
// if (checkPtrace()) {
// return JNI_TRUE;
// }
return JNI_FALSE;
}
@@ -0,0 +1,15 @@
package com.sefer_driver
import android.app.IntentService
import android.content.Intent
import android.content.Context
// TODO: Rename actions, choose action names that describe tasks that this
// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
private const val ACTION_FOO = "com.sefer_driver.action.FOO"
private const val ACTION_BAZ = "com.sefer_driver.action.BAZ"
// TODO: Rename parameters
private const val EXTRA_PARAM1 = "com.sefer_driver.extra.PARAM1"
private const val EXTRA_PARAM2 = "com.sefer_driver.extra.PARAM2"
@@ -0,0 +1,190 @@
package com.sefer_driver
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.view.setPadding
import com.scottyab.rootbeer.RootBeer
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.io.File
import java.util.Timer
import kotlin.concurrent.schedule
class MainActivity : FlutterFragmentActivity() {
private val SECURITY_CHANNEL = "com.sefer_driver/security"
private val APP_CONTROL_CHANNEL = "com.sefer_driver/app_control"
// 🔴 1. تعريف القناة لتكون متاحة للوصول من الدوال الأخرى
private var appControlChannel: MethodChannel? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 🔴 2. تهيئة القناة هنا
appControlChannel =
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, APP_CONTROL_CHANNEL)
// Channel for security checks (isRooted)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SECURITY_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"isNativeRooted" -> result.success(isDeviceCompromised())
else -> result.notImplemented()
}
}
// Channel for app control (bringing to foreground & overlay events)
appControlChannel?.setMethodCallHandler { call, result ->
when (call.method) {
"bringToForeground" -> {
Log.d("MainActivity", "Received bringToForeground request")
val intent =
Intent(this, MainActivity::class.java).apply {
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_SINGLE_TOP
)
}
try {
startActivity(intent)
Log.d("MainActivity", "App brought to foreground successfully")
result.success(true)
} catch (e: Exception) {
Log.e("MainActivity", "Error bringing app to foreground", e)
result.error("ACTIVITY_START_FAILED", e.message, e.stackTraceToString())
}
}
else -> result.notImplemented()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("MainActivity", "MainActivity onCreate")
if (isDeviceCompromised()) {
showSecurityWarningDialog()
}
// 🔴 3. فحص نية فتح التطبيق (هل جاء من زر قبول النافذة؟)
checkIntentForOverlayAccept(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
Log.d("MainActivity", "Received new intent: ${intent.action}")
// 🔴 4. فحص نية التطبيق عند الفتح من الخلفية
checkIntentForOverlayAccept(intent)
}
// =========================================================================
// 🔴 5. الدالة السحرية لالتقاط زر القبول وإرساله للـ Flutter 🔴
// =========================================================================
private fun checkIntentForOverlayAccept(intent: Intent) {
val acceptedTripId = intent.getStringExtra("acceptedTripId")
if (acceptedTripId != null) {
Log.d("MainActivity", "✅ Trip accepted via Native Intent: $acceptedTripId")
// إرسال الإشارة عبر القناة لتطبيق فلاتر
appControlChannel?.invokeMethod("onOverlayTripAccepted", acceptedTripId)
// مسح البيانات حتى لا تتكرر العملية إذا أغلق السائق التطبيق وفتحه يدوياً
intent.removeExtra("acceptedTripId")
}
}
// =========================================================================
// --- دوال الحماية الخاصة بك (لم يتم المساس بها) ---
// =========================================================================
private fun isDeviceCompromised(): Boolean {
return try {
val isRootedByRootBeer = RootBeer(this).isRooted
Log.d("MainActivity", "Root check result: $isRootedByRootBeer")
isRootedByRootBeer
} catch (e: Exception) {
Log.e("MainActivity", "Security check error: ${e.message}", e)
true // Fail-safe: assume compromised if check fails
}
}
private fun showSecurityWarningDialog() {
var secondsRemaining = 10
val progressBar =
ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal).apply {
max = 10
progress = 10
}
val textView =
TextView(this).apply {
text = getString(R.string.security_warning_message)
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
}
val layout =
LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(48)
addView(textView)
addView(progressBar)
}
val dialog =
AlertDialog.Builder(this)
.setTitle(getString(R.string.security_warning_title))
.setView(layout)
.setCancelable(false)
.create()
dialog.show()
val timer = Timer()
timer.schedule(0, 1000) {
secondsRemaining--
runOnUiThread {
progressBar.progress = secondsRemaining
if (secondsRemaining <= 0) {
timer.cancel()
dialog.dismiss()
clearAppDataAndExit()
}
}
}
}
private fun clearAppDataAndExit() {
try {
Runtime.getRuntime().exec("pm clear $packageName")
Log.d("MainActivity", "Cleared app data via package manager")
} catch (e: Exception) {
Log.e("MainActivity", "Error clearing app data: ${e.message}", e)
clearCache()
clearAppData()
}
finishAffinity()
System.exit(0)
}
private fun clearCache() {
deleteDir(cacheDir)
deleteDir(externalCacheDir)
Log.d("MainActivity", "Cleared cache directories")
}
private fun clearAppData() {
Log.d("MainActivity", "App data clearing skipped (commented out)")
}
private fun deleteDir(dir: File?): Boolean {
if (dir != null && dir.isDirectory) {
dir.list()?.forEach { deleteDir(File(dir, it)) }
}
val deleted = dir?.delete() ?: false
Log.d("MainActivity", "Deleted directory ${dir?.path}: $deleted")
return deleted
}
}
@@ -0,0 +1,41 @@
package com.sefer_driver
import android.app.Application
import android.content.Intent
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
class MyApplication : Application() {
companion object {
lateinit var instance: MyApplication
private set
val flutterEngine: FlutterEngine by lazy {
FlutterEngine(instance).apply {
dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
}
}
}
override fun onCreate() {
super.onCreate()
instance = this
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.sefer_driver/app_lifecycle")
.setMethodCallHandler { call, result ->
if (call.method == "bringAppToForeground") {
bringAppToForeground()
result.success(null)
} else {
result.notImplemented()
}
}
}
private fun bringAppToForeground() {
val intent = Intent(applicationContext, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
startActivity(intent)
}
}
@@ -0,0 +1,10 @@
package com.sefer_driver
object RootDetection {
init {
System.loadLibrary("native-lib") // Load the native library
}
external fun isNativeRooted(): Boolean // Declare the external function
}
@@ -0,0 +1,109 @@
import android.content.Context
import android.util.Base64
import android.util.Log
import com.google.android.gms.safetynet.SafetyNet
import com.google.android.gms.safetynet.SafetyNetApi
import com.google.android.gms.tasks.OnFailureListener
import com.google.android.gms.tasks.OnSuccessListener
import org.json.JSONObject
import java.io.IOException
import java.security.GeneralSecurityException
import java.security.SecureRandom
import java.util.Arrays
object SafetyNetCheck {
private const val TAG = "SafetyNetCheck"
fun checkSafetyNet(context: Context, apiKey: String, callback: (Boolean) -> Unit) {
// Generate a nonce. A good nonce is large, random, and used only once.
val nonce = generateNonce()
SafetyNet.getClient(context).attest(nonce, apiKey)
.addOnSuccessListener { response ->
// Success! Now, *verify* the response.
val jwsResult = response.jwsResult
if (jwsResult != null) {
try {
val isSafe = SafetyNetResponseVerifier.verify(jwsResult)
Log.d(TAG, "SafetyNet verification result: $isSafe")
callback(isSafe) // Now passing a *verified* result.
} catch (e: Exception) {
Log.e(TAG, "Error verifying SafetyNet response: ${e.message}", e)
callback(false) // Treat verification errors as failures.
}
} else {
Log.e(TAG, "SafetyNet jwsResult is null")
callback(false) // Null result is a failure.
}
}
.addOnFailureListener { e ->
Log.e(TAG, "SafetyNet attest API call failed: ${e.message}", e)
callback(false) // API call failure.
}
}
// Helper function to generate a nonce.
private fun generateNonce(): ByteArray {
val byteGenerator = SecureRandom()
val nonce = ByteArray(32)
byteGenerator.nextBytes(nonce)
return nonce
}
}
// Helper class to verify the SafetyNet response.
object SafetyNetResponseVerifier {
private const val TAG = "SafetyNetVerifier"
// This method *must* be implemented on a *backend server* for real security.
// This is just a *simplified example* for demonstration purposes and is
// *not* suitable for production without a backend check.
@Throws(GeneralSecurityException::class, IOException::class)
fun verify(jwsResult: String): Boolean {
// 1. Parse the JWS: Split into header, payload, and signature.
val parts = jwsResult.split(".")
if (parts.size != 3) {
Log.e(TAG, "Invalid JWS format")
return false // Invalid JWS format
}
val header = parts[0]
val payload = parts[1]
val signature = parts[2]
// 2. Decode the payload (it's Base64 encoded).
val decodedPayload = Base64.decode(payload, Base64.DEFAULT)
val payloadJson = JSONObject(String(decodedPayload))
// 3. Check the ctsProfileMatch and basicIntegrity.
val ctsProfileMatch = payloadJson.optBoolean("ctsProfileMatch", false)
val basicIntegrity = payloadJson.optBoolean("basicIntegrity", false)
Log.d(TAG, "ctsProfileMatch: $ctsProfileMatch, basicIntegrity: $basicIntegrity")
// 4. **CRITICAL: In a real application, you *must* send the JWS to your
// backend server for verification. The server should use the
// Google SafetyNet API (or a library that wraps it) to verify
// the signature and check the fields. This prevents attackers
// from tampering with the response on the device.**
//
// // Example (pseudo-code) of what the backend check would do:
// // GoogleCredential credential = ...;
// // SafetyNet safetyNet = new SafetyNet.Builder(httpTransport, jsonFactory)
// // .setApplicationName("YourAppName")
// // .setHttpRequestInitializer(credential)
// // .build();
// // SafetyNetApi.VerifyJwsRequest request = safetyNet.safetynet().verifyJws(jwsResult);
// // SafetyNetApi.VerifyJwsResponse response = request.execute();
// // return response.isValidSignature() && response.getCtsProfileMatch() && response.getBasicIntegrity();
// 5. For this *example* (without a backend), we'll just check the fields.
// This is *NOT SECURE* for production!
return ctsProfileMatch && basicIntegrity
}
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/notification_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
<TextView
android:id="@+id/notification_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,6 @@
<resources>
<string name="security_warning_title">تحذير أمني</string>
<string name="security_warning_message">تم اكتشاف مشكلة أمنية أو تعديل على هذا الجهاز. لا يمكن تشغيل التطبيق على هذا الجهاز.</string>
<string name="exit_button">إغلاق التطبيق</string>
<string name="device_secure">الجهاز آمن. الاستمرار بشكل طبيعي.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<!-- <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">-->
<style name="LaunchTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<!-- <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">-->
<style name="NormalTheme" parent="Theme.MaterialComponents">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,15 @@
<resources>
<string name="app_name">My App</string>
<string name="default_notification_channel_id">high_importance_channel</string>
<string name="api_key">kffk</string>
<string name="api_key_safety">AIzaSyB04YNW3LbvmQ5lX1t2bOwEU18-</string>
<string name="channel_name">FCM Notifications</string>
<string name="channel_description">Notifications from Firebase Cloud Messaging</string>
<string name="security_warning_title">Security Warning</string>
<string name="security_warning_message">A security issue or modification has been detected on
this device. The app cannot run on this device.</string>
<string name="exit_button">Exit App</string>
<string name="device_secure">Device is secure. Proceeding normally.</string>
</resources>
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<!-- <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">-->
<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<!-- <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">-->
<style name="NormalTheme" parent="Theme.MaterialComponents">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- منع أي اتصال HTTP غير مشفّر -->
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<!-- الاعتماد على شهادات النظام فقط -->
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- اسمح بالاتصال بدوميناتك فقط -->
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">tripz-egypt.com</domain>
</domain-config>
<!-- في debug فقط: اسمح بشهادات المستخدم لسهولة الاختبار عبر Proxy -->
<debug-overrides>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>