2026-02-28-1

This commit is contained in:
Hamza-Ayed
2026-02-28 01:12:28 +03:00
parent 33f34f7c50
commit 76c04702cd
409 changed files with 2858 additions and 1090 deletions

View File

@@ -100,4 +100,5 @@ dependencies {
implementation 'com.stripe:paymentsheet:21.4.2'
implementation 'com.scottyab:rootbeer-lib:0.1.0'
implementation 'com.google.android.gms:play-services-safetynet:18.1.0'
implementation 'com.google.android.gms:play-services-location:21.3.0'
}

View File

@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.your.package.name">
package="com.Intaleq.intaleq">
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
@@ -14,6 +14,9 @@
<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-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
@@ -27,6 +30,11 @@
android:usesCleartextTraffic="false"
android:networkSecurityConfig="@xml/network_security_config">
<!-- ✅ مهم جداً: تعريف أن المشروع يستخدم V2 Embedding -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<activity
android:name=".MainActivity"
android:exported="true"
@@ -47,7 +55,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- App Links (Verified Domain Only) -->
<!-- 🔗 App Links -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@@ -64,7 +72,7 @@
android:pathPrefix="/" />
</intent-filter>
<!-- Custom Scheme -->
<!-- 🔗 Custom Scheme -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@@ -98,7 +106,11 @@
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<service
android:name=".RideTrackingService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />
<!-- UCrop -->
<activity
android:name="com.yalantis.ucrop.UCropActivity"

View File

@@ -17,35 +17,89 @@ import kotlin.concurrent.schedule
class MainActivity : FlutterFragmentActivity() {
// The channel name is updated to use your new package name.
private val CHANNEL_NAME = "com.Intaleq.intaleq/security"
private lateinit var channel: MethodChannel
// قناة الأمان (كما هي)
private val SECURITY_CHANNEL_NAME = "com.Intaleq.intaleq/security"
private lateinit var securityChannel: MethodChannel
// قناة تتبّع الرحلة (Live Activity على أندرويد)
private val RIDE_TRACKING_CHANNEL = "intaleq/ride_tracking"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// Initialize the MethodChannel with the new name
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME)
// -------- 1) قناة الأمان --------
securityChannel =
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SECURITY_CHANNEL_NAME)
// Set a MethodCallHandler to handle method calls from Flutter
channel.setMethodCallHandler { call, result ->
securityChannel.setMethodCallHandler { call, result ->
when (call.method) {
"isNativeRooted" -> {
val isCompromised = isDeviceCompromised()
result.success(isCompromised) // Send the result back to Flutter
result.success(isCompromised)
}
else -> {
result.notImplemented() // Handle unknown method calls
result.notImplemented()
}
}
}
// -------- 2) قناة تتبع الرحلة (Ride Tracking) --------
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, RIDE_TRACKING_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"updateRideTracking" -> {
val driverName = call.argument<String>("driverName") ?: "السائق"
val driverPhone = call.argument<String>("driverPhone") ?: ""
val carDetails = call.argument<String>("carDetails") ?: ""
val driverLat = call.argument<Double>("driverLat") ?: 0.0
val driverLng = call.argument<Double>("driverLng") ?: 0.0
val passengerLat = call.argument<Double>("passengerLat") ?: 0.0
val passengerLng = call.argument<Double>("passengerLng") ?: 0.0
val destLat = call.argument<Double>("destLat") ?: 0.0
val destLng = call.argument<Double>("destLng") ?: 0.0
val rideState =
call.argument<String>("rideState")
?: "waiting" // "waiting" أو "inProgress"
val estimatedTime = call.argument<Int>("estimatedTime") ?: 5 // بالدقائق
val totalDistance =
call.argument<Double>("totalDistance") ?: 0.0 // بالمتر
RideTrackingService.startOrUpdate(
context = this,
driverName = driverName,
driverPhone = driverPhone,
carDetails = carDetails,
driverLat = driverLat,
driverLng = driverLng,
passengerLat = passengerLat,
passengerLng = passengerLng,
destLat = destLat,
destLng = destLng,
rideState = rideState,
estimatedTime = estimatedTime,
totalDistance = totalDistance
)
result.success(null)
}
"stopRideTracking" -> {
RideTrackingService.stop(this)
result.success(null)
}
else -> {
result.notImplemented()
}
}
}
}
// ---------------- أمن الجهاز (كما عندك تقريباً) ----------------
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Perform all checks. The order can matter; you might want to prioritize
// the faster checks first.
if (isDeviceCompromised()) {
showSecurityWarningDialog()
}
@@ -53,24 +107,22 @@ class MainActivity : FlutterFragmentActivity() {
private fun isDeviceCompromised(): Boolean {
return try {
// NOTE: The SafetyNet check is commented out, just like in your old code.
nativeRootCheck() || rootBeerCheck() // || !safetyNetCheck()
} catch (e: Exception) {
Log.e("SecurityCheck", "Error during security checks: ${e.message}", e)
true // Consider the device compromised on error
true
}
}
private fun nativeRootCheck(): Boolean {
Log.d("SecurityCheck", "Starting native root detection...")
return try {
// This assumes you have a 'RootDetection' object/class in your project.
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 // Consider rooted on exception
true
}
}
@@ -82,38 +134,36 @@ class MainActivity : FlutterFragmentActivity() {
return isRooted
}
// IMPORTANT: The SafetyNet API is deprecated. It's recommended to migrate to the Play Integrity
// API.
// This function is kept here from your old code for reference.
// SafetyNet (معلّق كما هو)
private fun safetyNetCheck(): Boolean {
Log.d("SecurityCheck", "Starting SafetyNet check...")
var isSafe = false // Initialize a variable to store result
val semaphore = java.util.concurrent.Semaphore(0) // Create a semaphore
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() // Release the semaphore when the callback is executed
semaphore.release()
}
try {
semaphore.acquire() // Wait for the callback to complete
semaphore.acquire()
} catch (e: InterruptedException) {
Log.e("SecurityCheck", "Interrupted while waiting for SafetyNet check", e)
return false // Or handle as appropriate for your app
return false
}
return isSafe
}
private fun showSecurityWarningDialog() {
var secondsRemaining = 10 // Start at 10 seconds
var secondsRemaining = 10
// Create views programmatically
val progressBar = ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal)
progressBar.max = 10 // Set max to 10 for 10 seconds
progressBar.progress = 10 // Start full
progressBar.max = 10
progressBar.progress = 10
val textView = TextView(this)
textView.text = getString(R.string.security_warning_message) // Your message
textView.text = getString(R.string.security_warning_message)
textView.textAlignment = TextView.TEXT_ALIGNMENT_CENTER
val layout = LinearLayout(this)
@@ -124,26 +174,22 @@ class MainActivity : FlutterFragmentActivity() {
val dialog =
AlertDialog.Builder(this)
.setTitle(getString(R.string.security_warning_title)) // Your title
.setView(layout) // Set the custom layout
.setCancelable(
false
) // Prevent dismissing by tapping outside or back button
.setTitle(getString(R.string.security_warning_title))
.setView(layout)
.setCancelable(false)
.create()
dialog.show()
// Use a Timer to update the progress bar and countdown
val timer = Timer()
timer.schedule(0, 1000) { // Update every 1000ms (1 second)
timer.schedule(0, 1000) {
secondsRemaining--
runOnUiThread { // Update UI on the main thread
progressBar.progress =
secondsRemaining // Set the progress bar to show remaining seconds
runOnUiThread {
progressBar.progress = secondsRemaining
if (secondsRemaining <= 0) {
timer.cancel() // Stop the timer
dialog.dismiss() // Dismiss the dialog
clearAppDataAndExit() // Clear data and exit
timer.cancel()
dialog.dismiss()
clearAppDataAndExit()
}
}
}
@@ -151,19 +197,16 @@ class MainActivity : FlutterFragmentActivity() {
private fun clearAppDataAndExit() {
try {
// Note: This command often requires system permissions and may fail on user apps.
// The fallback methods below will be used if it fails.
val packageName = applicationContext.packageName
val runtime = Runtime.getRuntime()
runtime.exec("pm clear $packageName") // Clear app data
runtime.exec("pm clear $packageName")
} catch (e: Exception) {
// Fallback to manual deletion if 'pm clear' fails
clearCache()
clearAppData()
}
finishAffinity() // Finish all activities from this app
System.exit(0) // Terminate the app's process
finishAffinity()
System.exit(0)
}
private fun clearCache() {
@@ -177,7 +220,6 @@ class MainActivity : FlutterFragmentActivity() {
private fun clearAppData() {
try {
// Be careful with this, as it deletes everything in the app's data directory.
deleteDir(applicationContext.dataDir)
} catch (e: Exception) {
Log.e("SecurityCheck", "Error clearing app data: ${e.message}", e)

View File

@@ -0,0 +1,373 @@
package com.Intaleq.intaleq
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.view.View
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import kotlin.math.*
class RideTrackingService : Service() {
companion object {
private const val TAG = "RideTrackingService"
const val CHANNEL_ID = "TRIP_LIVE_ACTIVITY_CHANNEL"
const val NOTIFICATION_ID = 1001
fun startOrUpdate(
context: Context,
driverName: String,
driverPhone: String,
carDetails: String,
driverLat: Double,
driverLng: Double,
passengerLat: Double,
passengerLng: Double,
destLat: Double,
destLng: Double,
rideState: String,
estimatedTime: Int,
totalDistance: Double
) {
val intent =
Intent(context, RideTrackingService::class.java).apply {
putExtra("driverName", driverName)
putExtra("driverPhone", driverPhone)
putExtra("carDetails", carDetails)
putExtra("driverLat", driverLat)
putExtra("driverLng", driverLng)
putExtra("passengerLat", passengerLat)
putExtra("passengerLng", passengerLng)
putExtra("destLat", destLat)
putExtra("destLng", destLng)
putExtra("rideState", rideState)
putExtra("estimatedTime", estimatedTime)
putExtra("totalDistance", totalDistance)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
fun stop(context: Context) {
val intent = Intent(context, RideTrackingService::class.java)
context.stopService(intent)
}
}
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
private lateinit var notificationManager: NotificationManager
private var driverLatitude = 0.0
private var driverLongitude = 0.0
private var passengerLatitude = 0.0
private var passengerLongitude = 0.0
private var destinationLatitude = 0.0
private var destinationLongitude = 0.0
private var rideState: String = "waiting"
private var driverName: String = "السائق"
private var driverPhone: String = ""
private var carDetails: String = ""
private var estimatedTimeMinutes: Int = 0
private var totalDistanceMeters: Double = 0.0
private var distanceCoveredMeters: Double = 0.0
private var initialDriverDistanceToPassenger: Double = -1.0
override fun onCreate() {
super.onCreate()
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
setupLocationCallback()
createNotificationChannel()
Log.d(TAG, "Service Created")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "Service Started")
intent?.let {
driverName = it.getStringExtra("driverName") ?: "السائق"
driverPhone = it.getStringExtra("driverPhone") ?: ""
carDetails = it.getStringExtra("carDetails") ?: ""
driverLatitude = it.getDoubleExtra("driverLat", 0.0)
driverLongitude = it.getDoubleExtra("driverLng", 0.0)
passengerLatitude = it.getDoubleExtra("passengerLat", 0.0)
passengerLongitude = it.getDoubleExtra("passengerLng", 0.0)
destinationLatitude = it.getDoubleExtra("destLat", 0.0)
destinationLongitude = it.getDoubleExtra("destLng", 0.0)
rideState = it.getStringExtra("rideState") ?: "waiting"
estimatedTimeMinutes = it.getIntExtra("estimatedTime", 5)
totalDistanceMeters = it.getDoubleExtra("totalDistance", 0.0)
}
if (rideState == "waiting" && initialDriverDistanceToPassenger < 0) {
val currentDist =
calculateDistance(
passengerLatitude,
passengerLongitude,
driverLatitude,
driverLongitude
)
initialDriverDistanceToPassenger =
if (totalDistanceMeters > currentDist) totalDistanceMeters else currentDist
}
startForeground(NOTIFICATION_ID, createNotification())
startLocationUpdates()
return START_STICKY
}
private fun setupLocationCallback() {
locationCallback =
object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
for (location in locationResult.locations) {
passengerLatitude = location.latitude
passengerLongitude = location.longitude
if (rideState == "inProgress" && totalDistanceMeters > 0) {
val remainingToDest =
calculateDistance(
passengerLatitude,
passengerLongitude,
destinationLatitude,
destinationLongitude
)
distanceCoveredMeters =
(totalDistanceMeters - remainingToDest).coerceAtLeast(0.0)
}
updateNotification()
}
}
}
}
private fun startLocationUpdates() {
val locationRequest =
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000L)
.setMinUpdateIntervalMillis(2000L)
.build()
try {
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
} catch (e: SecurityException) {
Log.e(TAG, "Location permission denied: ${e.message}")
}
}
private fun createNotification(): Notification {
val contentText = buildContentText()
// 1. جلب التصميم المخصص
val layoutId = resources.getIdentifier("notification_ride_live", "layout", packageName)
// إذا لم يجد الملف، سيطبع خطأ أحمر في اللوج
if (layoutId == 0) {
Log.e(TAG, "❌ خطأ فادح: ملف notification_ride_live.xml غير موجود!")
}
val remoteViews = RemoteViews(packageName, layoutId)
// 2. تعبئة النصوص
val subtitleId = resources.getIdentifier("tv_subtitle", "id", packageName)
val etaId = resources.getIdentifier("tv_eta", "id", packageName)
val titleId = resources.getIdentifier("tv_title", "id", packageName)
if (subtitleId != 0) remoteViews.setTextViewText(subtitleId, "$driverName$carDetails")
if (etaId != 0) remoteViews.setTextViewText(etaId, contentText)
if (titleId != 0)
remoteViews.setTextViewText(
titleId,
if (rideState == "waiting") "السائق في الطريق إليك"
else "رحلة Intaleq جارية"
)
// 3. حساب التقدم (موقع السيارة على الشارع)
var progressIndex = 0
if (rideState == "inProgress" && totalDistanceMeters > 0) {
val percent = (distanceCoveredMeters / totalDistanceMeters).coerceIn(0.0, 1.0)
progressIndex = (percent * 9).toInt()
} else if (rideState == "waiting") {
val remainingToPassenger =
calculateDistance(
passengerLatitude,
passengerLongitude,
driverLatitude,
driverLongitude
)
val total =
if (initialDriverDistanceToPassenger > 0) initialDriverDistanceToPassenger
else remainingToPassenger.coerceAtLeast(1.0)
val percent = 1.0 - (remainingToPassenger / total).coerceIn(0.0, 1.0)
progressIndex = (percent * 9).toInt()
}
// 4. إظهار السيارة في الموضع الصحيح وإخفائها من الباقي
for (i in 0..9) {
val resId = resources.getIdentifier("car_slot_$i", "id", packageName)
if (resId != 0) {
remoteViews.setViewVisibility(
resId,
if (i == progressIndex) View.VISIBLE else View.INVISIBLE
)
}
}
val intent = Intent(this, MainActivity::class.java)
val pendingIntent =
PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// 5. بناء الإشعار (بدون استخدام setProgress نهائياً لكي لا يظهر الخط الأزرق)
val builder =
NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_dialog_map) // أيقونة التطبيق الصغيرة
.setStyle(
NotificationCompat.DecoratedCustomViewStyle()
) // إجباري للتصميم المخصص
.setCustomContentView(remoteViews) // التصميم عند طي الإشعار
.setCustomBigContentView(remoteViews) // التصميم عند سحب الإشعار للأسفل
.setContentIntent(pendingIntent)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
return builder.build()
}
private fun buildContentText(): String {
return when (rideState) {
"waiting" -> {
val distanceToPassenger =
calculateDistance(
passengerLatitude,
passengerLongitude,
driverLatitude,
driverLongitude
)
val etaMinutes =
if (distanceToPassenger > 0) {
(distanceToPassenger / 250.0).toInt().coerceAtLeast(1)
} else {
estimatedTimeMinutes
}
"وصول خلال $etaMinutes د • ${String.format("%.1f", distanceToPassenger / 1000)} كم"
}
"inProgress" -> {
if (totalDistanceMeters > 0) {
val remaining = (totalDistanceMeters - distanceCoveredMeters).coerceAtLeast(0.0)
val progressPercent =
((distanceCoveredMeters / totalDistanceMeters) * 100)
.toInt()
.coerceIn(0, 100)
val etaMinutes =
if (estimatedTimeMinutes > 0) {
((remaining / totalDistanceMeters) * estimatedTimeMinutes)
.toInt()
.coerceAtLeast(1)
} else {
5
}
"المتبقي: ${String.format("%.1f", remaining / 1000)} كم • ~$etaMinutes د"
} else {
"الرحلة قيد التنفيذ..."
}
}
else -> "جاري تحديث موقع الرحلة..."
}
}
private fun updateNotification() {
val notification = createNotification()
notificationManager.notify(NOTIFICATION_ID, notification)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel(
CHANNEL_ID,
"تتبع الرحلة",
NotificationManager.IMPORTANCE_HIGH
)
.apply {
description = "إخطارات حية لتقدم الرحلة"
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
setSound(null, null)
}
notificationManager.createNotificationChannel(channel)
}
}
private fun calculateDistance(lat1: Double, lng1: Double, lat2: Double, lng2: Double): Double {
val r = 6371000.0
val dLat = Math.toRadians(lat2 - lat1)
val dLng = Math.toRadians(lng2 - lng1)
val a =
sin(dLat / 2) * sin(dLat / 2) +
cos(Math.toRadians(lat1)) *
cos(Math.toRadians(lat2)) *
sin(dLng / 2) *
sin(dLng / 2)
val c = 2 * atan2(sqrt(a), sqrt(1 - a))
return r * c
}
override fun onDestroy() {
super.onDestroy()
try {
fusedLocationClient.removeLocationUpdates(locationCallback)
} catch (e: Exception) {
Log.e(TAG, "Error removing location updates: ${e.message}")
}
Log.d(TAG, "Service Destroyed")
}
override fun onBind(intent: Intent?): IBinder? = null
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -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>

View File

@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- العنوان الرئيسي -->
<TextView
android:id="@+id/tv_title"
android:text="رحلة Intaleq"
android:textStyle="bold"
android:textSize="14sp"
android:textColor="#FFFFFF"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- سطر بيانات السائق / السيارة -->
<TextView
android:id="@+id/tv_subtitle"
android:text="أحمد محمد • أبيض • ABC 123"
android:textSize="12sp"
android:textColor="#DDDDDD"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="2dp" />
<!-- خط الطريق + سلوطات السيارة -->
<FrameLayout
android:layout_marginTop="6dp"
android:layout_marginBottom="4dp"
android:layout_width="match_parent"
android:layout_height="32dp">
<!-- خلفية الطريق -->
<ImageView
android:id="@+id/img_road"
android:src="@drawable/road_bg"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- شريط سلوطات السيارة -->
<LinearLayout
android:id="@+id/ll_car_slots"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<!-- 10 سلوطات للسيارة -->
<!-- كل Slot عبارة عن ImageView، نخلي واحد بس منهم ظاهر حسب الـ progress -->
<ImageView
android:id="@+id/car_slot_0"
android:src="@drawable/car_icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/car_slot_1"
android:src="@drawable/car_icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/car_slot_2"
android:src="@drawable/car_icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/car_slot_3"
android:src="@drawable/car_icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/car_slot_4"
android:src="@drawable/car_icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/car_slot_5"
android:src="@drawable/car_icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/car_slot_6"
android:src="@drawable/car_icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/car_slot_7"
android:src="@drawable/car_icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/car_slot_8"
android:src="@drawable/car_icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/car_slot_9"
android:src="@drawable/car_icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="visible"
android:layout_gravity="center_vertical" />
</LinearLayout>
</FrameLayout>
<!-- سطر الوقت/المسافة المتبقية -->
<TextView
android:id="@+id/tv_eta"
android:text="المتبقي: 8 كم • 12 دقيقة"
android:textSize="12sp"
android:textColor="#CCCCCC"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>