This commit is contained in:
Lorenzo Pichilli 2022-04-25 17:39:04 +02:00
parent 07768cdc81
commit e07ba2a675
12 changed files with 376 additions and 30 deletions

View File

@ -1,3 +1,7 @@
## 5.4.2
- Added `setActionButton` method to `ChromeSafariBrowser` class
## 5.4.1+2
- Fixed "Android ServiceWorkerControllerCompat.setServiceWorkerClient(null) makes Webivew Plugin Crashes" [#1151](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1151)

View File

@ -34,7 +34,7 @@ public class ActionBroadcastReceiver extends BroadcastReceiver {
obj.put("url", url);
obj.put("title", title);
obj.put("id", id);
channel.invokeMethod("onChromeSafariBrowserMenuItemActionPerform", obj);
channel.invokeMethod("onChromeSafariBrowserItemActionPerform", obj);
}
}
}

View File

@ -3,12 +3,14 @@ package com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsIntent;
@ -16,7 +18,10 @@ import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsSession;
import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.types.CustomTabsActionButton;
import com.pichillilorenzo.flutter_inappwebview.types.CustomTabsMenuItem;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -37,6 +42,10 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
protected boolean onChromeSafariBrowserOpened = false;
protected boolean onChromeSafariBrowserCompletedInitialLoad = false;
public ChromeSafariBrowserManager manager;
public String initialUrl;
public List<CustomTabsMenuItem> menuItems = new ArrayList<>();
@Nullable
public CustomTabsActionButton actionButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -55,20 +64,23 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
channel = new MethodChannel(manager.plugin.messenger, "com.pichillilorenzo/flutter_chromesafaribrowser_" + id);
channel.setMethodCallHandler(this);
final String url = b.getString("url");
initialUrl = b.getString("url");
options = new ChromeCustomTabsOptions();
options.parse((HashMap<String, Object>) b.getSerializable("options"));
final List<HashMap<String, Object>> menuItemList = (List<HashMap<String, Object>>) b.getSerializable("menuItemList");
options.parse((Map<String, Object>) b.getSerializable("options"));
actionButton = CustomTabsActionButton.fromMap((Map<String, Object>) b.getSerializable("actionButton"));
List<Map<String, Object>> menuItemList = (List<Map<String, Object>>) b.getSerializable("menuItemList");
for (Map<String, Object> menuItem : menuItemList) {
menuItems.add(CustomTabsMenuItem.fromMap(menuItem));
}
final ChromeCustomTabsActivity chromeCustomTabsActivity = this;
customTabActivityHelper = new CustomTabActivityHelper();
customTabActivityHelper.setConnectionCallback(new CustomTabActivityHelper.ConnectionCallback() {
@Override
public void onCustomTabsConnected() {
customTabsConnected(url, menuItemList);
customTabsConnected();
}
@Override
@ -140,13 +152,13 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
}
}
public void customTabsConnected (String url, List<HashMap<String, Object>> menuItemList) {
public void customTabsConnected() {
customTabsSession = customTabActivityHelper.getSession();
Uri uri = Uri.parse(url);
Uri uri = Uri.parse(initialUrl);
customTabActivityHelper.mayLaunchUrl(uri, null, null);
builder = new CustomTabsIntent.Builder(customTabsSession);
prepareCustomTabs(menuItemList);
prepareCustomTabs();
CustomTabsIntent customTabsIntent = builder.build();
prepareCustomTabsIntent(customTabsIntent);
@ -154,7 +166,7 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
CustomTabActivityHelper.openCustomTab(this, customTabsIntent, uri, CHROME_CUSTOM_TAB_REQUEST_CODE);
}
private void prepareCustomTabs(List<HashMap<String, Object>> menuItemList) {
private void prepareCustomTabs() {
if (options.addDefaultShareMenuItem != null) {
builder.setShareState(options.addDefaultShareMenuItem ?
CustomTabsIntent.SHARE_STATE_ON : CustomTabsIntent.SHARE_STATE_OFF);
@ -173,10 +185,21 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
builder.setUrlBarHidingEnabled(options.enableUrlBarHiding);
builder.setInstantAppsEnabled(options.instantAppsEnabled);
for (HashMap<String, Object> menuItem : menuItemList) {
int id = (int) menuItem.get("id");
String label = (String) menuItem.get("label");
builder.addMenuItem(label, createPendingIntent(id));
for (CustomTabsMenuItem menuItem : menuItems) {
builder.addMenuItem(menuItem.getLabel(),
createPendingIntent(menuItem.getId()));
}
if (actionButton != null) {
byte[] data = actionButton.getIcon();
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inMutable = true;
Bitmap bmp = BitmapFactory.decodeByteArray(
data, 0, data.length, bitmapOptions
);
builder.setActionButton(bmp, actionButton.getDescription(),
createPendingIntent(actionButton.getId()),
actionButton.isShouldTint());
}
}

View File

@ -41,15 +41,22 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
switch (call.method) {
case "open":
{
if (plugin != null) {
String url = (String) call.argument("url");
HashMap<String, Object> options = (HashMap<String, Object>) call.argument("options");
HashMap<String, Object> actionButton = (HashMap<String, Object>) call.argument("actionButton");
List<HashMap<String, Object>> menuItemList = (List<HashMap<String, Object>>) call.argument("menuItemList");
open(plugin.activity, id, url, options, menuItemList, result);
open(plugin.activity, id, url, options, actionButton, menuItemList, result);
} else {
result.success(false);
}
break;
case "isAvailable":
result.success(CustomTabActivityHelper.isAvailable(plugin.activity));
if (plugin != null) {
result.success(CustomTabActivityHelper.isAvailable(plugin.activity));
} else {
result.success(false);
}
break;
default:
result.notImplemented();
@ -57,6 +64,7 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
}
public void open(Activity activity, String id, String url, HashMap<String, Object> options,
HashMap<String, Object> actionButton,
List<HashMap<String, Object>> menuItemList, MethodChannel.Result result) {
Intent intent = null;
@ -66,6 +74,7 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
extras.putString("id", id);
extras.putString("managerId", this.id);
extras.putSerializable("options", options);
extras.putSerializable("actionButton", (Serializable) actionButton);
extras.putSerializable("menuItemList", (Serializable) menuItemList);
Boolean isSingleInstance = (Boolean) Util.getOrDefault(options, "isSingleInstance", false);

View File

@ -18,9 +18,9 @@ public class TrustedWebActivity extends ChromeCustomTabsActivity {
public TrustedWebActivityIntentBuilder builder;
@Override
public void customTabsConnected (String url, List<HashMap<String, Object>> menuItemList) {
public void customTabsConnected() {
customTabsSession = customTabActivityHelper.getSession();
Uri uri = Uri.parse(url);
Uri uri = Uri.parse(initialUrl);
customTabActivityHelper.mayLaunchUrl(uri, null, null);
builder = new TrustedWebActivityIntentBuilder(uri);

View File

@ -0,0 +1,104 @@
package com.pichillilorenzo.flutter_inappwebview.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.Map;
public class CustomTabsActionButton {
private int id;
@NonNull
private byte[] icon;
@NonNull
private String description;
private boolean shouldTint;
public CustomTabsActionButton(int id, @NonNull byte[] icon, @NonNull String description, boolean shouldTint) {
this.id = id;
this.icon = icon;
this.description = description;
this.shouldTint = shouldTint;
}
@Nullable
public static CustomTabsActionButton fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
int id = (int) map.get("id");
byte[] icon = (byte[]) map.get("icon");
String description = (String) map.get("description");
boolean shouldTint = (boolean) map.get("shouldTint");
return new CustomTabsActionButton(id, icon, description, shouldTint);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NonNull
public byte[] getIcon() {
return icon;
}
public void setIcon(@NonNull byte[] icon) {
this.icon = icon;
}
@NonNull
public String getDescription() {
return description;
}
public void setDescription(@NonNull String description) {
this.description = description;
}
public boolean isShouldTint() {
return shouldTint;
}
public void setShouldTint(boolean shouldTint) {
this.shouldTint = shouldTint;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomTabsActionButton that = (CustomTabsActionButton) o;
if (id != that.id) return false;
if (shouldTint != that.shouldTint) return false;
if (!Arrays.equals(icon, that.icon)) return false;
return description.equals(that.description);
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + Arrays.hashCode(icon);
result = 31 * result + description.hashCode();
result = 31 * result + (shouldTint ? 1 : 0);
return result;
}
@Override
public String toString() {
return "CustomTabsActionButton{" +
"id=" + id +
", icon=" + Arrays.toString(icon) +
", description='" + description + '\'' +
", shouldTint=" + shouldTint +
'}';
}
}

View File

@ -0,0 +1,72 @@
package com.pichillilorenzo.flutter_inappwebview.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.Map;
public class CustomTabsMenuItem {
private int id;
@NonNull
private String label;
public CustomTabsMenuItem(int id, @NonNull String label) {
this.id = id;
this.label = label;
}
@Nullable
public static CustomTabsMenuItem fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
int id = (int) map.get("id");
String label = (String) map.get("label");
return new CustomTabsMenuItem(id, label);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NonNull
public String getLabel() {
return label;
}
public void setLabel(@NonNull String label) {
this.label = label;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomTabsMenuItem that = (CustomTabsMenuItem) o;
if (id != that.id) return false;
return label.equals(that.label);
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + label.hashCode();
return result;
}
@Override
public String toString() {
return "CustomTabsMenuItem{" +
"id=" + id +
", label='" + label + '\'' +
'}';
}
}

View File

@ -4148,7 +4148,7 @@ setTimeout(function() {
if (Platform.isAndroid) {
await pageLoaded.future;
expect(await controller.evaluateJavascript(source: "document.body"),
isNull);
isEmpty);
} else if (Platform.isIOS) {
expect(pageLoaded.future, doesNotComplete);
}
@ -5847,7 +5847,57 @@ setTimeout(function() {
expect(chromeSafariBrowser.isOpened(), false);
});
test('add custom menu item', () async {
var chromeSafariBrowser = new MyChromeSafariBrowser();
chromeSafariBrowser.addMenuItem(ChromeSafariBrowserMenuItem(
id: 2,
label: 'Custom item menu 1',
action: (url, title) {
print('Custom item menu 1 clicked!');
}));
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(
url: Uri.parse("https://github.com/flutter"));
await chromeSafariBrowser.browserCreated.future;
expect(chromeSafariBrowser.isOpened(), true);
expect(() async {
await chromeSafariBrowser.open(url: Uri.parse("https://flutter.dev"));
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>()));
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await chromeSafariBrowser.browserClosed.future;
expect(chromeSafariBrowser.isOpened(), false);
});
group('Android Custom Tabs', () {
test('add custom action button', () async {
var chromeSafariBrowser = new MyChromeSafariBrowser();
var actionButtonIcon = await rootBundle.load('test_assets/images/flutter-logo.png');
chromeSafariBrowser.setActionButton(ChromeSafariBrowserActionButton(
id: 1,
description: 'Action Button description',
icon: actionButtonIcon.buffer.asUint8List(),
action: (url, title) {
print('Action Button 1 clicked!');
}));
expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(
url: Uri.parse("https://github.com/flutter"));
await chromeSafariBrowser.browserCreated.future;
expect(chromeSafariBrowser.isOpened(), true);
expect(() async {
await chromeSafariBrowser.open(url: Uri.parse("https://flutter.dev"));
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>()));
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close();
await chromeSafariBrowser.browserClosed.future;
expect(chromeSafariBrowser.isOpened(), false);
});
test('Custom Tabs single instance', () async {
var chromeSafariBrowser = new MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false);

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'main.dart';
@ -32,8 +33,20 @@ class _ChromeSafariBrowserExampleScreenState
extends State<ChromeSafariBrowserExampleScreen> {
@override
void initState() {
rootBundle.load('assets/images/flutter-logo.png').then((actionButtonIcon) {
widget.browser.setActionButton(ChromeSafariBrowserActionButton(
id: 1,
description: 'Action Button description',
icon: actionButtonIcon.buffer.asUint8List(),
action: (url, title) {
print('Action Button 1 clicked!');
print(url);
print(title);
}));
});
widget.browser.addMenuItem(ChromeSafariBrowserMenuItem(
id: 1,
id: 2,
label: 'Custom item menu 1',
action: (url, title) {
print('Custom item menu 1 clicked!');
@ -41,7 +54,7 @@ class _ChromeSafariBrowserExampleScreenState
print(title);
}));
widget.browser.addMenuItem(ChromeSafariBrowserMenuItem(
id: 2,
id: 3,
label: 'Custom item menu 2',
action: (url, title) {
print('Custom item menu 2 clicked!');

View File

@ -169,6 +169,20 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
this.url = url.toString();
urlController.text = this.url;
});
if (url?.host == 'github.com') {
await controller.loadUrl(
urlRequest: URLRequest(
url: Uri.parse('https://flutter-header-echo.herokuapp.com/'),
headers: {
'test_header': 'flutter_test_header'
}));
final String? currentUrl = (await controller.getUrl())?.toString();
print(currentUrl);
await Future.delayed(Duration(seconds: 2));
final String? content = await controller.evaluateJavascript(
source: 'document.documentElement.innerText');
print(content);
}
},
onLoadError: (controller, url, code, message) {
pullToRefreshController.endRefreshing();

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:collection';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/src/util.dart';
@ -40,6 +41,7 @@ class ChromeSafariBrowser {
///View ID used internally.
late final String id;
ChromeSafariBrowserActionButton? _actionButton;
Map<int, ChromeSafariBrowserMenuItem> _menuItems = new HashMap();
bool _isOpened = false;
late MethodChannel _channel;
@ -66,12 +68,14 @@ class ChromeSafariBrowser {
onClosed();
this._isOpened = false;
break;
case "onChromeSafariBrowserMenuItemActionPerform":
case "onChromeSafariBrowserItemActionPerform":
String url = call.arguments["url"];
String title = call.arguments["title"];
int id = call.arguments["id"].toInt();
if (this._menuItems[id] != null) {
this._menuItems[id]!.action(url, title);
if (this._actionButton?.id == id) {
this._actionButton?.action(url, title);
} else if (this._menuItems[id] != null) {
this._menuItems[id]?.action(url, title);
}
break;
default:
@ -91,13 +95,14 @@ class ChromeSafariBrowser {
List<Map<String, dynamic>> menuItemList = [];
_menuItems.forEach((key, value) {
menuItemList.add({"id": value.id, "label": value.label});
menuItemList.add(value.toMap());
});
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('id', () => id);
args.putIfAbsent('url', () => url.toString());
args.putIfAbsent('options', () => options?.toMap() ?? {});
args.putIfAbsent('actionButton', () => _actionButton?.toMap());
args.putIfAbsent('menuItemList', () => menuItemList);
await _sharedChannel.invokeMethod('open', args);
this._isOpened = true;
@ -109,6 +114,16 @@ class ChromeSafariBrowser {
await _channel.invokeMethod("close", args);
}
///Set a custom action button.
///
///**NOTE**: Not available in a Trusted Web Activity.
///
///**Supported Platforms/Implementations**:
///- Android ([Official API - CustomTabsIntent.Builder.setActionButton ](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsIntent.Builder#setActionButton(android.graphics.Bitmap,%20java.lang.String,%20android.app.PendingIntent,%20boolean)))
void setActionButton(ChromeSafariBrowserActionButton actionButton) {
this._actionButton = actionButton;
}
///Adds a [ChromeSafariBrowserMenuItem] to the menu.
///
///**NOTE**: Not available in an Android Trusted Web Activity.
@ -164,11 +179,54 @@ class ChromeSafariBrowser {
}
}
///Class that represents a custom action button for a [ChromeSafariBrowser] instance.
///
///**NOTE**: Not available in an Android Trusted Web Activity.
class ChromeSafariBrowserActionButton {
///The action button id. It should be different from the [ChromeSafariBrowserMenuItem.id].
int id;
///The icon byte data.
Uint8List icon;
///The description for the button. To be used for accessibility.
String description;
///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;
ChromeSafariBrowserActionButton(
{required this.id, required this.icon,
required this.description, required this.action,
this.shouldTint = false});
Map<String, dynamic> toMap() {
return {
"id": id,
"icon": icon,
"description": description,
"shouldTint": shouldTint
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents a custom menu item for a [ChromeSafariBrowser] instance.
///
///**NOTE**: Not available in an Android Trusted Web Activity.
class ChromeSafariBrowserMenuItem {
///The menu item id
///The menu item id. It should be different from [ChromeSafariBrowserActionButton.id].
int id;
///The label of the menu item

View File

@ -1,5 +1,4 @@
import 'dart:collection';
import 'dart:io';
import 'dart:typed_data';
import 'dart:convert';
import 'dart:ui';