diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md index 2cb8a74f5f26..7c29e3267872 100644 --- a/packages/url_launcher/url_launcher_android/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.3.30 + +* Updates internal implementation to use Kotlin Pigeon. + ## 6.3.29 * Updates build files from Groovy to Kotlin. diff --git a/packages/url_launcher/url_launcher_android/android/build.gradle.kts b/packages/url_launcher/url_launcher_android/android/build.gradle.kts index 70044901998b..071bce13b0db 100644 --- a/packages/url_launcher/url_launcher_android/android/build.gradle.kts +++ b/packages/url_launcher/url_launcher_android/android/build.gradle.kts @@ -1,7 +1,10 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + group = "io.flutter.plugins.urllauncher" version = "1.0-SNAPSHOT" buildscript { + val kotlinVersion = "2.3.0" 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/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java deleted file mode 100644 index be9d4c58a664..000000000000 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java +++ /dev/null @@ -1,464 +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.urllauncher; - -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.Map; -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; - } - - @Target(METHOD) - @Retention(CLASS) - @interface CanIgnoreReturnValue {} - - /** - * Configuration options for an in-app WebView. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class WebViewOptions { - private @NonNull Boolean enableJavaScript; - - public @NonNull Boolean getEnableJavaScript() { - return enableJavaScript; - } - - public void setEnableJavaScript(@NonNull Boolean setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"enableJavaScript\" is null."); - } - this.enableJavaScript = setterArg; - } - - private @NonNull Boolean enableDomStorage; - - public @NonNull Boolean getEnableDomStorage() { - return enableDomStorage; - } - - public void setEnableDomStorage(@NonNull Boolean setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"enableDomStorage\" is null."); - } - this.enableDomStorage = setterArg; - } - - private @NonNull Map headers; - - public @NonNull Map getHeaders() { - return headers; - } - - public void setHeaders(@NonNull Map setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"headers\" is null."); - } - this.headers = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - WebViewOptions() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - WebViewOptions that = (WebViewOptions) o; - return enableJavaScript.equals(that.enableJavaScript) - && enableDomStorage.equals(that.enableDomStorage) - && headers.equals(that.headers); - } - - @Override - public int hashCode() { - return Objects.hash(enableJavaScript, enableDomStorage, headers); - } - - public static final class Builder { - - private @Nullable Boolean enableJavaScript; - - @CanIgnoreReturnValue - public @NonNull Builder setEnableJavaScript(@NonNull Boolean setterArg) { - this.enableJavaScript = setterArg; - return this; - } - - private @Nullable Boolean enableDomStorage; - - @CanIgnoreReturnValue - public @NonNull Builder setEnableDomStorage(@NonNull Boolean setterArg) { - this.enableDomStorage = setterArg; - return this; - } - - private @Nullable Map headers; - - @CanIgnoreReturnValue - public @NonNull Builder setHeaders(@NonNull Map setterArg) { - this.headers = setterArg; - return this; - } - - public @NonNull WebViewOptions build() { - WebViewOptions pigeonReturn = new WebViewOptions(); - pigeonReturn.setEnableJavaScript(enableJavaScript); - pigeonReturn.setEnableDomStorage(enableDomStorage); - pigeonReturn.setHeaders(headers); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(3); - toListResult.add(enableJavaScript); - toListResult.add(enableDomStorage); - toListResult.add(headers); - return toListResult; - } - - static @NonNull WebViewOptions fromList(@NonNull ArrayList pigeonVar_list) { - WebViewOptions pigeonResult = new WebViewOptions(); - Object enableJavaScript = pigeonVar_list.get(0); - pigeonResult.setEnableJavaScript((Boolean) enableJavaScript); - Object enableDomStorage = pigeonVar_list.get(1); - pigeonResult.setEnableDomStorage((Boolean) enableDomStorage); - Object headers = pigeonVar_list.get(2); - pigeonResult.setHeaders((Map) headers); - return pigeonResult; - } - } - - /** - * Configuration options for in-app browser views. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class BrowserOptions { - /** Whether or not to show the webpage title. */ - private @NonNull Boolean showTitle; - - public @NonNull Boolean getShowTitle() { - return showTitle; - } - - public void setShowTitle(@NonNull Boolean setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"showTitle\" is null."); - } - this.showTitle = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - BrowserOptions() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - BrowserOptions that = (BrowserOptions) o; - return showTitle.equals(that.showTitle); - } - - @Override - public int hashCode() { - return Objects.hash(showTitle); - } - - public static final class Builder { - - private @Nullable Boolean showTitle; - - @CanIgnoreReturnValue - public @NonNull Builder setShowTitle(@NonNull Boolean setterArg) { - this.showTitle = setterArg; - return this; - } - - public @NonNull BrowserOptions build() { - BrowserOptions pigeonReturn = new BrowserOptions(); - pigeonReturn.setShowTitle(showTitle); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(1); - toListResult.add(showTitle); - return toListResult; - } - - static @NonNull BrowserOptions fromList(@NonNull ArrayList pigeonVar_list) { - BrowserOptions pigeonResult = new BrowserOptions(); - Object showTitle = pigeonVar_list.get(0); - pigeonResult.setShowTitle((Boolean) showTitle); - 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 WebViewOptions.fromList((ArrayList) readValue(buffer)); - case (byte) 130: - return BrowserOptions.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof WebViewOptions) { - stream.write(129); - writeValue(stream, ((WebViewOptions) value).toList()); - } else if (value instanceof BrowserOptions) { - stream.write(130); - writeValue(stream, ((BrowserOptions) value).toList()); - } else { - super.writeValue(stream, value); - } - } - } - - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ - public interface UrlLauncherApi { - /** Returns true if the URL can definitely be launched. */ - @NonNull - Boolean canLaunchUrl(@NonNull String url); - /** Opens the URL externally, returning true if successful. */ - @NonNull - Boolean launchUrl( - @NonNull String url, - @NonNull Map headers, - @NonNull Boolean requireNonBrowser); - /** - * Opens the URL in an in-app Custom Tab or WebView, returning true if it opens successfully. - */ - @NonNull - Boolean openUrlInApp( - @NonNull String url, - @NonNull Boolean allowCustomTab, - @NonNull WebViewOptions webViewOptions, - @NonNull BrowserOptions browserOptions); - - @NonNull - Boolean supportsCustomTabs(); - /** Closes the view opened by [openUrlInSafariViewController]. */ - void closeWebView(); - - /** The codec used by UrlLauncherApi. */ - static @NonNull MessageCodec getCodec() { - return PigeonCodec.INSTANCE; - } - /** Sets up an instance of `UrlLauncherApi` to handle messages through the `binaryMessenger`. */ - static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLauncherApi api) { - setUp(binaryMessenger, "", api); - } - - static void setUp( - @NonNull BinaryMessenger binaryMessenger, - @NonNull String messageChannelSuffix, - @Nullable UrlLauncherApi api) { - messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - String urlArg = (String) args.get(0); - try { - Boolean output = api.canLaunchUrl(urlArg); - 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.url_launcher_android.UrlLauncherApi.launchUrl" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - String urlArg = (String) args.get(0); - Map headersArg = (Map) args.get(1); - Boolean requireNonBrowserArg = (Boolean) args.get(2); - try { - Boolean output = api.launchUrl(urlArg, headersArg, requireNonBrowserArg); - 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.url_launcher_android.UrlLauncherApi.openUrlInApp" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - String urlArg = (String) args.get(0); - Boolean allowCustomTabArg = (Boolean) args.get(1); - WebViewOptions webViewOptionsArg = (WebViewOptions) args.get(2); - BrowserOptions browserOptionsArg = (BrowserOptions) args.get(3); - try { - Boolean output = - api.openUrlInApp( - urlArg, allowCustomTabArg, webViewOptionsArg, browserOptionsArg); - 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.url_launcher_android.UrlLauncherApi.supportsCustomTabs" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - try { - Boolean output = api.supportsCustomTabs(); - 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.url_launcher_android.UrlLauncherApi.closeWebView" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - try { - api.closeWebView(); - wrapped.add(0, null); - } catch (Throwable exception) { - wrapped = wrapError(exception); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - } - } -} diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java index b3f962f31b12..2598ef054339 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java @@ -19,9 +19,6 @@ import androidx.annotation.VisibleForTesting; import androidx.browser.customtabs.CustomTabsClient; import androidx.browser.customtabs.CustomTabsIntent; -import io.flutter.plugins.urllauncher.Messages.BrowserOptions; -import io.flutter.plugins.urllauncher.Messages.UrlLauncherApi; -import io.flutter.plugins.urllauncher.Messages.WebViewOptions; import java.util.Collections; import java.util.Locale; import java.util.Map; @@ -65,7 +62,7 @@ void setActivity(@Nullable Activity activity) { } @Override - public @NonNull Boolean canLaunchUrl(@NonNull String url) { + public boolean canLaunchUrl(@NonNull String url) { Intent launchIntent = new Intent(Intent.ACTION_VIEW); launchIntent.setData(Uri.parse(url)); String componentName = intentResolver.getHandlerComponentName(launchIntent); @@ -81,10 +78,8 @@ void setActivity(@Nullable Activity activity) { } @Override - public @NonNull Boolean launchUrl( - @NonNull String url, - @NonNull Map headers, - @NonNull Boolean requireNonBrowser) { + public boolean launchUrl( + @NonNull String url, @NonNull Map headers, boolean requireNonBrowser) { ensureActivity(); assert activity != null; @@ -105,9 +100,9 @@ void setActivity(@Nullable Activity activity) { } @Override - public @NonNull Boolean openUrlInApp( + public boolean openUrlInApp( @NonNull String url, - @NonNull Boolean allowCustomTab, + boolean allowCustomTab, @NonNull WebViewOptions webViewOptions, @NonNull BrowserOptions browserOptions) { ensureActivity(); @@ -147,7 +142,7 @@ public void closeWebView() { } @Override - public @NonNull Boolean supportsCustomTabs() { + public boolean supportsCustomTabs() { return CustomTabsClient.getPackageName(applicationContext, Collections.emptyList()) != null; } @@ -196,7 +191,7 @@ private static boolean containsRestrictedHeader(Map headersMap) private void ensureActivity() { if (activity == null) { - throw new Messages.FlutterError( + throw new FlutterError( "NO_ACTIVITY", "Launching a URL requires a foreground activity.", null); } } diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java index 6a7ad8d99a7e..3c6b7fe35bc2 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java @@ -23,7 +23,7 @@ public final class UrlLauncherPlugin implements FlutterPlugin, ActivityAware { @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { urlLauncher = new UrlLauncher(binding.getApplicationContext()); - Messages.UrlLauncherApi.setUp(binding.getBinaryMessenger(), urlLauncher); + UrlLauncherApi.Companion.setUp(binding.getBinaryMessenger(), urlLauncher); } @Override @@ -33,7 +33,7 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { return; } - Messages.UrlLauncherApi.setUp(binding.getBinaryMessenger(), null); + UrlLauncherApi.Companion.setUp(binding.getBinaryMessenger(), null); urlLauncher = null; } diff --git a/packages/url_launcher/url_launcher_android/android/src/main/kotlin/io/flutter/plugins/urllauncher/Messages.kt b/packages/url_launcher/url_launcher_android/android/src/main/kotlin/io/flutter/plugins/urllauncher/Messages.kt new file mode 100644 index 000000000000..f1e41cbc8eeb --- /dev/null +++ b/packages/url_launcher/url_launcher_android/android/src/main/kotlin/io/flutter/plugins/urllauncher/Messages.kt @@ -0,0 +1,456 @@ +// 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.urllauncher + +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 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() + +/** + * Configuration options for an in-app WebView. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class WebViewOptions( + val enableJavaScript: Boolean, + val enableDomStorage: Boolean, + val headers: Map +) { + companion object { + fun fromList(pigeonVar_list: List): WebViewOptions { + val enableJavaScript = pigeonVar_list[0] as Boolean + val enableDomStorage = pigeonVar_list[1] as Boolean + val headers = pigeonVar_list[2] as Map + return WebViewOptions(enableJavaScript, enableDomStorage, headers) + } + } + + fun toList(): List { + return listOf( + enableJavaScript, + enableDomStorage, + headers, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as WebViewOptions + return MessagesPigeonUtils.deepEquals(this.enableJavaScript, other.enableJavaScript) && + MessagesPigeonUtils.deepEquals(this.enableDomStorage, other.enableDomStorage) && + MessagesPigeonUtils.deepEquals(this.headers, other.headers) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.enableJavaScript) + result = 31 * result + MessagesPigeonUtils.deepHash(this.enableDomStorage) + result = 31 * result + MessagesPigeonUtils.deepHash(this.headers) + return result + } +} + +/** + * Configuration options for in-app browser views. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class BrowserOptions( + /** Whether or not to show the webpage title. */ + val showTitle: Boolean +) { + companion object { + fun fromList(pigeonVar_list: List): BrowserOptions { + val showTitle = pigeonVar_list[0] as Boolean + return BrowserOptions(showTitle) + } + } + + fun toList(): List { + return listOf( + showTitle, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as BrowserOptions + return MessagesPigeonUtils.deepEquals(this.showTitle, other.showTitle) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.showTitle) + 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 { WebViewOptions.fromList(it) } + } + 130.toByte() -> { + return (readValue(buffer) as? List)?.let { BrowserOptions.fromList(it) } + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is WebViewOptions -> { + stream.write(129) + writeValue(stream, value.toList()) + } + is BrowserOptions -> { + stream.write(130) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface UrlLauncherApi { + /** Returns true if the URL can definitely be launched. */ + fun canLaunchUrl(url: String): Boolean + /** Opens the URL externally, returning true if successful. */ + fun launchUrl(url: String, headers: Map, requireNonBrowser: Boolean): Boolean + /** Opens the URL in an in-app Custom Tab or WebView, returning true if it opens successfully. */ + fun openUrlInApp( + url: String, + allowCustomTab: Boolean, + webViewOptions: WebViewOptions, + browserOptions: BrowserOptions + ): Boolean + + fun supportsCustomTabs(): Boolean + /** Closes the view opened by [openUrlInSafariViewController]. */ + fun closeWebView() + + companion object { + /** The codec used by UrlLauncherApi. */ + val codec: MessageCodec by lazy { MessagesPigeonCodec() } + /** Sets up an instance of `UrlLauncherApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp( + binaryMessenger: BinaryMessenger, + api: UrlLauncherApi?, + messageChannelSuffix: String = "" + ) { + val separatedMessageChannelSuffix = + if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val urlArg = args[0] as String + val wrapped: List = + try { + listOf(api.canLaunchUrl(urlArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val urlArg = args[0] as String + val headersArg = args[1] as Map + val requireNonBrowserArg = args[2] as Boolean + val wrapped: List = + try { + listOf(api.launchUrl(urlArg, headersArg, requireNonBrowserArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val urlArg = args[0] as String + val allowCustomTabArg = args[1] as Boolean + val webViewOptionsArg = args[2] as WebViewOptions + val browserOptionsArg = args[3] as BrowserOptions + val wrapped: List = + try { + listOf( + api.openUrlInApp( + urlArg, allowCustomTabArg, webViewOptionsArg, browserOptionsArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = + try { + listOf(api.supportsCustomTabs()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = + try { + api.closeWebView() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java index 45dc645c01db..efc56260936a 100644 --- a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java +++ b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java @@ -53,7 +53,7 @@ public void canLaunch_returnsTrue() { UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext(), intent -> "some.component"); - Boolean result = api.canLaunchUrl("https://flutter.dev"); + boolean result = api.canLaunchUrl("https://flutter.dev"); assertTrue(result); } @@ -62,7 +62,7 @@ public void canLaunch_returnsTrue() { public void canLaunch_returnsFalse() { UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext(), intent -> null); - Boolean result = api.canLaunchUrl("https://flutter.dev"); + boolean result = api.canLaunchUrl("https://flutter.dev"); assertFalse(result); } @@ -76,7 +76,7 @@ public void canLaunch_returnsFalseForEmulatorFallbackComponent() { ApplicationProvider.getApplicationContext(), intent -> "{com.android.fallback/com.android.fallback.Fallback}"); - Boolean result = api.canLaunchUrl("https://flutter.dev"); + boolean result = api.canLaunchUrl("https://flutter.dev"); assertFalse(result); } @@ -86,11 +86,10 @@ public void launch_throwsForNoCurrentActivity() { UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(null); - Messages.FlutterError exception = + FlutterError exception = assertThrows( - Messages.FlutterError.class, - () -> api.launchUrl("https://flutter.dev", new HashMap<>(), false)); - assertEquals("NO_ACTIVITY", exception.code); + FlutterError.class, () -> api.launchUrl("https://flutter.dev", new HashMap<>(), false)); + assertEquals("NO_ACTIVITY", exception.getCode()); } @Test @@ -113,7 +112,6 @@ public void launch_createsIntentWithPassedUrl() { @Test public void launch_setsRequireNonBrowserWhenRequested() { Activity activity = mock(Activity.class); - String url = "https://flutter.dev"; UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); doThrow(new ActivityNotFoundException()).when(activity).startActivity(any()); @@ -166,12 +164,8 @@ public void openUrlInApp_opensUrlInWebViewIfNecessary() { api.openUrlInApp( url, true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(enableJavaScript) - .setEnableDomStorage(enableDomStorage) - .setHeaders(headers) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(showTitle).build()); + new WebViewOptions(enableJavaScript, enableDomStorage, headers), + new BrowserOptions(showTitle)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture()); @@ -196,12 +190,8 @@ public void openWebView_opensUrlInWebViewIfRequested() { api.openUrlInApp( url, false, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(new HashMap<>()) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(true).build()); + new WebViewOptions(false, false, new HashMap<>()), + new BrowserOptions(true)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture()); @@ -220,12 +210,8 @@ public void openWebView_opensUrlInCustomTabs() { api.openUrlInApp( url, true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(new HashMap<>()) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(false).build()); + new WebViewOptions(false, false, new HashMap<>()), + new BrowserOptions(false)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture(), isNull()); @@ -246,14 +232,7 @@ public void openWebView_opensUrlInCustomTabsWithCORSAllowedHeader() { boolean result = api.openUrlInApp( - url, - true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(headers) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(false).build()); + url, true, new WebViewOptions(false, false, headers), new BrowserOptions(false)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture(), isNull()); @@ -275,14 +254,7 @@ public void openWebView_opensUrlInCustomTabsWithShowTitle() { boolean result = api.openUrlInApp( - url, - true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(headers) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(true).build()); + url, true, new WebViewOptions(false, false, headers), new BrowserOptions(true)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture(), isNull()); @@ -290,8 +262,8 @@ public void openWebView_opensUrlInCustomTabsWithShowTitle() { assertEquals(Intent.ACTION_VIEW, intentCaptor.getValue().getAction()); assertNull(intentCaptor.getValue().getComponent()); assertEquals( - intentCaptor.getValue().getExtras().getInt(CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE), - CustomTabsIntent.SHOW_PAGE_TITLE); + CustomTabsIntent.SHOW_PAGE_TITLE, + intentCaptor.getValue().getExtras().getInt(CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE)); } @Test @@ -304,14 +276,7 @@ public void openWebView_opensUrlInCustomTabsWithoutShowTitle() { boolean result = api.openUrlInApp( - url, - true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(headers) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(false).build()); + url, true, new WebViewOptions(false, false, headers), new BrowserOptions(false)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture(), isNull()); @@ -319,8 +284,8 @@ public void openWebView_opensUrlInCustomTabsWithoutShowTitle() { assertEquals(Intent.ACTION_VIEW, intentCaptor.getValue().getAction()); assertNull(intentCaptor.getValue().getComponent()); assertEquals( - intentCaptor.getValue().getExtras().getInt(CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE), - CustomTabsIntent.NO_TITLE); + CustomTabsIntent.NO_TITLE, + intentCaptor.getValue().getExtras().getInt(CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE)); } @Test @@ -337,21 +302,15 @@ public void openWebView_fallsBackToWebViewIfCustomTabFails() { api.openUrlInApp( url, true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(new HashMap<>()) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(false).build()); + new WebViewOptions(false, false, new HashMap<>()), + new BrowserOptions(false)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture()); assertTrue(result); assertEquals(url, intentCaptor.getValue().getExtras().getString(WebViewActivity.URL_EXTRA)); - assertEquals( - false, intentCaptor.getValue().getExtras().getBoolean(WebViewActivity.ENABLE_JS_EXTRA)); - assertEquals( - false, intentCaptor.getValue().getExtras().getBoolean(WebViewActivity.ENABLE_DOM_EXTRA)); + assertFalse(intentCaptor.getValue().getExtras().getBoolean(WebViewActivity.ENABLE_JS_EXTRA)); + assertFalse(intentCaptor.getValue().getExtras().getBoolean(WebViewActivity.ENABLE_DOM_EXTRA)); } @Test @@ -366,12 +325,8 @@ public void openWebView_handlesEnableJavaScript() { api.openUrlInApp( "https://flutter.dev", true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(enableJavaScript) - .setEnableDomStorage(false) - .setHeaders(headers) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(false).build()); + new WebViewOptions(enableJavaScript, false, headers), + new BrowserOptions(false)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture()); @@ -394,12 +349,8 @@ public void openWebView_handlesHeaders() { api.openUrlInApp( "https://flutter.dev", true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(headers) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(false).build()); + new WebViewOptions(false, false, headers), + new BrowserOptions(false)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture()); @@ -422,12 +373,8 @@ public void openWebView_handlesEnableDomStorage() { api.openUrlInApp( "https://flutter.dev", true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(enableDomStorage) - .setHeaders(headers) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(false).build()); + new WebViewOptions(false, enableDomStorage, headers), + new BrowserOptions(false)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture()); @@ -448,19 +395,15 @@ public void openWebView_handlesEnableShowTitle() { api.openUrlInApp( "https://flutter.dev", true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(enableDomStorage) - .setHeaders(headers) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(showTitle).build()); + new WebViewOptions(false, enableDomStorage, headers), + new BrowserOptions(showTitle)); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture(), isNull()); assertEquals( - intentCaptor.getValue().getExtras().getInt(CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE), - CustomTabsIntent.SHOW_PAGE_TITLE); + CustomTabsIntent.SHOW_PAGE_TITLE, + intentCaptor.getValue().getExtras().getInt(CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE)); } @Test @@ -468,20 +411,16 @@ public void openWebView_throwsForNoCurrentActivity() { UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(null); - Messages.FlutterError exception = + FlutterError exception = assertThrows( - Messages.FlutterError.class, + FlutterError.class, () -> api.openUrlInApp( "https://flutter.dev", true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(new HashMap<>()) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(false).build())); - assertEquals("NO_ACTIVITY", exception.code); + new WebViewOptions(false, false, new HashMap<>()), + new BrowserOptions(false))); + assertEquals("NO_ACTIVITY", exception.getCode()); } @Test @@ -500,12 +439,8 @@ public void openWebView_returnsFalse() { api.openUrlInApp( "https://flutter.dev", true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(new HashMap<>()) - .build(), - new Messages.BrowserOptions.Builder().setShowTitle(false).build()); + new WebViewOptions(false, false, new HashMap<>()), + new BrowserOptions(false)); assertFalse(result); } diff --git a/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart index 049813a94700..213dc8273f6f 100644 --- a/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart +++ b/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart @@ -1,24 +1,52 @@ // 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; } 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( @@ -26,16 +54,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; +} + /// Configuration options for an in-app WebView. class WebViewOptions { WebViewOptions({ @@ -63,7 +127,7 @@ class WebViewOptions { return WebViewOptions( enableJavaScript: result[0]! as bool, enableDomStorage: result[1]! as bool, - headers: (result[2] as Map?)!.cast(), + headers: (result[2]! as Map).cast(), ); } @@ -76,12 +140,14 @@ class WebViewOptions { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(enableJavaScript, other.enableJavaScript) && + _deepEquals(enableDomStorage, other.enableDomStorage) && + _deepEquals(headers, other.headers); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } /// Configuration options for in-app browser views. @@ -113,12 +179,12 @@ class BrowserOptions { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(showTitle, other.showTitle); } @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 { @@ -171,35 +237,24 @@ class UrlLauncherApi { /// Returns true if the URL can definitely be launched. Future canLaunchUrl(String url) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl$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( [url], ); - 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 if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; } /// Opens the URL externally, returning true if successful. @@ -208,35 +263,24 @@ class UrlLauncherApi { Map headers, bool requireNonBrowser, ) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl$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( [url, headers, requireNonBrowser], ); - 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 if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; } /// Opens the URL in an in-app Custom Tab or WebView, returning true if it @@ -247,90 +291,61 @@ class UrlLauncherApi { WebViewOptions webViewOptions, BrowserOptions browserOptions, ) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp$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( [url, allowCustomTab, webViewOptions, browserOptions], ); - 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 if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; } Future supportsCustomTabs() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs$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 if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; } /// Closes the view opened by [openUrlInSafariViewController]. Future closeWebView() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView$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, + ); } } diff --git a/packages/url_launcher/url_launcher_android/pigeons/messages.dart b/packages/url_launcher/url_launcher_android/pigeons/messages.dart index bf10fbdd60ab..badef0ab2f16 100644 --- a/packages/url_launcher/url_launcher_android/pigeons/messages.dart +++ b/packages/url_launcher/url_launcher_android/pigeons/messages.dart @@ -7,9 +7,9 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( dartOut: 'lib/src/messages.g.dart', - javaOptions: JavaOptions(package: 'io.flutter.plugins.urllauncher'), - javaOut: - 'android/src/main/java/io/flutter/plugins/urllauncher/Messages.java', + kotlinOut: + 'android/src/main/kotlin/io/flutter/plugins/urllauncher/Messages.kt', + kotlinOptions: KotlinOptions(package: 'io.flutter.plugins.urllauncher'), copyrightHeader: 'pigeons/copyright.txt', ), ) diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml index 425b261484ae..2e676a118cd9 100644 --- a/packages/url_launcher/url_launcher_android/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_android description: Android implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.3.29 +version: 6.3.30 environment: sdk: ^3.9.0 @@ -20,13 +20,14 @@ flutter: dependencies: flutter: sdk: flutter + meta: ^1.10.0 url_launcher_platform_interface: ^2.3.1 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.4.4 - pigeon: ^26.1.0 + pigeon: ^26.3.4 plugin_platform_interface: ^2.1.7 test: ^1.16.3