feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل
قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ. الخريطة: backend · payment_server · loction_server · ride_server · passenger_server · docker · dashboard · stress_test → الجذر siro_rider → apps/rider siro_driver → apps/driver siro_admin → dashboards/admin siro_service → dashboards/service android_bot → apps/android_bot socialBot → apps/socialBot نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب) لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً: كل ما يلي يصير فرقاً مقروءاً مقابل المصدر. لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز، سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh (ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و dashboards/transit-web). ⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة: 1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر): كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner. 2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist) يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً. 3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع → يجب ضمّ الحزم داخله أسوة بـ apps/rider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@@ -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>
|
||||
@@ -0,0 +1,139 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.siro.siro_rider">
|
||||
|
||||
<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" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||
<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.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
||||
<!-- Package visibility declarations for external apps like maps and WhatsApp -->
|
||||
<queries>
|
||||
<package android:name="com.whatsapp" />
|
||||
<package android:name="com.google.android.apps.maps" />
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="geo" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:name="${applicationName}"
|
||||
android:label="@string/label"
|
||||
android:icon="@mipmap/launcher_icon"
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:usesCleartextTraffic="false"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
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>
|
||||
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" android:host="siromove.com" android:pathPrefix="/" />
|
||||
<data android:scheme="https" android:host="www.siromove.com"
|
||||
android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
|
||||
<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="siromove" />
|
||||
</intent-filter>
|
||||
|
||||
<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="geo" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Navigation Intents -->
|
||||
<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="google.navigation" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="vnd.android.cursor.item/map" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<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" />
|
||||
|
||||
<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" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name="com.yalantis.ucrop.UCropActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.10.2) # Use 3.10.2 for consistency.
|
||||
project(siro)
|
||||
|
||||
add_library(native-lib SHARED native-lib.cpp)
|
||||
|
||||
find_library(log-lib log)
|
||||
|
||||
target_link_libraries(native-lib ${log-lib})
|
||||
@@ -0,0 +1,192 @@
|
||||
#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 (including Magisk, KernelSU, APatch)
|
||||
bool isRooted()
|
||||
{
|
||||
std::string paths[] = {
|
||||
"/system/app/Superuser.apk",
|
||||
"/system/xbin/su",
|
||||
"/system/bin/su",
|
||||
"/system/bin/magisk",
|
||||
"/system/xbin/magisk",
|
||||
"/sbin/magisk",
|
||||
"/data/adb/magisk/magiskinit",
|
||||
"/data/adb/magisk/magisk",
|
||||
"/data/adb/magisk.db",
|
||||
"/data/adb/ksu",
|
||||
"/data/adb/apatch",
|
||||
"/data/adb/ap/single"};
|
||||
|
||||
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_siro_siro_1rider_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,328 @@
|
||||
package com.siro.siro_rider
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
|
||||
/**
|
||||
* خدمة تحديث حالة الرحلة في شريط الإشعارات على أندرويد.
|
||||
*
|
||||
* تعمل على ثلاثة مستويات حسب الجهاز:
|
||||
* - أندرويد 16 فما فوق (API 36+) مع موافقة المستخدم: تُستخدم
|
||||
* ميزة Live Updates الحقيقية عبر Notification.ProgressStyle
|
||||
* (نفس الفئة والدوال التي تحققنا منها مباشرة من ملف الـSDK).
|
||||
* - أندرويد 16 فما فوق بدون موافقة المستخدم، أو أي إصدار من
|
||||
* API 23 حتى 35: إشعار عادٍ ثابت (Ongoing) بشريط تقدّم كلاسيكي،
|
||||
* يعمل بشكل متطابق تقريباً بصرياً لكن بدون معاملة النظام الخاصة.
|
||||
* - أقل من API 26: لا يتم إنشاء قناة إشعارات (الكلاس غير موجود
|
||||
* أصلاً في النظام قبل هذا الإصدار)، ويُستخدم مسار الإشعار
|
||||
* الكلاسيكي مباشرة بدون قناة.
|
||||
*/
|
||||
class AndroidLiveUpdatePlugin(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL_ID = "siro_live_ride_progress"
|
||||
private const val CHANNEL_NAME = "Siro Live Ride Progress"
|
||||
private const val CHANNEL_DESC = "Live progress updates for your current Siro ride"
|
||||
private const val NOTIFICATION_ID = 999
|
||||
|
||||
private const val STATUS_SEARCHING = "searching"
|
||||
private const val STATUS_DRIVER_ON_WAY = "driver_on_way"
|
||||
private const val STATUS_DRIVER_ARRIVED = "driver_arrived"
|
||||
private const val STATUS_IN_PROGRESS = "in_progress"
|
||||
private const val STATUS_FINISHED = "finished"
|
||||
}
|
||||
|
||||
private val notificationManager: NotificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
private var currentProgress: Int = 0
|
||||
private var currentMaxProgress: Int = 100
|
||||
private var currentStatus: String = STATUS_SEARCHING
|
||||
private var currentTitle: String = ""
|
||||
private var currentBody: String = ""
|
||||
|
||||
init {
|
||||
// NotificationChannel نفسها غير موجودة في النظام قبل API 26،
|
||||
// فاستدعاؤها بدون هذا الشرط يتسبب بانهيار فوري للتطبيق عند
|
||||
// الإقلاع على أي جهاز أقدم (Android 6/7).
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel() {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
CHANNEL_NAME,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = CHANNEL_DESC
|
||||
setShowBadge(false)
|
||||
enableVibration(false)
|
||||
enableLights(false)
|
||||
}
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
/**
|
||||
* هل يمكن فعلياً استخدام Live Updates الحقيقية الآن؟
|
||||
* تتطلب أندرويد 16+ وموافقة صريحة من المستخدم عبر إعدادات النظام
|
||||
* (Settings > Apps > Siro > Notifications > Live Updates).
|
||||
*/
|
||||
@Suppress("NewApi")
|
||||
private fun canUsePromotedLiveUpdate(): Boolean {
|
||||
if (Build.VERSION.SDK_INT < 36) return false
|
||||
return try {
|
||||
notificationManager.canPostPromotedNotifications()
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun statusColor(): Int = when (currentStatus) {
|
||||
STATUS_DRIVER_ARRIVED -> Color.parseColor("#22C55E")
|
||||
STATUS_IN_PROGRESS -> Color.parseColor("#1A2340")
|
||||
STATUS_SEARCHING -> Color.parseColor("#F59E0B")
|
||||
else -> Color.parseColor("#1A2340")
|
||||
}
|
||||
|
||||
private fun buildContentIntent(): PendingIntent {
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra("notification_ride", true)
|
||||
}
|
||||
return PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* بدء التحديث المباشر للرحلة
|
||||
*/
|
||||
fun startLiveUpdate(
|
||||
rideId: String,
|
||||
driverName: String,
|
||||
etaText: String,
|
||||
progress: Double,
|
||||
carDetails: String
|
||||
) {
|
||||
currentStatus = STATUS_DRIVER_ON_WAY
|
||||
currentTitle = "🚗 السائق في الطريق إليك"
|
||||
currentBody = "$driverName • $carDetails • $etaText"
|
||||
currentProgress = (progress * 100).toInt().coerceIn(0, 100)
|
||||
currentMaxProgress = 100
|
||||
|
||||
render(indeterminate = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* تحديث التقدم والوقت المتبقي
|
||||
*/
|
||||
fun updateProgress(progress: Double, etaText: String) {
|
||||
currentProgress = (progress * 100).toInt().coerceIn(0, 100)
|
||||
currentBody = when (currentStatus) {
|
||||
STATUS_DRIVER_ON_WAY -> {
|
||||
val parts = currentBody.split(" • ")
|
||||
if (parts.size >= 3) {
|
||||
"${parts[0]} • ${parts[1]} • $etaText"
|
||||
} else {
|
||||
"$currentBody • $etaText"
|
||||
}
|
||||
}
|
||||
STATUS_IN_PROGRESS -> "المتبقي تقريبًا: $etaText"
|
||||
else -> currentBody
|
||||
}
|
||||
|
||||
render(indeterminate = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* تحديث حالة الرحلة بالكامل
|
||||
*/
|
||||
fun updateStatus(
|
||||
status: String,
|
||||
driverName: String,
|
||||
etaText: String,
|
||||
progress: Double,
|
||||
carDetails: String
|
||||
) {
|
||||
currentStatus = status
|
||||
currentProgress = (progress * 100).toInt().coerceIn(0, 100)
|
||||
|
||||
when (status) {
|
||||
STATUS_SEARCHING -> {
|
||||
currentTitle = "🔍 جاري البحث عن سائق"
|
||||
currentBody = etaText
|
||||
render(indeterminate = true)
|
||||
}
|
||||
STATUS_DRIVER_ON_WAY -> {
|
||||
currentTitle = "🚗 السائق في الطريق إليك"
|
||||
currentBody = "$driverName • $carDetails • $etaText"
|
||||
render(indeterminate = false)
|
||||
}
|
||||
STATUS_DRIVER_ARRIVED -> {
|
||||
currentTitle = "📍 السائق وصل"
|
||||
currentBody = "الرجاء التوجّه لمقابلة $driverName عند نقطة الالتقاء"
|
||||
currentProgress = 100
|
||||
render(indeterminate = false)
|
||||
}
|
||||
STATUS_IN_PROGRESS -> {
|
||||
currentTitle = "🚀 الرحلة جارية الآن"
|
||||
currentBody = "المتبقي تقريبًا: $etaText"
|
||||
render(indeterminate = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* إنهاء التحديث المباشر وإزالة الإشعار
|
||||
*/
|
||||
fun endLiveUpdate() {
|
||||
notificationManager.cancel(NOTIFICATION_ID)
|
||||
currentStatus = STATUS_FINISHED
|
||||
currentProgress = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* يختار المسار المناسب: Live Update الحقيقية إن كانت متاحة ومسموحة،
|
||||
* وإلا الإشعار الكلاسيكي الآمن على كل الإصدارات من API 23.
|
||||
*/
|
||||
private fun render(indeterminate: Boolean) {
|
||||
if (canUsePromotedLiveUpdate()) {
|
||||
try {
|
||||
showPromotedProgressNotification(indeterminate)
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
// أي فشل غير متوقع بالمسار الجديد يرجعنا فوراً للمسار الآمن
|
||||
}
|
||||
}
|
||||
showClassicNotification(indeterminate)
|
||||
}
|
||||
|
||||
/**
|
||||
* المسار الجديد: Notification.ProgressStyle الحقيقية (Android 16 / API 36+).
|
||||
* التوقيعات هنا مؤكدة مباشرة من ملف android-36/android.jar (javap)، وليست تخميناً.
|
||||
*/
|
||||
@RequiresApi(36)
|
||||
private fun showPromotedProgressNotification(indeterminate: Boolean) {
|
||||
val trackerIcon = Icon.createWithResource(context, android.R.drawable.ic_menu_directions)
|
||||
|
||||
val progressStyle = Notification.ProgressStyle()
|
||||
.setProgressSegments(listOf(
|
||||
Notification.ProgressStyle.Segment(100).setColor(statusColor())
|
||||
))
|
||||
.setProgress(currentProgress)
|
||||
.setProgressIndeterminate(indeterminate)
|
||||
.setProgressTrackerIcon(trackerIcon)
|
||||
.setStyledByProgress(true)
|
||||
|
||||
val notification = Notification.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setContentTitle(currentTitle)
|
||||
.setContentText(currentBody)
|
||||
.setStyle(progressStyle)
|
||||
.setCategory(Notification.CATEGORY_PROGRESS)
|
||||
.setOngoing(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentIntent(buildContentIntent())
|
||||
.setFlag(Notification.FLAG_PROMOTED_ONGOING, true)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
/**
|
||||
* المسار الكلاسيكي: إشعار ثابت بشريط تقدّم عادي، متوافق من API 23 وما فوق
|
||||
* (وبدون قناة إشعارات على الإصدارات الأقدم من API 26).
|
||||
*/
|
||||
private fun showClassicNotification(indeterminate: Boolean) {
|
||||
val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
android.R.drawable.ic_menu_directions
|
||||
)
|
||||
)
|
||||
.setContentTitle(currentTitle)
|
||||
.setContentText(currentBody)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.bigText(currentBody)
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentIntent(buildContentIntent())
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
|
||||
if (indeterminate) {
|
||||
notificationBuilder.setProgress(0, 0, true)
|
||||
} else {
|
||||
notificationBuilder.setProgress(currentMaxProgress, currentProgress, false)
|
||||
}
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
/**
|
||||
* معالجة استدعاءات MethodChannel
|
||||
*/
|
||||
fun handleMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"startLiveUpdate" -> {
|
||||
val rideId = call.argument<String>("rideId") ?: ""
|
||||
val driverName = call.argument<String>("driverName") ?: ""
|
||||
val etaText = call.argument<String>("etaText") ?: ""
|
||||
val progress = call.argument<Double>("progress") ?: 0.0
|
||||
val carDetails = call.argument<String>("carDetails") ?: ""
|
||||
startLiveUpdate(rideId, driverName, etaText, progress, carDetails)
|
||||
result.success(true)
|
||||
}
|
||||
"updateProgress" -> {
|
||||
val progress = call.argument<Double>("progress") ?: 0.0
|
||||
val etaText = call.argument<String>("etaText") ?: ""
|
||||
updateProgress(progress, etaText)
|
||||
result.success(true)
|
||||
}
|
||||
"updateStatus" -> {
|
||||
val status = call.argument<String>("status") ?: ""
|
||||
val driverName = call.argument<String>("driverName") ?: ""
|
||||
val etaText = call.argument<String>("etaText") ?: ""
|
||||
val progress = call.argument<Double>("progress") ?: 0.0
|
||||
val carDetails = call.argument<String>("carDetails") ?: ""
|
||||
updateStatus(status, driverName, etaText, progress, carDetails)
|
||||
result.success(true)
|
||||
}
|
||||
"endLiveUpdate" -> {
|
||||
endLiveUpdate()
|
||||
result.success(true)
|
||||
}
|
||||
"isSupported" -> {
|
||||
// الإشعار الكلاسيكي يعمل من API 23 وما فوق؛ الأجهزة الأحدث
|
||||
// تحصل تلقائياً على Live Update الحقيقية عبر canUsePromotedLiveUpdate().
|
||||
result.success(Build.VERSION.SDK_INT >= 23)
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package com.siro.siro_rider
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.PictureInPictureParams
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.util.Rational
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.setPadding
|
||||
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_NAME = "com.siro.siro_rider/security"
|
||||
private lateinit var securityChannel: MethodChannel
|
||||
|
||||
// قناة PiP الجديدة
|
||||
private val PIP_CHANNEL = "siro/pip"
|
||||
private var pipEnabled = false // هل الرحلة نشطة ويجب تفعيل PiP عند الخروج؟
|
||||
|
||||
// قناة Android Live Update (Android 16 Progress Style)
|
||||
private val LIVE_UPDATE_CHANNEL = "siro/android_live_update"
|
||||
private lateinit var liveUpdatePlugin: AndroidLiveUpdatePlugin
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
// -------- 1) قناة الأمان --------
|
||||
securityChannel =
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SECURITY_CHANNEL_NAME)
|
||||
|
||||
securityChannel.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"isNativeRooted" -> {
|
||||
val isCompromised = isDeviceCompromised()
|
||||
result.success(isCompromised)
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------- 2) قناة PiP (Picture-in-Picture) --------
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, PIP_CHANNEL)
|
||||
.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"enablePip" -> {
|
||||
pipEnabled = true
|
||||
result.success(true)
|
||||
}
|
||||
"disablePip" -> {
|
||||
pipEnabled = false
|
||||
result.success(true)
|
||||
}
|
||||
"enterPip" -> {
|
||||
val success = enterPipMode()
|
||||
result.success(success)
|
||||
}
|
||||
"isPipSupported" -> {
|
||||
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------- 3) قناة Android Live Update (Progress Style) --------
|
||||
liveUpdatePlugin = AndroidLiveUpdatePlugin(this)
|
||||
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, LIVE_UPDATE_CHANNEL)
|
||||
.setMethodCallHandler { call, result ->
|
||||
liveUpdatePlugin.handleMethodCall(call, result)
|
||||
}
|
||||
}
|
||||
|
||||
// -------- PiP Helper Methods --------
|
||||
|
||||
private fun enterPipMode(): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val params = PictureInPictureParams.Builder()
|
||||
.setAspectRatio(Rational(9, 16)) // نسبة عمودية مناسبة لعرض الخريطة
|
||||
.build()
|
||||
return enterPictureInPictureMode(params)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// عند ضغط المستخدم على زر الرجوع للشاشة الرئيسية أثناء رحلة نشطة
|
||||
override fun onUserLeaveHint() {
|
||||
super.onUserLeaveHint()
|
||||
if (pipEnabled) {
|
||||
enterPipMode()
|
||||
}
|
||||
}
|
||||
|
||||
// إعلام Flutter بتغيير وضع PiP
|
||||
override fun onPictureInPictureModeChanged(
|
||||
isInPipMode: Boolean,
|
||||
newConfig: Configuration
|
||||
) {
|
||||
super.onPictureInPictureModeChanged(isInPipMode, newConfig)
|
||||
// يمكن لاحقاً إرسال حدث لـ Flutter لإخفاء/إظهار عناصر الواجهة
|
||||
flutterEngine?.dartExecutor?.binaryMessenger?.let { messenger ->
|
||||
MethodChannel(messenger, PIP_CHANNEL)
|
||||
.invokeMethod("onPipChanged", isInPipMode)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- أمن الجهاز (كما عندك تقريباً) ----------------
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (isDeviceCompromised()) {
|
||||
showSecurityWarningDialog()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDeviceCompromised(): Boolean {
|
||||
return try {
|
||||
nativeRootCheck() // || !safetyNetCheck()
|
||||
} catch (e: Exception) {
|
||||
Log.e("SecurityCheck", "Error during security checks: ${e.message}", e)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun nativeRootCheck(): Boolean {
|
||||
Log.d("SecurityCheck", "Starting native root detection...")
|
||||
return try {
|
||||
val isNativeRooted = RootDetection.isNativeRooted()
|
||||
Log.d("SecurityCheck", "Native root detection result: $isNativeRooted")
|
||||
isNativeRooted
|
||||
} catch (e: Exception) {
|
||||
Log.e("SecurityCheck", "Error in native root detection: ${e.message}", e)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// SafetyNet (معلّق كما هو)
|
||||
private fun safetyNetCheck(): Boolean {
|
||||
Log.d("SecurityCheck", "Starting SafetyNet check...")
|
||||
var isSafe = false
|
||||
val semaphore = java.util.concurrent.Semaphore(0)
|
||||
|
||||
SafetyNetCheck.checkSafetyNet(this, getString(R.string.api_key_safety)) { result ->
|
||||
isSafe = result
|
||||
Log.d("SecurityCheck", "SafetyNet check result: $isSafe")
|
||||
semaphore.release()
|
||||
}
|
||||
|
||||
try {
|
||||
semaphore.acquire()
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e("SecurityCheck", "Interrupted while waiting for SafetyNet check", e)
|
||||
return false
|
||||
}
|
||||
return isSafe
|
||||
}
|
||||
|
||||
private fun showSecurityWarningDialog() {
|
||||
var secondsRemaining = 10
|
||||
|
||||
val progressBar = ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal)
|
||||
progressBar.max = 10
|
||||
progressBar.progress = 10
|
||||
|
||||
val textView = TextView(this)
|
||||
textView.text = getString(R.string.security_warning_message)
|
||||
textView.textAlignment = TextView.TEXT_ALIGNMENT_CENTER
|
||||
|
||||
val layout = LinearLayout(this)
|
||||
layout.orientation = LinearLayout.VERTICAL
|
||||
layout.setPadding(48)
|
||||
layout.addView(textView)
|
||||
layout.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 {
|
||||
val packageName = applicationContext.packageName
|
||||
val runtime = Runtime.getRuntime()
|
||||
runtime.exec("pm clear $packageName")
|
||||
} catch (e: Exception) {
|
||||
clearCache()
|
||||
clearAppData()
|
||||
}
|
||||
|
||||
finishAffinity()
|
||||
System.exit(0)
|
||||
}
|
||||
|
||||
private fun clearCache() {
|
||||
try {
|
||||
deleteDir(cacheDir)
|
||||
deleteDir(externalCacheDir)
|
||||
} catch (e: Exception) {
|
||||
Log.e("SecurityCheck", "Error clearing cache: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearAppData() {
|
||||
try {
|
||||
deleteDir(applicationContext.dataDir)
|
||||
} catch (e: Exception) {
|
||||
Log.e("SecurityCheck", "Error clearing app data: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteDir(dir: File?): Boolean {
|
||||
if (dir != null && dir.isDirectory) {
|
||||
val children = dir.list()
|
||||
if (children != null) {
|
||||
for (child in children) {
|
||||
if (!deleteDir(File(dir, child))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dir?.delete() ?: false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.siro.siro_rider
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 5.0 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/launcher_icon" />
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#2C2C2C" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:top="15dp" android:bottom="15dp">
|
||||
<shape android:shape="line">
|
||||
<stroke
|
||||
android:color="#FFFFFF"
|
||||
android:width="2dp"
|
||||
android:dashWidth="10dp"
|
||||
android:dashGap="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 61 KiB |
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
<string name="security_warning_title">تحذير أمني</string>
|
||||
<string name="security_warning_message">تم اكتشاف مشكلة أمنية أو تعديل على هذا الجهاز. لا يمكن
|
||||
تشغيل التطبيق على هذا الجهاز.</string>
|
||||
<string name="exit_button">إغلاق التطبيق</string>
|
||||
<string name="device_secure">الجهاز آمن. الاستمرار بشكل طبيعي.</string>
|
||||
<string name="label">سيرو</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,16 @@
|
||||
<resources>
|
||||
<string name="app_name">My App</string>
|
||||
<!-- <string name="default_notification_channel_id">ride_channel</string> -->
|
||||
<!-- <string name="default_notification_channel_id">default_channel</string> -->
|
||||
<string name="default_notification_channel_id">high_importance_channel</string>
|
||||
<string name="api_key">k</string>
|
||||
<!-- <string name="api_key">AIzaSyCFsWBqvkXzk1Gb-bCGxwqTwJQKIeHjH64</string> -->
|
||||
<string name="security_warning_title">Security Warning</string>
|
||||
<string name="api_key_safety">AIzaSyB04YNW3LbvmQ5lX1t2bOwEU18-KUoovzw</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>
|
||||
<string name="label">Siro Rider</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 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,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="true">intaleq.xyz</domain>
|
||||
<domain includeSubdomains="true">siromove.com</domain>
|
||||
|
||||
<pin-set expiration="2028-01-01">
|
||||
<!-- Primary: ISRG Root X1 (RSA) -->
|
||||
<pin digest="SHA-256">C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHESsl=</pin>
|
||||
<!-- Backup: ISRG Root X2 (ECDSA) -->
|
||||
<pin digest="SHA-256">diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=</pin>
|
||||
</pin-set>
|
||||
</domain-config>
|
||||
|
||||
</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>
|
||||