release 6.0.0-beta.9

This commit is contained in:
Lorenzo Pichilli 2022-10-26 17:52:35 +02:00
parent 8e9c10246a
commit cfd70fda6e
50 changed files with 1494 additions and 208 deletions

View File

@ -1,17 +1,20 @@
## 6.0.0-beta.9
- Added `headers`, `otherLikelyURLs` arguments on `ChromeSafariBrowser.open` method for Android
- Added `headers`, `otherLikelyURLs`, `referrer` 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 `mayLaunchUrl`, `launchUrl`, `updateActionButton`, `validateRelationship`, `setSecondaryToolbar`, `updateSecondaryToolbar` methods on `ChromeSafariBrowser` for Android
- Added `startAnimations`, `exitAnimations`, `navigationBarColor`, `navigationBarDividerColor`, `secondaryToolbarColor`, `alwaysUseBrowserUI` ChromeSafariBrowser settings for Android
- Added `ChromeSafariBrowserMenuItem.image` property for iOS
- 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 `activityButton`, `eventAttribution` ChromeSafariBrowser settings for iOS
- Added `clearWebsiteData`, `prewarmConnections`, `invalidatePrewarmingToken`, `getMaxToolbarItems` static methods on `ChromeSafariBrowser` for iOS
- Added `getVariationsHeader` WebView static method
### BREAKING CHANGES
- `ChromeSafariBrowser.onCompletedInitialLoad` event has an optional argument
- `ChromeSafariBrowserMenuItem.action` and `ChromeSafariBrowserActionButton.action` can be null
- All `ChromeSafariBrowserSettings` properties are optionals
## 6.0.0-beta.8

View File

@ -4,6 +4,6 @@ import androidx.core.content.FileProvider;
public class InAppWebViewFileProvider extends FileProvider {
// This class intentionally left blank.
public static final String fileProviderAuthorityExtension = "flutter_inappwebview.fileprovider";
}

View File

@ -4,6 +4,9 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.browser.customtabs.CustomTabsIntent;
public class ActionBroadcastReceiver extends BroadcastReceiver {
protected static final String LOG_TAG = "ActionBroadcastReceiver";
@ -13,16 +16,25 @@ public class ActionBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int clickedId = intent.getIntExtra(CustomTabsIntent.EXTRA_REMOTEVIEWS_CLICKED_ID, -1);
String url = intent.getDataString();
if (url != null) {
Bundle b = intent.getExtras();
String viewId = b.getString(KEY_ACTION_VIEW_ID);
int id = b.getInt(KEY_ACTION_ID);
String title = b.getString(KEY_URL_TITLE);
ChromeCustomTabsActivity browser = ChromeSafariBrowserManager.browsers.get(viewId);
if (browser != null && browser.channelDelegate != null) {
browser.channelDelegate.onItemActionPerform(id, url, title);
if (clickedId == -1) {
int id = b.getInt(KEY_ACTION_ID);
String title = b.getString(KEY_URL_TITLE);
ChromeCustomTabsActivity browser = ChromeSafariBrowserManager.browsers.get(viewId);
if (browser != null && browser.channelDelegate != null) {
browser.channelDelegate.onItemActionPerform(id, url, title);
}
} else {
ChromeCustomTabsActivity browser = ChromeSafariBrowserManager.browsers.get(viewId);
if (browser != null && browser.channelDelegate != null) {
browser.channelDelegate.onSecondaryItemActionPerform(browser.getResources().getResourceName(clickedId), url);
}
}
}
}

View File

@ -9,7 +9,7 @@ import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.RemoteViews;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
@ -24,6 +24,7 @@ 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.CustomTabsSecondaryToolbar;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
import java.util.ArrayList;
@ -55,10 +56,14 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
public List<String> initialOtherLikelyURLs;
@Nullable
public Map<String, String> initialHeaders;
@Nullable
public String initialReferrer;
public List<CustomTabsMenuItem> menuItems = new ArrayList<>();
@Nullable
public CustomTabsActionButton actionButton;
@Nullable
public CustomTabsSecondaryToolbar secondaryToolbar;
@Nullable
public ChromeCustomTabsChannelDelegate channelDelegate;
@CallSuper
@ -84,11 +89,13 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
initialUrl = b.getString("url");
initialHeaders = (Map<String, String>) b.getSerializable("headers");
initialReferrer = b.getString("referrer");
initialOtherLikelyURLs = b.getStringArrayList("otherLikelyURLs");
customSettings = new ChromeCustomTabsSettings();
customSettings.parse((HashMap<String, Object>) b.getSerializable("settings"));
actionButton = CustomTabsActionButton.fromMap((Map<String, Object>) b.getSerializable("actionButton"));
secondaryToolbar = CustomTabsSecondaryToolbar.fromMap((Map<String, Object>) b.getSerializable("secondaryToolbar"));
List<Map<String, Object>> menuItemList = (List<Map<String, Object>>) b.getSerializable("menuItemList");
for (Map<String, Object> menuItem : menuItemList) {
menuItems.add(CustomTabsMenuItem.fromMap(menuItem));
@ -155,8 +162,9 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
}
public void launchUrl(@NonNull String url,
@Nullable Map<String, String> headers,
@Nullable List<String> otherLikelyURLs) {
@Nullable Map<String, String> headers,
@Nullable String referrer,
@Nullable List<String> otherLikelyURLs) {
mayLaunchUrl(url, otherLikelyURLs);
builder = new CustomTabsIntent.Builder(customTabsSession);
prepareCustomTabs();
@ -164,7 +172,8 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
CustomTabsIntent customTabsIntent = builder.build();
prepareCustomTabsIntent(customTabsIntent);
CustomTabActivityHelper.openCustomTab(this, customTabsIntent, Uri.parse(url), headers, CHROME_CUSTOM_TAB_REQUEST_CODE);
CustomTabActivityHelper.openCustomTab(this, customTabsIntent, Uri.parse(url), headers,
referrer != null ? Uri.parse(referrer) : null, CHROME_CUSTOM_TAB_REQUEST_CODE);
}
public boolean mayLaunchUrl(@Nullable String url, @Nullable List<String> otherLikelyURLs) {
@ -183,7 +192,7 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
public void customTabsConnected() {
customTabsSession = customTabActivityHelper.getSession();
if (initialUrl != null) {
launchUrl(initialUrl, initialHeaders, initialOtherLikelyURLs);
launchUrl(initialUrl, initialHeaders, initialReferrer, initialOtherLikelyURLs);
}
}
@ -244,6 +253,33 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
createPendingIntent(actionButton.getId()),
actionButton.isShouldTint());
}
if (secondaryToolbar != null) {
AndroidResource layout = secondaryToolbar.getLayout();
RemoteViews remoteViews = new RemoteViews(layout.getDefPackage(), layout.getIdentifier(this));
int[] clickableIDs = new int[secondaryToolbar.getClickableIDs().size()];
for (int i = 0, length = secondaryToolbar.getClickableIDs().size(); i < length; i++) {
AndroidResource clickableID = secondaryToolbar.getClickableIDs().get(i);
clickableIDs[i] = clickableID.getIdentifier(this);
}
builder.setSecondaryToolbarViews(remoteViews, clickableIDs, getSecondaryToolbarOnClickPendingIntent());
}
}
public PendingIntent getSecondaryToolbarOnClickPendingIntent() {
Intent broadcastIntent = new Intent(this, ActionBroadcastReceiver.class);
Bundle extras = new Bundle();
extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id);
broadcastIntent.putExtras(extras);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return PendingIntent.getBroadcast(
this, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
} else {
return PendingIntent.getBroadcast(
this, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}
private void prepareCustomTabsIntent(CustomTabsIntent customTabsIntent) {
@ -254,6 +290,9 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
if (customSettings.keepAliveEnabled)
CustomTabsHelper.addKeepAliveExtra(this, customTabsIntent.intent);
if (customSettings.alwaysUseBrowserUI)
CustomTabsIntent.setAlwaysUseBrowserUI(customTabsIntent.intent);
}
public void updateActionButton(@NonNull byte[] icon, @NonNull String description) {
@ -270,6 +309,21 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
actionButton.setDescription(description);
}
public void updateSecondaryToolbar(CustomTabsSecondaryToolbar secondaryToolbar) {
if (customTabsSession == null) {
return;
}
AndroidResource layout = secondaryToolbar.getLayout();
RemoteViews remoteViews = new RemoteViews(layout.getDefPackage(), layout.getIdentifier(this));
int[] clickableIDs = new int[secondaryToolbar.getClickableIDs().size()];
for (int i = 0, length = secondaryToolbar.getClickableIDs().size(); i < length; i++) {
AndroidResource clickableID = secondaryToolbar.getClickableIDs().get(i);
clickableIDs[i] = clickableID.getIdentifier(this);
}
customTabsSession.setSecondaryToolbarViews(remoteViews, clickableIDs, getSecondaryToolbarOnClickPendingIntent());
this.secondaryToolbar = secondaryToolbar;
}
@Override
protected void onStart() {
super.onStart();

View File

@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview.types.CustomTabsActionButton;
import com.pichillilorenzo.flutter_inappwebview.types.CustomTabsSecondaryToolbar;
import java.util.ArrayList;
import java.util.HashMap;
@ -35,8 +36,9 @@ public class ChromeCustomTabsChannelDelegate extends ChannelDelegateImpl {
String url = (String) call.argument("url");
if (url != null) {
Map<String, String> headers = (Map<String, String>) call.argument("headers");
String referrer = (String) call.argument("referrer");
List<String> otherLikelyURLs = (List<String>) call.argument("otherLikelyURLs");
chromeCustomTabsActivity.launchUrl(url, headers, otherLikelyURLs);
chromeCustomTabsActivity.launchUrl(url, headers, referrer, otherLikelyURLs);
result.success(true);
} else {
result.success(false);
@ -73,6 +75,15 @@ public class ChromeCustomTabsChannelDelegate extends ChannelDelegateImpl {
result.success(false);
}
break;
case "updateSecondaryToolbar":
if (chromeCustomTabsActivity != null) {
CustomTabsSecondaryToolbar secondaryToolbar = CustomTabsSecondaryToolbar.fromMap((Map<String, Object>) call.argument("secondaryToolbar"));
chromeCustomTabsActivity.updateSecondaryToolbar(secondaryToolbar);
result.success(true);
} else {
result.success(false);
}
break;
case "close":
if (chromeCustomTabsActivity != null) {
chromeCustomTabsActivity.onStop();
@ -145,6 +156,15 @@ public class ChromeCustomTabsChannelDelegate extends ChannelDelegateImpl {
channel.invokeMethod("onItemActionPerform", obj);
}
public void onSecondaryItemActionPerform(String name, String url) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("name", name);
obj.put("url", url);
channel.invokeMethod("onSecondaryItemActionPerform", obj);
}
public void onRelationshipValidationResult(int relation, @NonNull Uri requestedOrigin, boolean result) {
MethodChannel channel = getChannel();
if (channel == null) return;

View File

@ -44,6 +44,7 @@ public class ChromeCustomTabsSettings implements ISettings<ChromeCustomTabsActiv
public Integer screenOrientation = ScreenOrientation.DEFAULT;
public List<AndroidResource> startAnimations = new ArrayList<>();
public List<AndroidResource> exitAnimations = new ArrayList<>();
public Boolean alwaysUseBrowserUI = false;
@NonNull
@Override
@ -138,6 +139,9 @@ public class ChromeCustomTabsSettings implements ISettings<ChromeCustomTabsActiv
}
}
break;
case "alwaysUseBrowserUI":
alwaysUseBrowserUI = (Boolean) value;
break;
}
}
@ -163,6 +167,7 @@ public class ChromeCustomTabsSettings implements ISettings<ChromeCustomTabsActiv
options.put("isTrustedWebActivity", isTrustedWebActivity);
options.put("additionalTrustedOrigins", additionalTrustedOrigins);
options.put("screenOrientation", screenOrientation);
options.put("alwaysUseBrowserUI", alwaysUseBrowserUI);
return options;
}

View File

@ -5,6 +5,7 @@ import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsIntent;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.Util;
@ -48,11 +49,13 @@ public class ChromeSafariBrowserManager extends ChannelDelegateImpl {
if (plugin != null && plugin.activity != null) {
String url = (String) call.argument("url");
HashMap<String, Object> headers = (HashMap<String, Object>) call.argument("headers");
String referrer = (String) call.argument("referrer");
ArrayList<String> otherLikelyURLs = (ArrayList<String>) call.argument("otherLikelyURLs");
HashMap<String, Object> settings = (HashMap<String, Object>) call.argument("settings");
HashMap<String, Object> actionButton = (HashMap<String, Object>) call.argument("actionButton");
HashMap<String, Object> secondaryToolbar = (HashMap<String, Object>) call.argument("secondaryToolbar");
List<HashMap<String, Object>> menuItemList = (List<HashMap<String, Object>>) call.argument("menuItemList");
open(plugin.activity, viewId, url, headers, otherLikelyURLs, settings, actionButton, menuItemList, result);
open(plugin.activity, viewId, url, headers, referrer, otherLikelyURLs, settings, actionButton, secondaryToolbar, menuItemList, result);
} else {
result.success(false);
}
@ -64,26 +67,31 @@ public class ChromeSafariBrowserManager extends ChannelDelegateImpl {
result.success(false);
}
break;
case "getMaxToolbarItems":
result.success(CustomTabsIntent.getMaxToolbarItems());
break;
default:
result.notImplemented();
}
}
public void open(Activity activity, String viewId, @Nullable String url, @Nullable HashMap<String, Object> headers,
@Nullable ArrayList<String> otherLikelyURLs,
@Nullable String referrer, @Nullable ArrayList<String> otherLikelyURLs,
HashMap<String, Object> settings, HashMap<String, Object> actionButton,
HashMap<String, Object> secondaryToolbar,
List<HashMap<String, Object>> menuItemList, MethodChannel.Result result) {
Intent intent = null;
Bundle extras = new Bundle();
extras.putString("url", url);
extras.putBoolean("isData", false);
extras.putString("id", viewId);
extras.putString("managerId", this.id);
extras.putSerializable("headers", headers);
extras.putString("referrer", referrer);
extras.putSerializable("otherLikelyURLs", otherLikelyURLs);
extras.putSerializable("settings", settings);
extras.putSerializable("actionButton", (Serializable) actionButton);
extras.putSerializable("secondaryToolbar", (Serializable) secondaryToolbar);
extras.putSerializable("menuItemList", (Serializable) menuItemList);
Boolean isSingleInstance = Util.<Boolean>getOrDefault(settings, "isSingleInstance", false);

View File

@ -1,7 +1,7 @@
package com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser;
@ -28,42 +28,50 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
private CustomTabsCallback mCustomTabsCallback;
/**
* Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView.
* Opens the URL on a Custom Tab if possible.
*
* @param activity The host activity.
* @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available.
* @param intent a intent to be used if Custom Tabs is available.
* @param uri the Uri to be opened.
*/
public static void openCustomTab(Activity activity,
Intent intent,
Uri uri,
@Nullable Map<String, String> headers,
@Nullable Uri referrer,
int requestCode) {
intent.setData(uri);
if (headers != null) {
Bundle bundleHeaders = new Bundle();
for (Map.Entry<String, String> header : headers.entrySet()) {
bundleHeaders.putString(header.getKey(), header.getValue());
}
intent.putExtra(Browser.EXTRA_HEADERS, bundleHeaders);
}
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
activity.startActivityForResult(intent, requestCode);
}
public static void openCustomTab(Activity activity,
CustomTabsIntent customTabsIntent,
Uri uri,
@Nullable Map<String, String> headers,
@Nullable Uri referrer,
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);
CustomTabActivityHelper.openCustomTab(activity, customTabsIntent.intent, uri,
headers, referrer, 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 void openTrustedWebActivity(Activity activity,
TrustedWebActivityIntent trustedWebActivityIntent,
Uri uri,
@Nullable Map<String, String> headers,
@Nullable Uri referrer,
int requestCode) {
CustomTabActivityHelper.openCustomTab(activity, trustedWebActivityIntent.getIntent(), uri,
headers, referrer, requestCode);
}

View File

@ -3,18 +3,14 @@ package com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.trusted.TrustedWebActivityIntent;
import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -27,6 +23,7 @@ public class TrustedWebActivity extends ChromeCustomTabsActivity {
@Override
public void launchUrl(@NonNull String url,
@Nullable Map<String, String> headers,
@Nullable String referrer,
@Nullable List<String> otherLikelyURLs) {
if (customTabsSession == null) {
return;
@ -40,24 +37,33 @@ public class TrustedWebActivity extends ChromeCustomTabsActivity {
TrustedWebActivityIntent trustedWebActivityIntent = builder.build(customTabsSession);
prepareCustomTabsIntent(trustedWebActivityIntent);
CustomTabActivityHelper.openCustomTab(this, trustedWebActivityIntent, uri, headers, CHROME_CUSTOM_TAB_REQUEST_CODE);
CustomTabActivityHelper.openTrustedWebActivity(this, trustedWebActivityIntent, uri, headers,
referrer != null ? Uri.parse(referrer) : null, CHROME_CUSTOM_TAB_REQUEST_CODE);
}
@Override
public void customTabsConnected() {
customTabsSession = customTabActivityHelper.getSession();
if (initialUrl != null) {
launchUrl(initialUrl, initialHeaders, initialOtherLikelyURLs);
launchUrl(initialUrl, initialHeaders, initialReferrer, initialOtherLikelyURLs);
}
}
private void prepareCustomTabs() {
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());
if (customSettings.additionalTrustedOrigins != null && !customSettings.additionalTrustedOrigins.isEmpty()) {
builder.setAdditionalTrustedOrigins(customSettings.additionalTrustedOrigins);
@ -79,5 +85,8 @@ public class TrustedWebActivity extends ChromeCustomTabsActivity {
if (customSettings.keepAliveEnabled)
CustomTabsHelper.addKeepAliveExtra(this, intent);
if (customSettings.alwaysUseBrowserUI)
CustomTabsIntent.setAlwaysUseBrowserUI(intent);
}
}

View File

@ -10,11 +10,11 @@ import java.util.Map;
public class AndroidResource {
@NonNull
String name;
private String name;
@Nullable
String defType;
private String defType;
@Nullable
String defPackage;
private String defPackage;
public AndroidResource(@NonNull String name, @Nullable String defType, @Nullable String defPackage) {
this.name = name;

View File

@ -0,0 +1,83 @@
package com.pichillilorenzo.flutter_inappwebview.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class CustomTabsSecondaryToolbar {
@NonNull
private AndroidResource layout;
@NonNull
private List<AndroidResource> clickableIDs = new ArrayList<>();
public CustomTabsSecondaryToolbar(@NonNull AndroidResource layout, @NonNull List<AndroidResource> clickableIDs) {
this.layout = layout;
this.clickableIDs = clickableIDs;
}
@Nullable
public static CustomTabsSecondaryToolbar fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
AndroidResource layout = AndroidResource.fromMap((Map<String, Object>) map.get("layout"));
List<AndroidResource> clickableIDs = new ArrayList<>();
List<Map<String, Object>> clickableIDList = (List<Map<String, Object>>) map.get("clickableIDs");
if (clickableIDList != null) {
for (Map<String, Object> clickableIDMap : clickableIDList) {
AndroidResource clickableID = AndroidResource.fromMap((Map<String, Object>) clickableIDMap.get("id"));
if (clickableID != null) {
clickableIDs.add(clickableID);
}
}
}
return new CustomTabsSecondaryToolbar(layout, clickableIDs);
}
@NonNull
public AndroidResource getLayout() {
return layout;
}
public void setLayout(@NonNull AndroidResource layout) {
this.layout = layout;
}
@NonNull
public List<AndroidResource> getClickableIDs() {
return clickableIDs;
}
public void setClickableIDs(@NonNull List<AndroidResource> clickableIDs) {
this.clickableIDs = clickableIDs;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomTabsSecondaryToolbar that = (CustomTabsSecondaryToolbar) o;
if (!layout.equals(that.layout)) return false;
return clickableIDs.equals(that.clickableIDs);
}
@Override
public int hashCode() {
int result = layout.hashCode();
result = 31 * result + clickableIDs.hashCode();
return result;
}
@Override
public String toString() {
return "CustomTabsSecondaryToolbar{" +
"layout=" + layout +
", clickableIDs=" + clickableIDs +
'}';
}
}

View File

@ -41,6 +41,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFileProvider;
import com.pichillilorenzo.flutter_inappwebview.types.CreateWindowAction;
import com.pichillilorenzo.flutter_inappwebview.in_app_browser.ActivityResultListener;
import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserDelegate;
@ -73,8 +74,6 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
public static Map<Integer, Message> windowWebViewMessages = new HashMap<>();
private static int windowAutoincrementId = 0;
private static final String fileProviderAuthorityExtension = "flutter_inappwebview.fileprovider";
private static final int PICKER = 1;
private static final int PICKER_LEGACY = 3;
final String DEFAULT_MIME_TYPES = "*/*";
@ -1148,8 +1147,11 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
return null;
}
// for versions 6.0+ (23) we use the FileProvider to avoid runtime permissions
String packageName = activity.getApplicationContext().getPackageName();
return FileProvider.getUriForFile(activity.getApplicationContext(), packageName + "." + fileProviderAuthorityExtension, capturedFile);
String fileProviderAuthority = activity.getApplicationContext().getPackageName() + "." +
InAppWebViewFileProvider.fileProviderAuthorityExtension;
return FileProvider.getUriForFile(activity.getApplicationContext(),
fileProviderAuthority,
capturedFile);
}
@Nullable

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:orientation="horizontal">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Button 2" />
</LinearLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:orientation="horizontal">
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Button 3" />
</LinearLayout>

View File

@ -1,44 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_test/flutter_test.dart';
import '../constants.dart';
import '../util.dart';
void customActionButton() {
final shouldSkip = kIsWeb
? true
: ![
TargetPlatform.android,
].contains(defaultTargetPlatform);
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',
icon: actionButtonIcon.buffer.asUint8List(),
action: (url, title) {}));
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(url: TEST_URL_1);
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.closed.future;
expect(chromeSafariBrowser.isOpened(), false);
}, skip: shouldSkip);
}

View File

@ -14,7 +14,10 @@ void customMenuItem() {
test('add custom menu item', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
chromeSafariBrowser.addMenuItem(ChromeSafariBrowserMenuItem(
id: 2, label: 'Custom item menu 1', action: (url, title) {}));
id: 2,
label: 'Custom item menu 1',
image: UIImage(systemName: "pencil"),
onClick: (url, title) {}));
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(url: TEST_URL_1);

View File

@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_test/flutter_test.dart';
@ -13,6 +14,26 @@ void customTabs() {
].contains(defaultTargetPlatform);
group('Custom Tabs', () {
test('custom referrer', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(
url: TEST_URL_1,
referrer: Uri.parse("android-app://custom-referrer"),
settings: ChromeSafariBrowserSettings(isSingleInstance: true));
await expectLater(chromeSafariBrowser.opened.future, completes);
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.close();
await expectLater(chromeSafariBrowser.closed.future, completes);
expect(chromeSafariBrowser.isOpened(), false);
});
test('single instance', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);
@ -32,6 +53,35 @@ void customTabs() {
expect(chromeSafariBrowser.isOpened(), false);
});
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',
icon: actionButtonIcon.buffer.asUint8List(),
onClick: (url, title) {}));
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(url: TEST_URL_1);
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.closed.future;
expect(chromeSafariBrowser.isOpened(), false);
}, skip: shouldSkip);
test('mayLaunchUrl and launchUrl', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);
@ -67,5 +117,64 @@ void customTabs() {
await expectLater(chromeSafariBrowser.closed.future, completes);
expect(chromeSafariBrowser.isOpened(), false);
});
test('add and update secondary toolbar', () async {
var chromeSafariBrowser = MyChromeSafariBrowser();
chromeSafariBrowser.setSecondaryToolbar(
ChromeSafariBrowserSecondaryToolbar(
layout: AndroidResource.layout(
name: "remote_view",
defPackage:
"com.pichillilorenzo.flutter_inappwebviewexample"),
clickableIDs: [
ChromeSafariBrowserSecondaryToolbarClickableID(
id: AndroidResource.id(
name: "button1",
defPackage:
"com.pichillilorenzo.flutter_inappwebviewexample"),
onClick: (Uri? url) {
print("Button 1 with $url");
}),
ChromeSafariBrowserSecondaryToolbarClickableID(
id: AndroidResource.id(
name: "button2",
defPackage:
"com.pichillilorenzo.flutter_inappwebviewexample"),
onClick: (Uri? url) {
print("Button 2 with $url");
}),
]));
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(url: TEST_URL_1);
await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.updateSecondaryToolbar(
ChromeSafariBrowserSecondaryToolbar(
layout: AndroidResource.layout(
name: "remote_view_2",
defPackage:
"com.pichillilorenzo.flutter_inappwebviewexample"),
clickableIDs: [
ChromeSafariBrowserSecondaryToolbarClickableID(
id: AndroidResource.id(
name: "button3",
defPackage:
"com.pichillilorenzo.flutter_inappwebviewexample"),
onClick: (Uri? url) {
print("Button 3 with $url");
}),
]));
await chromeSafariBrowser.close();
await chromeSafariBrowser.closed.future;
expect(chromeSafariBrowser.isOpened(), false);
});
test('getMaxToolbarItems', () async {
expect(await ChromeSafariBrowser.getMaxToolbarItems(),
greaterThanOrEqualTo(0));
});
}, skip: shouldSkip);
}

View File

@ -1,7 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'custom_action_button.dart';
import 'custom_menu_item.dart';
import 'custom_tabs.dart';
import 'open_and_close.dart';
@ -15,7 +14,6 @@ void main() {
group('ChromeSafariBrowser', () {
openAndClose();
customMenuItem();
customActionButton();
customTabs();
trustedWebActivity();
sfSafariViewController();

View File

@ -20,29 +20,32 @@ void openAndClose() {
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")
AndroidResource.anim(
name: "slide_in_left", defPackage: "android"),
AndroidResource.anim(
name: "slide_out_right", defPackage: "android")
],
exitAnimations: [
AndroidResource(
AndroidResource.anim(
name: "abc_slide_in_top",
defType: "anim",
defPackage:
"com.pichillilorenzo.flutter_inappwebviewexample"),
AndroidResource(
AndroidResource.anim(
name: "abc_slide_out_top",
defType: "anim",
defPackage: "com.pichillilorenzo.flutter_inappwebviewexample")
],
keepAliveEnabled: true,
dismissButtonStyle: DismissButtonStyle.CLOSE,
presentationStyle: ModalPresentationStyle.OVER_FULL_SCREEN));
presentationStyle: ModalPresentationStyle.OVER_FULL_SCREEN,
eventAttribution: UIEventAttribution(
sourceIdentifier: 4,
destinationURL: Uri.parse("https://shop.example/test.html"),
sourceDescription: "Banner ad for Test.",
purchaser: "Shop Example, Inc."),
activityButton: ActivityButton(
templateImage: UIImage(systemName: "sun.max"),
extensionIdentifier:
"com.pichillilorenzo.flutter-inappwebview-6-Example.test")));
await chromeSafariBrowser.opened.future;
expect(chromeSafariBrowser.isOpened(), true);
expect(() async {

View File

@ -9,8 +9,8 @@ void sfSafariViewController() {
final shouldSkip = kIsWeb
? true
: ![
TargetPlatform.android,
].contains(defaultTargetPlatform);
TargetPlatform.iOS,
].contains(defaultTargetPlatform);
group('SF Safari View Controller', () {
test('onCompletedInitialLoad did load successfully', () async {
@ -30,14 +30,18 @@ void sfSafariViewController() {
expect(chromeSafariBrowser.isOpened(), false);
});
test('clearWebsiteData', () async {
await expectLater(ChromeSafariBrowser.clearWebsiteData(), completes);
});
// TODO: this test takes a lot of time to complete. Tested on iOS 16.0.
// test('clearWebsiteData', () async {
// await expectLater(ChromeSafariBrowser.clearWebsiteData(), completes);
// });
test('create and invalidate Prewarming Token', () async {
final prewarmingToken = await ChromeSafariBrowser.prewarmConnections([TEST_URL_1]);
final prewarmingToken =
await ChromeSafariBrowser.prewarmConnections([TEST_URL_1]);
expect(prewarmingToken, isNotNull);
await expectLater(ChromeSafariBrowser.invalidatePrewarmingToken(prewarmingToken!), completes);
await expectLater(
ChromeSafariBrowser.invalidatePrewarmingToken(prewarmingToken!),
completes);
});
}, skip: shouldSkip);
}

View File

@ -59,8 +59,13 @@ void trustedWebActivity() {
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);
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);

View File

@ -56,9 +56,10 @@ class MyInAppBrowser extends InAppBrowser {
class MyChromeSafariBrowser extends ChromeSafariBrowser {
final Completer<void> serviceConnected = Completer<void>();
final Completer<void> opened = Completer<void>();
final Completer<bool> firstPageLoaded = Completer<bool>();
final Completer<bool?> firstPageLoaded = Completer<bool?>();
final Completer<void> closed = Completer<void>();
final Completer<CustomTabsNavigationEventType?> navigationEvent = Completer<CustomTabsNavigationEventType?>();
final Completer<CustomTabsNavigationEventType?> navigationEvent =
Completer<CustomTabsNavigationEventType?>();
final Completer<bool> relationshipValidationResult = Completer<bool>();
@override
@ -83,7 +84,6 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser {
}
}
@override
void onRelationshipValidationResult(
CustomTabsRelationType? relation, Uri? requestedOrigin, bool result) {

View File

@ -3,12 +3,11 @@
export "FLUTTER_ROOT=/Users/lorenzopichilli/fvm/versions/3.3.5"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ=="
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/.dart_tool/package_config.json"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"

View File

@ -11,6 +11,11 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
25A517508F43E58C47090625 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
6143FF37290959650014A1FC /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6143FF36290959650014A1FC /* UniformTypeIdentifiers.framework */; };
6143FF3A290959650014A1FC /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6143FF39290959650014A1FC /* Media.xcassets */; };
6143FF3C290959650014A1FC /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6143FF3B290959650014A1FC /* ActionViewController.swift */; };
6143FF3F290959650014A1FC /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6143FF3D290959650014A1FC /* MainInterface.storyboard */; };
6143FF43290959650014A1FC /* test.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6143FF35290959650014A1FC /* test.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
61FF730023634CA10069C557 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 61FF72FF23634CA10069C557 /* libsqlite3.tbd */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
@ -18,6 +23,16 @@
EDC1147F21735BC200D2247A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
6143FF41290959650014A1FC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 6143FF34290959650014A1FC;
remoteInfo = test;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
6174FE1725CEB74E00A5020C /* Embed App Extensions */ = {
isa = PBXCopyFilesBuildPhase;
@ -25,6 +40,7 @@
dstPath = "";
dstSubfolderSpec = 13;
files = (
6143FF43290959650014A1FC /* test.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0;
@ -35,6 +51,12 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
6143FF35290959650014A1FC /* test.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = test.appex; sourceTree = BUILT_PRODUCTS_DIR; };
6143FF36290959650014A1FC /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
6143FF39290959650014A1FC /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; };
6143FF3B290959650014A1FC /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = "<group>"; };
6143FF3E290959650014A1FC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
6143FF40290959650014A1FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
61FF72FF23634CA10069C557 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
61FF730123634DD10069C557 /* flutter_downloader.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = flutter_downloader.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
@ -53,6 +75,14 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
6143FF32290959650014A1FC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6143FF37290959650014A1FC /* UniformTypeIdentifiers.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -72,10 +102,22 @@
61FF730123634DD10069C557 /* flutter_downloader.framework */,
61FF72FF23634CA10069C557 /* libsqlite3.tbd */,
B0FC2CF7A6002799890B3102 /* Pods_Runner.framework */,
6143FF36290959650014A1FC /* UniformTypeIdentifiers.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
6143FF38290959650014A1FC /* test */ = {
isa = PBXGroup;
children = (
6143FF39290959650014A1FC /* Media.xcassets */,
6143FF3B290959650014A1FC /* ActionViewController.swift */,
6143FF3D290959650014A1FC /* MainInterface.storyboard */,
6143FF40290959650014A1FC /* Info.plist */,
);
path = test;
sourceTree = "<group>";
};
647DC95AB5350DB6D2264FFE /* Pods */ = {
isa = PBXGroup;
children = (
@ -101,6 +143,7 @@
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
6143FF38290959650014A1FC /* test */,
97C146EF1CF9000F007C117D /* Products */,
647DC95AB5350DB6D2264FFE /* Pods */,
50BAF1DF19E018DDF2B149B9 /* Frameworks */,
@ -111,6 +154,7 @@
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
6143FF35290959650014A1FC /* test.appex */,
);
name = Products;
sourceTree = "<group>";
@ -141,6 +185,23 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
6143FF34290959650014A1FC /* test */ = {
isa = PBXNativeTarget;
buildConfigurationList = 6143FF46290959660014A1FC /* Build configuration list for PBXNativeTarget "test" */;
buildPhases = (
6143FF31290959650014A1FC /* Sources */,
6143FF32290959650014A1FC /* Frameworks */,
6143FF33290959650014A1FC /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = test;
productName = test;
productReference = 6143FF35290959650014A1FC /* test.appex */;
productType = "com.apple.product-type.app-extension";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
@ -157,6 +218,7 @@
buildRules = (
);
dependencies = (
6143FF42290959650014A1FC /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
@ -169,10 +231,15 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1240;
LastSwiftUpdateCheck = 1400;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
6143FF34290959650014A1FC = {
CreatedOnToolsVersion = 14.0;
DevelopmentTeam = PFP8UV45Y6;
ProvisioningStyle = Automatic;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = PFP8UV45Y6;
@ -200,11 +267,21 @@
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
6143FF34290959650014A1FC /* test */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
6143FF33290959650014A1FC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6143FF3A290959650014A1FC /* Media.xcassets in Resources */,
6143FF3F290959650014A1FC /* MainInterface.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -296,6 +373,14 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
6143FF31290959650014A1FC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6143FF3C290959650014A1FC /* ActionViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -307,7 +392,23 @@
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
6143FF42290959650014A1FC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 6143FF34290959650014A1FC /* test */;
targetProxy = 6143FF41290959650014A1FC /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
6143FF3D290959650014A1FC /* MainInterface.storyboard */ = {
isa = PBXVariantGroup;
children = (
6143FF3E290959650014A1FC /* Base */,
);
name = MainInterface.storyboard;
sourceTree = "<group>";
};
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
@ -327,6 +428,72 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
6143FF44290959660014A1FC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = PFP8UV45Y6;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = test/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = test;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 The Chromium Authors. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-inappwebview-6-Example.test";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
6143FF45290959660014A1FC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = PFP8UV45Y6;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = test/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = test;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 The Chromium Authors. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-inappwebview-6-Example.test";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -442,6 +609,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
@ -459,7 +627,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-inappwebview-Example";
PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-inappwebview-6-Example";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -473,6 +641,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
@ -490,7 +659,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-inappwebview-Example";
PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-inappwebview-6-Example";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -502,6 +671,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
6143FF46290959660014A1FC /* Build configuration list for PBXNativeTarget "test" */ = {
isa = XCConfigurationList;
buildConfigurations = (
6143FF44290959660014A1FC /* Debug */,
6143FF45290959660014A1FC /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -0,0 +1,58 @@
//
// ActionViewController.swift
// test
//
// Created by Lorenzo Pichilli on 26/10/22.
// Copyright © 2022 The Chromium Authors. All rights reserved.
//
import UIKit
import MobileCoreServices
import UniformTypeIdentifiers
class ActionViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Get the item[s] we're handling from the extension context.
// For example, look for an image and place it into an image view.
// Replace this with something appropriate for the type[s] your extension supports.
var imageFound = false
for item in self.extensionContext!.inputItems as! [NSExtensionItem] {
for provider in item.attachments! {
if provider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
// This is an image. We'll load it, then place it in our image view.
weak var weakImageView = self.imageView
provider.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil, completionHandler: { (imageURL, error) in
OperationQueue.main.addOperation {
if let strongImageView = weakImageView {
if let imageURL = imageURL as? URL {
strongImageView.image = UIImage(data: try! Data(contentsOf: imageURL))
}
}
}
})
imageFound = true
break
}
}
if (imageFound) {
// We only handle one image, so stop looking for more.
break
}
}
}
@IBAction func done() {
// Return any edited content to the host app.
// This template doesn't do anything, so we just echo the passed in items.
self.extensionContext!.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil)
}
}

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ObA-dk-sSI">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Image-->
<scene sceneID="7MM-of-jgj">
<objects>
<viewController title="Image" id="ObA-dk-sSI" customClass="ActionViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="zMn-AG-sqS">
<rect key="frame" x="0.0" y="0.0" width="320" height="528"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<navigationBar contentMode="scaleToFill" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" translatesAutoresizingMaskIntoConstraints="NO" id="NOA-Dm-cuz">
<items>
<navigationItem id="3HJ-uW-3hn">
<barButtonItem key="leftBarButtonItem" title="Done" style="done" id="WYi-yp-eM6">
<connections>
<action selector="done" destination="ObA-dk-sSI" id="Qdu-qn-U6V"/>
</connections>
</barButtonItem>
</navigationItem>
</items>
</navigationBar>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="9ga-4F-77Z"/>
</subviews>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="VVe-Uw-JpX" firstAttribute="trailing" secondItem="NOA-Dm-cuz" secondAttribute="trailing" id="A05-Pj-hrr"/>
<constraint firstItem="9ga-4F-77Z" firstAttribute="top" secondItem="NOA-Dm-cuz" secondAttribute="bottom" id="Fps-3D-QQW"/>
<constraint firstItem="NOA-Dm-cuz" firstAttribute="leading" secondItem="VVe-Uw-JpX" secondAttribute="leading" id="HxO-8t-aoh"/>
<constraint firstItem="VVe-Uw-JpX" firstAttribute="trailing" secondItem="9ga-4F-77Z" secondAttribute="trailing" id="Ozw-Hg-0yh"/>
<constraint firstItem="9ga-4F-77Z" firstAttribute="leading" secondItem="VVe-Uw-JpX" secondAttribute="leading" id="XH5-ld-ONA"/>
<constraint firstItem="VVe-Uw-JpX" firstAttribute="bottom" secondItem="9ga-4F-77Z" secondAttribute="bottom" id="eQg-nn-Zy4"/>
<constraint firstItem="NOA-Dm-cuz" firstAttribute="top" secondItem="VVe-Uw-JpX" secondAttribute="top" id="we0-1t-bgp"/>
</constraints>
<viewLayoutGuide key="safeArea" id="VVe-Uw-JpX"/>
</view>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<size key="freeformSize" width="320" height="528"/>
<connections>
<outlet property="imageView" destination="9ga-4F-77Z" id="5y6-5w-9QO"/>
<outlet property="view" destination="zMn-AG-sqS" id="Qma-de-2ek"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="X47-rx-isc" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>TRUEPREDICATE</string>
<key>NSExtensionServiceAllowsFinderPreviewItem</key>
<true/>
<key>NSExtensionServiceAllowsTouchBarItem</key>
<true/>
<key>NSExtensionServiceFinderPreviewIconName</key>
<string>NSActionTemplate</string>
<key>NSExtensionServiceTouchBarBezelColorName</key>
<string>TouchBarBezel</string>
<key>NSExtensionServiceTouchBarIconName</key>
<string>NSActionTemplate</string>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.ui-services</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,14 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "mac",
"color" : {
"reference" : "systemPurpleColor"
}
}
]
}

View File

@ -38,7 +38,7 @@ class _ChromeSafariBrowserExampleScreenState
id: 1,
description: 'Action Button description',
icon: actionButtonIcon.buffer.asUint8List(),
action: (url, title) {
onClick: (url, title) {
print('Action Button 1 clicked!');
print(url);
print(title);
@ -48,7 +48,8 @@ class _ChromeSafariBrowserExampleScreenState
widget.browser.addMenuItem(ChromeSafariBrowserMenuItem(
id: 2,
label: 'Custom item menu 1',
action: (url, title) {
image: UIImage(systemName: "sun.max"),
onClick: (url, title) {
print('Custom item menu 1 clicked!');
print(url);
print(title);
@ -56,7 +57,8 @@ class _ChromeSafariBrowserExampleScreenState
widget.browser.addMenuItem(ChromeSafariBrowserMenuItem(
id: 3,
label: 'Custom item menu 2',
action: (url, title) {
image: UIImage(systemName: "pencil"),
onClick: (url, title) {
print('Custom item menu 2 clicked!');
print(url);
print(title);
@ -82,6 +84,22 @@ class _ChromeSafariBrowserExampleScreenState
isSingleInstance: false,
isTrustedWebActivity: false,
keepAliveEnabled: true,
startAnimations: [
AndroidResource.anim(
name: "slide_in_left", defPackage: "android"),
AndroidResource.anim(
name: "slide_out_right", defPackage: "android")
],
exitAnimations: [
AndroidResource.anim(
name: "abc_slide_in_top",
defPackage:
"com.pichillilorenzo.flutter_inappwebviewexample"),
AndroidResource.anim(
name: "abc_slide_out_top",
defPackage:
"com.pichillilorenzo.flutter_inappwebviewexample")
],
dismissButtonStyle: DismissButtonStyle.CLOSE,
presentationStyle:
ModalPresentationStyle.OVER_FULL_SCREEN));

View File

@ -101,9 +101,6 @@ public class ChromeSafariBrowserManager: ChannelDelegate {
if #available(iOS 11.0, *) {
let config = SFSafariViewController.Configuration()
config.entersReaderIfAvailable = safariSettings.entersReaderIfAvailable
config.barCollapsingEnabled = safariSettings.barCollapsingEnabled
safari = SafariViewController(id: id, url: absoluteUrl, configuration: config,
menuItemList: menuItemList, safariSettings: safariSettings)
} else {

View File

@ -18,11 +18,20 @@ public class SafariBrowserSettings: ISettings<SafariViewController> {
var preferredControlTintColor: String?
var presentationStyle = 0 //fullscreen
var transitionStyle = 0 //coverVertical
var activityButton: [String:Any?]?
var eventAttribution: [String:Any?]?
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> SafariBrowserSettings {
let _ = super.parse(settings: settings)
activityButton = settings["activityButton"] as? [String : Any?]
eventAttribution = settings["eventAttribution"] as? [String : Any?]
return self
}
override func getRealSettings(obj: SafariViewController?) -> [String: Any?] {
var realOptions: [String: Any?] = toMap()
if let safariViewController = obj {

View File

@ -21,6 +21,7 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle
self.id = id
self.menuItemList = menuItemList
self.safariSettings = safariSettings
SafariViewController.prepareConfig(configuration: configuration, safariSettings: safariSettings)
super.init(url: url, configuration: configuration)
let channel = FlutterMethodChannel(name: SafariViewController.METHOD_CHANNEL_NAME_PREFIX + id,
binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger())
@ -39,19 +40,17 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle
self.delegate = self
}
public override func viewWillAppear(_ animated: Bool) {
// prepareSafariBrowser()
super.viewWillAppear(animated)
channelDelegate?.onOpened()
public static func prepareConfig(configuration: SFSafariViewController.Configuration, safariSettings: SafariBrowserSettings) {
configuration.entersReaderIfAvailable = safariSettings.entersReaderIfAvailable
configuration.barCollapsingEnabled = safariSettings.barCollapsingEnabled
if #available(iOS 15.0, *), let activityButtonMap = safariSettings.activityButton {
configuration.activityButton = .fromMap(map: activityButtonMap)
}
if #available(iOS 15.2, *), let eventAttributionMap = safariSettings.eventAttribution {
configuration.eventAttribution = .fromMap(map: eventAttributionMap)
}
}
public override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
channelDelegate?.onClosed()
self.dispose()
}
func prepareSafariBrowser() {
if #available(iOS 11.0, *) {
self.dismissButtonStyle = SFSafariViewController.DismissButtonStyle(rawValue: safariSettings.dismissButtonStyle)!
@ -70,6 +69,18 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle
self.modalTransitionStyle = UIModalTransitionStyle(rawValue: safariSettings.transitionStyle)!
}
public override func viewWillAppear(_ animated: Bool) {
// prepareSafariBrowser()
super.viewWillAppear(animated)
channelDelegate?.onOpened()
}
public override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
channelDelegate?.onClosed()
self.dispose()
}
func close(result: FlutterResult?) {
dismiss(animated: true)
@ -93,7 +104,7 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle
public func safariViewController(_ controller: SFSafariViewController, activityItemsFor URL: URL, title: String?) -> [UIActivity] {
var uiActivities: [UIActivity] = []
menuItemList.forEach { (menuItem) in
let activity = CustomUIActivity(viewId: id, id: menuItem["id"] as! Int64, url: URL, title: title, label: menuItem["label"] as? String, type: nil, image: nil)
let activity = CustomUIActivity(viewId: id, id: menuItem["id"] as! Int64, url: URL, title: title, label: menuItem["label"] as? String, type: nil, image: .fromMap(map: menuItem["image"] as? [String:Any?]))
uiActivities.append(activity)
}
return uiActivities

View File

@ -0,0 +1,24 @@
//
// ActivityButton.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 26/10/22.
//
import Foundation
import SafariServices
@available(iOS 15.0, *)
extension SFSafariViewController.ActivityButton {
public static func fromMap(map: [String:Any?]?) -> SFSafariViewController.ActivityButton? {
guard let map = map else {
return nil
}
if let templateImageMap = map["templateImage"] as? [String:Any?],
let templateImage = UIImage.fromMap(map: templateImageMap),
let extensionIdentifier = map["extensionIdentifier"] as? String {
return .init(templateImage: templateImage, extensionIdentifier: extensionIdentifier)
}
return nil
}
}

View File

@ -0,0 +1,28 @@
//
// UIEventAttribution.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 26/10/22.
//
import Foundation
@available(iOS 14.5, *)
extension UIEventAttribution {
public static func fromMap(map: [String:Any?]?) -> UIEventAttribution? {
guard let map = map else {
return nil
}
if let sourceIdentifier = map["sourceIdentifier"] as? UInt8,
let destinationURLString = map["destinationURL"] as? String,
let destinationURL = URL(string: destinationURLString),
let sourceDescription = map["sourceDescription"] as? String,
let purchaser = map["purchaser"] as? String {
return UIEventAttribution(sourceIdentifier: sourceIdentifier,
destinationURL: destinationURL,
sourceDescription: sourceDescription,
purchaser: purchaser)
}
return nil
}
}

View File

@ -0,0 +1,27 @@
//
// UIImage.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 26/10/22.
//
import Foundation
import Flutter
extension UIImage {
public static func fromMap(map: [String:Any?]?) -> UIImage? {
guard let map = map else {
return nil
}
if let name = map["name"] as? String {
return UIImage(named: name)
}
if #available(iOS 13.0, *), let systemName = map["systemName"] as? String {
return UIImage(systemName: systemName)
}
if let data = map["data"] as? FlutterStandardTypedData {
return UIImage(data: data.data)
}
return nil
}
}

View File

@ -219,8 +219,7 @@ class WebViewFeature_ {
///This feature covers [InAppWebViewController.getVariationsHeader].
static const GET_VARIATIONS_HEADER =
const WebViewFeature_._internal(
"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,

View File

@ -4,9 +4,11 @@ import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import '../types/android_resource.dart';
import '../types/custom_tabs_navigation_event_type.dart';
import '../types/custom_tabs_relation_type.dart';
import '../types/prewarming_token.dart';
import '../types/ui_image.dart';
import '../util.dart';
import '../debug_logging_settings.dart';
@ -55,6 +57,7 @@ class ChromeSafariBrowser {
ChromeSafariBrowserActionButton? _actionButton;
Map<int, ChromeSafariBrowserMenuItem> _menuItems = new HashMap();
ChromeSafariBrowserSecondaryToolbar? _secondaryToolbar;
bool _isOpened = false;
late MethodChannel _channel;
static const MethodChannel _sharedChannel =
@ -129,9 +132,46 @@ class ChromeSafariBrowser {
String title = call.arguments["title"];
int id = call.arguments["id"].toInt();
if (this._actionButton?.id == id) {
this._actionButton?.action(url, title);
if (this._actionButton?.action != null) {
this._actionButton?.action!(url, title);
}
if (this._actionButton?.onClick != null) {
this._actionButton?.onClick!(Uri.tryParse(url), title);
}
} else if (this._menuItems[id] != null) {
this._menuItems[id]?.action(url, title);
if (this._menuItems[id]?.action != null) {
this._menuItems[id]?.action!(url, title);
}
if (this._menuItems[id]?.onClick != null) {
this._menuItems[id]?.onClick!(Uri.tryParse(url), title);
}
}
break;
case "onSecondaryItemActionPerform":
final clickableIDs = this._secondaryToolbar?.clickableIDs;
if (clickableIDs != null) {
Uri? url = call.arguments["url"] != null
? Uri.tryParse(call.arguments["url"])
: null;
String name = call.arguments["name"];
for (final clickable in clickableIDs) {
var clickableFullname = clickable.id.name;
if (clickable.id.defType != null &&
!clickableFullname.contains("/")) {
clickableFullname = "${clickable.id.defType}/$clickableFullname";
}
if (clickable.id.defPackage != null &&
!clickableFullname.contains(":")) {
clickableFullname =
"${clickable.id.defPackage}:$clickableFullname";
}
if (clickableFullname == name) {
if (clickable.onClick != null) {
clickable.onClick!(url);
}
break;
}
}
}
break;
default:
@ -149,7 +189,9 @@ class ChromeSafariBrowser {
///
///[otherLikelyURLs] - Other likely destinations, sorted in decreasing likelihood order. Supported only on Android.
///
///[options] - Options for the [ChromeSafariBrowser].
///[referrer] - referrer header. Supported only on Android.
///
///[options] - Deprecated. Use `settings` instead.
///
///[settings] - Settings for the [ChromeSafariBrowser].
///
@ -160,6 +202,7 @@ class ChromeSafariBrowser {
{Uri? url,
Map<String, String>? headers,
List<Uri>? otherLikelyURLs,
Uri? referrer,
@Deprecated('Use settings instead')
// ignore: deprecated_member_use_from_same_package
ChromeSafariBrowserClassOptions? options,
@ -191,8 +234,10 @@ class ChromeSafariBrowser {
args.putIfAbsent('headers', () => headers);
args.putIfAbsent('otherLikelyURLs',
() => otherLikelyURLs?.map((e) => e.toString()).toList());
args.putIfAbsent('referrer', () => referrer?.toString());
args.putIfAbsent('settings', () => initialSettings);
args.putIfAbsent('actionButton', () => _actionButton?.toMap());
args.putIfAbsent('secondaryToolbar', () => _secondaryToolbar?.toMap());
args.putIfAbsent('menuItemList', () => menuItemList);
await _sharedChannel.invokeMethod('open', args);
}
@ -207,17 +252,22 @@ class ChromeSafariBrowser {
///
///[otherLikelyURLs] - Other likely destinations, sorted in decreasing likelihood order.
///
///[referrer] - referrer header. Supported only on Android.
///
///**Supported Platforms/Implementations**:
///- Android
Future<void> launchUrl(
{required Uri url,
Map<String, String>? headers,
List<Uri>? otherLikelyURLs}) async {
Future<void> launchUrl({
required Uri url,
Map<String, String>? headers,
List<Uri>? otherLikelyURLs,
Uri? referrer,
}) 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());
args.putIfAbsent('referrer', () => referrer?.toString());
await _channel.invokeMethod("launchUrl", args);
}
@ -233,9 +283,7 @@ class ChromeSafariBrowser {
///
///**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<bool> mayLaunchUrl(
{Uri? url,
List<Uri>? otherLikelyURLs}) async {
Future<bool> mayLaunchUrl({Uri? url, List<Uri>? otherLikelyURLs}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url?.toString());
args.putIfAbsent('otherLikelyURLs',
@ -303,6 +351,31 @@ class ChromeSafariBrowser {
_actionButton?.description = description;
}
///Sets the remote views displayed in the secondary toolbar in a custom tab.
///
///**NOTE**: Not available in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsIntent.Builder.setSecondaryToolbarViews](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsIntent.Builder#setSecondaryToolbarViews(android.widget.RemoteViews,int[],android.app.PendingIntent)))
void setSecondaryToolbar(
ChromeSafariBrowserSecondaryToolbar secondaryToolbar) {
this._secondaryToolbar = secondaryToolbar;
}
///Sets or updates (if already present) the Remote Views of the secondary toolbar in an existing custom tab session.
///
///**NOTE**: Not available in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsSession.setSecondaryToolbarViews](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsSession#setSecondaryToolbarViews(android.widget.RemoteViews,int[],android.app.PendingIntent)))
Future<void> updateSecondaryToolbar(
ChromeSafariBrowserSecondaryToolbar secondaryToolbar) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('secondaryToolbar', () => secondaryToolbar.toMap());
await _channel.invokeMethod("updateSecondaryToolbar", args);
this._secondaryToolbar = secondaryToolbar;
}
///Adds a [ChromeSafariBrowserMenuItem] to the menu.
///
///**NOTE**: Not available in an Android Trusted Web Activity.
@ -339,6 +412,15 @@ class ChromeSafariBrowser {
return await _sharedChannel.invokeMethod("isAvailable", args);
}
///The maximum number of allowed secondary toolbar items.
///
///**Supported Platforms/Implementations**:
///- Android
static Future<int> getMaxToolbarItems() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _sharedChannel.invokeMethod("getMaxToolbarItems", args);
}
///Clear associated website data accrued from browsing activity within your app.
///This includes all local storage, cached resources, and cookies.
///
@ -375,8 +457,8 @@ class ChromeSafariBrowser {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('URLs', () => URLs.map((e) => e.toString()).toList());
Map<String, dynamic>? result =
(await _sharedChannel.invokeMethod("prewarmConnections", args))
?.cast<String, dynamic>();
(await _sharedChannel.invokeMethod("prewarmConnections", args))
?.cast<String, dynamic>();
return PrewarmingToken.fromMap(result);
}
@ -386,7 +468,8 @@ class ChromeSafariBrowser {
///
///**Supported Platforms/Implementations**:
///- iOS ([Official API - SFSafariViewController.prewarmConnections](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/3752133-prewarmconnections))
static Future<void> invalidatePrewarmingToken(PrewarmingToken prewarmingToken) async {
static Future<void> invalidatePrewarmingToken(
PrewarmingToken prewarmingToken) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('prewarmingToken', () => prewarmingToken.toMap());
await _sharedChannel.invokeMethod("invalidatePrewarmingToken", args);
@ -499,14 +582,19 @@ class ChromeSafariBrowserActionButton {
///Whether the action button should be tinted.
bool shouldTint;
///Callback function to be invoked when the menu item is clicked
final void Function(String url, String title) action;
///Use onClick instead.
@Deprecated("Use onClick instead")
void Function(String url, String title)? action;
///Callback function to be invoked when the action button is clicked
void Function(Uri? url, String title)? onClick;
ChromeSafariBrowserActionButton(
{required this.id,
required this.icon,
required this.description,
required this.action,
@Deprecated("Use onClick instead") this.action,
this.onClick,
this.shouldTint = false});
Map<String, dynamic> toMap() {
@ -539,17 +627,98 @@ class ChromeSafariBrowserMenuItem {
///The menu item id. It should be different from [ChromeSafariBrowserActionButton.id].
int id;
///The label of the menu item
///The label of the menu item.
String label;
///Item image.
UIImage? image;
///Use onClick instead.
@Deprecated("Use onClick instead")
void Function(String url, String title)? action;
///Callback function to be invoked when the menu item is clicked
final void Function(String url, String title) action;
void Function(Uri? url, String title)? onClick;
ChromeSafariBrowserMenuItem(
{required this.id, required this.label, required this.action});
{required this.id,
required this.label,
this.image,
@Deprecated("Use onClick instead") this.action,
this.onClick});
Map<String, dynamic> toMap() {
return {"id": id, "label": label};
return {"id": id, "label": label, "image": image?.toMap()};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents the [RemoteViews](https://developer.android.com/reference/android/widget/RemoteViews.html)
///that will be shown on the secondary toolbar of a custom tab.
///
///This class describes a view hierarchy that can be displayed in another process.
///The hierarchy is inflated from an Android layout resource file.
///
///RemoteViews has limited to support to Android layouts.
///Check the [RemoteViews Official API](https://developer.android.com/reference/android/widget/RemoteViews.html) for more details.
///
///**NOTE**: Not available in an Android Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
class ChromeSafariBrowserSecondaryToolbar {
///The android layout resource.
AndroidResource layout;
///The IDs of clickable views. The `onClick` event of these views will be handled by custom tabs.
List<ChromeSafariBrowserSecondaryToolbarClickableID> clickableIDs;
ChromeSafariBrowserSecondaryToolbar(
{required this.layout, this.clickableIDs = const []});
Map<String, dynamic> toMap() {
return {
"layout": layout.toMap(),
"clickableIDs": clickableIDs.map((e) => e.toMap()).toList()
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents a clickable ID item of the secondary toolbar for a [ChromeSafariBrowser] instance.
///
///**NOTE**: Not available in an Android Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android
class ChromeSafariBrowserSecondaryToolbarClickableID {
///The android id resource
AndroidResource id;
///Callback function to be invoked when the item is clicked
void Function(Uri? url)? onClick;
ChromeSafariBrowserSecondaryToolbarClickableID(
{required this.id, this.onClick});
Map<String, dynamic> toMap() {
return {"id": id.toMap()};
}
Map<String, dynamic> toJson() {

View File

@ -3,6 +3,7 @@ import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
import '../types/activity_button.dart';
import '../types/android_resource.dart';
import '../types/custom_tabs_share_state.dart';
import '../types/dismiss_button_style.dart';
@ -11,6 +12,7 @@ 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 '../types/ui_event_attribution.dart';
import '../util.dart';
import 'android/chrome_custom_tabs_options.dart';
import 'apple/safari_options.dart';
@ -188,6 +190,15 @@ class ChromeSafariBrowserSettings_ implements ChromeSafariBrowserOptions {
///- Android
List<AndroidResource_>? exitAnimations;
///Adds the necessary flags and extras to signal any browser supporting custom tabs to use the browser UI
///at all times and avoid showing custom tab like UI.
///Calling this with an intent will override any custom tabs related customizations.
///The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- Android
bool? alwaysUseBrowserUI;
///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**:
@ -236,6 +247,24 @@ class ChromeSafariBrowserSettings_ implements ChromeSafariBrowserOptions {
///- iOS
ModalTransitionStyle_? transitionStyle;
///An additional button to be shown in `SFSafariViewController`'s toolbar.
///This allows the user to access powerful functionality from your extension without needing to first show the `UIActivityViewController`.
///
///**NOTE**: available on iOS 15.0+.
///
///**Supported Platforms/Implementations**:
///- iOS
ActivityButton_? activityButton;
///An event attribution associated with a click that caused this `SFSafariViewController` to be opened.
///This attribute is ignored if the `SFSafariViewController` url has a scheme of 'http'.
///
///**NOTE**: available on iOS 15.2+.
///
///**Supported Platforms/Implementations**:
///- iOS
UIEventAttribution_? eventAttribution;
@ExchangeableObjectConstructor()
ChromeSafariBrowserSettings_(
{this.shareState = CustomTabsShareState_.SHARE_STATE_DEFAULT,
@ -256,13 +285,16 @@ class ChromeSafariBrowserSettings_ implements ChromeSafariBrowserOptions {
this.screenOrientation = TrustedWebActivityScreenOrientation_.DEFAULT,
this.startAnimations,
this.exitAnimations,
this.alwaysUseBrowserUI = false,
this.entersReaderIfAvailable = false,
this.barCollapsingEnabled = false,
this.dismissButtonStyle = DismissButtonStyle_.DONE,
this.preferredBarTintColor,
this.preferredControlTintColor,
this.presentationStyle = ModalPresentationStyle_.FULL_SCREEN,
this.transitionStyle = ModalTransitionStyle_.COVER_VERTICAL}) {
this.transitionStyle = ModalTransitionStyle_.COVER_VERTICAL,
this.activityButton,
this.eventAttribution}) {
if (startAnimations != null) {
assert(startAnimations!.length == 2,
"start animations must be have 2 android resources");

View File

@ -138,6 +138,15 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///- Android
List<AndroidResource>? exitAnimations;
///Adds the necessary flags and extras to signal any browser supporting custom tabs to use the browser UI
///at all times and avoid showing custom tab like UI.
///Calling this with an intent will override any custom tabs related customizations.
///The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- Android
bool? alwaysUseBrowserUI;
///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**:
@ -185,6 +194,24 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
///**Supported Platforms/Implementations**:
///- iOS
ModalTransitionStyle? transitionStyle;
///An additional button to be shown in `SFSafariViewController`'s toolbar.
///This allows the user to access powerful functionality from your extension without needing to first show the `UIActivityViewController`.
///
///**NOTE**: available on iOS 15.0+.
///
///**Supported Platforms/Implementations**:
///- iOS
ActivityButton? activityButton;
///An event attribution associated with a click that caused this `SFSafariViewController` to be opened.
///This attribute is ignored if the `SFSafariViewController` url has a scheme of 'http'.
///
///**NOTE**: available on iOS 15.2+.
///
///**Supported Platforms/Implementations**:
///- iOS
UIEventAttribution? eventAttribution;
ChromeSafariBrowserSettings(
{this.shareState = CustomTabsShareState.SHARE_STATE_DEFAULT,
this.showTitle = true,
@ -204,13 +231,16 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
this.screenOrientation = TrustedWebActivityScreenOrientation.DEFAULT,
this.startAnimations,
this.exitAnimations,
this.alwaysUseBrowserUI = false,
this.entersReaderIfAvailable = false,
this.barCollapsingEnabled = false,
this.dismissButtonStyle = DismissButtonStyle.DONE,
this.preferredBarTintColor,
this.preferredControlTintColor,
this.presentationStyle = ModalPresentationStyle.FULL_SCREEN,
this.transitionStyle = ModalTransitionStyle.COVER_VERTICAL}) {
this.transitionStyle = ModalTransitionStyle.COVER_VERTICAL,
this.activityButton,
this.eventAttribution}) {
if (startAnimations != null) {
assert(startAnimations!.length == 2,
"start animations must be have 2 android resources");
@ -255,6 +285,10 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
preferredControlTintColor: map['preferredControlTintColor'] != null
? UtilColor.fromStringRepresentation(map['preferredControlTintColor'])
: null,
activityButton: ActivityButton.fromMap(
map['activityButton']?.cast<String, dynamic>()),
eventAttribution: UIEventAttribution.fromMap(
map['eventAttribution']?.cast<String, dynamic>()),
);
instance.shareState =
CustomTabsShareState.fromNativeValue(map['shareState']);
@ -270,6 +304,7 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
instance.screenOrientation =
TrustedWebActivityScreenOrientation.fromNativeValue(
map['screenOrientation']);
instance.alwaysUseBrowserUI = map['alwaysUseBrowserUI'];
instance.entersReaderIfAvailable = map['entersReaderIfAvailable'];
instance.barCollapsingEnabled = map['barCollapsingEnabled'];
instance.dismissButtonStyle =
@ -302,6 +337,7 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
"screenOrientation": screenOrientation?.toNativeValue(),
"startAnimations": startAnimations?.map((e) => e.toMap()).toList(),
"exitAnimations": exitAnimations?.map((e) => e.toMap()).toList(),
"alwaysUseBrowserUI": alwaysUseBrowserUI,
"entersReaderIfAvailable": entersReaderIfAvailable,
"barCollapsingEnabled": barCollapsingEnabled,
"dismissButtonStyle": dismissButtonStyle?.toNativeValue(),
@ -309,6 +345,8 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
"preferredControlTintColor": preferredControlTintColor?.toHex(),
"presentationStyle": presentationStyle?.toNativeValue(),
"transitionStyle": transitionStyle?.toNativeValue(),
"activityButton": activityButton?.toMap(),
"eventAttribution": eventAttribution?.toMap(),
};
}
@ -325,6 +363,6 @@ class ChromeSafariBrowserSettings implements ChromeSafariBrowserOptions {
@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}';
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, alwaysUseBrowserUI: $alwaysUseBrowserUI, entersReaderIfAvailable: $entersReaderIfAvailable, barCollapsingEnabled: $barCollapsingEnabled, dismissButtonStyle: $dismissButtonStyle, preferredBarTintColor: $preferredBarTintColor, preferredControlTintColor: $preferredControlTintColor, presentationStyle: $presentationStyle, transitionStyle: $transitionStyle, activityButton: $activityButton, eventAttribution: $eventAttribution}';
}
}

View File

@ -783,22 +783,3 @@ class _InAppWebViewState extends State<InAppWebView> {
}
}
}
extension on PlatformViewsService {
static SurfaceAndroidViewController initExpensiveAndroidView2({
required int id,
required String viewType,
required TextDirection layoutDirection,
dynamic creationParams,
MessageCodec<dynamic>? creationParamsCodec,
VoidCallback? onFocus,
}) {
return PlatformViewsService.initSurfaceAndroidView(
id: id,
viewType: viewType,
layoutDirection: layoutDirection,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
}

View File

@ -0,0 +1,24 @@
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
import 'ui_image.dart';
part 'activity_button.g.dart';
///Class that represents a custom button to show in `SFSafariViewController`'s toolbar.
///When tapped, it will invoke a Share or Action Extension bundled with your app.
///The default VoiceOver description of this button is the `CFBundleDisplayName` set in the extension's `Info.plist`.
///
///**Supported Platforms/Implementations**:
///- iOS
@ExchangeableObject()
class ActivityButton_ {
///The name of the image asset or file.
UIImage_ templateImage;
///The name of the system symbol image.
String extensionIdentifier;
@ExchangeableObjectConstructor()
ActivityButton_(
{required this.templateImage, required this.extensionIdentifier});
}

View File

@ -0,0 +1,54 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'activity_button.dart';
// **************************************************************************
// ExchangeableObjectGenerator
// **************************************************************************
///Class that represents a custom button to show in `SFSafariViewController`'s toolbar.
///When tapped, it will invoke a Share or Action Extension bundled with your app.
///The default VoiceOver description of this button is the `CFBundleDisplayName` set in the extension's `Info.plist`.
///
///**Supported Platforms/Implementations**:
///- iOS
class ActivityButton {
///The name of the image asset or file.
UIImage templateImage;
///The name of the system symbol image.
String extensionIdentifier;
ActivityButton(
{required this.templateImage, required this.extensionIdentifier});
///Gets a possible [ActivityButton] instance from a [Map] value.
static ActivityButton? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
final instance = ActivityButton(
templateImage:
UIImage.fromMap(map['templateImage']?.cast<String, dynamic>())!,
extensionIdentifier: map['extensionIdentifier'],
);
return instance;
}
///Converts instance to a map.
Map<String, dynamic> toMap() {
return {
"templateImage": templateImage.toMap(),
"extensionIdentifier": extensionIdentifier,
};
}
///Converts instance to a map.
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return 'ActivityButton{templateImage: $templateImage, extensionIdentifier: $extensionIdentifier}';
}
}

View File

@ -28,6 +28,19 @@ class AndroidResource_ {
///Example: "android" if you want use resources from `android.R.`
String? defPackage;
AndroidResource_({required this.name,
this.defType, this.defPackage});
AndroidResource_({required this.name, this.defType, this.defPackage});
static AndroidResource_ anim({required String name, String? defPackage}) {
return AndroidResource_(
name: name, defType: "anim", defPackage: defPackage);
}
static AndroidResource_ layout({required String name, String? defPackage}) {
return AndroidResource_(
name: name, defType: "layout", defPackage: defPackage);
}
static AndroidResource_ id({required String name, String? defPackage}) {
return AndroidResource_(name: name, defType: "id", defPackage: defPackage);
}
}

View File

@ -45,6 +45,19 @@ class AndroidResource {
return instance;
}
static AndroidResource anim({required String name, String? defPackage}) {
return AndroidResource(name: name, defType: "anim", defPackage: defPackage);
}
static AndroidResource layout({required String name, String? defPackage}) {
return AndroidResource(
name: name, defType: "layout", defPackage: defPackage);
}
static AndroidResource id({required String name, String? defPackage}) {
return AndroidResource(name: name, defType: "id", defPackage: defPackage);
}
///Converts instance to a map.
Map<String, dynamic> toMap() {
return {

View File

@ -215,7 +215,11 @@ export 'printer.dart' show Printer;
export 'window_type.dart' show WindowType;
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_navigation_event_type.dart'
show CustomTabsNavigationEventType;
export 'custom_tabs_relation_type.dart' show CustomTabsRelationType;
export 'prewarming_token.dart' show PrewarmingToken;
export 'android_resource.dart' show AndroidResource;
export 'ui_image.dart' show UIImage;
export 'activity_button.dart' show ActivityButton;
export 'ui_event_attribution.dart' show UIEventAttribution;

View File

@ -0,0 +1,35 @@
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
part 'ui_event_attribution.g.dart';
///Class that represents an object that contains event attribution information for Private Click Measurement.
///
///Apps use event attribution objects to send data to the browser when opening an external website that supports Private Click Measurement (PCM).
///For more information on the proposed PCM web standard, see [Introducing Private Click Measurement](https://webkit.org/blog/11529/introducing-private-click-measurement-pcm/)
///and [Private Click Measurement Draft Community Group Report](https://privacycg.github.io/private-click-measurement/).
///
///Check [UIEventAttribution](https://developer.apple.com/documentation/uikit/uieventattribution) for details.
///
///**Supported Platforms/Implementations**:
///- iOS
@ExchangeableObject()
class UIEventAttribution_ {
///An 8-bit number that identifies the source of the click for attribution. Value must be between 0 and 255.
int sourceIdentifier;
///The destination URL of the attribution.
Uri destinationURL;
///A description of the source of the attribution.
String sourceDescription;
///A string that describes the entity that purchased the attributed content.
String purchaser;
@ExchangeableObjectConstructor()
UIEventAttribution_(
{required this.sourceIdentifier,
required this.destinationURL,
required this.sourceDescription,
required this.purchaser});
}

View File

@ -0,0 +1,70 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'ui_event_attribution.dart';
// **************************************************************************
// ExchangeableObjectGenerator
// **************************************************************************
///Class that represents an object that contains event attribution information for Private Click Measurement.
///
///Apps use event attribution objects to send data to the browser when opening an external website that supports Private Click Measurement (PCM).
///For more information on the proposed PCM web standard, see [Introducing Private Click Measurement](https://webkit.org/blog/11529/introducing-private-click-measurement-pcm/)
///and [Private Click Measurement Draft Community Group Report](https://privacycg.github.io/private-click-measurement/).
///
///Check [UIEventAttribution](https://developer.apple.com/documentation/uikit/uieventattribution) for details.
///
///**Supported Platforms/Implementations**:
///- iOS
class UIEventAttribution {
///An 8-bit number that identifies the source of the click for attribution. Value must be between 0 and 255.
int sourceIdentifier;
///The destination URL of the attribution.
Uri destinationURL;
///A description of the source of the attribution.
String sourceDescription;
///A string that describes the entity that purchased the attributed content.
String purchaser;
UIEventAttribution(
{required this.sourceIdentifier,
required this.destinationURL,
required this.sourceDescription,
required this.purchaser});
///Gets a possible [UIEventAttribution] instance from a [Map] value.
static UIEventAttribution? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
final instance = UIEventAttribution(
sourceIdentifier: map['sourceIdentifier'],
destinationURL: (Uri.tryParse(map['destinationURL']) ?? Uri()),
sourceDescription: map['sourceDescription'],
purchaser: map['purchaser'],
);
return instance;
}
///Converts instance to a map.
Map<String, dynamic> toMap() {
return {
"sourceIdentifier": sourceIdentifier,
"destinationURL": destinationURL.toString(),
"sourceDescription": sourceDescription,
"purchaser": purchaser,
};
}
///Converts instance to a map.
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return 'UIEventAttribution{sourceIdentifier: $sourceIdentifier, destinationURL: $destinationURL, sourceDescription: $sourceDescription, purchaser: $purchaser}';
}
}

View File

@ -0,0 +1,28 @@
import 'dart:typed_data';
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
part 'ui_image.g.dart';
///Class that represents an object that manages iOS image data in your app.
///
///**Supported Platforms/Implementations**:
///- iOS
@ExchangeableObject()
class UIImage_ {
///The name of the image asset or file.
String? name;
///The name of the system symbol image.
///
///**NOTE**: available on iOS 13.0+.
String? systemName;
///The data object containing the image data.
Uint8List? data;
@ExchangeableObjectConstructor()
UIImage_({this.name, this.systemName, this.data}) {
assert(this.name != null || this.systemName != null || this.data != null);
}
}

View File

@ -0,0 +1,59 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'ui_image.dart';
// **************************************************************************
// ExchangeableObjectGenerator
// **************************************************************************
///Class that represents an object that manages iOS image data in your app.
///
///**Supported Platforms/Implementations**:
///- iOS
class UIImage {
///The name of the image asset or file.
String? name;
///The name of the system symbol image.
///
///**NOTE**: available on iOS 13.0+.
String? systemName;
///The data object containing the image data.
Uint8List? data;
UIImage({this.name, this.systemName, this.data}) {
assert(this.name != null || this.systemName != null || this.data != null);
}
///Gets a possible [UIImage] instance from a [Map] value.
static UIImage? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
final instance = UIImage(
name: map['name'],
systemName: map['systemName'],
data: map['data'],
);
return instance;
}
///Converts instance to a map.
Map<String, dynamic> toMap() {
return {
"name": name,
"systemName": systemName,
"data": data,
};
}
///Converts instance to a map.
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return 'UIImage{name: $name, systemName: $systemName, data: $data}';
}
}