Added startAnimations, exitAnimations, navigationBarColor, navigationBarDividerColor, secondaryToolbarColor ChromeSafariBrowser settings for Android, Added getVariationsHeader WebView static method, All ChromeSafariBrowserSettings properties are optionals

This commit is contained in:
Lorenzo Pichilli 2022-10-25 11:18:53 +02:00
parent db7beffc03
commit 8e9c10246a
29 changed files with 990 additions and 169 deletions

View File

@ -3,13 +3,16 @@
- Added `headers`, `otherLikelyURLs` arguments on `ChromeSafariBrowser.open` method for Android
- Added `onNavigationEvent`, `onServiceConnected`, `onRelationshipValidationResult` events on `ChromeSafariBrowser` for Android
- Added `mayLaunchUrl`, `launchUrl`, `updateActionButton`, `validateRelationship` methods on `ChromeSafariBrowser` for Android
- Added `startAnimations`, `exitAnimations`, `navigationBarColor`, `navigationBarDividerColor`, `secondaryToolbarColor` ChromeSafariBrowser settings for Android
- Added `didLoadSuccessfully` optional argument on `ChromeSafariBrowser.onCompletedInitialLoad` event for iOS
- Added `onInitialLoadDidRedirect`, `onWillOpenInBrowser` events on `ChromeSafariBrowser` for iOS
- Added `clearWebsiteData`, `prewarmConnections`, `invalidatePrewarmingToken` static methods on `ChromeSafariBrowser` for iOS
- Added `getVariationsHeader` WebView static method
### BREAKING CHANGES
- `ChromeSafariBrowser.onCompletedInitialLoad` event has an optional argument
- All `ChromeSafariBrowserSettings` properties are optionals
## 6.0.0-beta.8
@ -78,7 +81,7 @@
- Added `PullToRefreshController.isEnabled` method
- Updated `getMetaThemeColor` on iOS 15.0+
- Deprecated `onLoadError` for `onReceivedError`. `onReceivedError` will be called also for subframes
- Deprecated `onLoadHttpError` for `onReceivedError`. `onReceivedHttpError` will be called also for subframes
- Deprecated `onLoadHttpError` for `onReceivedHttpError`. `onReceivedHttpError` will be called also for subframes
### BREAKING CHANGES

View File

@ -114,6 +114,14 @@ public class InAppWebViewStatic extends ChannelDelegateImpl {
}
result.success(true);
break;
case "getVariationsHeader":
if (WebViewFeature.isFeatureSupported(WebViewFeature.GET_VARIATIONS_HEADER)) {
result.success(WebViewCompat.getVariationsHeader());
}
else {
result.success(null);
}
break;
default:
result.notImplemented();
}

View File

@ -21,6 +21,7 @@ import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsSession;
import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.types.AndroidResource;
import com.pichillilorenzo.flutter_inappwebview.types.CustomTabsActionButton;
import com.pichillilorenzo.flutter_inappwebview.types.CustomTabsMenuItem;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
@ -156,35 +157,27 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
public void launchUrl(@NonNull String url,
@Nullable Map<String, String> headers,
@Nullable List<String> otherLikelyURLs) {
Uri uri = mayLaunchUrl(url, headers, otherLikelyURLs);
mayLaunchUrl(url, otherLikelyURLs);
builder = new CustomTabsIntent.Builder(customTabsSession);
prepareCustomTabs();
CustomTabsIntent customTabsIntent = builder.build();
prepareCustomTabsIntent(customTabsIntent);
CustomTabActivityHelper.openCustomTab(this, customTabsIntent, uri, CHROME_CUSTOM_TAB_REQUEST_CODE);
CustomTabActivityHelper.openCustomTab(this, customTabsIntent, Uri.parse(url), headers, CHROME_CUSTOM_TAB_REQUEST_CODE);
}
public Uri mayLaunchUrl(@NonNull String url,
@Nullable Map<String, String> headers,
@Nullable List<String> otherLikelyURLs) {
Uri uri = Uri.parse(url);
Bundle bundleHeaders = new Bundle();
if (headers != null) {
for (Map.Entry<String, String> header : headers.entrySet()) {
bundleHeaders.putString(header.getKey(), header.getValue());
}
}
public boolean mayLaunchUrl(@Nullable String url, @Nullable List<String> otherLikelyURLs) {
Uri uri = url != null ? Uri.parse(url) : null;
List<Bundle> bundleOtherLikelyURLs = new ArrayList<>();
if (otherLikelyURLs != null) {
Bundle bundleOtherLikelyURL = new Bundle();
for (String otherLikelyURL : otherLikelyURLs) {
Bundle bundleOtherLikelyURL = new Bundle();
bundleOtherLikelyURL.putString(CustomTabsService.KEY_URL, otherLikelyURL);
}
}
customTabActivityHelper.mayLaunchUrl(uri, bundleHeaders, bundleOtherLikelyURLs);
return uri;
return customTabActivityHelper.mayLaunchUrl(uri, null, bundleOtherLikelyURLs);
}
public void customTabsConnected() {
@ -206,16 +199,34 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
builder.setShareState(customSettings.shareState);
}
CustomTabColorSchemeParams.Builder defaultColorSchemeBuilder = new CustomTabColorSchemeParams.Builder();
if (customSettings.toolbarBackgroundColor != null && !customSettings.toolbarBackgroundColor.isEmpty()) {
CustomTabColorSchemeParams.Builder defaultColorSchemeBuilder = new CustomTabColorSchemeParams.Builder();
builder.setDefaultColorSchemeParams(defaultColorSchemeBuilder
.setToolbarColor(Color.parseColor(customSettings.toolbarBackgroundColor))
.build());
defaultColorSchemeBuilder.setToolbarColor(Color.parseColor(customSettings.toolbarBackgroundColor));
}
if (customSettings.navigationBarColor != null && !customSettings.navigationBarColor.isEmpty()) {
defaultColorSchemeBuilder.setNavigationBarColor(Color.parseColor(customSettings.navigationBarColor));
}
if (customSettings.navigationBarDividerColor != null && !customSettings.navigationBarDividerColor.isEmpty()) {
defaultColorSchemeBuilder.setNavigationBarDividerColor(Color.parseColor(customSettings.navigationBarDividerColor));
}
if (customSettings.secondaryToolbarColor != null && !customSettings.secondaryToolbarColor.isEmpty()) {
defaultColorSchemeBuilder.setSecondaryToolbarColor(Color.parseColor(customSettings.secondaryToolbarColor));
}
builder.setDefaultColorSchemeParams(defaultColorSchemeBuilder.build());
builder.setShowTitle(customSettings.showTitle);
builder.setUrlBarHidingEnabled(customSettings.enableUrlBarHiding);
builder.setInstantAppsEnabled(customSettings.instantAppsEnabled);
if (customSettings.startAnimations.size() == 2) {
builder.setStartAnimations(this,
customSettings.startAnimations.get(0).getIdentifier(this),
customSettings.startAnimations.get(1).getIdentifier(this));
}
if (customSettings.exitAnimations.size() == 2) {
builder.setExitAnimations(this,
customSettings.exitAnimations.get(0).getIdentifier(this),
customSettings.exitAnimations.get(1).getIdentifier(this));
}
for (CustomTabsMenuItem menuItem : menuItems) {
builder.addMenuItem(menuItem.getLabel(),

View File

@ -48,14 +48,8 @@ public class ChromeCustomTabsChannelDelegate extends ChannelDelegateImpl {
case "mayLaunchUrl":
if (chromeCustomTabsActivity != null) {
String url = (String) call.argument("url");
if (url != null) {
Map<String, String> headers = (Map<String, String>) call.argument("headers");
List<String> otherLikelyURLs = (List<String>) call.argument("otherLikelyURLs");
chromeCustomTabsActivity.mayLaunchUrl(url, headers, otherLikelyURLs);
result.success(true);
} else {
result.success(false);
}
List<String> otherLikelyURLs = (List<String>) call.argument("otherLikelyURLs");
result.success(chromeCustomTabsActivity.mayLaunchUrl(url, otherLikelyURLs));
} else {
result.success(false);
}
@ -74,8 +68,7 @@ public class ChromeCustomTabsChannelDelegate extends ChannelDelegateImpl {
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
Integer relation = (Integer) call.argument("relation");
String origin = (String) call.argument("origin");
chromeCustomTabsActivity.customTabsSession.validateRelationship(relation, Uri.parse(origin), null);
result.success(true);
result.success(chromeCustomTabsActivity.customTabsSession.validateRelationship(relation, Uri.parse(origin), null));
} else {
result.success(false);
}

View File

@ -9,6 +9,7 @@ import androidx.browser.trusted.ScreenOrientation;
import androidx.browser.trusted.TrustedWebActivityDisplayMode;
import com.pichillilorenzo.flutter_inappwebview.ISettings;
import com.pichillilorenzo.flutter_inappwebview.types.AndroidResource;
import java.util.ArrayList;
import java.util.HashMap;
@ -25,6 +26,12 @@ public class ChromeCustomTabsSettings implements ISettings<ChromeCustomTabsActiv
public Boolean showTitle = true;
@Nullable
public String toolbarBackgroundColor;
@Nullable
public String navigationBarColor;
@Nullable
public String navigationBarDividerColor;
@Nullable
public String secondaryToolbarColor;
public Boolean enableUrlBarHiding = false;
public Boolean instantAppsEnabled = false;
public String packageName;
@ -35,6 +42,8 @@ public class ChromeCustomTabsSettings implements ISettings<ChromeCustomTabsActiv
public List<String> additionalTrustedOrigins = new ArrayList<>();
public TrustedWebActivityDisplayMode displayMode = null;
public Integer screenOrientation = ScreenOrientation.DEFAULT;
public List<AndroidResource> startAnimations = new ArrayList<>();
public List<AndroidResource> exitAnimations = new ArrayList<>();
@NonNull
@Override
@ -59,6 +68,15 @@ public class ChromeCustomTabsSettings implements ISettings<ChromeCustomTabsActiv
case "toolbarBackgroundColor":
toolbarBackgroundColor = (String) value;
break;
case "navigationBarColor":
navigationBarColor = (String) value;
break;
case "navigationBarDividerColor":
navigationBarDividerColor = (String) value;
break;
case "secondaryToolbarColor":
secondaryToolbarColor = (String) value;
break;
case "enableUrlBarHiding":
enableUrlBarHiding = (Boolean) value;
break;
@ -102,6 +120,24 @@ public class ChromeCustomTabsSettings implements ISettings<ChromeCustomTabsActiv
case "screenOrientation":
screenOrientation = (Integer) value;
break;
case "startAnimations":
List<Map<String, Object>> startAnimationsList = (List<Map<String, Object>>) value;
for (Map<String, Object> startAnimation : startAnimationsList) {
AndroidResource androidResource = AndroidResource.fromMap(startAnimation);
if (androidResource != null) {
startAnimations.add(AndroidResource.fromMap(startAnimation));
}
}
break;
case "exitAnimations":
List<Map<String, Object>> exitAnimationsList = (List<Map<String, Object>>) value;
for (Map<String, Object> exitAnimation : exitAnimationsList) {
AndroidResource androidResource = AndroidResource.fromMap(exitAnimation);
if (androidResource != null) {
exitAnimations.add(AndroidResource.fromMap(exitAnimation));
}
}
break;
}
}
@ -115,6 +151,9 @@ public class ChromeCustomTabsSettings implements ISettings<ChromeCustomTabsActiv
options.put("addDefaultShareMenuItem", addDefaultShareMenuItem);
options.put("showTitle", showTitle);
options.put("toolbarBackgroundColor", toolbarBackgroundColor);
options.put("navigationBarColor", navigationBarColor);
options.put("navigationBarDividerColor", navigationBarDividerColor);
options.put("secondaryToolbarColor", secondaryToolbarColor);
options.put("enableUrlBarHiding", enableUrlBarHiding);
options.put("instantAppsEnabled", instantAppsEnabled);
options.put("packageName", packageName);

View File

@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsCallback;
@ -14,6 +15,7 @@ import androidx.browser.customtabs.CustomTabsSession;
import androidx.browser.trusted.TrustedWebActivityIntent;
import java.util.List;
import java.util.Map;
/**
* This is a helper class to manage the connection to the Custom Tabs Service.
@ -35,18 +37,35 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
public static void openCustomTab(Activity activity,
CustomTabsIntent customTabsIntent,
Uri uri,
@Nullable Map<String, String> headers,
int requestCode) {
customTabsIntent.intent.setData(uri);
if (headers != null) {
Bundle bundleHeaders = new Bundle();
for (Map.Entry<String, String> header : headers.entrySet()) {
bundleHeaders.putString(header.getKey(), header.getValue());
}
customTabsIntent.intent.putExtra(Browser.EXTRA_HEADERS, bundleHeaders);
}
activity.startActivityForResult(customTabsIntent.intent, requestCode);
}
public static void openCustomTab(Activity activity,
TrustedWebActivityIntent trustedWebActivityIntent,
Uri uri,
@Nullable Map<String, String> headers,
int requestCode) {
trustedWebActivityIntent.getIntent().setData(uri);
if (headers != null) {
Bundle bundleHeaders = new Bundle();
for (Map.Entry<String, String> header : headers.entrySet()) {
bundleHeaders.putString(header.getKey(), header.getValue());
}
trustedWebActivityIntent.getIntent().putExtra(Browser.EXTRA_HEADERS, bundleHeaders);
}
activity.startActivityForResult(trustedWebActivityIntent.getIntent(), requestCode);
}
public static boolean isAvailable(Activity activity) {
return CustomTabsHelper.getPackageNameToUse(activity) != null;

View File

@ -31,15 +31,16 @@ public class TrustedWebActivity extends ChromeCustomTabsActivity {
if (customTabsSession == null) {
return;
}
Uri uri = Uri.parse(url);
Uri uri = mayLaunchUrl(url, headers, otherLikelyURLs);
mayLaunchUrl(url, otherLikelyURLs);
builder = new TrustedWebActivityIntentBuilder(uri);
prepareCustomTabs();
TrustedWebActivityIntent trustedWebActivityIntent = builder.build(customTabsSession);
prepareCustomTabsIntent(trustedWebActivityIntent);
CustomTabActivityHelper.openCustomTab(this, trustedWebActivityIntent, uri, CHROME_CUSTOM_TAB_REQUEST_CODE);
CustomTabActivityHelper.openCustomTab(this, trustedWebActivityIntent, uri, headers, CHROME_CUSTOM_TAB_REQUEST_CODE);
}
@Override
@ -48,7 +49,6 @@ public class TrustedWebActivity extends ChromeCustomTabsActivity {
if (initialUrl != null) {
launchUrl(initialUrl, initialHeaders, initialOtherLikelyURLs);
}
}
private void prepareCustomTabs() {

View File

@ -0,0 +1,103 @@
package com.pichillilorenzo.flutter_inappwebview.types;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class AndroidResource {
@NonNull
String name;
@Nullable
String defType;
@Nullable
String defPackage;
public AndroidResource(@NonNull String name, @Nullable String defType, @Nullable String defPackage) {
this.name = name;
this.defType = defType;
this.defPackage = defPackage;
}
@Nullable
public static AndroidResource fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
String name = (String) map.get("name");
String defType = (String) map.get("defType");
String defPackage = (String) map.get("defPackage");
return new AndroidResource(name, defType, defPackage);
}
public Map<String, Object> toMap() {
Map<String, Object> urlRequestMap = new HashMap<>();
urlRequestMap.put("name", name);
urlRequestMap.put("defType", defType);
urlRequestMap.put("defPackage", defPackage);
return urlRequestMap;
}
@NonNull
public String getName() {
return name;
}
public void setName(@NonNull String name) {
this.name = name;
}
@Nullable
public String getDefType() {
return defType;
}
public void setDefType(@Nullable String defType) {
this.defType = defType;
}
@Nullable
public String getDefPackage() {
return defPackage;
}
public void setDefPackage(@Nullable String defPackage) {
this.defPackage = defPackage;
}
public int getIdentifier(@NonNull Context ctx) {
return ctx.getResources().getIdentifier(name, defType, defPackage);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AndroidResource that = (AndroidResource) o;
if (!name.equals(that.name)) return false;
if (defType != null ? !defType.equals(that.defType) : that.defType != null) return false;
return defPackage != null ? defPackage.equals(that.defPackage) : that.defPackage == null;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + (defType != null ? defType.hashCode() : 0);
result = 31 * result + (defPackage != null ? defPackage.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "AndroidResource{" +
"name='" + name + '\'' +
", type='" + defType + '\'' +
", defPackage='" + defPackage + '\'' +
'}';
}
}

View File

@ -13,10 +13,12 @@ void customActionButton() {
TargetPlatform.android,
].contains(defaultTargetPlatform);
test('add custom action button', () async {
test('add custom action button and update icon', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
var actionButtonIcon =
await rootBundle.load('test_assets/images/flutter-logo.png');
var actionButtonIcon2 =
await rootBundle.load('test_assets/images/flutter-logo.jpg');
chromeSafariBrowser.setActionButton(ChromeSafariBrowserActionButton(
id: 1,
description: 'Action Button description',
@ -25,15 +27,18 @@ void customActionButton() {
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(url: TEST_URL_1);
await chromeSafariBrowser.browserCreated.future;
await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true);
expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>()));
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.updateActionButton(
icon: actionButtonIcon2.buffer.asUint8List(),
description: 'New Action Button description');
await chromeSafariBrowser.close();
await chromeSafariBrowser.browserClosed.future;
await chromeSafariBrowser.closed.future;
expect(chromeSafariBrowser.isOpened(), false);
}, skip: shouldSkip);
}

View File

@ -18,7 +18,7 @@ void customMenuItem() {
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(url: TEST_URL_1);
await chromeSafariBrowser.browserCreated.future;
await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true);
expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
@ -26,7 +26,7 @@ void customMenuItem() {
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await chromeSafariBrowser.browserClosed.future;
await chromeSafariBrowser.closed.future;
expect(chromeSafariBrowser.isOpened(), false);
}, skip: shouldSkip);
}

View File

@ -20,7 +20,7 @@ void customTabs() {
await chromeSafariBrowser.open(
url: TEST_URL_1,
settings: ChromeSafariBrowserSettings(isSingleInstance: true));
await chromeSafariBrowser.browserCreated.future;
await expectLater(chromeSafariBrowser.opened.future, completes);
expect(chromeSafariBrowser.isOpened(), true);
expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
@ -28,7 +28,43 @@ void customTabs() {
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await chromeSafariBrowser.browserClosed.future;
await expectLater(chromeSafariBrowser.closed.future, completes);
expect(chromeSafariBrowser.isOpened(), false);
});
test('mayLaunchUrl and launchUrl', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open();
await expectLater(chromeSafariBrowser.serviceConnected.future, completes);
expect(chromeSafariBrowser.isOpened(), true);
expect(
await chromeSafariBrowser.mayLaunchUrl(
url: TEST_URL_1, otherLikelyURLs: [TEST_CROSS_PLATFORM_URL_1]),
true);
await chromeSafariBrowser.launchUrl(
url: TEST_URL_1,
headers: {'accept-language': 'it-IT'},
otherLikelyURLs: [TEST_CROSS_PLATFORM_URL_1]);
await expectLater(chromeSafariBrowser.opened.future, completes);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await expectLater(chromeSafariBrowser.closed.future, completes);
expect(chromeSafariBrowser.isOpened(), false);
});
test('onNavigationEvent', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(url: TEST_URL_1);
await expectLater(chromeSafariBrowser.opened.future, completes);
expect(chromeSafariBrowser.isOpened(), true);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
expect(await chromeSafariBrowser.navigationEvent.future, isNotNull);
await chromeSafariBrowser.close();
await expectLater(chromeSafariBrowser.closed.future, completes);
expect(chromeSafariBrowser.isOpened(), false);
});
}, skip: shouldSkip);

View File

@ -6,6 +6,7 @@ import 'custom_menu_item.dart';
import 'custom_tabs.dart';
import 'open_and_close.dart';
import 'trusted_web_activity.dart';
import 'sf_safari_view_controller.dart';
void main() {
final shouldSkip =
@ -17,5 +18,6 @@ void main() {
customActionButton();
customTabs();
trustedWebActivity();
sfSafariViewController();
}, skip: shouldSkip);
}

View File

@ -15,8 +15,35 @@ void openAndClose() {
var chromeSafariBrowser = MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(url: TEST_URL_1);
await chromeSafariBrowser.browserCreated.future;
await chromeSafariBrowser.open(
url: TEST_URL_1,
settings: ChromeSafariBrowserSettings(
shareState: CustomTabsShareState.SHARE_STATE_OFF,
startAnimations: [
AndroidResource(
name: "slide_in_left",
defType: "anim",
defPackage: "android"),
AndroidResource(
name: "slide_out_right",
defType: "anim",
defPackage: "android")
],
exitAnimations: [
AndroidResource(
name: "abc_slide_in_top",
defType: "anim",
defPackage:
"com.pichillilorenzo.flutter_inappwebviewexample"),
AndroidResource(
name: "abc_slide_out_top",
defType: "anim",
defPackage: "com.pichillilorenzo.flutter_inappwebviewexample")
],
keepAliveEnabled: true,
dismissButtonStyle: DismissButtonStyle.CLOSE,
presentationStyle: ModalPresentationStyle.OVER_FULL_SCREEN));
await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true);
expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
@ -24,7 +51,7 @@ void openAndClose() {
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await chromeSafariBrowser.browserClosed.future;
await chromeSafariBrowser.closed.future;
expect(chromeSafariBrowser.isOpened(), false);
}, skip: shouldSkip);
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_test/flutter_test.dart';
import '../constants.dart';
import '../util.dart';
void sfSafariViewController() {
final shouldSkip = kIsWeb
? true
: ![
TargetPlatform.android,
].contains(defaultTargetPlatform);
group('SF Safari View Controller', () {
test('onCompletedInitialLoad did load successfully', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(url: TEST_URL_1);
await expectLater(chromeSafariBrowser.opened.future, completes);
expect(chromeSafariBrowser.isOpened(), true);
expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>()));
expect(await chromeSafariBrowser.firstPageLoaded.future, true);
await chromeSafariBrowser.close();
await expectLater(chromeSafariBrowser.closed.future, completes);
expect(chromeSafariBrowser.isOpened(), false);
});
test('clearWebsiteData', () async {
await expectLater(ChromeSafariBrowser.clearWebsiteData(), completes);
});
test('create and invalidate Prewarming Token', () async {
final prewarmingToken = await ChromeSafariBrowser.prewarmConnections([TEST_URL_1]);
expect(prewarmingToken, isNotNull);
await expectLater(ChromeSafariBrowser.invalidatePrewarmingToken(prewarmingToken!), completes);
});
}, skip: shouldSkip);
}

View File

@ -20,7 +20,7 @@ void trustedWebActivity() {
await chromeSafariBrowser.open(
url: TEST_URL_1,
settings: ChromeSafariBrowserSettings(isTrustedWebActivity: true));
await chromeSafariBrowser.browserCreated.future;
await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true);
expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
@ -28,7 +28,7 @@ void trustedWebActivity() {
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await chromeSafariBrowser.browserClosed.future;
await chromeSafariBrowser.closed.future;
expect(chromeSafariBrowser.isOpened(), false);
});
@ -40,7 +40,7 @@ void trustedWebActivity() {
url: TEST_URL_1,
settings: ChromeSafariBrowserSettings(
isTrustedWebActivity: true, isSingleInstance: true));
await chromeSafariBrowser.browserCreated.future;
await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true);
expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
@ -48,7 +48,25 @@ void trustedWebActivity() {
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await chromeSafariBrowser.browserClosed.future;
await chromeSafariBrowser.closed.future;
expect(chromeSafariBrowser.isOpened(), false);
});
test('validate relationship', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(
settings: ChromeSafariBrowserSettings(isTrustedWebActivity: true));
await chromeSafariBrowser.serviceConnected.future;
expect(await chromeSafariBrowser.validateRelationship(relation: CustomTabsRelationType.USE_AS_ORIGIN, origin: TEST_CROSS_PLATFORM_URL_1), true);
expect(await chromeSafariBrowser.relationshipValidationResult.future, true);
await chromeSafariBrowser.launchUrl(url: TEST_CROSS_PLATFORM_URL_1);
await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await chromeSafariBrowser.closed.future;
expect(chromeSafariBrowser.isOpened(), false);
});
}, skip: shouldSkip);

View File

@ -54,22 +54,44 @@ class MyInAppBrowser extends InAppBrowser {
}
class MyChromeSafariBrowser extends ChromeSafariBrowser {
final Completer<void> browserCreated = Completer<void>();
final Completer<void> firstPageLoaded = Completer<void>();
final Completer<void> browserClosed = Completer<void>();
final Completer<void> serviceConnected = Completer<void>();
final Completer<void> opened = Completer<void>();
final Completer<bool> firstPageLoaded = Completer<bool>();
final Completer<void> closed = Completer<void>();
final Completer<CustomTabsNavigationEventType?> navigationEvent = Completer<CustomTabsNavigationEventType?>();
final Completer<bool> relationshipValidationResult = Completer<bool>();
@override
void onOpened() {
browserCreated.complete();
void onServiceConnected() {
serviceConnected.complete();
}
@override
void onCompletedInitialLoad() {
firstPageLoaded.complete();
void onOpened() {
opened.complete();
}
@override
void onCompletedInitialLoad(didLoadSuccessfully) {
firstPageLoaded.complete(didLoadSuccessfully);
}
@override
void onNavigationEvent(CustomTabsNavigationEventType? type) {
if (!navigationEvent.isCompleted) {
navigationEvent.complete(type);
}
}
@override
void onRelationshipValidationResult(
CustomTabsRelationType? relation, Uri? requestedOrigin, bool result) {
relationshipValidationResult.complete(result);
}
@override
void onClosed() {
browserClosed.complete();
closed.complete();
}
}

View File

@ -17,7 +17,7 @@ public class SafariBrowserSettings: ISettings<SafariViewController> {
var preferredBarTintColor: String?
var preferredControlTintColor: String?
var presentationStyle = 0 //fullscreen
var transitionStyle = 0 //crossDissolve
var transitionStyle = 0 //coverVertical
override init(){
super.init()

View File

@ -217,6 +217,11 @@ class WebViewFeature_ {
const WebViewFeature_._internal(
"ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY");
///This feature covers [InAppWebViewController.getVariationsHeader].
static const GET_VARIATIONS_HEADER =
const WebViewFeature_._internal(
"GET_VARIATIONS_HEADER");
///Return whether a feature is supported at run-time. On devices running Android version `Build.VERSION_CODES.LOLLIPOP` and higher,
///this will check whether a feature is supported, depending on the combination of the desired feature, the Android version of device,
///and the WebView APK on the device. If running on a device with a lower API level, this will always return `false`.

View File

@ -217,6 +217,10 @@ class WebViewFeature {
WebViewFeature._internal('ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY',
'ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY');
///This feature covers [InAppWebViewController.getVariationsHeader].
static const GET_VARIATIONS_HEADER = WebViewFeature._internal(
'GET_VARIATIONS_HEADER', 'GET_VARIATIONS_HEADER');
///Set of all values of [WebViewFeature].
static final Set<WebViewFeature> values = [
WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL,
@ -266,6 +270,7 @@ class WebViewFeature {
WebViewFeature.ALGORITHMIC_DARKENING,
WebViewFeature.REQUESTED_WITH_HEADER_CONTROL,
WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY,
WebViewFeature.GET_VARIATIONS_HEADER,
].toSet();
///Gets a possible [WebViewFeature] instance from [String] value.

View File

@ -143,7 +143,9 @@ class ChromeSafariBrowser {
///
///[url] - The [url] to load. On iOS, the [url] is required and must use the `http` or `https` scheme.
///
///[headers] - extra request headers. Supported only on Android.
///[headers] (Supported only on Android) - [whitelisted](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) cross-origin request headers.
///It is possible to attach non-whitelisted headers to cross-origin requests, when the server and client are related using a
///[digital asset link](https://developers.google.com/digital-asset-links/v1/getting-started).
///
///[otherLikelyURLs] - Other likely destinations, sorted in decreasing likelihood order. Supported only on Android.
///
@ -199,7 +201,9 @@ class ChromeSafariBrowser {
///
///[url] - initial url.
///
///[headers] - extra request headers.
///[headers] (Supported only on Android) - [whitelisted](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) cross-origin request headers.
///It is possible to attach non-whitelisted headers to cross-origin requests, when the server and client are related using a
///[digital asset link](https://developers.google.com/digital-asset-links/v1/getting-started).
///
///[otherLikelyURLs] - Other likely destinations, sorted in decreasing likelihood order.
///
@ -225,22 +229,18 @@ class ChromeSafariBrowser {
///
///[url] - Most likely URL, may be null if otherLikelyBundles is provided.
///
///[headers] - extra request headers.
///
///[otherLikelyURLs] - Other likely destinations, sorted in decreasing likelihood order.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsSession.mayLaunchUrl](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsSession#mayLaunchUrl(android.net.Uri,android.os.Bundle,java.util.List%3Candroid.os.Bundle%3E)))
Future<void> mayLaunchUrl(
Future<bool> mayLaunchUrl(
{Uri? url,
Map<String, String>? headers,
List<Uri>? otherLikelyURLs}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url?.toString());
args.putIfAbsent('headers', () => headers);
args.putIfAbsent('otherLikelyURLs',
() => otherLikelyURLs?.map((e) => e.toString()).toList());
await _channel.invokeMethod("mayLaunchUrl", args);
return await _channel.invokeMethod("mayLaunchUrl", args);
}
///Requests to validate a relationship between the application and an origin.
@ -251,14 +251,20 @@ class ChromeSafariBrowser {
///If this method returns `true`, the validation result will be provided through [onRelationshipValidationResult].
///Otherwise the request didn't succeed.
///
///[relation] Relation to check, must be one of the [CustomTabsRelationType] constants.
///
///[origin] Origin.
///
///[extras] Reserved for future use.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsSession.validateRelationship](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsSession#validateRelationship(int,android.net.Uri,android.os.Bundle)))
Future<void> validateRelationship(
Future<bool> validateRelationship(
{required CustomTabsRelationType relation, required Uri origin}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('relation', () => relation.toNativeValue());
args.putIfAbsent('origin', () => origin.toString());
await _channel.invokeMethod("validateRelationship", args);
return await _channel.invokeMethod("validateRelationship", args);
}
///Closes the [ChromeSafariBrowser] instance.

View File

@ -1,11 +1,35 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
import '../types/android_resource.dart';
import '../types/custom_tabs_share_state.dart';
import '../types/dismiss_button_style.dart';
import '../types/main.dart';
import '../types/modal_presentation_style.dart';
import '../types/modal_transition_style.dart';
import '../types/trusted_web_activity_display_mode.dart';
import '../types/trusted_web_activity_screen_orientation.dart';
import '../util.dart';
import 'android/chrome_custom_tabs_options.dart';
import 'apple/safari_options.dart';
import '../types/main.dart';
part 'chrome_safari_browser_settings.g.dart';
TrustedWebActivityDisplayMode? _deserializeDisplayMode(
Map<String, dynamic>? displayMode) {
if (displayMode == null) {
return null;
}
switch (displayMode["type"]) {
case "IMMERSIVE_MODE":
return TrustedWebActivityImmersiveDisplayMode.fromMap(displayMode);
case "DEFAULT_MODE":
default:
return TrustedWebActivityDefaultDisplayMode();
}
}
class ChromeSafariBrowserOptions {
Map<String, dynamic> toMap() {
@ -31,14 +55,15 @@ class ChromeSafariBrowserOptions {
}
///Class that represents the settings that can be used for an [ChromeSafariBrowser] window.
class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
@ExchangeableObject(copyMethod: true)
class ChromeSafariBrowserSettings_ implements ChromeSafariBrowserOptions {
///The share state that should be applied to the custom tab. The default value is [CustomTabsShareState.SHARE_STATE_DEFAULT].
///
///**NOTE**: Not available in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
CustomTabsShareState shareState;
CustomTabsShareState_? shareState;
///Set to `false` if the title shouldn't be shown in the custom tab. The default value is `true`.
///
@ -46,7 +71,7 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///
///**Supported Platforms/Implementations**:
///- Android
bool showTitle;
bool? showTitle;
///Set the custom background color of the toolbar.
///
@ -54,13 +79,31 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///- Android
Color? toolbarBackgroundColor;
///Sets the navigation bar color. Has no effect on Android API versions below L.
///
///**Supported Platforms/Implementations**:
///- Android
Color? navigationBarColor;
///Sets the navigation bar divider color. Has no effect on Android API versions below P.
///
///**Supported Platforms/Implementations**:
///- Android
Color? navigationBarDividerColor;
///Sets the color of the secondary toolbar.
///
///**Supported Platforms/Implementations**:
///- Android
Color? secondaryToolbarColor;
///Set to `true` to enable the url bar to hide as the user scrolls down on the page. The default value is `false`.
///
///**NOTE**: Not available in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
bool enableUrlBarHiding;
bool? enableUrlBarHiding;
///Set to `true` to enable Instant Apps. The default value is `false`.
///
@ -68,7 +111,7 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///
///**Supported Platforms/Implementations**:
///- Android
bool instantAppsEnabled;
bool? instantAppsEnabled;
///Set an explicit application package name that limits
///the components this Intent will resolve to. If left to the default
@ -84,25 +127,25 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///
///**Supported Platforms/Implementations**:
///- Android
bool keepAliveEnabled;
bool? keepAliveEnabled;
///Set to `true` to launch the Android activity in `singleInstance` mode. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- Android
bool isSingleInstance;
bool? isSingleInstance;
///Set to `true` to launch the Android intent with the flag `FLAG_ACTIVITY_NO_HISTORY`. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- Android
bool noHistory;
bool? noHistory;
///Set to `true` to launch the Custom Tab as a Trusted Web Activity. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- Android
bool isTrustedWebActivity;
bool? isTrustedWebActivity;
///Sets a list of additional trusted origins that the user may navigate or be redirected to from the starting uri.
///
@ -110,7 +153,7 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///
///**Supported Platforms/Implementations**:
///- Android
List<String> additionalTrustedOrigins;
List<String>? additionalTrustedOrigins;
///Sets a display mode of a Trusted Web Activity.
///
@ -118,7 +161,8 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///
///**Supported Platforms/Implementations**:
///- Android
TrustedWebActivityDisplayMode? displayMode;
@ExchangeableObjectProperty(deserializer: _deserializeDisplayMode)
TrustedWebActivityDisplayMode_? displayMode;
///Sets a screen orientation. This can be used e.g. to enable the locking of an orientation lock type.
///
@ -126,19 +170,35 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///
///**Supported Platforms/Implementations**:
///- Android
TrustedWebActivityScreenOrientation screenOrientation;
TrustedWebActivityScreenOrientation_? screenOrientation;
///Sets the start animations.
///It must contain 2 [AndroidResource], where the first one represents the "enter" animation for the browser
///and the second one represents the "exit" animation for the application.
///
///**Supported Platforms/Implementations**:
///- Android
List<AndroidResource_>? startAnimations;
///Sets the exit animations.
///It must contain 2 [AndroidResource], where the first one represents the "enter" animation for the application
///and the second one represents the "exit" animation for the browser.
///
///**Supported Platforms/Implementations**:
///- Android
List<AndroidResource_>? exitAnimations;
///Set to `true` if Reader mode should be entered automatically when it is available for the webpage. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- iOS
bool entersReaderIfAvailable;
bool? entersReaderIfAvailable;
///Set to `true` to enable bar collapsing. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- iOS
bool barCollapsingEnabled;
bool? barCollapsingEnabled;
///Set the custom style for the dismiss button. The default value is [DismissButtonStyle.DONE].
///
@ -146,7 +206,7 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///
///**Supported Platforms/Implementations**:
///- iOS
DismissButtonStyle dismissButtonStyle;
DismissButtonStyle_? dismissButtonStyle;
///Set the custom background color of the navigation bar and the toolbar.
///
@ -168,18 +228,22 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///
///**Supported Platforms/Implementations**:
///- iOS
ModalPresentationStyle presentationStyle;
ModalPresentationStyle_? presentationStyle;
///Set to the custom transition style when presenting the WebView. The default value is [ModalTransitionStyle.COVER_VERTICAL].
///
///**Supported Platforms/Implementations**:
///- iOS
ModalTransitionStyle transitionStyle;
ModalTransitionStyle_? transitionStyle;
ChromeSafariBrowserSettings(
{this.shareState = CustomTabsShareState.SHARE_STATE_DEFAULT,
@ExchangeableObjectConstructor()
ChromeSafariBrowserSettings_(
{this.shareState = CustomTabsShareState_.SHARE_STATE_DEFAULT,
this.showTitle = true,
this.toolbarBackgroundColor,
this.navigationBarColor,
this.navigationBarDividerColor,
this.secondaryToolbarColor,
this.enableUrlBarHiding = false,
this.instantAppsEnabled = false,
this.packageName,
@ -189,99 +253,42 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
this.isTrustedWebActivity = false,
this.additionalTrustedOrigins = const [],
this.displayMode,
this.screenOrientation = TrustedWebActivityScreenOrientation.DEFAULT,
this.screenOrientation = TrustedWebActivityScreenOrientation_.DEFAULT,
this.startAnimations,
this.exitAnimations,
this.entersReaderIfAvailable = false,
this.barCollapsingEnabled = false,
this.dismissButtonStyle = DismissButtonStyle.DONE,
this.dismissButtonStyle = DismissButtonStyle_.DONE,
this.preferredBarTintColor,
this.preferredControlTintColor,
this.presentationStyle = ModalPresentationStyle.FULL_SCREEN,
this.transitionStyle = ModalTransitionStyle.COVER_VERTICAL});
@override
Map<String, dynamic> toMap() {
return {
"shareState": shareState.toNativeValue(),
"showTitle": showTitle,
"toolbarBackgroundColor": toolbarBackgroundColor?.toHex(),
"enableUrlBarHiding": enableUrlBarHiding,
"instantAppsEnabled": instantAppsEnabled,
"packageName": packageName,
"keepAliveEnabled": keepAliveEnabled,
"isSingleInstance": isSingleInstance,
"noHistory": noHistory,
"isTrustedWebActivity": isTrustedWebActivity,
"additionalTrustedOrigins": additionalTrustedOrigins,
"displayMode": displayMode?.toMap(),
"screenOrientation": screenOrientation.toNativeValue(),
"entersReaderIfAvailable": entersReaderIfAvailable,
"barCollapsingEnabled": barCollapsingEnabled,
"dismissButtonStyle": dismissButtonStyle.toNativeValue(),
"preferredBarTintColor": preferredBarTintColor?.toHex(),
"preferredControlTintColor": preferredControlTintColor?.toHex(),
"presentationStyle": presentationStyle.toNativeValue(),
"transitionStyle": transitionStyle.toNativeValue()
};
}
static ChromeSafariBrowserSettings fromMap(Map<String, dynamic> map) {
ChromeSafariBrowserSettings settings = new ChromeSafariBrowserSettings();
if (defaultTargetPlatform == TargetPlatform.android) {
settings.shareState = map["shareState"];
settings.showTitle = map["showTitle"];
settings.toolbarBackgroundColor =
UtilColor.fromHex(map["toolbarBackgroundColor"]);
settings.enableUrlBarHiding = map["enableUrlBarHiding"];
settings.instantAppsEnabled = map["instantAppsEnabled"];
settings.packageName = map["packageName"];
settings.keepAliveEnabled = map["keepAliveEnabled"];
settings.isSingleInstance = map["isSingleInstance"];
settings.noHistory = map["noHistory"];
settings.isTrustedWebActivity = map["isTrustedWebActivity"];
settings.additionalTrustedOrigins = map["additionalTrustedOrigins"];
switch (map["displayMode"]["type"]) {
case "IMMERSIVE_MODE":
settings.displayMode = TrustedWebActivityImmersiveDisplayMode.fromMap(
map["displayMode"]);
break;
case "DEFAULT_MODE":
default:
settings.displayMode = TrustedWebActivityDefaultDisplayMode();
break;
}
settings.screenOrientation = map["screenOrientation"];
this.presentationStyle = ModalPresentationStyle_.FULL_SCREEN,
this.transitionStyle = ModalTransitionStyle_.COVER_VERTICAL}) {
if (startAnimations != null) {
assert(startAnimations!.length == 2,
"start animations must be have 2 android resources");
}
if (defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS) {
settings.entersReaderIfAvailable = map["entersReaderIfAvailable"];
settings.barCollapsingEnabled = map["barCollapsingEnabled"];
settings.dismissButtonStyle =
DismissButtonStyle.fromNativeValue(map["dismissButtonStyle"])!;
settings.preferredBarTintColor =
UtilColor.fromHex(map["preferredBarTintColor"]);
settings.preferredControlTintColor =
UtilColor.fromHex(map["preferredControlTintColor"]);
settings.presentationStyle =
ModalPresentationStyle.fromNativeValue(map["presentationStyle"])!;
settings.transitionStyle =
ModalTransitionStyle.fromNativeValue(map["transitionStyle"])!;
if (exitAnimations != null) {
assert(exitAnimations!.length == 2,
"exit animations must be have 2 android resources");
}
return settings;
}
@override
@ExchangeableObjectMethod(ignore: true)
ChromeSafariBrowserSettings_ copy() {
throw UnimplementedError();
}
@override
@ExchangeableObjectMethod(ignore: true)
Map<String, dynamic> toJson() {
return this.toMap();
throw UnimplementedError();
}
@override
String toString() {
return toMap().toString();
}
@override
ChromeSafariBrowserSettings copy() {
return ChromeSafariBrowserSettings.fromMap(this.toMap());
@ExchangeableObjectMethod(ignore: true)
Map<String, dynamic> toMap() {
throw UnimplementedError();
}
}

View File

@ -0,0 +1,330 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chrome_safari_browser_settings.dart';
// **************************************************************************
// ExchangeableObjectGenerator
// **************************************************************************
///Class that represents the settings that can be used for an [ChromeSafariBrowser] window.
class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///The share state that should be applied to the custom tab. The default value is [CustomTabsShareState.SHARE_STATE_DEFAULT].
///
///**NOTE**: Not available in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
CustomTabsShareState? shareState;
///Set to `false` if the title shouldn't be shown in the custom tab. The default value is `true`.
///
///**NOTE**: Not available in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
bool? showTitle;
///Set the custom background color of the toolbar.
///
///**Supported Platforms/Implementations**:
///- Android
Color? toolbarBackgroundColor;
///Sets the navigation bar color. Has no effect on Android API versions below L.
///
///**Supported Platforms/Implementations**:
///- Android
Color? navigationBarColor;
///Sets the navigation bar divider color. Has no effect on Android API versions below P.
///
///**Supported Platforms/Implementations**:
///- Android
Color? navigationBarDividerColor;
///Sets the color of the secondary toolbar.
///
///**Supported Platforms/Implementations**:
///- Android
Color? secondaryToolbarColor;
///Set to `true` to enable the url bar to hide as the user scrolls down on the page. The default value is `false`.
///
///**NOTE**: Not available in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
bool? enableUrlBarHiding;
///Set to `true` to enable Instant Apps. The default value is `false`.
///
///**NOTE**: Not available in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
bool? instantAppsEnabled;
///Set an explicit application package name that limits
///the components this Intent will resolve to. If left to the default
///value of null, all components in all applications will considered.
///If non-null, the Intent can only match the components in the given
///application package.
///
///**Supported Platforms/Implementations**:
///- Android
String? packageName;
///Set to `true` to enable Keep Alive. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- Android
bool? keepAliveEnabled;
///Set to `true` to launch the Android activity in `singleInstance` mode. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- Android
bool? isSingleInstance;
///Set to `true` to launch the Android intent with the flag `FLAG_ACTIVITY_NO_HISTORY`. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- Android
bool? noHistory;
///Set to `true` to launch the Custom Tab as a Trusted Web Activity. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- Android
bool? isTrustedWebActivity;
///Sets a list of additional trusted origins that the user may navigate or be redirected to from the starting uri.
///
///**NOTE**: Available only in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
List<String>? additionalTrustedOrigins;
///Sets a display mode of a Trusted Web Activity.
///
///**NOTE**: Available only in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
TrustedWebActivityDisplayMode? displayMode;
///Sets a screen orientation. This can be used e.g. to enable the locking of an orientation lock type.
///
///**NOTE**: Available only in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
TrustedWebActivityScreenOrientation? screenOrientation;
///Sets the start animations.
///It must contain 2 [AndroidResource], where the first one represents the "enter" animation for the browser
///and the second one represents the "exit" animation for the application.
///
///**Supported Platforms/Implementations**:
///- Android
List<AndroidResource>? startAnimations;
///Sets the exit animations.
///It must contain 2 [AndroidResource], where the first one represents the "enter" animation for the application
///and the second one represents the "exit" animation for the browser.
///
///**Supported Platforms/Implementations**:
///- Android
List<AndroidResource>? exitAnimations;
///Set to `true` if Reader mode should be entered automatically when it is available for the webpage. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- iOS
bool? entersReaderIfAvailable;
///Set to `true` to enable bar collapsing. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- iOS
bool? barCollapsingEnabled;
///Set the custom style for the dismiss button. The default value is [DismissButtonStyle.DONE].
///
///**NOTE**: available on iOS 11.0+.
///
///**Supported Platforms/Implementations**:
///- iOS
DismissButtonStyle? dismissButtonStyle;
///Set the custom background color of the navigation bar and the toolbar.
///
///**NOTE**: available on iOS 10.0+.
///
///**Supported Platforms/Implementations**:
///- iOS
Color? preferredBarTintColor;
///Set the custom color of the control buttons on the navigation bar and the toolbar.
///
///**NOTE**: available on iOS 10.0+.
///
///**Supported Platforms/Implementations**:
///- iOS
Color? preferredControlTintColor;
///Set the custom modal presentation style when presenting the WebView. The default value is [ModalPresentationStyle.FULL_SCREEN].
///
///**Supported Platforms/Implementations**:
///- iOS
ModalPresentationStyle? presentationStyle;
///Set to the custom transition style when presenting the WebView. The default value is [ModalTransitionStyle.COVER_VERTICAL].
///
///**Supported Platforms/Implementations**:
///- iOS
ModalTransitionStyle? transitionStyle;
ChromeSafariBrowserSettings(
{this.shareState = CustomTabsShareState.SHARE_STATE_DEFAULT,
this.showTitle = true,
this.toolbarBackgroundColor,
this.navigationBarColor,
this.navigationBarDividerColor,
this.secondaryToolbarColor,
this.enableUrlBarHiding = false,
this.instantAppsEnabled = false,
this.packageName,
this.keepAliveEnabled = false,
this.isSingleInstance = false,
this.noHistory = false,
this.isTrustedWebActivity = false,
this.additionalTrustedOrigins = const [],
this.displayMode,
this.screenOrientation = TrustedWebActivityScreenOrientation.DEFAULT,
this.startAnimations,
this.exitAnimations,
this.entersReaderIfAvailable = false,
this.barCollapsingEnabled = false,
this.dismissButtonStyle = DismissButtonStyle.DONE,
this.preferredBarTintColor,
this.preferredControlTintColor,
this.presentationStyle = ModalPresentationStyle.FULL_SCREEN,
this.transitionStyle = ModalTransitionStyle.COVER_VERTICAL}) {
if (startAnimations != null) {
assert(startAnimations!.length == 2,
"start animations must be have 2 android resources");
}
if (exitAnimations != null) {
assert(exitAnimations!.length == 2,
"exit animations must be have 2 android resources");
}
}
///Gets a possible [ChromeSafariBrowserSettings] instance from a [Map] value.
static ChromeSafariBrowserSettings? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
final instance = ChromeSafariBrowserSettings(
toolbarBackgroundColor: map['toolbarBackgroundColor'] != null
? UtilColor.fromStringRepresentation(map['toolbarBackgroundColor'])
: null,
navigationBarColor: map['navigationBarColor'] != null
? UtilColor.fromStringRepresentation(map['navigationBarColor'])
: null,
navigationBarDividerColor: map['navigationBarDividerColor'] != null
? UtilColor.fromStringRepresentation(map['navigationBarDividerColor'])
: null,
secondaryToolbarColor: map['secondaryToolbarColor'] != null
? UtilColor.fromStringRepresentation(map['secondaryToolbarColor'])
: null,
packageName: map['packageName'],
displayMode: _deserializeDisplayMode(map['displayMode']),
startAnimations: map['startAnimations'] != null
? List<AndroidResource>.from(map['startAnimations']
.map((e) => AndroidResource.fromMap(e?.cast<String, dynamic>())!))
: null,
exitAnimations: map['exitAnimations'] != null
? List<AndroidResource>.from(map['exitAnimations']
.map((e) => AndroidResource.fromMap(e?.cast<String, dynamic>())!))
: null,
preferredBarTintColor: map['preferredBarTintColor'] != null
? UtilColor.fromStringRepresentation(map['preferredBarTintColor'])
: null,
preferredControlTintColor: map['preferredControlTintColor'] != null
? UtilColor.fromStringRepresentation(map['preferredControlTintColor'])
: null,
);
instance.shareState =
CustomTabsShareState.fromNativeValue(map['shareState']);
instance.showTitle = map['showTitle'];
instance.enableUrlBarHiding = map['enableUrlBarHiding'];
instance.instantAppsEnabled = map['instantAppsEnabled'];
instance.keepAliveEnabled = map['keepAliveEnabled'];
instance.isSingleInstance = map['isSingleInstance'];
instance.noHistory = map['noHistory'];
instance.isTrustedWebActivity = map['isTrustedWebActivity'];
instance.additionalTrustedOrigins =
map['additionalTrustedOrigins']?.cast<String>();
instance.screenOrientation =
TrustedWebActivityScreenOrientation.fromNativeValue(
map['screenOrientation']);
instance.entersReaderIfAvailable = map['entersReaderIfAvailable'];
instance.barCollapsingEnabled = map['barCollapsingEnabled'];
instance.dismissButtonStyle =
DismissButtonStyle.fromNativeValue(map['dismissButtonStyle']);
instance.presentationStyle =
ModalPresentationStyle.fromNativeValue(map['presentationStyle']);
instance.transitionStyle =
ModalTransitionStyle.fromNativeValue(map['transitionStyle']);
return instance;
}
///Converts instance to a map.
Map<String, dynamic> toMap() {
return {
"shareState": shareState?.toNativeValue(),
"showTitle": showTitle,
"toolbarBackgroundColor": toolbarBackgroundColor?.toHex(),
"navigationBarColor": navigationBarColor?.toHex(),
"navigationBarDividerColor": navigationBarDividerColor?.toHex(),
"secondaryToolbarColor": secondaryToolbarColor?.toHex(),
"enableUrlBarHiding": enableUrlBarHiding,
"instantAppsEnabled": instantAppsEnabled,
"packageName": packageName,
"keepAliveEnabled": keepAliveEnabled,
"isSingleInstance": isSingleInstance,
"noHistory": noHistory,
"isTrustedWebActivity": isTrustedWebActivity,
"additionalTrustedOrigins": additionalTrustedOrigins,
"displayMode": displayMode?.toMap(),
"screenOrientation": screenOrientation?.toNativeValue(),
"startAnimations": startAnimations?.map((e) => e.toMap()).toList(),
"exitAnimations": exitAnimations?.map((e) => e.toMap()).toList(),
"entersReaderIfAvailable": entersReaderIfAvailable,
"barCollapsingEnabled": barCollapsingEnabled,
"dismissButtonStyle": dismissButtonStyle?.toNativeValue(),
"preferredBarTintColor": preferredBarTintColor?.toHex(),
"preferredControlTintColor": preferredControlTintColor?.toHex(),
"presentationStyle": presentationStyle?.toNativeValue(),
"transitionStyle": transitionStyle?.toNativeValue(),
};
}
///Converts instance to a map.
Map<String, dynamic> toJson() {
return toMap();
}
///Returns a copy of ChromeSafariBrowserSettings.
ChromeSafariBrowserSettings copy() {
return ChromeSafariBrowserSettings.fromMap(toMap()) ??
ChromeSafariBrowserSettings();
}
@override
String toString() {
return 'ChromeSafariBrowserSettings{shareState: $shareState, showTitle: $showTitle, toolbarBackgroundColor: $toolbarBackgroundColor, navigationBarColor: $navigationBarColor, navigationBarDividerColor: $navigationBarDividerColor, secondaryToolbarColor: $secondaryToolbarColor, enableUrlBarHiding: $enableUrlBarHiding, instantAppsEnabled: $instantAppsEnabled, packageName: $packageName, keepAliveEnabled: $keepAliveEnabled, isSingleInstance: $isSingleInstance, noHistory: $noHistory, isTrustedWebActivity: $isTrustedWebActivity, additionalTrustedOrigins: $additionalTrustedOrigins, displayMode: $displayMode, screenOrientation: $screenOrientation, startAnimations: $startAnimations, exitAnimations: $exitAnimations, entersReaderIfAvailable: $entersReaderIfAvailable, barCollapsingEnabled: $barCollapsingEnabled, dismissButtonStyle: $dismissButtonStyle, preferredBarTintColor: $preferredBarTintColor, preferredControlTintColor: $preferredControlTintColor, presentationStyle: $presentationStyle, transitionStyle: $transitionStyle}';
}
}

View File

@ -1,4 +1,8 @@
export 'chrome_safari_browser.dart';
export 'chrome_safari_browser_settings.dart';
export 'chrome_safari_browser_settings.dart'
show
ChromeSafariBrowserOptions,
ChromeSafariBrowserSettings,
ChromeSafariBrowserClassOptions;
export 'android/main.dart';
export 'apple/main.dart';

View File

@ -3683,6 +3683,24 @@ class InAppWebViewController {
'setWebContentsDebuggingEnabled', args);
}
///Gets the WebView variations encoded to be used as the X-Client-Data HTTP header.
///
///The app is responsible for adding the X-Client-Data header to any request
///that may use variations metadata, such as requests to Google web properties.
///The returned string will be a base64 encoded ClientVariations proto:
///https://source.chromium.org/chromium/chromium/src/+/main:components/variations/proto/client_variations.proto
///
///The string may be empty if the header is not available.
///
///**NOTE for Android native WebView**: This method should only be called if [WebViewFeature.isFeatureSupported] returns `true` for [WebViewFeature.GET_VARIATIONS_HEADER].
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebViewCompat.getVariationsHeader](https://developer.android.com/reference/androidx/webkit/WebViewCompat#getVariationsHeader()))
static Future<String?> getVariationsHeader() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _staticChannel.invokeMethod('getVariationsHeader', args);
}
///Returns a Boolean value that indicates whether WebKit natively supports resources with the specified URL scheme.
///
///[urlScheme] represents the URL scheme associated with the resource.

View File

@ -0,0 +1,33 @@
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
part 'android_resource.g.dart';
///Class that represents an android resource.
@ExchangeableObject()
class AndroidResource_ {
///Android resource name.
///
///A list of available `android.R.anim` can be found
///[here](https://developer.android.com/reference/android/R.anim).
///
///A list of available `androidx.appcompat.R.anim` can be found
///[here](https://android.googlesource.com/platform/frameworks/support/+/HEAD/appcompat/appcompat/src/main/res/anim/)
///(abc_*.xml files).
///In this case, [defPackage] must match your App Android package name.
String name;
///Optional default resource type to find, if "type/" is not included in the name.
///Can be `null` to require an explicit type.
///
///Example: "anim"
String? defType;
///Optional default package to find, if "package:" is not included in the name.
///Can be `null` to require an explicit package.
///
///Example: "android" if you want use resources from `android.R.`
String? defPackage;
AndroidResource_({required this.name,
this.defType, this.defPackage});
}

View File

@ -0,0 +1,66 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'android_resource.dart';
// **************************************************************************
// ExchangeableObjectGenerator
// **************************************************************************
///Class that represents an android resource.
class AndroidResource {
///Android resource name.
///
///A list of available `android.R.anim` can be found
///[here](https://developer.android.com/reference/android/R.anim).
///
///A list of available `androidx.appcompat.R.anim` can be found
///[here](https://android.googlesource.com/platform/frameworks/support/+/HEAD/appcompat/appcompat/src/main/res/anim/)
///(abc_*.xml files).
///In this case, [defPackage] must match your App Android package name.
String name;
///Optional default resource type to find, if "type/" is not included in the name.
///Can be `null` to require an explicit type.
///
///Example: "anim"
String? defType;
///Optional default package to find, if "package:" is not included in the name.
///Can be `null` to require an explicit package.
///
///Example: "android" if you want use resources from `android.R.`
String? defPackage;
AndroidResource({required this.name, this.defType, this.defPackage});
///Gets a possible [AndroidResource] instance from a [Map] value.
static AndroidResource? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
final instance = AndroidResource(
name: map['name'],
defType: map['defType'],
defPackage: map['defPackage'],
);
return instance;
}
///Converts instance to a map.
Map<String, dynamic> toMap() {
return {
"name": name,
"defType": defType,
"defPackage": defPackage,
};
}
///Converts instance to a map.
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return 'AndroidResource{name: $name, defType: $defType, defPackage: $defPackage}';
}
}

View File

@ -217,4 +217,5 @@ export 'window_style_mask.dart' show WindowStyleMask;
export 'window_titlebar_separator_style.dart' show WindowTitlebarSeparatorStyle;
export 'custom_tabs_navigation_event_type.dart' show CustomTabsNavigationEventType;
export 'custom_tabs_relation_type.dart' show CustomTabsRelationType;
export 'prewarming_token.dart' show PrewarmingToken;
export 'prewarming_token.dart' show PrewarmingToken;
export 'android_resource.dart' show AndroidResource;

View File

@ -34,13 +34,17 @@ dart $PROJECT_DIR/tool/env.dart
cd $PROJECT_DIR/test_node_server
node index.js &
# Only for Android
# Open Chrome on the development device, navigate to chrome://flags, search for an item called Enable command line on non-rooted devices and change it to ENABLED and then restart the browser.
adb shell "echo '_ --disable-digital-asset-link-verification-for-url=\"https://flutter.dev\"' > /data/local/tmp/chrome-command-line" || true
flutter --version
flutter clean
flutter pub get
cd $PROJECT_DIR/example
flutter clean
if [ ! -z "$2" ] && [ $PLATFORM = "web" ]; then
flutter driver --driver=test_driver/integration_test.dart --target=integration_test/webview_flutter_test.dart --device-id=chrome
flutter driver --driver=test_driver/integration_test.dart --target=integration_test/webview_flutter_test.dart --device-id=chrome
else
flutter driver --driver=test_driver/integration_test.dart --target=integration_test/webview_flutter_test.dart
fi

View File

@ -153,6 +153,19 @@ app.get("/", (req, res) => {
res.end()
})
app.get("/echo-headers", (req, res) => {
res.send(`
<html>
<head>
</head>
<body>
<pre style="word-wrap: break-word; white-space: pre-wrap;">${JSON.stringify(req.headers)}</pre>
</body>
</html>
`);
res.end()
})
app.get('/test-index', (req, res) => {
res.sendFile(__dirname + '/public/index.html');
})