2026-02-20-overlay
This commit is contained in:
33
trip_overlay_plugin/.gitignore
vendored
Normal file
33
trip_overlay_plugin/.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
/build/
|
||||
/coverage/
|
||||
30
trip_overlay_plugin/.metadata
Normal file
30
trip_overlay_plugin/.metadata
Normal file
@@ -0,0 +1,30 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "67323de285b00232883f53b84095eb72be97d35c"
|
||||
channel: "stable"
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||
- platform: android
|
||||
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
3
trip_overlay_plugin/CHANGELOG.md
Normal file
3
trip_overlay_plugin/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
1
trip_overlay_plugin/LICENSE
Normal file
1
trip_overlay_plugin/LICENSE
Normal file
@@ -0,0 +1 @@
|
||||
TODO: Add your license here.
|
||||
15
trip_overlay_plugin/README.md
Normal file
15
trip_overlay_plugin/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# trip_overlay_plugin
|
||||
|
||||
A new Flutter plugin project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter
|
||||
[plug-in package](https://flutter.dev/to/develop-plugins),
|
||||
a specialized package that includes platform-specific implementation code for
|
||||
Android and/or iOS.
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
||||
4
trip_overlay_plugin/analysis_options.yaml
Normal file
4
trip_overlay_plugin/analysis_options.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
9
trip_overlay_plugin/android/.gitignore
vendored
Normal file
9
trip_overlay_plugin/android/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
66
trip_overlay_plugin/android/build.gradle
Normal file
66
trip_overlay_plugin/android/build.gradle
Normal file
@@ -0,0 +1,66 @@
|
||||
group = "com.intaleq_driver.trip_overlay_plugin"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = "2.2.20"
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.11.1")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "com.android.library"
|
||||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
namespace = "com.intaleq_driver.trip_overlay_plugin"
|
||||
|
||||
compileSdk = 36
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += "src/main/kotlin"
|
||||
test.java.srcDirs += "src/test/kotlin"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.mockito:mockito-core:5.0.0")
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
trip_overlay_plugin/android/settings.gradle
Normal file
1
trip_overlay_plugin/android/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'trip_overlay_plugin'
|
||||
3
trip_overlay_plugin/android/src/main/AndroidManifest.xml
Normal file
3
trip_overlay_plugin/android/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.intaleq_driver.trip_overlay_plugin">
|
||||
</manifest>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
45
trip_overlay_plugin/example/.gitignore
vendored
Normal file
45
trip_overlay_plugin/example/.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
16
trip_overlay_plugin/example/README.md
Normal file
16
trip_overlay_plugin/example/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# trip_overlay_plugin_example
|
||||
|
||||
Demonstrates how to use the trip_overlay_plugin plugin.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
28
trip_overlay_plugin/example/analysis_options.yaml
Normal file
28
trip_overlay_plugin/example/analysis_options.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
14
trip_overlay_plugin/example/android/.gitignore
vendored
Normal file
14
trip_overlay_plugin/example/android/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
44
trip_overlay_plugin/example/android/app/build.gradle.kts
Normal file
44
trip_overlay_plugin/example/android/app/build.gradle.kts
Normal file
@@ -0,0 +1,44 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.intaleq_driver.trip_overlay_plugin_example"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.intaleq_driver.trip_overlay_plugin_example"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@@ -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,45 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="trip_overlay_plugin_example"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<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>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.intaleq_driver.trip_overlay_plugin_example
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
@@ -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:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -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/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
Binary file not shown.
|
After Width: | Height: | Size: 721 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,18 @@
|
||||
<?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">
|
||||
<!-- 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">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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">
|
||||
<!-- 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">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -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>
|
||||
24
trip_overlay_plugin/example/android/build.gradle.kts
Normal file
24
trip_overlay_plugin/example/android/build.gradle.kts
Normal file
@@ -0,0 +1,24 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
2
trip_overlay_plugin/example/android/gradle.properties
Normal file
2
trip_overlay_plugin/example/android/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
5
trip_overlay_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
trip_overlay_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
|
||||
26
trip_overlay_plugin/example/android/settings.gradle.kts
Normal file
26
trip_overlay_plugin/example/android/settings.gradle.kts
Normal file
@@ -0,0 +1,26 @@
|
||||
pluginManagement {
|
||||
val flutterSdkPath =
|
||||
run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.11.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
@@ -0,0 +1,25 @@
|
||||
// This is a basic Flutter integration test.
|
||||
//
|
||||
// Since integration tests run in a full Flutter application, they can interact
|
||||
// with the host side of a plugin implementation, unlike Dart unit tests.
|
||||
//
|
||||
// For more information about Flutter integration tests, please see
|
||||
// https://flutter.dev/to/integration-testing
|
||||
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'package:trip_overlay_plugin/trip_overlay_plugin.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('getPlatformVersion test', (WidgetTester tester) async {
|
||||
final TripOverlayPlugin plugin = TripOverlayPlugin();
|
||||
final String? version = await plugin.getPlatformVersion();
|
||||
// The version string depends on the host platform running the test, so
|
||||
// just assert that some non-empty string is returned.
|
||||
expect(version?.isNotEmpty, true);
|
||||
});
|
||||
}
|
||||
63
trip_overlay_plugin/example/lib/main.dart
Normal file
63
trip_overlay_plugin/example/lib/main.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:trip_overlay_plugin/trip_overlay_plugin.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
String _platformVersion = 'Unknown';
|
||||
final _tripOverlayPlugin = TripOverlayPlugin();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initPlatformState();
|
||||
}
|
||||
|
||||
// Platform messages are asynchronous, so we initialize in an async method.
|
||||
Future<void> initPlatformState() async {
|
||||
String platformVersion;
|
||||
// Platform messages may fail, so we use a try/catch PlatformException.
|
||||
// We also handle the message potentially returning null.
|
||||
try {
|
||||
platformVersion =
|
||||
await _tripOverlayPlugin.getPlatformVersion() ?? 'Unknown platform version';
|
||||
} on PlatformException {
|
||||
platformVersion = 'Failed to get platform version.';
|
||||
}
|
||||
|
||||
// If the widget was removed from the tree while the asynchronous platform
|
||||
// message was in flight, we want to discard the reply rather than calling
|
||||
// setState to update our non-existent appearance.
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_platformVersion = platformVersion;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Plugin example app'),
|
||||
),
|
||||
body: Center(
|
||||
child: Text('Running on: $_platformVersion\n'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
283
trip_overlay_plugin/example/pubspec.lock
Normal file
283
trip_overlay_plugin/example/pubspec.lock
Normal file
@@ -0,0 +1,283 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_driver:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fuchsia_remote_debug_protocol:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
integration_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.5"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
sync_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sync_http
|
||||
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
trip_overlay_plugin:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.2"
|
||||
webdriver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webdriver
|
||||
sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
sdks:
|
||||
dart: ">=3.10.8 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
85
trip_overlay_plugin/example/pubspec.yaml
Normal file
85
trip_overlay_plugin/example/pubspec.yaml
Normal file
@@ -0,0 +1,85 @@
|
||||
name: trip_overlay_plugin_example
|
||||
description: "Demonstrates how to use the trip_overlay_plugin plugin."
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
environment:
|
||||
sdk: ^3.10.8
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||
# dependencies can be manually updated by changing the version numbers below to
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
trip_overlay_plugin:
|
||||
# When depending on this package from a real application you should use:
|
||||
# trip_overlay_plugin: ^x.y.z
|
||||
# See https://dart.dev/tools/pub/dependencies#version-constraints
|
||||
# The example app is bundled with the plugin so we use a path dependency on
|
||||
# the parent directory to use the current plugin's version.
|
||||
path: ../
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/to/font-from-package
|
||||
27
trip_overlay_plugin/example/test/widget_test.dart
Normal file
27
trip_overlay_plugin/example/test/widget_test.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:trip_overlay_plugin_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that platform version is retrieved.
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(Widget widget) => widget is Text &&
|
||||
widget.data!.startsWith('Running on:'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
||||
168
trip_overlay_plugin/lib/trip_overlay_plugin.dart
Normal file
168
trip_overlay_plugin/lib/trip_overlay_plugin.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Model for trip data passed to the overlay
|
||||
class TripData {
|
||||
final String tripId;
|
||||
final String passengerName;
|
||||
final String pickupAddress;
|
||||
final String dropoffAddress;
|
||||
final double distanceKm;
|
||||
final double estimatedFare;
|
||||
final int estimatedMinutes;
|
||||
final double pickupLat;
|
||||
final double pickupLng;
|
||||
final String? passengerAvatarUrl;
|
||||
|
||||
TripData({
|
||||
required this.tripId,
|
||||
required this.passengerName,
|
||||
required this.pickupAddress,
|
||||
required this.dropoffAddress,
|
||||
required this.distanceKm,
|
||||
required this.estimatedFare,
|
||||
required this.estimatedMinutes,
|
||||
required this.pickupLat,
|
||||
required this.pickupLng,
|
||||
this.passengerAvatarUrl,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
'tripId': tripId,
|
||||
'passengerName': passengerName,
|
||||
'pickupAddress': pickupAddress,
|
||||
'dropoffAddress': dropoffAddress,
|
||||
'distanceKm': distanceKm,
|
||||
'estimatedFare': estimatedFare,
|
||||
'estimatedMinutes': estimatedMinutes,
|
||||
'pickupLat': pickupLat,
|
||||
'pickupLng': pickupLng,
|
||||
'passengerAvatarUrl': passengerAvatarUrl ?? '',
|
||||
};
|
||||
|
||||
factory TripData.fromMap(Map<String, dynamic> map) => TripData(
|
||||
tripId: map['tripId'] ?? '',
|
||||
passengerName: map['passengerName'] ?? '',
|
||||
pickupAddress: map['pickupAddress'] ?? '',
|
||||
dropoffAddress: map['dropoffAddress'] ?? '',
|
||||
distanceKm: (map['distanceKm'] ?? 0.0).toDouble(),
|
||||
estimatedFare: (map['estimatedFare'] ?? 0.0).toDouble(),
|
||||
estimatedMinutes: map['estimatedMinutes'] ?? 0,
|
||||
pickupLat: (map['pickupLat'] ?? 0.0).toDouble(),
|
||||
pickupLng: (map['pickupLng'] ?? 0.0).toDouble(),
|
||||
passengerAvatarUrl: map['passengerAvatarUrl'],
|
||||
);
|
||||
|
||||
factory TripData.fromJson(String json) =>
|
||||
TripData.fromMap(jsonDecode(json) as Map<String, dynamic>);
|
||||
|
||||
String toJson() => jsonEncode(toMap());
|
||||
}
|
||||
|
||||
/// Result returned when the driver accepts a trip
|
||||
class TripAcceptedResult {
|
||||
final String tripId;
|
||||
final DateTime acceptedAt;
|
||||
|
||||
TripAcceptedResult({required this.tripId, required this.acceptedAt});
|
||||
}
|
||||
|
||||
/// Main plugin class — single entry point for Flutter side
|
||||
class TripOverlayPlugin {
|
||||
static const MethodChannel _channel = MethodChannel('trip_overlay_plugin');
|
||||
|
||||
// Stream controller for trip accepted events coming FROM Android overlay
|
||||
static final StreamController<TripAcceptedResult> _tripAcceptedController =
|
||||
StreamController<TripAcceptedResult>.broadcast();
|
||||
|
||||
// Stream controller for trip rejected/expired events
|
||||
static final StreamController<String> _tripRejectedController =
|
||||
StreamController<String>.broadcast();
|
||||
|
||||
static bool _isInitialized = false;
|
||||
|
||||
/// Stream that fires when the driver taps "Accept" in the overlay
|
||||
static Stream<TripAcceptedResult> get onTripAccepted =>
|
||||
_tripAcceptedController.stream;
|
||||
|
||||
/// Stream that fires when the driver rejects or overlay times out
|
||||
static Stream<String> get onTripRejected => _tripRejectedController.stream;
|
||||
|
||||
/// Initialize the plugin — call this once in main() or initState()
|
||||
static Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
_channel.setMethodCallHandler(_handleMethodCall);
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
/// Handle incoming calls FROM Android → Flutter
|
||||
static Future<dynamic> _handleMethodCall(MethodCall call) async {
|
||||
switch (call.method) {
|
||||
case 'onTripAccepted':
|
||||
final tripId = call.arguments['tripId'] as String;
|
||||
_tripAcceptedController.add(
|
||||
TripAcceptedResult(tripId: tripId, acceptedAt: DateTime.now()),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'onTripRejected':
|
||||
final tripId = call.arguments['tripId'] as String;
|
||||
_tripRejectedController.add(tripId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw PlatformException(
|
||||
code: 'UNKNOWN_METHOD',
|
||||
message: 'Method ${call.method} not implemented',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if SYSTEM_ALERT_WINDOW permission is granted
|
||||
static Future<bool> isPermissionGranted() async {
|
||||
final result = await _channel.invokeMethod<bool>('isPermissionGranted');
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
/// Open system settings to grant SYSTEM_ALERT_WINDOW permission
|
||||
static Future<void> requestPermission() async {
|
||||
await _channel.invokeMethod('requestPermission');
|
||||
}
|
||||
|
||||
/// Show the trip overlay with the given [tripData]
|
||||
/// [autoCloseSeconds] — how long before auto-dismiss (default 30s)
|
||||
static Future<bool> showOverlay(
|
||||
TripData tripData, {
|
||||
int autoCloseSeconds = 30,
|
||||
}) async {
|
||||
final granted = await isPermissionGranted();
|
||||
if (!granted) {
|
||||
await requestPermission();
|
||||
return false;
|
||||
}
|
||||
|
||||
final result = await _channel.invokeMethod<bool>('showOverlay', {
|
||||
'tripData': tripData.toJson(),
|
||||
'autoCloseSeconds': autoCloseSeconds,
|
||||
});
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
/// Programmatically close the overlay (e.g. if trip was cancelled)
|
||||
static Future<void> hideOverlay() async {
|
||||
await _channel.invokeMethod('hideOverlay');
|
||||
}
|
||||
|
||||
/// Check if the overlay is currently visible
|
||||
static Future<bool> isOverlayActive() async {
|
||||
final result = await _channel.invokeMethod<bool>('isOverlayActive');
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
/// Dispose streams — call in app's dispose()
|
||||
static void dispose() {
|
||||
_tripAcceptedController.close();
|
||||
_tripRejectedController.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'trip_overlay_plugin_platform_interface.dart';
|
||||
|
||||
/// An implementation of [TripOverlayPluginPlatform] that uses method channels.
|
||||
class MethodChannelTripOverlayPlugin extends TripOverlayPluginPlatform {
|
||||
/// The method channel used to interact with the native platform.
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('trip_overlay_plugin');
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() async {
|
||||
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
import 'trip_overlay_plugin_method_channel.dart';
|
||||
|
||||
abstract class TripOverlayPluginPlatform extends PlatformInterface {
|
||||
/// Constructs a TripOverlayPluginPlatform.
|
||||
TripOverlayPluginPlatform() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static TripOverlayPluginPlatform _instance = MethodChannelTripOverlayPlugin();
|
||||
|
||||
/// The default instance of [TripOverlayPluginPlatform] to use.
|
||||
///
|
||||
/// Defaults to [MethodChannelTripOverlayPlugin].
|
||||
static TripOverlayPluginPlatform get instance => _instance;
|
||||
|
||||
/// Platform-specific implementations should set this with their own
|
||||
/// platform-specific class that extends [TripOverlayPluginPlatform] when
|
||||
/// they register themselves.
|
||||
static set instance(TripOverlayPluginPlatform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
Future<String?> getPlatformVersion() {
|
||||
throw UnimplementedError('platformVersion() has not been implemented.');
|
||||
}
|
||||
}
|
||||
70
trip_overlay_plugin/pubspec.yaml
Normal file
70
trip_overlay_plugin/pubspec.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
name: trip_overlay_plugin
|
||||
description: "A new Flutter plugin project."
|
||||
version: 0.0.1
|
||||
homepage:
|
||||
|
||||
environment:
|
||||
sdk: ^3.10.8
|
||||
flutter: '>=3.3.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
plugin_platform_interface: ^2.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
|
||||
# which should be registered in the plugin registry. This is required for
|
||||
# using method channels.
|
||||
# The Android 'package' specifies package in which the registered class is.
|
||||
# This is required for using method channels on Android.
|
||||
# The 'ffiPlugin' specifies that native code should be built and bundled.
|
||||
# This is required for using `dart:ffi`.
|
||||
# All these are used by the tooling to maintain consistency when
|
||||
# adding or updating assets for this project.
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: com.intaleq_driver.trip_overlay_plugin
|
||||
pluginClass: TripOverlayPlugin
|
||||
|
||||
# To add assets to your plugin package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
# To add custom fonts to your plugin package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/to/font-from-package
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:trip_overlay_plugin/trip_overlay_plugin_method_channel.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
MethodChannelTripOverlayPlugin platform = MethodChannelTripOverlayPlugin();
|
||||
const MethodChannel channel = MethodChannel('trip_overlay_plugin');
|
||||
|
||||
setUp(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
channel,
|
||||
(MethodCall methodCall) async {
|
||||
return '42';
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
|
||||
});
|
||||
|
||||
test('getPlatformVersion', () async {
|
||||
expect(await platform.getPlatformVersion(), '42');
|
||||
});
|
||||
}
|
||||
29
trip_overlay_plugin/test/trip_overlay_plugin_test.dart
Normal file
29
trip_overlay_plugin/test/trip_overlay_plugin_test.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:trip_overlay_plugin/trip_overlay_plugin.dart';
|
||||
import 'package:trip_overlay_plugin/trip_overlay_plugin_platform_interface.dart';
|
||||
import 'package:trip_overlay_plugin/trip_overlay_plugin_method_channel.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
class MockTripOverlayPluginPlatform
|
||||
with MockPlatformInterfaceMixin
|
||||
implements TripOverlayPluginPlatform {
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() => Future.value('42');
|
||||
}
|
||||
|
||||
void main() {
|
||||
final TripOverlayPluginPlatform initialPlatform = TripOverlayPluginPlatform.instance;
|
||||
|
||||
test('$MethodChannelTripOverlayPlugin is the default instance', () {
|
||||
expect(initialPlatform, isInstanceOf<MethodChannelTripOverlayPlugin>());
|
||||
});
|
||||
|
||||
test('getPlatformVersion', () async {
|
||||
TripOverlayPlugin tripOverlayPlugin = TripOverlayPlugin();
|
||||
MockTripOverlayPluginPlatform fakePlatform = MockTripOverlayPluginPlatform();
|
||||
TripOverlayPluginPlatform.instance = fakePlatform;
|
||||
|
||||
expect(await tripOverlayPlugin.getPlatformVersion(), '42');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user