2026-04-03-maplibra come next
This commit is contained in:
@@ -30,6 +30,7 @@ android {
|
||||
version "3.22.1"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
@@ -47,8 +48,8 @@ android {
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdkVersion = 24
|
||||
targetSdk = 36
|
||||
versionCode = 60
|
||||
versionName = '1.1.60'
|
||||
versionCode = 63
|
||||
versionName = '1.1.63'
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "arm64-v8a"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.Intaleq.intaleq">
|
||||
|
||||
<!-- 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" />
|
||||
@@ -14,9 +13,6 @@
|
||||
<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" />
|
||||
@@ -30,7 +26,6 @@
|
||||
android:usesCleartextTraffic="false"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<!-- ✅ مهم جداً: تعريف أن المشروع يستخدم V2 Embedding -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
@@ -39,49 +34,37 @@
|
||||
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">
|
||||
|
||||
<!-- Flutter -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
|
||||
<!-- Launcher -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- 🔗 App Links -->
|
||||
<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="intaleqapp.com"
|
||||
android:pathPrefix="/" />
|
||||
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="www.intaleqapp.com"
|
||||
<data android:scheme="https" android:host="intaleqapp.com" android:pathPrefix="/" />
|
||||
<data android:scheme="https" android:host="www.intaleqapp.com"
|
||||
android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- 🔗 Custom Scheme -->
|
||||
<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="intaleq" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- 🔗 Intercept Geo URIs (geo:lat,lng) -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@@ -89,34 +72,16 @@
|
||||
<data android:scheme="geo" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- 🔗 Intercept External Map URLs (Google/Apple) -->
|
||||
<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="http" android:host="maps.google.com" />
|
||||
<data android:scheme="https" android:host="maps.google.com" />
|
||||
<data android:scheme="https" android:host="maps.apple.com" />
|
||||
<data android:scheme="https" android:host="goo.gl" />
|
||||
<data android:scheme="http" android:host="goo.gl" />
|
||||
<data android:scheme="https" android:host="maps.app.goo.gl" />
|
||||
<data android:scheme="http" android:host="maps.app.goo.gl" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<!-- Google Maps API -->
|
||||
<meta-data
|
||||
android:name="com.google.android.geo.API_KEY"
|
||||
android:value="${mapsApiKey}" />
|
||||
|
||||
<!-- Firebase Notification Channel -->
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||
android:value="@string/default_notification_channel_id" />
|
||||
|
||||
<!-- Local Notifications Receivers -->
|
||||
<receiver
|
||||
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver"
|
||||
android:exported="false" />
|
||||
@@ -129,12 +94,7 @@
|
||||
<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"
|
||||
android:screenOrientation="portrait"
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package com.Intaleq.intaleq
|
||||
|
||||
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
|
||||
@@ -21,8 +25,9 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
private val SECURITY_CHANNEL_NAME = "com.Intaleq.intaleq/security"
|
||||
private lateinit var securityChannel: MethodChannel
|
||||
|
||||
// قناة تتبّع الرحلة (Live Activity على أندرويد)
|
||||
private val RIDE_TRACKING_CHANNEL = "intaleq/ride_tracking"
|
||||
// قناة PiP الجديدة
|
||||
private val PIP_CHANNEL = "intaleq/pip"
|
||||
private var pipEnabled = false // هل الرحلة نشطة ويجب تفعيل PiP عند الخروج؟
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
@@ -43,50 +48,24 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// -------- 2) قناة تتبع الرحلة (Ride Tracking) --------
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, RIDE_TRACKING_CHANNEL)
|
||||
// -------- 2) قناة PiP (Picture-in-Picture) --------
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, PIP_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)
|
||||
"enablePip" -> {
|
||||
pipEnabled = true
|
||||
result.success(true)
|
||||
}
|
||||
"stopRideTracking" -> {
|
||||
RideTrackingService.stop(this)
|
||||
result.success(null)
|
||||
"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()
|
||||
@@ -95,6 +74,39 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// -------- 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?) {
|
||||
|
||||
@@ -1,373 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,21 +1,4 @@
|
||||
// allprojects {
|
||||
// repositories {
|
||||
// google()
|
||||
// mavenCentral()
|
||||
// }
|
||||
// }
|
||||
|
||||
// rootProject.buildDir = "../build"
|
||||
// subprojects {
|
||||
// project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
// }
|
||||
// subprojects {
|
||||
// project.evaluationDependsOn(":app")
|
||||
// }
|
||||
|
||||
// tasks.register("clean", Delete) {
|
||||
// delete rootProject.buildDir
|
||||
// }
|
||||
buildscript {
|
||||
ext.kotlin_version = '2.1.0'
|
||||
repositories {
|
||||
|
||||
Reference in New Issue
Block a user