2026-02-20-overlay

This commit is contained in:
Hamza-Ayed
2026-02-20 17:55:51 +03:00
parent 0b826f6e01
commit d697de9c25
206 changed files with 2635 additions and 1359 deletions

View File

@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.intaleq_driver.trip_overlay_plugin">
</manifest>

View File

@@ -0,0 +1,179 @@
package com.intaleq_driver.trip_overlay_plugin
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry
class TripOverlayPlugin :
FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.ActivityResultListener {
private lateinit var channel: MethodChannel
private lateinit var context: Context
private var activity: Activity? = null
companion object {
const val CHANNEL = "trip_overlay_plugin"
const val TAG = "TripOverlayPlugin"
const val REQUEST_CODE_OVERLAY = 1001
// Static reference so TripOverlayService can call back into Flutter
var methodChannel: MethodChannel? = null
/**
* Called by TripOverlayService when the driver taps Accept. Sends event Flutter → Dart
* side.
*/
fun notifyTripAccepted(tripId: String) {
methodChannel?.invokeMethod("onTripAccepted", mapOf("tripId" to tripId))
Log.d(TAG, "notifyTripAccepted: $tripId")
}
/** Called by TripOverlayService when the driver taps Reject or timer expires. */
fun notifyTripRejected(tripId: String) {
methodChannel?.invokeMethod("onTripRejected", mapOf("tripId" to tripId))
Log.d(TAG, "notifyTripRejected: $tripId")
}
}
// ─── FlutterPlugin ───────────────────────────────────────────────────────
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
context = binding.applicationContext
channel = MethodChannel(binding.binaryMessenger, CHANNEL)
channel.setMethodCallHandler(this)
methodChannel = channel
Log.d(TAG, "Plugin attached to engine")
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
methodChannel = null
}
// ─── MethodCallHandler ───────────────────────────────────────────────────
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"isPermissionGranted" -> {
result.success(isOverlayPermissionGranted())
}
"requestPermission" -> {
requestOverlayPermission()
result.success(null)
}
"showOverlay" -> {
val tripDataJson =
call.argument<String>("tripData")
?: run {
result.error("INVALID_ARGS", "tripData is required", null)
return
}
val autoCloseSeconds = call.argument<Int>("autoCloseSeconds") ?: 30
val success = showOverlay(tripDataJson, autoCloseSeconds)
result.success(success)
}
"hideOverlay" -> {
hideOverlay()
result.success(null)
}
"isOverlayActive" -> {
result.success(TripOverlayService.isRunning)
}
else -> result.notImplemented()
}
}
// ─── Permission Helpers ───────────────────────────────────────────────────
private fun isOverlayPermissionGranted(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Settings.canDrawOverlays(context)
} else {
true // Pre-M devices don't need runtime permission
}
}
private fun requestOverlayPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val intent =
Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:${context.packageName}")
)
.apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
activity?.startActivityForResult(intent, REQUEST_CODE_OVERLAY)
?: context.startActivity(intent)
}
}
// ─── Overlay Control ──────────────────────────────────────────────────────
private fun showOverlay(tripDataJson: String, autoCloseSeconds: Int): Boolean {
if (!isOverlayPermissionGranted()) {
Log.w(TAG, "Overlay permission not granted")
return false
}
val intent =
Intent(context, TripOverlayService::class.java).apply {
action = TripOverlayService.ACTION_SHOW
putExtra(TripOverlayService.EXTRA_TRIP_DATA, tripDataJson)
putExtra(TripOverlayService.EXTRA_AUTO_CLOSE_SECONDS, autoCloseSeconds)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
Log.d(TAG, "showOverlay called with tripData: $tripDataJson")
return true
}
private fun hideOverlay() {
val intent =
Intent(context, TripOverlayService::class.java).apply {
action = TripOverlayService.ACTION_HIDE
}
context.startService(intent)
Log.d(TAG, "hideOverlay called")
}
// ─── ActivityAware ────────────────────────────────────────────────────────
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addActivityResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivity() {
activity = null
}
// ─── ActivityResultListener ───────────────────────────────────────────────
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (requestCode == REQUEST_CODE_OVERLAY) {
Log.d(TAG, "Overlay permission result received")
return true
}
return false
}
}

View File

@@ -0,0 +1,109 @@
package com.intaleq_driver.trip_overlay_plugin
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import org.json.JSONObject
/**
* TripOverlayReceiver
*
* A BroadcastReceiver you can trigger directly from your FCM background handler (or anywhere
* outside Flutter context) to show the overlay without needing an active MethodChannel.
*
* Usage from your backgroundMessageHandler in main.dart: — Call the static helper
* [TripOverlayReceiver.show(context, tripDataJson)]
*
* Or register in AndroidManifest and send an explicit broadcast.
*
* Register in AndroidManifest.xml: <receiver
* ```
* android:name="com.trip_overlay.TripOverlayReceiver"
* android:exported="false" />
* ```
*/
class TripOverlayReceiver : BroadcastReceiver() {
companion object {
const val TAG = "TripOverlayReceiver"
const val ACTION = "com.intaleq_driver.SHOW_OVERLAY"
const val EXTRA_TRIP_DATA = "trip_data"
const val EXTRA_AUTO_CLOSE = "auto_close_seconds"
/**
* Convenience method — call this from your FCM handler (Kotlin/Java) or from a Flutter
* MethodChannel invocation.
*/
fun show(context: Context, tripDataJson: String, autoCloseSeconds: Int = 30) {
val intent =
Intent(context, TripOverlayService::class.java).apply {
action = TripOverlayService.ACTION_SHOW
putExtra(TripOverlayService.EXTRA_TRIP_DATA, tripDataJson)
putExtra(TripOverlayService.EXTRA_AUTO_CLOSE_SECONDS, autoCloseSeconds)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
Log.d(TAG, "Dispatched show overlay request")
}
/** Hide the overlay programmatically from native side */
fun hide(context: Context) {
val intent =
Intent(context, TripOverlayService::class.java).apply {
action = TripOverlayService.ACTION_HIDE
}
context.startService(intent)
Log.d(TAG, "Dispatched hide overlay request")
}
/**
* Build a TripData JSON string from FCM message data map. Keys match what your server sends
* inside the FCM data payload.
*
* Expected FCM data keys: tripId, passengerName, pickupAddress, dropoffAddress, distanceKm,
* estimatedFare, estimatedMinutes, pickupLat, pickupLng
*/
fun buildTripJsonFromFcmData(data: Map<String, String>): String {
return try {
JSONObject()
.apply {
put("tripId", data["tripId"] ?: "")
put("passengerName", data["passengerName"] ?: "غير معروف")
put("pickupAddress", data["pickupAddress"] ?: "")
put("dropoffAddress", data["dropoffAddress"] ?: "")
put("distanceKm", data["distanceKm"]?.toDoubleOrNull() ?: 0.0)
put("estimatedFare", data["estimatedFare"]?.toDoubleOrNull() ?: 0.0)
put("estimatedMinutes", data["estimatedMinutes"]?.toIntOrNull() ?: 0)
put("pickupLat", data["pickupLat"]?.toDoubleOrNull() ?: 0.0)
put("pickupLng", data["pickupLng"]?.toDoubleOrNull() ?: 0.0)
put("passengerAvatarUrl", data["passengerAvatarUrl"] ?: "")
}
.toString()
} catch (e: Exception) {
Log.e(TAG, "Error building trip JSON: ${e.message}")
"{}"
}
}
}
// ─── Broadcast received ───────────────────────────────────────────────────
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != ACTION) return
val tripData =
intent.getStringExtra(EXTRA_TRIP_DATA)
?: run {
Log.e(TAG, "No trip data in broadcast intent")
return
}
val autoClose = intent.getIntExtra(EXTRA_AUTO_CLOSE, 30)
show(context, tripData, autoClose)
Log.d(TAG, "Received broadcast, showing overlay")
}
}

View File

@@ -0,0 +1,465 @@
package com.intaleq_driver.trip_overlay_plugin
import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.PixelFormat
import android.os.Build
import android.os.CountDownTimer
import android.os.IBinder
import android.util.Log
import android.view.*
import android.widget.*
import androidx.core.app.NotificationCompat
import org.json.JSONObject
/**
* TripOverlayService — Foreground service responsible for:
* 1. Showing a system-level overlay window (SYSTEM_ALERT_WINDOW)
* 2. Displaying trip details inside it
* 3. Handling Accept / Reject actions
* 4. Notifying Flutter via TripOverlayPlugin static callbacks
* 5. Launching the host app to foreground on accept
*/
class TripOverlayService : Service() {
companion object {
const val TAG = "TripOverlayService"
const val ACTION_SHOW = "com.intaleq_driver.SHOW"
const val ACTION_HIDE = "com.intaleq_driver.HIDE"
const val EXTRA_TRIP_DATA = "trip_data"
const val EXTRA_AUTO_CLOSE_SECONDS = "auto_close_seconds"
private const val NOTIFICATION_ID = 9900
private const val CHANNEL_ID = "trip_overlay_service_channel"
@Volatile
var isRunning: Boolean = false
private set
}
private var windowManager: WindowManager? = null
private var overlayView: View? = null
private var countDownTimer: CountDownTimer? = null
private var currentTripId: String = ""
// ─── Lifecycle ────────────────────────────────────────────────────────────
override fun onBind(intent: Intent?): IBinder? = null
override fun onCreate() {
super.onCreate()
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
createNotificationChannel()
Log.d(TAG, "Service created")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_SHOW -> {
val tripDataJson = intent.getStringExtra(EXTRA_TRIP_DATA) ?: return START_NOT_STICKY
val autoClose = intent.getIntExtra(EXTRA_AUTO_CLOSE_SECONDS, 30)
startForegroundWithNotification()
showTripOverlay(tripDataJson, autoClose)
}
ACTION_HIDE -> {
dismissOverlay(reason = "programmatic")
}
}
return START_NOT_STICKY
}
override fun onDestroy() {
removeOverlayView()
countDownTimer?.cancel()
isRunning = false
Log.d(TAG, "Service destroyed")
super.onDestroy()
}
// ─── Foreground Notification ──────────────────────────────────────────────
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel(
CHANNEL_ID,
"Trip Overlay Service",
NotificationManager.IMPORTANCE_LOW
)
.apply {
description = "Keeps overlay active for incoming trips"
setSound(null, null)
enableVibration(false)
}
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager)
.createNotificationChannel(channel)
}
}
private fun startForegroundWithNotification() {
val notification =
NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("طلب رحلة جديد")
.setContentText("يوجد طلب رحلة في انتظارك")
.setSmallIcon(android.R.drawable.ic_dialog_map)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSilent(true)
.build()
startForeground(NOTIFICATION_ID, notification)
}
// ─── Overlay Window ───────────────────────────────────────────────────────
private fun showTripOverlay(tripDataJson: String, autoCloseSeconds: Int) {
// Remove any existing overlay first
removeOverlayView()
// Parse trip data
val tripData =
parseTripData(tripDataJson)
?: run {
Log.e(TAG, "Failed to parse trip data")
stopSelf()
return
}
currentTripId = tripData.tripId
// Build overlay view programmatically (no XML required)
val view = buildOverlayView(tripData, autoCloseSeconds)
overlayView = view
// Window layout params
val type =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
else @Suppress("DEPRECATION") WindowManager.LayoutParams.TYPE_PHONE
val params =
WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT,
type,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
PixelFormat.TRANSLUCENT
)
.apply {
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
// ✅ التعديل هنا: إنزال النافذة بنسبة 18% من طول الشاشة
val metrics = resources.displayMetrics
y = (metrics.heightPixels * 0.18).toInt()
}
try {
windowManager?.addView(view, params)
isRunning = true
Log.d(TAG, "Overlay added to window manager")
} catch (e: Exception) {
Log.e(TAG, "Failed to add overlay view: ${e.message}", e)
stopSelf()
return
}
// Start countdown timer
startCountdown(autoCloseSeconds)
}
/**
* Builds the overlay card entirely in code — no XML needed. Override this to customise the UI.
*/
private fun buildOverlayView(trip: TripInfo, autoCloseSeconds: Int): View {
val ctx = this
val card =
FrameLayout(ctx).apply {
setBackgroundResource(android.R.drawable.dialog_holo_light_frame)
elevation = 24f
}
val root =
LinearLayout(ctx).apply {
orientation = LinearLayout.VERTICAL
setPadding(40, 32, 40, 32)
}
card.addView(root)
// ── العنوان والوقت ──
val headerRow = LinearLayout(ctx).apply { orientation = LinearLayout.HORIZONTAL }
val titleText =
TextView(ctx).apply {
text = "🚗 طلب توصيل جديد"
textSize = 18f
setTextColor(Color.parseColor("#1a1a2e"))
typeface = android.graphics.Typeface.DEFAULT_BOLD
layoutParams =
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
}
headerRow.addView(titleText)
root.addView(headerRow)
root.addView(divider(ctx))
// ── بيانات الرحلة (اسم، انطلاق، مسافة، سعر) ──
// أزلنا الإيميل واكتفينا بالمعلومات المهمة
root.addView(labeledRow(ctx, "👤 الراكب:", trip.passengerName))
root.addView(labeledRow(ctx, "📍 الانطلاق:", trip.pickupAddress))
val statsRow =
LinearLayout(ctx).apply {
orientation = LinearLayout.HORIZONTAL
setPadding(0, 16, 0, 16)
}
statsRow.addView(statChip(ctx, "${trip.distanceKm} كم", "المسافة"))
statsRow.addView(statChip(ctx, "${trip.estimatedFare} ل.س", "الأجرة"))
root.addView(statsRow)
// ── شريط الوقت ──
val countdownLabel =
TextView(ctx).apply {
text = "ينتهي خلال $autoCloseSeconds ثانية"
textSize = 12f
setTextColor(Color.parseColor("#e74c3c"))
gravity = Gravity.CENTER
id = android.R.id.text1
}
val progressBar =
ProgressBar(ctx, null, android.R.attr.progressBarStyleHorizontal).apply {
max = autoCloseSeconds
progress = autoCloseSeconds
id = android.R.id.progress
progressTintList =
android.content.res.ColorStateList.valueOf(Color.parseColor("#e74c3c"))
}
root.addView(countdownLabel)
root.addView(progressBar)
// ── أزرار القبول والرفض ──
val buttonsRow =
LinearLayout(ctx).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams =
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
.apply { topMargin = 24 }
}
val rejectBtn =
Button(ctx).apply {
text = "✖ رفض"
textSize = 16f
setTextColor(Color.WHITE)
setBackgroundColor(Color.parseColor("#e74c3c")) // لون أحمر
layoutParams =
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
.apply { rightMargin = 16 }
setOnClickListener { dismissOverlay(reason = "rejected") }
}
val acceptBtn =
Button(ctx).apply {
text = "✔ قبول"
textSize = 16f
setTextColor(Color.WHITE)
setBackgroundColor(Color.parseColor("#27ae60")) // لون أخضر
layoutParams =
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
setOnClickListener { onTripAccepted() }
}
buttonsRow.addView(rejectBtn)
buttonsRow.addView(acceptBtn)
root.addView(buttonsRow)
card.tag = mapOf("countdownLabel" to countdownLabel, "progressBar" to progressBar)
return card
}
// ─── Countdown Timer ──────────────────────────────────────────────────────
private fun startCountdown(totalSeconds: Int) {
countDownTimer?.cancel()
countDownTimer =
object : CountDownTimer(totalSeconds * 1000L, 1000L) {
override fun onTick(millisUntilFinished: Long) {
val secondsLeft = (millisUntilFinished / 1000).toInt()
updateCountdownUI(secondsLeft)
}
override fun onFinish() {
Log.d(TAG, "Countdown finished — auto-dismissing overlay")
dismissOverlay(reason = "timeout")
}
}
.start()
}
private fun updateCountdownUI(secondsLeft: Int) {
val tagMap = overlayView?.tag as? Map<*, *> ?: return
val label = tagMap["countdownLabel"] as? TextView ?: return
val bar = tagMap["progressBar"] as? ProgressBar ?: return
label.text = "ينتهي خلال $secondsLeft ثانية"
bar.progress = secondsLeft
if (secondsLeft <= 5) {
label.setTextColor(Color.parseColor("#c0392b"))
}
}
// ─── Actions ──────────────────────────────────────────────────────────────
private fun onTripAccepted() {
Log.d(TAG, "Trip accepted: $currentTripId")
countDownTimer?.cancel()
TripOverlayPlugin.notifyTripAccepted(currentTripId)
bringAppToForeground()
removeOverlayView()
stopSelf()
}
private fun dismissOverlay(reason: String) {
Log.d(TAG, "Overlay dismissed — reason: $reason")
countDownTimer?.cancel()
if (reason == "rejected" || reason == "timeout") {
TripOverlayPlugin.notifyTripRejected(currentTripId)
}
removeOverlayView()
stopSelf()
}
private fun bringAppToForeground() {
val packageManager = packageManager
val launchIntent =
packageManager.getLaunchIntentForPackage(packageName)
?: run {
Log.w(TAG, "No launch intent found for $packageName")
return
}
launchIntent.apply {
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or
Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP // إضافة مهمة لتنشيط الـ Activity
)
putExtra("acceptedTripId", currentTripId)
}
startActivity(launchIntent)
Log.d(TAG, "Launched app to foreground")
}
// ─── Helpers ──────────────────────────────────────────────────────────────
private fun removeOverlayView() {
overlayView?.let {
try {
windowManager?.removeView(it)
Log.d(TAG, "Overlay view removed")
} catch (e: Exception) {
Log.e(TAG, "Error removing overlay view: ${e.message}")
}
overlayView = null
isRunning = false
}
}
private fun labeledRow(ctx: Context, label: String, value: String): LinearLayout {
return LinearLayout(ctx).apply {
orientation = LinearLayout.VERTICAL
setPadding(0, 12, 0, 4)
addView(
TextView(ctx).apply {
text = label
textSize = 11f
setTextColor(Color.parseColor("#888888"))
}
)
addView(
TextView(ctx).apply {
text = value
textSize = 14f
setTextColor(Color.parseColor("#1a1a2e"))
typeface = android.graphics.Typeface.DEFAULT_BOLD
}
)
}
}
private fun statChip(ctx: Context, value: String, label: String): LinearLayout {
return LinearLayout(ctx).apply {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
setPadding(12, 16, 12, 16)
setBackgroundColor(Color.parseColor("#f0f4f8"))
addView(
TextView(ctx).apply {
text = value
textSize = 16f
setTextColor(Color.parseColor("#2c3e50"))
gravity = Gravity.CENTER
typeface = android.graphics.Typeface.DEFAULT_BOLD
}
)
addView(
TextView(ctx).apply {
text = label
textSize = 10f
setTextColor(Color.parseColor("#7f8c8d"))
gravity = Gravity.CENTER
}
)
}
}
private fun divider(ctx: Context): View {
return View(ctx).apply {
setBackgroundColor(Color.parseColor("#e0e0e0"))
layoutParams =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 1).apply {
setMargins(0, 16, 0, 16)
}
}
}
// ─── Data Parsing ─────────────────────────────────────────────────────────
private fun parseTripData(json: String): TripInfo? {
return try {
val obj = JSONObject(json)
TripInfo(
tripId = obj.getString("tripId"),
passengerName = obj.getString("passengerName"),
pickupAddress = obj.getString("pickupAddress"),
dropoffAddress = obj.getString("dropoffAddress"),
distanceKm = obj.getDouble("distanceKm"),
estimatedFare = obj.getDouble("estimatedFare"),
estimatedMinutes = obj.getInt("estimatedMinutes"),
pickupLat = obj.getDouble("pickupLat"),
pickupLng = obj.getDouble("pickupLng"),
)
} catch (e: Exception) {
Log.e(TAG, "JSON parse error: ${e.message}")
null
}
}
data class TripInfo(
val tripId: String,
val passengerName: String,
val pickupAddress: String,
val dropoffAddress: String,
val distanceKm: Double,
val estimatedFare: Double,
val estimatedMinutes: Int,
val pickupLat: Double,
val pickupLng: Double,
)
}

View File

@@ -0,0 +1,27 @@
package com.intaleq_driver.trip_overlay_plugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import org.mockito.Mockito
import kotlin.test.Test
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
internal class TripOverlayPluginTest {
@Test
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
val plugin = TripOverlayPlugin()
val call = MethodCall("getPlatformVersion", null)
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
plugin.onMethodCall(call, mockResult)
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
}
}