Fixes & Updates - 2026-06-01: Integrate Back-End v3 updates, fix call/connection issues across apps

This commit is contained in:
Hamza-Ayed
2026-06-01 23:35:29 +03:00
parent 8f555691b9
commit cbf693c804
56 changed files with 6091 additions and 1217 deletions

View File

@@ -97,6 +97,8 @@ flutter {
}
dependencies {
implementation "androidx.car.app:app:1.4.0"
implementation "org.maplibre.gl:android-sdk:11.0.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// تمت الترقية لتطابق تطبيق الراكب
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'

View File

@@ -35,4 +35,16 @@
#-keep class com.sefer_driver.RootDetection { *; }
-keep class com.sefer_driver.RootDetection {
native <methods>;
}
}
# Android Auto Car App Library
-keep class androidx.car.app.** { *; }
-keep class com.intaleq_driver.MyCarAppService { *; }
-keep class com.intaleq_driver.MyCarSession { *; }
-keep class com.intaleq_driver.MyCarScreen { *; }
-keep class com.intaleq_driver.CarNavigationData { *; }
-keep class com.intaleq_driver.MapPresentation { *; }
# MapLibre Native SDK
-keep class org.maplibre.android.** { *; }
-dontwarn org.maplibre.android.**

View File

@@ -70,6 +70,27 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="intaleqapp" />
</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="geo" />
</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="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>
<!-- أنشطة ومكوّنات إضافية -->
<activity android:name="com.yalantis.ucrop.UCropActivity" android:screenOrientation="portrait"
@@ -146,6 +167,22 @@
</receiver>
<!-- مستقبل برودكاست خاص بك -->
<receiver android:name=".YourBroadcastReceiver" android:exported="false" />
<!-- Android Auto Support -->
<meta-data
android:name="androidx.car.app.minCarAppApiLevel"
android:value="1" />
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<service
android:name=".MyCarAppService"
android:exported="true">
<intent-filter>
<action android:name="androidx.car.app.CarAppService" />
<category android:name="androidx.car.app.category.NAVIGATION" />
</intent-filter>
</service>
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@@ -0,0 +1,44 @@
package com.intaleq_driver
import android.os.Handler
import android.os.Looper
/**
* كائن مشترك (Singleton) يحمل بيانات التوجيه في الوقت الحقيقي.
* يتم تحديثه من فلاتر عبر MethodChannel في MainActivity،
* ويتم قراءته من MyCarScreen و MapPresentation لعرض البيانات على شاشة السيارة.
*/
object CarNavigationData {
// --- بيانات الموقع ---
var currentLat: Double = 0.0
var currentLng: Double = 0.0
var currentBearing: Double = 0.0
// --- بيانات التوجيه ---
var currentInstruction: String = "في انتظار بدء الرحلة..."
var distanceToNextStepMeters: Double = 0.0
var totalDistanceRemainingMeters: Double = 0.0
var estimatedTimeRemainingSeconds: Double = 0.0
var maneuverType: Int = 0 // يطابق قيم Maneuver.TYPE_* من Car App Library
// --- حالة التوجيه ---
var isNavigating: Boolean = false
var currentSpeed: Double = 0.0 // km/h
// --- نظام المستمعين ---
private val listeners = mutableListOf<() -> Unit>()
fun addListener(listener: () -> Unit) {
listeners.add(listener)
}
fun removeListener(listener: () -> Unit) {
listeners.remove(listener)
}
fun notifyListeners() {
Handler(Looper.getMainLooper()).post {
listeners.forEach { it.invoke() }
}
}
}

View File

@@ -60,6 +60,50 @@ class MainActivity : FlutterFragmentActivity() {
else -> result.notImplemented()
}
}
// ✅ Channel for Android Auto Navigation Updates
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.intaleq_driver/car_navigation")
.setMethodCallHandler { call, result ->
when (call.method) {
"updateNavState" -> {
// تحديث شامل لجميع بيانات التوجيه دفعة واحدة
CarNavigationData.currentLat = call.argument<Double>("lat") ?: CarNavigationData.currentLat
CarNavigationData.currentLng = call.argument<Double>("lng") ?: CarNavigationData.currentLng
CarNavigationData.currentBearing = call.argument<Double>("bearing") ?: CarNavigationData.currentBearing
CarNavigationData.currentSpeed = call.argument<Double>("speed") ?: CarNavigationData.currentSpeed
CarNavigationData.currentInstruction = call.argument<String>("instruction") ?: CarNavigationData.currentInstruction
CarNavigationData.distanceToNextStepMeters = call.argument<Double>("distanceToStep") ?: CarNavigationData.distanceToNextStepMeters
CarNavigationData.totalDistanceRemainingMeters = call.argument<Double>("totalDistance") ?: CarNavigationData.totalDistanceRemainingMeters
CarNavigationData.estimatedTimeRemainingSeconds = call.argument<Double>("eta") ?: CarNavigationData.estimatedTimeRemainingSeconds
CarNavigationData.maneuverType = call.argument<Int>("maneuver") ?: CarNavigationData.maneuverType
CarNavigationData.isNavigating = call.argument<Boolean>("isNavigating") ?: CarNavigationData.isNavigating
CarNavigationData.notifyListeners()
result.success(true)
}
"updateLocation" -> {
CarNavigationData.currentLat = call.argument<Double>("lat") ?: 0.0
CarNavigationData.currentLng = call.argument<Double>("lng") ?: 0.0
CarNavigationData.currentBearing = call.argument<Double>("bearing") ?: CarNavigationData.currentBearing
CarNavigationData.currentSpeed = call.argument<Double>("speed") ?: CarNavigationData.currentSpeed
CarNavigationData.notifyListeners()
result.success(true)
}
"updateInstruction" -> {
CarNavigationData.currentInstruction = call.argument<String>("instruction") ?: ""
CarNavigationData.maneuverType = call.argument<Int>("maneuver") ?: 0
CarNavigationData.distanceToNextStepMeters = call.argument<Double>("distanceToStep") ?: 0.0
CarNavigationData.notifyListeners()
result.success(true)
}
"stopNavigation" -> {
CarNavigationData.isNavigating = false
CarNavigationData.currentInstruction = "تمت الرحلة بنجاح!"
CarNavigationData.notifyListeners()
result.success(true)
}
else -> result.notImplemented()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -0,0 +1,119 @@
package com.intaleq_driver
import android.app.Presentation
import android.content.Context
import android.os.Bundle
import android.view.Display
import android.view.ViewGroup
import android.widget.FrameLayout
import org.maplibre.android.MapLibre
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.camera.CameraUpdateFactory
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.Style
/**
* شاشة عرض وهمية (Presentation) تُرسم على VirtualDisplay الخاص بشاشة السيارة.
* تستخدم MapLibre Native SDK لعرض الخريطة بنفس ستايل تطبيق انطلق.
*/
class MapPresentation(outerContext: Context, display: Display) : Presentation(outerContext, display) {
lateinit var mapView: MapView
private var mapboxMap: MapLibreMap? = null
private var isMapReady = false
private val locationListener: () -> Unit = {
updateCameraPosition()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MapLibre.getInstance(context)
val root = FrameLayout(context)
root.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
mapView = MapView(context)
mapView.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
root.addView(mapView)
setContentView(root)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync { map ->
mapboxMap = map
// تحميل ستايل خرائط انطلق من أصول فلاتر
val styleUrl = "asset://flutter_assets/assets/style.json"
map.setStyle(Style.Builder().fromUri(styleUrl)) {
isMapReady = true
updateCameraPosition()
}
// إعدادات مناسبة لشاشة السيارة
map.uiSettings.isCompassEnabled = false
map.uiSettings.isLogoEnabled = false
map.uiSettings.isAttributionEnabled = false
}
// الاستماع لتحديثات الموقع القادمة من فلاتر
CarNavigationData.addListener(locationListener)
}
private fun updateCameraPosition() {
if (!isMapReady || mapboxMap == null) return
val lat = CarNavigationData.currentLat
val lng = CarNavigationData.currentLng
val bearing = CarNavigationData.currentBearing
val speed = CarNavigationData.currentSpeed
if (lat == 0.0 && lng == 0.0) return
// حساب الزوم المناسب بناءً على السرعة (نفس المنطق في فلاتر)
val zoom = when {
speed < 15 -> 17.0
speed < 40 -> 16.5
speed < 70 -> 15.5
speed < 100 -> 15.0
else -> 14.0
}
// حساب الميل (Tilt) بناءً على السرعة لتأثير ثلاثي الأبعاد
val tilt = when {
speed < 10 -> 0.0
speed < 40 -> 40.0
else -> 55.0
}
val position = CameraPosition.Builder()
.target(LatLng(lat, lng))
.zoom(zoom)
.bearing(bearing)
.tilt(tilt)
.build()
mapboxMap?.animateCamera(
CameraUpdateFactory.newCameraPosition(position),
1000 // انتقال سلس خلال ثانية
)
}
override fun onStart() { super.onStart(); mapView.onStart() }
override fun onStop() { super.onStop(); mapView.onStop() }
fun onResume() { mapView.onResume() }
fun onPause() { mapView.onPause() }
fun onDestroy() {
CarNavigationData.removeListener(locationListener)
mapView.onDestroy()
}
}

View File

@@ -0,0 +1,24 @@
package com.intaleq_driver
import android.content.pm.ApplicationInfo
import androidx.car.app.CarAppService
import androidx.car.app.Session
import androidx.car.app.validation.HostValidator
class MyCarAppService : CarAppService() {
override fun createHostValidator(): HostValidator {
// في وضع التطوير: نسمح لجميع المستضيفين (DHU + أي تطبيق)
// في وضع الإنتاج: نسمح فقط لتطبيقات Android Auto و Google الرسمية
return if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) {
HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
} else {
HostValidator.Builder(applicationContext)
.addAllowedHosts(androidx.car.app.R.array.hosts_allowlist_sample)
.build()
}
}
override fun onCreateSession(): Session {
return MyCarSession()
}
}

View File

@@ -0,0 +1,80 @@
package com.intaleq_driver
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Distance
import androidx.car.app.model.MessageTemplate
import androidx.car.app.model.Template
import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.NavigationTemplate
import androidx.car.app.navigation.model.RoutingInfo
import androidx.car.app.navigation.model.Step
class MyCarScreen(carContext: CarContext) : Screen(carContext) {
init {
CarNavigationData.addListener {
invalidate()
}
}
override fun onGetTemplate(): Template {
// إذا لم يكن التوجيه نشطاً بعد، نعرض شاشة ترحيبية
if (!CarNavigationData.isNavigating) {
return MessageTemplate.Builder("مرحباً بك في انطلق درايفر\nبانتظار بدء رحلة جديدة...")
.setTitle("Intaleq Driver")
.setHeaderAction(Action.APP_ICON)
.build()
}
// --- بناء معلومات التوجيه (Turn-by-Turn) ---
val maneuverType = mapIntaleqManeuverToCarManeuver(CarNavigationData.maneuverType)
val maneuver = Maneuver.Builder(maneuverType).build()
val step = Step.Builder(CarNavigationData.currentInstruction)
.setManeuver(maneuver)
.build()
val distanceToStep = Distance.create(
CarNavigationData.distanceToNextStepMeters,
Distance.UNIT_METERS
)
val routingInfo = RoutingInfo.Builder()
.setCurrentStep(step, distanceToStep)
.build()
// --- بناء قالب التوجيه ---
return NavigationTemplate.Builder()
.setNavigationInfo(routingInfo)
.setActionStrip(
androidx.car.app.model.ActionStrip.Builder()
.addAction(Action.APP_ICON)
.build()
)
.setBackgroundColor(CarColor.PRIMARY)
.build()
}
/**
* تحويل أكواد الانعطاف الخاصة بتطبيق انطلق (NavigationController.currentManeuverModifier)
* إلى أكواد Maneuver الرسمية من مكتبة Android for Cars.
*/
private fun mapIntaleqManeuverToCarManeuver(intaleqCode: Int): Int {
return when (intaleqCode) {
0 -> Maneuver.TYPE_STRAIGHT // مستقيم
2 -> Maneuver.TYPE_TURN_NORMAL_RIGHT // يمين
3 -> Maneuver.TYPE_TURN_SLIGHT_RIGHT // يمين خفيف
-2 -> Maneuver.TYPE_TURN_NORMAL_LEFT // يسار
-1 -> Maneuver.TYPE_TURN_SLIGHT_LEFT // يسار خفيف
4 -> Maneuver.TYPE_DESTINATION // وصلت
6 -> Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW // دوار
7 -> Maneuver.TYPE_KEEP_RIGHT // ابق يمين
-7 -> Maneuver.TYPE_KEEP_LEFT // ابق يسار
else -> Maneuver.TYPE_UNKNOWN
}
}
}

View File

@@ -0,0 +1,69 @@
package com.intaleq_driver
import android.content.Intent
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.os.Handler
import android.os.Looper
import androidx.car.app.AppManager
import androidx.car.app.Screen
import androidx.car.app.Session
import androidx.car.app.SurfaceCallback
import androidx.car.app.SurfaceContainer
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
class MyCarSession : Session(), DefaultLifecycleObserver {
private var virtualDisplay: VirtualDisplay? = null
private var presentation: MapPresentation? = null
override fun onCreateScreen(intent: Intent): Screen {
lifecycle.addObserver(this)
val appManager = carContext.getCarService(AppManager::class.java)
appManager.setSurfaceCallback(object : SurfaceCallback {
override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) {
val surface = surfaceContainer.surface ?: return
val width = surfaceContainer.width
val height = surfaceContainer.height
val dpi = surfaceContainer.dpi
val displayManager = carContext.getSystemService(DisplayManager::class.java)
virtualDisplay = displayManager.createVirtualDisplay(
"CarAppMapDisplay",
width,
height,
dpi,
surface,
DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
)
virtualDisplay?.display?.let { display ->
Handler(Looper.getMainLooper()).post {
presentation = MapPresentation(carContext, display)
presentation?.show()
}
}
}
override fun onVisibleAreaChanged(visibleArea: android.graphics.Rect) {
// تحديث المساحة المرئية إذا لزم الأمر
}
override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) {
Handler(Looper.getMainLooper()).post {
presentation?.dismiss()
presentation = null
}
virtualDisplay?.release()
virtualDisplay = null
}
})
return MyCarScreen(carContext)
}
override fun onResume(owner: LifecycleOwner) { presentation?.onResume() }
override fun onPause(owner: LifecycleOwner) { presentation?.onPause() }
override fun onDestroy(owner: LifecycleOwner) { presentation?.onDestroy() }
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<automotiveApp>
<uses name="navigation" />
</automotiveApp>