diff --git a/packages/simple_intent_receiver/analysis_options.yaml b/packages/simple_intent_receiver/analysis_options.yaml index a5744c1c..9357549d 100644 --- a/packages/simple_intent_receiver/analysis_options.yaml +++ b/packages/simple_intent_receiver/analysis_options.yaml @@ -1,4 +1,35 @@ -include: package:flutter_lints/flutter.yaml +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. -# Additional information about this file can be found at +include: package:lint/package.yaml +# Uncomment the following section to specify additional rules. + +linter: + rules: + unawaited_futures: true + discarded_futures: true + collection_methods_unrelated_type: true + +analyzer: + plugins: + - custom_lint + exclude: + - "**.g.dart" + - "**.swagger.dart" + - "**.freezed.dart" + - "**.chopper.dart" +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see # https://dart.dev/guides/language/analysis-options diff --git a/packages/simple_intent_receiver/android/build.gradle b/packages/simple_intent_receiver/android/build.gradle index d226c901..2c12488a 100644 --- a/packages/simple_intent_receiver/android/build.gradle +++ b/packages/simple_intent_receiver/android/build.gradle @@ -2,14 +2,14 @@ group = "me.movenext.simple_intent_receiver" version = "1.0-SNAPSHOT" buildscript { - ext.kotlin_version = "1.8.22" + ext.kotlin_version = "2.1.20" repositories { google() mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:8.7.0") + classpath("com.android.tools.build:gradle:8.7.3") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") } } @@ -30,12 +30,12 @@ android { compileSdk = 35 compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_11 + jvmTarget = JavaVersion.VERSION_17 } sourceSets { diff --git a/packages/simple_intent_receiver/android/src/main/kotlin/me/movenext/simple_intent_receiver/IntentReceiver.kt b/packages/simple_intent_receiver/android/src/main/kotlin/me/movenext/simple_intent_receiver/IntentReceiver.kt new file mode 100644 index 00000000..20957e74 --- /dev/null +++ b/packages/simple_intent_receiver/android/src/main/kotlin/me/movenext/simple_intent_receiver/IntentReceiver.kt @@ -0,0 +1,13 @@ +package me.movenext.simple_intent_receiver + +import io.flutter.plugin.common.BinaryMessenger +import me.movenext.simple_intent_receiver.pigeons.IntentEvents +import me.movenext.simple_intent_receiver.pigeons.Intent as PigeonIntent + +class IntentReceiver(messenger: BinaryMessenger) { + private val intentEvents: IntentEvents = IntentEvents(messenger) + + fun sendIntent(timestamp: Long, intent: PigeonIntent) { + intentEvents.onIntentReceived(timestamp, intent) { } + } +} diff --git a/packages/simple_intent_receiver/android/src/main/kotlin/me/movenext/simple_intent_receiver/SimpleIntentReceiverPlugin.kt b/packages/simple_intent_receiver/android/src/main/kotlin/me/movenext/simple_intent_receiver/SimpleIntentReceiverPlugin.kt index 25b37424..ed250d0a 100644 --- a/packages/simple_intent_receiver/android/src/main/kotlin/me/movenext/simple_intent_receiver/SimpleIntentReceiverPlugin.kt +++ b/packages/simple_intent_receiver/android/src/main/kotlin/me/movenext/simple_intent_receiver/SimpleIntentReceiverPlugin.kt @@ -1,33 +1,85 @@ package me.movenext.simple_intent_receiver +import android.content.Context +import android.content.Intent import io.flutter.embedding.engine.plugins.FlutterPlugin -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.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.PluginRegistry +import me.movenext.simple_intent_receiver.pigeons.Intent as PigeonIntent -/** SimpleIntentReceiverPlugin */ -class SimpleIntentReceiverPlugin: FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private lateinit var channel : MethodChannel +class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.NewIntentListener { + private lateinit var context: Context + private var intentReceiver: IntentReceiver? = null + private var handledInitialIntent = false override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "simple_intent_receiver") - channel.setMethodCallHandler(this) - } + context = flutterPluginBinding.applicationContext - override fun onMethodCall(call: MethodCall, result: Result) { - if (call.method == "getPlatformVersion") { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } else { - result.notImplemented() - } + intentReceiver = IntentReceiver(flutterPluginBinding.binaryMessenger) } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) + intentReceiver = null + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + binding.addOnNewIntentListener(this) + + // Process the initial intent if available + binding.activity.intent?.let { intent -> + if (!handledInitialIntent) { + handleIntent(intent) + handledInitialIntent = true + } + } + } + + override fun onDetachedFromActivityForConfigChanges() { + // No implementation needed + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + binding.addOnNewIntentListener(this) + } + + override fun onDetachedFromActivity() { + // No implementation needed + } + + override fun onNewIntent(intent: Intent): Boolean { + return handleIntent(intent) + } + + private fun handleIntent(intent: Intent): Boolean { + val pigeonIntent = convertToPigeonIntent(intent) + intentReceiver?.sendIntent(System.currentTimeMillis(), pigeonIntent) + return true + } + + private fun convertToPigeonIntent(intent: Intent): PigeonIntent { + val action = intent.action + val data = intent.dataString + val fromPackageName = intent.getPackage() + + // Extract categories + val categories = ArrayList() + intent.categories?.let { + categories.addAll(it) + } + + // Extract extras + val extras = HashMap() + intent.extras?.keySet()?.forEach { key -> + extras[key] = intent.extras?.get(key) + } + + return PigeonIntent( + fromPackageName = fromPackageName, + action = action, + data = data, + categories = categories, + extra = extras + ) } } diff --git a/packages/simple_intent_receiver/android/src/main/kotlin/me/movenext/simple_intent_receiver/pigeons/Intent.g.kt b/packages/simple_intent_receiver/android/src/main/kotlin/me/movenext/simple_intent_receiver/pigeons/Intent.g.kt new file mode 100644 index 00000000..6e943757 --- /dev/null +++ b/packages/simple_intent_receiver/android/src/main/kotlin/me/movenext/simple_intent_receiver/pigeons/Intent.g.kt @@ -0,0 +1,150 @@ +// Autogenerated from Pigeon (v25.3.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package me.movenext.simple_intent_receiver.pigeons + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object IntentPigeonUtils { + + fun createConnectionError(channelName: String): FlutterError { + return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "") } + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a is ByteArray && b is ByteArray) { + return a.contentEquals(b) + } + if (a is IntArray && b is IntArray) { + return a.contentEquals(b) + } + if (a is LongArray && b is LongArray) { + return a.contentEquals(b) + } + if (a is DoubleArray && b is DoubleArray) { + return a.contentEquals(b) + } + if (a is Array<*> && b is Array<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is List<*> && b is List<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is Map<*, *> && b is Map<*, *>) { + return a.size == b.size && a.all { + (b as Map).containsKey(it.key) && + deepEquals(it.value, b[it.key]) + } + } + return a == b + } + +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() + +/** Generated class from Pigeon that represents data sent in messages. */ +data class Intent ( + val fromPackageName: String? = null, + val action: String? = null, + val data: String? = null, + val categories: List, + val extra: Map +) + { + companion object { + fun fromList(pigeonVar_list: List): Intent { + val fromPackageName = pigeonVar_list[0] as String? + val action = pigeonVar_list[1] as String? + val data = pigeonVar_list[2] as String? + val categories = pigeonVar_list[3] as List + val extra = pigeonVar_list[4] as Map + return Intent(fromPackageName, action, data, categories, extra) + } + } + fun toList(): List { + return listOf( + fromPackageName, + action, + data, + categories, + extra, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is Intent) { + return false + } + if (this === other) { + return true + } + return IntentPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} +private open class IntentPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as? List)?.let { + Intent.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is Intent -> { + stream.write(129) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + +/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ +class IntentEvents(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { + companion object { + /** The codec used by IntentEvents. */ + val codec: MessageCodec by lazy { + IntentPigeonCodec() + } + } + fun onIntentReceived(timestampArg: Long, intentArg: Intent, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(timestampArg, intentArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(IntentPigeonUtils.createConnectionError(channelName))) + } + } + } +} diff --git a/packages/simple_intent_receiver/example/android/app/src/main/AndroidManifest.xml b/packages/simple_intent_receiver/example/android/app/src/main/AndroidManifest.xml index 40c39882..41c8d77d 100644 --- a/packages/simple_intent_receiver/example/android/app/src/main/AndroidManifest.xml +++ b/packages/simple_intent_receiver/example/android/app/src/main/AndroidManifest.xml @@ -23,6 +23,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +