Added requestPostMessageChannel, postMessage, isEngagementSignalsApiAvailable methods on ChromeSafariBrowser for Android, Added onMessageChannelReady, onPostMessage, onVerticalScrollEvent, onGreatestScrollPercentageIncreased, onSessionEnded events on ChromeSafariBrowser for Android

This commit is contained in:
Lorenzo Pichilli 2023-11-12 18:56:11 +01:00
parent 5009bf1fa4
commit 6a486b2fa9
16 changed files with 545 additions and 24 deletions

View File

@ -1,3 +1,8 @@
## 6.0.0-beta.27
- Added `requestPostMessageChannel`, `postMessage`, `isEngagementSignalsApiAvailable` methods on `ChromeSafariBrowser` for Android
- Added `onMessageChannelReady`, `onPostMessage`, `onVerticalScrollEvent`, `onGreatestScrollPercentageIncreased`, `onSessionEnded` events on `ChromeSafariBrowser` for Android
## 6.0.0-beta.26 ## 6.0.0-beta.26
- Throw an error if any controller is used after being disposed - Throw an error if any controller is used after being disposed

View File

@ -9,6 +9,7 @@ import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.RemoteException;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
@ -19,6 +20,7 @@ import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsService; import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsSession; import androidx.browser.customtabs.CustomTabsSession;
import androidx.browser.customtabs.EngagementSignalsCallback;
import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.types.AndroidResource; import com.pichillilorenzo.flutter_inappwebview.types.AndroidResource;
@ -32,12 +34,13 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import io.flutter.Log;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
public class ChromeCustomTabsActivity extends Activity implements Disposable { public class ChromeCustomTabsActivity extends Activity implements Disposable {
protected static final String LOG_TAG = "CustomTabsActivity"; protected static final String LOG_TAG = "CustomTabsActivity";
public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_chromesafaribrowser_"; public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_chromesafaribrowser_";
public String id; public String id;
@Nullable @Nullable
public CustomTabsIntent.Builder builder; public CustomTabsIntent.Builder builder;
@ -76,7 +79,7 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
Bundle b = getIntent().getExtras(); Bundle b = getIntent().getExtras();
if (b == null) return; if (b == null) return;
id = b.getString("id"); id = b.getString("id");
String managerId = b.getString("managerId"); String managerId = b.getString("managerId");
@ -143,13 +146,22 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
} }
@Override @Override
public void extraCallback(@NonNull String callbackName, Bundle args) {} public void extraCallback(@NonNull String callbackName, Bundle args) {
}
@Override @Override
public void onMessageChannelReady(Bundle extras) {} public void onMessageChannelReady(Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onMessageChannelReady();
}
}
@Override @Override
public void onPostMessage(@NonNull String message, Bundle extras) {} public void onPostMessage(@NonNull String message, Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onPostMessage(message);
}
}
@Override @Override
public void onRelationshipValidationResult(@CustomTabsService.Relation int relation, public void onRelationshipValidationResult(@CustomTabsService.Relation int relation,
@ -198,8 +210,41 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
return customTabActivityHelper.mayLaunchUrl(uri, null, bundleOtherLikelyURLs); return customTabActivityHelper.mayLaunchUrl(uri, null, bundleOtherLikelyURLs);
} }
@CallSuper
public void customTabsConnected() { public void customTabsConnected() {
customTabsSession = customTabActivityHelper.getSession(); customTabsSession = customTabActivityHelper.getSession();
if (customTabsSession != null) {
try {
Bundle bundle = new Bundle();
if (customTabsSession.isEngagementSignalsApiAvailable(bundle)) {
customTabsSession.setEngagementSignalsCallback(new EngagementSignalsCallback() {
@Override
public void onVerticalScrollEvent(boolean isDirectionUp, @NonNull Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onVerticalScrollEvent(isDirectionUp);
}
}
@Override
public void onGreatestScrollPercentageIncreased(int scrollPercentage, @NonNull Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onGreatestScrollPercentageIncreased(scrollPercentage);
}
}
@Override
public void onSessionEnded(boolean didUserInteract, @NonNull Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onSessionEnded(didUserInteract);
}
}
}, bundle);
}
} catch (Exception ignored) {
}
}
// avoid webpage reopen if isBindSuccess is false: onServiceConnected->launchUrl // avoid webpage reopen if isBindSuccess is false: onServiceConnected->launchUrl
if (isBindSuccess && initialUrl != null) { if (isBindSuccess && initialUrl != null) {
launchUrl(initialUrl, initialHeaders, initialReferrer, initialOtherLikelyURLs); launchUrl(initialUrl, initialHeaders, initialReferrer, initialOtherLikelyURLs);
@ -248,7 +293,7 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
} }
for (CustomTabsMenuItem menuItem : menuItems) { for (CustomTabsMenuItem menuItem : menuItems) {
builder.addMenuItem(menuItem.getLabel(), builder.addMenuItem(menuItem.getLabel(),
createPendingIntent(menuItem.getId())); createPendingIntent(menuItem.getId()));
} }

View File

@ -3,9 +3,12 @@ package com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsService;
import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl; import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview.types.CustomTabsActionButton; import com.pichillilorenzo.flutter_inappwebview.types.CustomTabsActionButton;
@ -84,6 +87,35 @@ public class ChromeCustomTabsChannelDelegate extends ChannelDelegateImpl {
result.success(false); result.success(false);
} }
break; break;
case "requestPostMessageChannel":
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
String sourceOrigin = (String) call.argument("sourceOrigin");
String targetOrigin = (String) call.argument("targetOrigin");
result.success(chromeCustomTabsActivity.customTabsSession.requestPostMessageChannel(Uri.parse(sourceOrigin),
targetOrigin != null ? Uri.parse(targetOrigin) : null, new Bundle()));
} else {
result.success(false);
}
break;
case "postMessage":
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
String message = (String) call.argument("message");
result.success(chromeCustomTabsActivity.customTabsSession.postMessage(message, new Bundle()));
} else {
result.success(CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR);
}
break;
case "isEngagementSignalsApiAvailable":
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
try {
result.success(chromeCustomTabsActivity.customTabsSession.isEngagementSignalsApiAvailable(new Bundle()));
} catch (Exception e) {
result.success(false);
}
} else {
result.success(false);
}
break;
case "close": case "close":
if (chromeCustomTabsActivity != null) { if (chromeCustomTabsActivity != null) {
chromeCustomTabsActivity.onStop(); chromeCustomTabsActivity.onStop();
@ -131,7 +163,7 @@ public class ChromeCustomTabsChannelDelegate extends ChannelDelegateImpl {
channel.invokeMethod("onCompletedInitialLoad", obj); channel.invokeMethod("onCompletedInitialLoad", obj);
} }
public void onNavigationEvent(int navigationEvent) {; public void onNavigationEvent(int navigationEvent) {
MethodChannel channel = getChannel(); MethodChannel channel = getChannel();
if (channel == null) return; if (channel == null) return;
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
@ -175,6 +207,45 @@ public class ChromeCustomTabsChannelDelegate extends ChannelDelegateImpl {
channel.invokeMethod("onRelationshipValidationResult", obj); channel.invokeMethod("onRelationshipValidationResult", obj);
} }
public void onMessageChannelReady() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onMessageChannelReady", obj);
}
public void onPostMessage(@NonNull String message) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("message", message);
channel.invokeMethod("onPostMessage", obj);
}
public void onVerticalScrollEvent(boolean isDirectionUp) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("isDirectionUp", isDirectionUp);
channel.invokeMethod("onVerticalScrollEvent", obj);
}
public void onGreatestScrollPercentageIncreased(int scrollPercentage) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("scrollPercentage", scrollPercentage);
channel.invokeMethod("onGreatestScrollPercentageIncreased", obj);
}
public void onSessionEnded(boolean didUserInteract) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("didUserInteract", didUserInteract);
channel.invokeMethod("onSessionEnded", obj);
}
@Override @Override
public void dispose() { public void dispose() {
super.dispose(); super.dispose();

View File

@ -41,14 +41,6 @@ public class TrustedWebActivity extends ChromeCustomTabsActivity {
referrer != null ? Uri.parse(referrer) : null, CHROME_CUSTOM_TAB_REQUEST_CODE); referrer != null ? Uri.parse(referrer) : null, CHROME_CUSTOM_TAB_REQUEST_CODE);
} }
@Override
public void customTabsConnected() {
customTabsSession = customTabActivityHelper.getSession();
if (initialUrl != null) {
launchUrl(initialUrl, initialHeaders, initialReferrer, initialOtherLikelyURLs);
}
}
private void prepareCustomTabs() { private void prepareCustomTabs() {
CustomTabColorSchemeParams.Builder defaultColorSchemeBuilder = new CustomTabColorSchemeParams.Builder(); CustomTabColorSchemeParams.Builder defaultColorSchemeBuilder = new CustomTabColorSchemeParams.Builder();
if (customSettings.toolbarBackgroundColor != null && !customSettings.toolbarBackgroundColor.isEmpty()) { if (customSettings.toolbarBackgroundColor != null && !customSettings.toolbarBackgroundColor.isEmpty()) {

View File

@ -93,6 +93,7 @@
android:name="io.flutter.embedded_views_preview" android:name="io.flutter.embedded_views_preview"
android:value="true" /> android:value="true" />
<service android:name="androidx.browser.customtabs.PostMessageService"
android:exported="true"/>
</application> </application>
</manifest> </manifest>

View File

@ -6,6 +6,12 @@
\"target\": { \"target\": {
\"namespace\": \"web\", \"namespace\": \"web\",
\"site\": \"https://flutter.dev\"} \"site\": \"https://flutter.dev\"}
},
{
\"relation\": [\"delegate_permission/common.handle_all_urls\"],
\"target\": {
\"namespace\": \"web\",
\"site\": \"https://inappwebview.dev\"}
}] }]
</string> </string>
</resources> </resources>

View File

@ -170,5 +170,52 @@ void customTabs() {
expect(await ChromeSafariBrowser.getMaxToolbarItems(), expect(await ChromeSafariBrowser.getMaxToolbarItems(),
greaterThanOrEqualTo(0)); greaterThanOrEqualTo(0));
}); });
skippableTest('request and send post messages', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(
url: TEST_CUSTOM_TABS_POST_MESSAGE_URL,
settings: ChromeSafariBrowserSettings(isSingleInstance: true));
await expectLater(chromeSafariBrowser.opened.future, completes);
expect(chromeSafariBrowser.isOpened(), true);
await expectLater(
chromeSafariBrowser.navigationFinished.future, completes);
expect(
await chromeSafariBrowser.requestPostMessageChannel(
sourceOrigin: WebUri(TEST_CUSTOM_TABS_POST_MESSAGE_URL.origin)),
true);
await expectLater(
chromeSafariBrowser.messageChannelReady.future, completes);
expect(await chromeSafariBrowser.postMessage("Message from Flutter"),
CustomTabsPostMessageResultType.SUCCESS);
await expectLater(chromeSafariBrowser.postMessageReceived.future,
completion("Message from JavaScript"));
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await expectLater(chromeSafariBrowser.closed.future, completes);
expect(chromeSafariBrowser.isOpened(), false);
});
skippableTest('Engagement Signals Api', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(
url: TEST_URL_1,
settings: ChromeSafariBrowserSettings(isSingleInstance: true));
await expectLater(chromeSafariBrowser.opened.future, completes);
await expectLater(
chromeSafariBrowser.isEngagementSignalsApiAvailable(), completes);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await expectLater(chromeSafariBrowser.closed.future, completes);
expect(chromeSafariBrowser.isOpened(), false);
});
}, skip: shouldSkip); }, skip: shouldSkip);
} }

View File

@ -13,12 +13,12 @@ void trustedWebActivity() {
expect(chromeSafariBrowser.isOpened(), false); expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open( await chromeSafariBrowser.open(
url: TEST_URL_1, url: TEST_TWA_URL,
settings: ChromeSafariBrowserSettings(isTrustedWebActivity: true)); settings: ChromeSafariBrowserSettings(isTrustedWebActivity: true));
await chromeSafariBrowser.opened.future; await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.open(url: TEST_TWA_URL);
}, throwsAssertionError); }, throwsAssertionError);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
@ -32,13 +32,13 @@ void trustedWebActivity() {
expect(chromeSafariBrowser.isOpened(), false); expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open( await chromeSafariBrowser.open(
url: TEST_URL_1, url: TEST_TWA_URL,
settings: ChromeSafariBrowserSettings( settings: ChromeSafariBrowserSettings(
isTrustedWebActivity: true, isSingleInstance: true)); isTrustedWebActivity: true, isSingleInstance: true));
await chromeSafariBrowser.opened.future; await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.open(url: TEST_TWA_URL);
}, throwsAssertionError); }, throwsAssertionError);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
@ -57,11 +57,11 @@ void trustedWebActivity() {
expect( expect(
await chromeSafariBrowser.validateRelationship( await chromeSafariBrowser.validateRelationship(
relation: CustomTabsRelationType.USE_AS_ORIGIN, relation: CustomTabsRelationType.USE_AS_ORIGIN,
origin: TEST_CROSS_PLATFORM_URL_1), origin: TEST_TWA_URL),
true); true);
expect( expect(
await chromeSafariBrowser.relationshipValidationResult.future, true); await chromeSafariBrowser.relationshipValidationResult.future, true);
await chromeSafariBrowser.launchUrl(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.launchUrl(url: TEST_TWA_URL);
await chromeSafariBrowser.opened.future; await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);

View File

@ -28,3 +28,7 @@ final TEST_SERVICE_WORKER_URL = WebUri(
final TEST_WEBVIEW_ASSET_LOADER_DOMAIN = 'my.custom.domain.com'; final TEST_WEBVIEW_ASSET_LOADER_DOMAIN = 'my.custom.domain.com';
final TEST_WEBVIEW_ASSET_LOADER_URL = WebUri( final TEST_WEBVIEW_ASSET_LOADER_URL = WebUri(
'https://$TEST_WEBVIEW_ASSET_LOADER_DOMAIN/assets/flutter_assets/test_assets/website/index.html'); 'https://$TEST_WEBVIEW_ASSET_LOADER_DOMAIN/assets/flutter_assets/test_assets/website/index.html');
final TEST_TWA_URL =
WebUri('https://inappwebview.dev/test-twa-postmessage.html');
final TEST_CUSTOM_TABS_POST_MESSAGE_URL =
WebUri('https://inappwebview.dev/test-twa-postmessage.html');

View File

@ -118,6 +118,9 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser {
final Completer<void> closed = Completer<void>(); final Completer<void> closed = Completer<void>();
final Completer<CustomTabsNavigationEventType?> navigationEvent = final Completer<CustomTabsNavigationEventType?> navigationEvent =
Completer<CustomTabsNavigationEventType?>(); Completer<CustomTabsNavigationEventType?>();
final Completer<void> navigationFinished = Completer<void>();
final Completer<void> messageChannelReady = Completer<void>();
final Completer<String> postMessageReceived = Completer<String>();
final Completer<bool> relationshipValidationResult = Completer<bool>(); final Completer<bool> relationshipValidationResult = Completer<bool>();
@override @override
@ -140,6 +143,24 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser {
if (!navigationEvent.isCompleted) { if (!navigationEvent.isCompleted) {
navigationEvent.complete(type); navigationEvent.complete(type);
} }
if (!navigationFinished.isCompleted &&
type == CustomTabsNavigationEventType.FINISHED) {
navigationFinished.complete();
}
}
@override
void onMessageChannelReady() async {
if (!messageChannelReady.isCompleted) {
messageChannelReady.complete();
}
}
@override
void onPostMessage(String message) {
if (!postMessageReceived.isCompleted) {
postMessageReceived.complete();
}
} }
@override @override

View File

@ -19,6 +19,21 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser {
void onClosed() { void onClosed() {
print("ChromeSafari browser closed"); print("ChromeSafari browser closed");
} }
@override
void onVerticalScrollEvent(bool isDirectionUp) {
print("onVerticalScrollEvent $isDirectionUp");
}
@override
void onGreatestScrollPercentageIncreased(int scrollPercentage) {
print("onGreatestScrollPercentageIncreased $scrollPercentage");
}
@override
void onSessionEnded(bool didUserInteract) {
print("onSessionEnded $didUserInteract");
}
} }
class ChromeSafariBrowserExampleScreen extends StatefulWidget { class ChromeSafariBrowserExampleScreen extends StatefulWidget {
@ -103,6 +118,8 @@ class _ChromeSafariBrowserExampleScreenState
dismissButtonStyle: DismissButtonStyle.CLOSE, dismissButtonStyle: DismissButtonStyle.CLOSE,
presentationStyle: presentationStyle:
ModalPresentationStyle.OVER_FULL_SCREEN)); ModalPresentationStyle.OVER_FULL_SCREEN));
await Future.delayed(Duration(seconds: 5));
widget.browser.close();
}, },
child: Text("Open Chrome Safari Browser")), child: Text("Open Chrome Safari Browser")),
)); ));

View File

@ -5,6 +5,7 @@ import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../types/custom_tabs_navigation_event_type.dart'; import '../types/custom_tabs_navigation_event_type.dart';
import '../types/custom_tabs_post_message_result_type.dart';
import '../types/custom_tabs_relation_type.dart'; import '../types/custom_tabs_relation_type.dart';
import '../types/prewarming_token.dart'; import '../types/prewarming_token.dart';
import '../util.dart'; import '../util.dart';
@ -153,6 +154,25 @@ class ChromeSafariBrowser extends ChannelController {
} }
} }
break; break;
case "onMessageChannelReady":
onMessageChannelReady();
break;
case "onPostMessage":
final String message = call.arguments["message"];
onPostMessage(message);
break;
case "onVerticalScrollEvent":
final bool isDirectionUp = call.arguments["isDirectionUp"];
onVerticalScrollEvent(isDirectionUp);
break;
case "onGreatestScrollPercentageIncreased":
final int scrollPercentage = call.arguments["scrollPercentage"];
onGreatestScrollPercentageIncreased(scrollPercentage);
break;
case "onSessionEnded":
final bool didUserInteract = call.arguments["didUserInteract"];
onSessionEnded(didUserInteract);
break;
default: default:
throw UnimplementedError("Unimplemented ${call.method} method"); throw UnimplementedError("Unimplemented ${call.method} method");
} }
@ -384,6 +404,67 @@ class ChromeSafariBrowser extends ChannelController {
}); });
} }
///Sends a request to create a two way postMessage channel between the client
///and the browser.
///If you want to specifying the target origin to communicate with, set the [targetOrigin].
///
///[sourceOrigin] - A origin that the client is requesting to be
///identified as during the postMessage communication.
///It has to either start with http or https.
///
///[targetOrigin] - The target Origin to establish the postMessage communication with.
///This can be the app's package name, it has to either start with http or https.
///
///Returns whether the implementation accepted the request.
///Note that returning true here doesn't mean an origin has already been
///assigned as the validation is asynchronous.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsSession.requestPostMessageChannel](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsSession#requestPostMessageChannel(android.net.Uri,android.net.Uri,android.os.Bundle)))
Future<bool> requestPostMessageChannel(
{required WebUri sourceOrigin, WebUri? targetOrigin}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("sourceOrigin", () => sourceOrigin.toString());
args.putIfAbsent("targetOrigin", () => targetOrigin.toString());
return await channel?.invokeMethod<bool>(
"requestPostMessageChannel", args) ??
false;
}
///Sends a postMessage request using the origin communicated via [requestPostMessageChannel].
///Fails when called before [onMessageChannelReady] event.
///
///[message] The message that is being sent.
///
///Returns an integer constant about the postMessage request result.
///Will return CustomTabsService.RESULT_SUCCESS if successful.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsSession.postMessage](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsSession#postMessage(java.lang.String,android.os.Bundle)))
Future<CustomTabsPostMessageResultType> postMessage(String message) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("message", () => message);
return CustomTabsPostMessageResultType.fromNativeValue(
await channel?.invokeMethod<int>("postMessage", args)) ??
CustomTabsPostMessageResultType.FAILURE_MESSAGING_ERROR;
}
///Returns whether the Engagement Signals API is available.
///The availability of the Engagement Signals API may change at runtime.
///If an EngagementSignalsCallback has been set, an [onSessionEnded]
///signal will be sent if the API becomes unavailable later.
///
///Returns whether the Engagement Signals API is available.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsSession.isEngagementSignalsApiAvailable](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsSession#isEngagementSignalsApiAvailable(android.os.Bundle)))
Future<bool> isEngagementSignalsApiAvailable() async {
Map<String, dynamic> args = <String, dynamic>{};
return await channel?.invokeMethod<bool>(
"isEngagementSignalsApiAvailable", args) ??
false;
}
///On Android, returns `true` if Chrome Custom Tabs is available. ///On Android, returns `true` if Chrome Custom Tabs is available.
///On iOS, returns `true` if SFSafariViewController is available. ///On iOS, returns `true` if SFSafariViewController is available.
///Otherwise returns `false`. ///Otherwise returns `false`.
@ -393,7 +474,8 @@ class ChromeSafariBrowser extends ChannelController {
///- iOS ///- iOS
static Future<bool> isAvailable() async { static Future<bool> isAvailable() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _sharedChannel.invokeMethod("isAvailable", args); return await _sharedChannel.invokeMethod<bool>("isAvailable", args) ??
false;
} }
///The maximum number of allowed secondary toolbar items. ///The maximum number of allowed secondary toolbar items.
@ -515,6 +597,57 @@ class ChromeSafariBrowser extends ChannelController {
///- iOS ([Official API - SFSafariViewControllerDelegate.safariViewControllerWillOpenInBrowser](https://developer.apple.com/documentation/safariservices/sfsafariviewcontrollerdelegate/3650426-safariviewcontrollerwillopeninbr)) ///- iOS ([Official API - SFSafariViewControllerDelegate.safariViewControllerWillOpenInBrowser](https://developer.apple.com/documentation/safariservices/sfsafariviewcontrollerdelegate/3650426-safariviewcontrollerwillopeninbr))
void onWillOpenInBrowser() {} void onWillOpenInBrowser() {}
///Called when the [ChromeSafariBrowser] has requested a postMessage channel through
///[requestPostMessageChannel] and the channel is ready for sending and receiving messages on both ends.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsCallback.onMessageChannelReady](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsCallback#onMessageChannelReady(android.os.Bundle)))
void onMessageChannelReady() {}
///Called when a tab controlled by this [ChromeSafariBrowser] has sent a postMessage.
///If [postMessage] is called from a single thread, then the messages will be posted in the same order.
///When received on the client side, it is the client's responsibility to preserve the ordering further.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsCallback.onPostMessage](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsCallback#onPostMessage(java.lang.String,android.os.Bundle)))
void onPostMessage(String message) {}
///Called when a user scrolls the tab.
///
///[isDirectionUp] - `false` when the user scrolls farther down the page,
///and `true` when the user scrolls back up toward the top of the page.
///
///**NOTE**: available only if [isEngagementSignalsApiAvailable] returns `true`.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - EngagementSignalsCallback.onVerticalScrollEvent](https://developer.android.com/reference/androidx/browser/customtabs/EngagementSignalsCallback#onVerticalScrollEvent(boolean,android.os.Bundle)))
void onVerticalScrollEvent(bool isDirectionUp) {}
///Called when a user has reached a greater scroll percentage on the page. The greatest scroll
///percentage is reset if the user navigates to a different page. If the current page's total
///height changes, this method will be called again only if the scroll progress reaches a
///higher percentage based on the new and current height of the page.
///
///[scrollPercentage] - An integer indicating the percent of scrollable progress
///the user hasmade down the current page.
///
///**NOTE**: available only if [isEngagementSignalsApiAvailable] returns `true`.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - EngagementSignalsCallback.onGreatestScrollPercentageIncreased](https://developer.android.com/reference/androidx/browser/customtabs/EngagementSignalsCallback#onGreatestScrollPercentageIncreased(int,android.os.Bundle)))
void onGreatestScrollPercentageIncreased(int scrollPercentage) {}
///Called when a `CustomTabsSession` is ending or when no further Engagement Signals
///callbacks are expected to report whether any user action has occurred during the session.
///
///[didUserInteract] - Whether the user has interacted with the page in any way, e.g. scrolling.
///
///**NOTE**: available only if [isEngagementSignalsApiAvailable] returns `true`.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - EngagementSignalsCallback.onSessionEnded](https://developer.android.com/reference/androidx/browser/customtabs/EngagementSignalsCallback#onSessionEnded(boolean,android.os.Bundle)))
void onSessionEnded(bool didUserInteract) {}
///Event fired when the [ChromeSafariBrowser] is closed. ///Event fired when the [ChromeSafariBrowser] is closed.
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:

View File

@ -0,0 +1,36 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
part 'custom_tabs_post_message_result_type.g.dart';
///Custom Tabs postMessage result type.
@ExchangeableEnum()
class CustomTabsPostMessageResultType_ {
// ignore: unused_field
final int _value;
// ignore: unused_field
final int? _nativeValue = null;
const CustomTabsPostMessageResultType_._internal(this._value);
///Indicates that the postMessage request was accepted.
@EnumSupportedPlatforms(platforms: [EnumAndroidPlatform(value: 0)])
static const SUCCESS = const CustomTabsPostMessageResultType_._internal(0);
///Indicates that the postMessage request was not allowed due to a bad argument
///or requesting at a disallowed time like when in background.
@EnumSupportedPlatforms(platforms: [EnumAndroidPlatform(value: -1)])
static const FAILURE_DISALLOWED =
const CustomTabsPostMessageResultType_._internal(-1);
///Indicates that the postMessage request has failed due to a `RemoteException`.
@EnumSupportedPlatforms(platforms: [EnumAndroidPlatform(value: -2)])
static const FAILURE_REMOTE_ERROR =
const CustomTabsPostMessageResultType_._internal(-2);
///Indicates that the postMessage request has failed due to an internal error on the browser message channel.
@EnumSupportedPlatforms(platforms: [EnumAndroidPlatform(value: -3)])
static const FAILURE_MESSAGING_ERROR =
const CustomTabsPostMessageResultType_._internal(-3);
}

View File

@ -0,0 +1,141 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'custom_tabs_post_message_result_type.dart';
// **************************************************************************
// ExchangeableEnumGenerator
// **************************************************************************
///Custom Tabs postMessage result type.
class CustomTabsPostMessageResultType {
final int _value;
final int? _nativeValue;
const CustomTabsPostMessageResultType._internal(
this._value, this._nativeValue);
// ignore: unused_element
factory CustomTabsPostMessageResultType._internalMultiPlatform(
int value, Function nativeValue) =>
CustomTabsPostMessageResultType._internal(value, nativeValue());
///Indicates that the postMessage request was not allowed due to a bad argument
///or requesting at a disallowed time like when in background.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
static final FAILURE_DISALLOWED =
CustomTabsPostMessageResultType._internalMultiPlatform(-1, () {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return -1;
default:
break;
}
return null;
});
///Indicates that the postMessage request has failed due to an internal error on the browser message channel.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
static final FAILURE_MESSAGING_ERROR =
CustomTabsPostMessageResultType._internalMultiPlatform(-3, () {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return -3;
default:
break;
}
return null;
});
///Indicates that the postMessage request has failed due to a `RemoteException`.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
static final FAILURE_REMOTE_ERROR =
CustomTabsPostMessageResultType._internalMultiPlatform(-2, () {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return -2;
default:
break;
}
return null;
});
///Indicates that the postMessage request was accepted.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
static final SUCCESS =
CustomTabsPostMessageResultType._internalMultiPlatform(0, () {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return 0;
default:
break;
}
return null;
});
///Set of all values of [CustomTabsPostMessageResultType].
static final Set<CustomTabsPostMessageResultType> values = [
CustomTabsPostMessageResultType.FAILURE_DISALLOWED,
CustomTabsPostMessageResultType.FAILURE_MESSAGING_ERROR,
CustomTabsPostMessageResultType.FAILURE_REMOTE_ERROR,
CustomTabsPostMessageResultType.SUCCESS,
].toSet();
///Gets a possible [CustomTabsPostMessageResultType] instance from [int] value.
static CustomTabsPostMessageResultType? fromValue(int? value) {
if (value != null) {
try {
return CustomTabsPostMessageResultType.values
.firstWhere((element) => element.toValue() == value);
} catch (e) {
return null;
}
}
return null;
}
///Gets a possible [CustomTabsPostMessageResultType] instance from a native value.
static CustomTabsPostMessageResultType? fromNativeValue(int? value) {
if (value != null) {
try {
return CustomTabsPostMessageResultType.values
.firstWhere((element) => element.toNativeValue() == value);
} catch (e) {
return null;
}
}
return null;
}
///Gets [int] value.
int toValue() => _value;
///Gets [int?] native value.
int? toNativeValue() => _nativeValue;
@override
int get hashCode => _value.hashCode;
@override
bool operator ==(value) => value == _value;
@override
String toString() {
switch (_value) {
case -1:
return 'FAILURE_DISALLOWED';
case -3:
return 'FAILURE_MESSAGING_ERROR';
case -2:
return 'FAILURE_REMOTE_ERROR';
case 0:
return 'SUCCESS';
}
return _value.toString();
}
}

View File

@ -223,3 +223,5 @@ export 'activity_button.dart' show ActivityButton;
export 'ui_event_attribution.dart' show UIEventAttribution; export 'ui_event_attribution.dart' show UIEventAttribution;
export 'tracing_mode.dart' show TracingMode; export 'tracing_mode.dart' show TracingMode;
export 'tracing_category.dart' show TracingCategory; export 'tracing_category.dart' show TracingCategory;
export 'custom_tabs_post_message_result_type.dart'
show CustomTabsPostMessageResultType;

View File

@ -1,6 +1,6 @@
name: flutter_inappwebview name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
version: 6.0.0-beta.26 version: 6.0.0-beta.27
homepage: https://inappwebview.dev/ homepage: https://inappwebview.dev/
repository: https://github.com/pichillilorenzo/flutter_inappwebview repository: https://github.com/pichillilorenzo/flutter_inappwebview
issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues