diff --git a/packages/quick_actions/quick_actions_android/CHANGELOG.md b/packages/quick_actions/quick_actions_android/CHANGELOG.md index fcacbb356d3c..a3cb3b341293 100644 --- a/packages/quick_actions/quick_actions_android/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.29 + +* Updates internal implementation to use Kotlin Pigeon. + ## 1.0.28 * Updates build files from Groovy to Kotlin. diff --git a/packages/quick_actions/quick_actions_android/android/build.gradle.kts b/packages/quick_actions/quick_actions_android/android/build.gradle.kts index bb8fa232e967..c9007e37c083 100644 --- a/packages/quick_actions/quick_actions_android/android/build.gradle.kts +++ b/packages/quick_actions/quick_actions_android/android/build.gradle.kts @@ -1,7 +1,10 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + group = "io.flutter.plugins.quickactions" version = "1.0-SNAPSHOT" buildscript { + val kotlinVersion = "2.3.20" repositories { google() mavenCentral() @@ -9,6 +12,7 @@ buildscript { dependencies { classpath("com.android.tools.build:gradle:8.13.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") } } @@ -21,6 +25,13 @@ allprojects { plugins { id("com.android.library") + id("kotlin-android") +} + +kotlin { + compilerOptions { + jvmTarget = JvmTarget.fromTarget(JavaVersion.VERSION_17.toString()) + } } android { diff --git a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/Messages.java b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/Messages.java deleted file mode 100644 index 8b5af38eb85d..000000000000 --- a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/Messages.java +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -package io.flutter.plugins.quickactions; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.CLASS; - -import android.util.Log; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import io.flutter.plugin.common.BasicMessageChannel; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MessageCodec; -import io.flutter.plugin.common.StandardMessageCodec; -import java.io.ByteArrayOutputStream; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** Generated class from Pigeon. */ -@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) -public class Messages { - - /** Error class for passing custom error details to Flutter via a thrown PlatformException. */ - public static class FlutterError extends RuntimeException { - - /** The error code. */ - public final String code; - - /** The error details. Must be a datatype supported by the api codec. */ - public final Object details; - - public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) { - super(message); - this.code = code; - this.details = details; - } - } - - @NonNull - protected static ArrayList wrapError(@NonNull Throwable exception) { - ArrayList errorList = new ArrayList<>(3); - if (exception instanceof FlutterError) { - FlutterError error = (FlutterError) exception; - errorList.add(error.code); - errorList.add(error.getMessage()); - errorList.add(error.details); - } else { - errorList.add(exception.toString()); - errorList.add(exception.getClass().getSimpleName()); - errorList.add( - "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); - } - return errorList; - } - - @NonNull - protected static FlutterError createConnectionError(@NonNull String channelName) { - return new FlutterError( - "channel-error", "Unable to establish connection on channel: " + channelName + ".", ""); - } - - @Target(METHOD) - @Retention(CLASS) - @interface CanIgnoreReturnValue {} - - /** - * Home screen quick-action shortcut item. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class ShortcutItemMessage { - /** The identifier of this item; should be unique within the app. */ - private @NonNull String type; - - public @NonNull String getType() { - return type; - } - - public void setType(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"type\" is null."); - } - this.type = setterArg; - } - - /** Localized title of the item. */ - private @NonNull String localizedTitle; - - public @NonNull String getLocalizedTitle() { - return localizedTitle; - } - - public void setLocalizedTitle(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"localizedTitle\" is null."); - } - this.localizedTitle = setterArg; - } - - /** Name of native resource to be displayed as the icon for this item. */ - private @Nullable String icon; - - public @Nullable String getIcon() { - return icon; - } - - public void setIcon(@Nullable String setterArg) { - this.icon = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - ShortcutItemMessage() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ShortcutItemMessage that = (ShortcutItemMessage) o; - return type.equals(that.type) - && localizedTitle.equals(that.localizedTitle) - && Objects.equals(icon, that.icon); - } - - @Override - public int hashCode() { - return Objects.hash(type, localizedTitle, icon); - } - - public static final class Builder { - - private @Nullable String type; - - @CanIgnoreReturnValue - public @NonNull Builder setType(@NonNull String setterArg) { - this.type = setterArg; - return this; - } - - private @Nullable String localizedTitle; - - @CanIgnoreReturnValue - public @NonNull Builder setLocalizedTitle(@NonNull String setterArg) { - this.localizedTitle = setterArg; - return this; - } - - private @Nullable String icon; - - @CanIgnoreReturnValue - public @NonNull Builder setIcon(@Nullable String setterArg) { - this.icon = setterArg; - return this; - } - - public @NonNull ShortcutItemMessage build() { - ShortcutItemMessage pigeonReturn = new ShortcutItemMessage(); - pigeonReturn.setType(type); - pigeonReturn.setLocalizedTitle(localizedTitle); - pigeonReturn.setIcon(icon); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(3); - toListResult.add(type); - toListResult.add(localizedTitle); - toListResult.add(icon); - return toListResult; - } - - static @NonNull ShortcutItemMessage fromList(@NonNull ArrayList pigeonVar_list) { - ShortcutItemMessage pigeonResult = new ShortcutItemMessage(); - Object type = pigeonVar_list.get(0); - pigeonResult.setType((String) type); - Object localizedTitle = pigeonVar_list.get(1); - pigeonResult.setLocalizedTitle((String) localizedTitle); - Object icon = pigeonVar_list.get(2); - pigeonResult.setIcon((String) icon); - return pigeonResult; - } - } - - private static class PigeonCodec extends StandardMessageCodec { - public static final PigeonCodec INSTANCE = new PigeonCodec(); - - private PigeonCodec() {} - - @Override - protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { - switch (type) { - case (byte) 129: - return ShortcutItemMessage.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof ShortcutItemMessage) { - stream.write(129); - writeValue(stream, ((ShortcutItemMessage) value).toList()); - } else { - super.writeValue(stream, value); - } - } - } - - /** Asynchronous error handling return type for non-nullable API method returns. */ - public interface Result { - /** Success case callback method for handling returns. */ - void success(@NonNull T result); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - /** Asynchronous error handling return type for nullable API method returns. */ - public interface NullableResult { - /** Success case callback method for handling returns. */ - void success(@Nullable T result); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - /** Asynchronous error handling return type for void API method returns. */ - public interface VoidResult { - /** Success case callback method for handling returns. */ - void success(); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ - public interface AndroidQuickActionsApi { - /** Checks for, and returns the action that launched the app. */ - @Nullable - String getLaunchAction(); - /** Sets the dynamic shortcuts for the app. */ - void setShortcutItems(@NonNull List itemsList, @NonNull VoidResult result); - /** Removes all dynamic shortcuts. */ - void clearShortcutItems(); - - /** The codec used by AndroidQuickActionsApi. */ - static @NonNull MessageCodec getCodec() { - return PigeonCodec.INSTANCE; - } - /** - * Sets up an instance of `AndroidQuickActionsApi` to handle messages through the - * `binaryMessenger`. - */ - static void setUp( - @NonNull BinaryMessenger binaryMessenger, @Nullable AndroidQuickActionsApi api) { - setUp(binaryMessenger, "", api); - } - - static void setUp( - @NonNull BinaryMessenger binaryMessenger, - @NonNull String messageChannelSuffix, - @Nullable AndroidQuickActionsApi api) { - messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.getLaunchAction" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - try { - String output = api.getLaunchAction(); - wrapped.add(0, output); - } catch (Throwable exception) { - wrapped = wrapError(exception); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.setShortcutItems" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - List itemsListArg = (List) args.get(0); - VoidResult resultCallback = - new VoidResult() { - public void success() { - wrapped.add(0, null); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.setShortcutItems(itemsListArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.clearShortcutItems" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - try { - api.clearShortcutItems(); - wrapped.add(0, null); - } catch (Throwable exception) { - wrapped = wrapError(exception); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - } - } - /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ - public static class AndroidQuickActionsFlutterApi { - private final @NonNull BinaryMessenger binaryMessenger; - private final String messageChannelSuffix; - - public AndroidQuickActionsFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { - this(argBinaryMessenger, ""); - } - - public AndroidQuickActionsFlutterApi( - @NonNull BinaryMessenger argBinaryMessenger, @NonNull String messageChannelSuffix) { - this.binaryMessenger = argBinaryMessenger; - this.messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; - } - - /** Public interface for sending reply. The codec used by AndroidQuickActionsFlutterApi. */ - static @NonNull MessageCodec getCodec() { - return PigeonCodec.INSTANCE; - } - /** Sends a string representing a shortcut from the native platform to the app. */ - public void launchAction(@NonNull String actionArg, @NonNull VoidResult result) { - final String channelName = - "dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsFlutterApi.launchAction" - + messageChannelSuffix; - BasicMessageChannel channel = - new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); - channel.send( - new ArrayList<>(Collections.singletonList(actionArg)), - channelReply -> { - if (channelReply instanceof List) { - List listReply = (List) channelReply; - if (listReply.size() > 1) { - result.error( - new FlutterError( - (String) listReply.get(0), (String) listReply.get(1), listReply.get(2))); - } else { - result.success(); - } - } else { - result.error(createConnectionError(channelName)); - } - }); - } - } -} diff --git a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActions.java b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActions.java index 0890399b58c0..5532c3b0473f 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActions.java +++ b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActions.java @@ -19,15 +19,16 @@ import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.graphics.drawable.IconCompat; -import io.flutter.plugins.quickactions.Messages.AndroidQuickActionsApi; -import io.flutter.plugins.quickactions.Messages.FlutterError; -import io.flutter.plugins.quickactions.Messages.ShortcutItemMessage; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import kotlin.Result; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; +import org.jetbrains.annotations.NotNull; final class QuickActions implements AndroidQuickActionsApi { static final String EXTRA_ACTION = "some unique action key"; @@ -56,9 +57,10 @@ boolean isVersionAllowed() { @Override public void setShortcutItems( - @NonNull List itemsList, @NonNull Messages.VoidResult result) { + @NonNull List itemsList, + @NotNull Function1, @NotNull Unit> callback) { if (!isVersionAllowed()) { - result.success(); + ResultUtilsKt.completeWithUnitSuccess(callback); return; } List shortcuts = shortcutItemMessageToShortcutInfo(itemsList); @@ -82,9 +84,10 @@ public void setShortcutItems( uiThreadExecutor.execute( () -> { if (didSucceed) { - result.success(); + ResultUtilsKt.completeWithUnitSuccess(callback); } else { - result.error( + ResultUtilsKt.completeWithError( + callback, new FlutterError( "quick_action_setshortcutitems_failure", "Exception thrown when setting dynamic shortcuts", diff --git a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java index 90b15e4ae1e8..f64b3235efea 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java +++ b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java @@ -17,7 +17,7 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.PluginRegistry.NewIntentListener; -import io.flutter.plugins.quickactions.Messages.AndroidQuickActionsFlutterApi; +import kotlin.Unit; /** QuickActionsPlugin */ public class QuickActionsPlugin implements FlutterPlugin, ActivityAware, NewIntentListener { @@ -46,13 +46,14 @@ public QuickActionsPlugin() { @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { this.quickActions = new QuickActions(binding.getApplicationContext()); - Messages.AndroidQuickActionsApi.setUp(binding.getBinaryMessenger(), quickActions); - this.quickActionsFlutterApi = new AndroidQuickActionsFlutterApi(binding.getBinaryMessenger()); + AndroidQuickActionsApi.Companion.setUp(binding.getBinaryMessenger(), quickActions); + this.quickActionsFlutterApi = + new AndroidQuickActionsFlutterApi(binding.getBinaryMessenger(), ""); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - Messages.AndroidQuickActionsApi.setUp(binding.getBinaryMessenger(), null); + AndroidQuickActionsApi.Companion.setUp(binding.getBinaryMessenger(), null); this.quickActions = null; } @@ -99,15 +100,14 @@ public boolean onNewIntent(@NonNull Intent intent) { if (shortcutId != null) { quickActionsFlutterApi.launchAction( shortcutId, - new Messages.VoidResult() { - @Override - public void success() {} - - @Override - public void error(@NonNull Throwable error) { - Log.e(TAG, "Failed to handle launch action: " + error.getMessage()); - } - }); + ResultCompat.asCompatCallback( + result -> { + Throwable error = result.exceptionOrNull(); + if (error != null) { + Log.e(TAG, "Failed to handle launch action: " + error.getMessage()); + } + return Unit.INSTANCE; + })); ShortcutManagerCompat.reportShortcutUsed(context, shortcutId); } } diff --git a/packages/quick_actions/quick_actions_android/android/src/main/kotlin/io/flutter/plugins/quickactions/Messages.kt b/packages/quick_actions/quick_actions_android/android/src/main/kotlin/io/flutter/plugins/quickactions/Messages.kt new file mode 100644 index 000000000000..0f7ab5ebaded --- /dev/null +++ b/packages/quick_actions/quick_actions_android/android/src/main/kotlin/io/flutter/plugins/quickactions/Messages.kt @@ -0,0 +1,390 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package io.flutter.plugins.quickactions + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +private object MessagesPigeonUtils { + + fun createConnectionError(channelName: String): FlutterError { + return FlutterError( + "channel-error", "Unable to establish connection on channel: '$channelName'.", "") + } + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf(exception.code, exception.message, exception.details) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) + } + } + + fun doubleEquals(a: Double, b: Double): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN()) + } + + fun floatEquals(a: Float, b: Float): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN()) + } + + fun doubleHash(d: Double): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (d == 0.0) 0.0 else d + val bits = java.lang.Double.doubleToLongBits(normalized) + return (bits xor (bits ushr 32)).toInt() + } + + fun floatHash(f: Float): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (f == 0.0f) 0.0f else f + return java.lang.Float.floatToIntBits(normalized) + } + + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a === b) { + return true + } + if (a == null || b == null) { + return false + } + 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) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!doubleEquals(a[i], b[i])) return false + } + return true + } + if (a is FloatArray && b is FloatArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!floatEquals(a[i], b[i])) return false + } + return true + } + if (a is Array<*> && b is Array<*>) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!deepEquals(a[i], b[i])) return false + } + return true + } + if (a is List<*> && b is List<*>) { + if (a.size != b.size) return false + val iterA = a.iterator() + val iterB = b.iterator() + while (iterA.hasNext() && iterB.hasNext()) { + if (!deepEquals(iterA.next(), iterB.next())) return false + } + return true + } + if (a is Map<*, *> && b is Map<*, *>) { + if (a.size != b.size) return false + for (entry in a) { + val key = entry.key + var found = false + for (bEntry in b) { + if (deepEquals(key, bEntry.key)) { + if (deepEquals(entry.value, bEntry.value)) { + found = true + break + } else { + return false + } + } + } + if (!found) return false + } + return true + } + if (a is Double && b is Double) { + return doubleEquals(a, b) + } + if (a is Float && b is Float) { + return floatEquals(a, b) + } + return a == b + } + + fun deepHash(value: Any?): Int { + return when (value) { + null -> 0 + is ByteArray -> value.contentHashCode() + is IntArray -> value.contentHashCode() + is LongArray -> value.contentHashCode() + is DoubleArray -> { + var result = 1 + for (item in value) { + result = 31 * result + doubleHash(item) + } + result + } + is FloatArray -> { + var result = 1 + for (item in value) { + result = 31 * result + floatHash(item) + } + result + } + is Array<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is List<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is Map<*, *> -> { + var result = 0 + for (entry in value) { + result += ((deepHash(entry.key) * 31) xor deepHash(entry.value)) + } + result + } + is Double -> doubleHash(value) + is Float -> floatHash(value) + else -> value.hashCode() + } + } +} + +/** + * 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 +) : RuntimeException() + +/** + * Home screen quick-action shortcut item. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class ShortcutItemMessage( + /** The identifier of this item; should be unique within the app. */ + val type: String, + /** Localized title of the item. */ + val localizedTitle: String, + /** Name of native resource to be displayed as the icon for this item. */ + val icon: String? = null +) { + companion object { + fun fromList(pigeonVar_list: List): ShortcutItemMessage { + val type = pigeonVar_list[0] as String + val localizedTitle = pigeonVar_list[1] as String + val icon = pigeonVar_list[2] as String? + return ShortcutItemMessage(type, localizedTitle, icon) + } + } + + fun toList(): List { + return listOf( + type, + localizedTitle, + icon, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as ShortcutItemMessage + return MessagesPigeonUtils.deepEquals(this.type, other.type) && + MessagesPigeonUtils.deepEquals(this.localizedTitle, other.localizedTitle) && + MessagesPigeonUtils.deepEquals(this.icon, other.icon) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.type) + result = 31 * result + MessagesPigeonUtils.deepHash(this.localizedTitle) + result = 31 * result + MessagesPigeonUtils.deepHash(this.icon) + return result + } +} + +private open class MessagesPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as? List)?.let { ShortcutItemMessage.fromList(it) } + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is ShortcutItemMessage -> { + stream.write(129) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface AndroidQuickActionsApi { + /** Checks for, and returns the action that launched the app. */ + fun getLaunchAction(): String? + /** Sets the dynamic shortcuts for the app. */ + fun setShortcutItems(itemsList: List, callback: (Result) -> Unit) + /** Removes all dynamic shortcuts. */ + fun clearShortcutItems() + + companion object { + /** The codec used by AndroidQuickActionsApi. */ + val codec: MessageCodec by lazy { MessagesPigeonCodec() } + /** + * Sets up an instance of `AndroidQuickActionsApi` to handle messages through the + * `binaryMessenger`. + */ + @JvmOverloads + fun setUp( + binaryMessenger: BinaryMessenger, + api: AndroidQuickActionsApi?, + messageChannelSuffix: String = "" + ) { + val separatedMessageChannelSuffix = + if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.getLaunchAction$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = + try { + listOf(api.getLaunchAction()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.setShortcutItems$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val itemsListArg = args[0] as List + api.setShortcutItems(itemsListArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + reply.reply(MessagesPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.clearShortcutItems$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = + try { + api.clearShortcutItems() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} +/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ +class AndroidQuickActionsFlutterApi( + private val binaryMessenger: BinaryMessenger, + private val messageChannelSuffix: String = "" +) { + companion object { + /** The codec used by AndroidQuickActionsFlutterApi. */ + val codec: MessageCodec by lazy { MessagesPigeonCodec() } + } + /** Sends a string representing a shortcut from the native platform to the app. */ + fun launchAction(actionArg: String, callback: (Result) -> Unit) { + val separatedMessageChannelSuffix = + if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = + "dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsFlutterApi.launchAction$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(actionArg)) { + 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(MessagesPigeonUtils.createConnectionError(channelName))) + } + } + } +} diff --git a/packages/quick_actions/quick_actions_android/android/src/main/kotlin/io/flutter/plugins/quickactions/ResultUtils.kt b/packages/quick_actions/quick_actions_android/android/src/main/kotlin/io/flutter/plugins/quickactions/ResultUtils.kt new file mode 100644 index 000000000000..db7e398748ea --- /dev/null +++ b/packages/quick_actions/quick_actions_android/android/src/main/kotlin/io/flutter/plugins/quickactions/ResultUtils.kt @@ -0,0 +1,42 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.quickactions + +fun completeWithError(callback: (Result<@JvmSuppressWildcards T>) -> Unit, failure: Throwable) { + callback(Result.failure(failure)) +} + +fun completeWithUnitSuccess(callback: (Result) -> Unit) { + callback(Result.success(Unit)) +} + +@Suppress("UNCHECKED_CAST") +class ResultCompat(private val result: Result) { + private val value: T? = result.getOrNull() + private val exception = result.exceptionOrNull() + val isSuccess = result.isSuccess + val isFailure = result.isFailure + + companion object { + @JvmStatic + fun success(value: T, callback: Any) { + val castedCallback: (Result) -> Unit = callback as (Result) -> Unit + castedCallback(Result.success(value)) + } + + @JvmStatic + fun asCompatCallback(result: (ResultCompat) -> Unit): (Result) -> Unit { + return { result(ResultCompat(it)) } + } + } + + fun getOrNull(): T? { + return value + } + + fun exceptionOrNull(): Throwable? { + return exception + } +} diff --git a/packages/quick_actions/quick_actions_android/lib/src/messages.g.dart b/packages/quick_actions/quick_actions_android/lib/src/messages.g.dart index 8e1d033ff27a..3912c5d2ceb7 100644 --- a/packages/quick_actions/quick_actions_android/lib/src/messages.g.dart +++ b/packages/quick_actions/quick_actions_android/lib/src/messages.g.dart @@ -1,21 +1,40 @@ // Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers +// ignore_for_file: unused_import, unused_shown_name +// ignore_for_file: type=lint import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'dart:typed_data' show Float64List, Int32List, Int64List; -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; -PlatformException _createConnectionError(String channelName) { - return PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel: "$channelName".', - ); +Object? _extractReplyValueOrThrow( + List? replyList, + String channelName, { + required bool isNullValid, +}) { + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } + return replyList.firstOrNull; } List wrapResponse({ @@ -33,6 +52,15 @@ List wrapResponse({ } bool _deepEquals(Object? a, Object? b) { + if (identical(a, b)) { + return true; + } + if (a is double && b is double) { + if (a.isNaN && b.isNaN) { + return true; + } + return a == b; + } if (a is List && b is List) { return a.length == b.length && a.indexed.every( @@ -40,16 +68,52 @@ bool _deepEquals(Object? a, Object? b) { ); } if (a is Map && b is Map) { - return a.length == b.length && - a.entries.every( - (MapEntry entry) => - (b as Map).containsKey(entry.key) && - _deepEquals(entry.value, b[entry.key]), - ); + if (a.length != b.length) { + return false; + } + for (final MapEntry entryA in a.entries) { + bool found = false; + for (final MapEntry entryB in b.entries) { + if (_deepEquals(entryA.key, entryB.key)) { + if (_deepEquals(entryA.value, entryB.value)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; } return a == b; } +int _deepHash(Object? value) { + if (value is List) { + return Object.hashAll(value.map(_deepHash)); + } + if (value is Map) { + int result = 0; + for (final MapEntry entry in value.entries) { + result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value); + } + return result; + } + if (value is double && value.isNaN) { + // Normalize NaN to a consistent hash. + return 0x7FF8000000000000.hashCode; + } + if (value is double && value == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return 0.0.hashCode; + } + return value.hashCode; +} + /// Home screen quick-action shortcut item. class ShortcutItemMessage { ShortcutItemMessage({ @@ -93,12 +157,14 @@ class ShortcutItemMessage { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(type, other.type) && + _deepEquals(localizedTitle, other.localizedTitle) && + _deepEquals(icon, other.icon); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class _PigeonCodec extends StandardMessageCodec { @@ -146,82 +212,62 @@ class AndroidQuickActionsApi { /// Checks for, and returns the action that launched the app. Future getLaunchAction() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.getLaunchAction$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as String?); - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + return pigeonVar_replyValue as String?; } /// Sets the dynamic shortcuts for the app. Future setShortcutItems(List itemsList) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.setShortcutItems$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [itemsList], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } /// Removes all dynamic shortcuts. Future clearShortcutItems() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.clearShortcutItems$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } } @@ -240,8 +286,7 @@ abstract class AndroidQuickActionsFlutterApi { ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( + final pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsFlutterApi.launchAction$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger, @@ -250,18 +295,10 @@ abstract class AndroidQuickActionsFlutterApi { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsFlutterApi.launchAction was null.', - ); - final List args = (message as List?)!; - final String? arg_action = (args[0] as String?); - assert( - arg_action != null, - 'Argument for dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsFlutterApi.launchAction was null, expected non-null String.', - ); + final List args = message! as List; + final String arg_action = args[0]! as String; try { - api.launchAction(arg_action!); + api.launchAction(arg_action); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); diff --git a/packages/quick_actions/quick_actions_android/pigeons/messages.dart b/packages/quick_actions/quick_actions_android/pigeons/messages.dart index 86572ac17d05..ed34960d920a 100644 --- a/packages/quick_actions/quick_actions_android/pigeons/messages.dart +++ b/packages/quick_actions/quick_actions_android/pigeons/messages.dart @@ -7,9 +7,9 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( dartOut: 'lib/src/messages.g.dart', - javaOut: - 'android/src/main/java/io/flutter/plugins/quickactions/Messages.java', - javaOptions: JavaOptions(package: 'io.flutter.plugins.quickactions'), + kotlinOut: + 'android/src/main/kotlin/io/flutter/plugins/quickactions/Messages.kt', + kotlinOptions: KotlinOptions(package: 'io.flutter.plugins.quickactions'), copyrightHeader: 'pigeons/copyright.txt', ), ) diff --git a/packages/quick_actions/quick_actions_android/pubspec.yaml b/packages/quick_actions/quick_actions_android/pubspec.yaml index dd05368fd9de..773075d4af9f 100644 --- a/packages/quick_actions/quick_actions_android/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/pubspec.yaml @@ -2,7 +2,7 @@ name: quick_actions_android description: An implementation for the Android platform of the Flutter `quick_actions` plugin. repository: https://github.com/flutter/packages/tree/main/packages/quick_actions/quick_actions_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 1.0.28 +version: 1.0.29 environment: sdk: ^3.9.0 @@ -20,12 +20,13 @@ flutter: dependencies: flutter: sdk: flutter + meta: ^1.10.0 quick_actions_platform_interface: ^1.0.0 dev_dependencies: flutter_test: sdk: flutter - pigeon: ^26.1.0 + pigeon: ^26.3.4 plugin_platform_interface: ^2.1.7 topics: