Skip to content

Commit

Permalink
Added enableDropDownWorkaroud webview option for Android to enable a …
Browse files Browse the repository at this point in the history
…temporary workaround for html dropdowns (issue #182)
  • Loading branch information
pichillilorenzo committed Dec 11, 2019
1 parent 7f2eea6 commit ebfd521
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 93 deletions.
180 changes: 92 additions & 88 deletions .idea/workspace.xml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- Added `regexToCancelSubFramesLoading` webview option for Android to cancel subframe requests on `shouldOverrideUrlLoading` event based on a Regular Expression
- Updated default value for `domStorageEnabled` option to `true` for Android
- Fix for Android `InAppBrowser` for some controller methods not exposed.
- Merge "Fixes null error when calling getOptions for InAppBrowser class" [#214](https://github.com/pichillilorenzo/flutter_inappwebview/pull/214) (thanks to [panndoraBoo](https://github.com/panndoraBoo))
- Added `enableDropDownWorkaroud` webview option for Android to enable a temporary workaround for html dropdowns (issue [#182](https://github.com/pichillilorenzo/flutter_inappwebview/issues/182))

### BREAKING CHANGES

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
* `thirdPartyCookiesEnabled`: Boolean value to enable third party cookies in the WebView.
* `hardwareAcceleration`: Boolean value to enable Hardware Acceleration in the WebView.
* `supportMultipleWindows`: Sets whether the WebView whether supports multiple windows.
* `regexToCancelSubFramesLoading`: Regular expression used by `shouldOverrideUrlLoading` event to cancel navigation for frames that are not the main frame. If the url request of a subframe matches the regular expression, then the request of that subframe is canceled.

##### `InAppWebView` iOS-specific options

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
Expand All @@ -20,8 +21,13 @@
import android.webkit.WebHistoryItem;
import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.ListView;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;

import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlocker;
import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerAction;
Expand All @@ -31,6 +37,7 @@
import com.pichillilorenzo.flutter_inappwebview.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.Util;

import java.io.ByteArrayOutputStream;
Expand Down Expand Up @@ -507,6 +514,128 @@ final public class InAppWebView extends InputAwareWebView {
" };" +
"})(window.fetch);";

// Android API 19+
static final String dropDownWorkaroundJS = "(function() {" +
" function getIndexSelectValues(select) {" +
" var result = [];" +
" var options = select && select.options;" +
" for (var i = 0, iLen=options.length; i < iLen; i++) {" +
" var opt = options[i];" +
" if (opt.selected) {" +
" result.push(i);" +
" }" +
" }" +
" return result;" +
" }" +
" function setMultipleValues(select, values) {" +
" var options = select && select.options;" +
" for (var i = 0, iLen=options.length; i < iLen; i++) {" +
" var opt = options[i];" +
" opt.selected = values.indexOf(opt.value) >= 0;" +
" }" +
" }" +
" function addDivWrapper(selectElement) {" +
" var divElement = document.createElement('div');" +
" divElement.flutterInAppWebViewSelect = selectElement;" +
" divElement.class = 'flutterInAppWebViewSelect';" +
" divElement.style.position = 'absolute';" +
" divElement.style.zIndex = 99999999;" +
" divElement.style.backgroundColor = 'transparent';" +
" selectElement.flutterInAppWebViewDivWrapper = divElement;" +
" updateBoundingClientRectDivWrapper(selectElement);" +
" var mutationObserver = new MutationObserver(function(mutations) {" +
" mutations.forEach(function(mutation) {" +
" updateBoundingClientRectDivWrapper(selectElement);" +
" });" +
" });" +
" mutationObserver.observe(selectElement, {" +
" attributes: true," +
" attributeFilter: ['style']" +
" });" +
" selectElement.mutationObserver = mutationObserver;" +
" var clickEventListener = function(event) {" +
" var self = this;" +
" event.preventDefault();" +
" this.flutterInAppWebViewSelect.focus();" +
" var options = [];" +
" var optionElements = this.flutterInAppWebViewSelect.querySelectorAll('option');" +
" for (var i = 0; i < optionElements.length; i++) {" +
" var optionElement = optionElements[i];" +
" options.push({" +
" key: optionElement.textContent," +
" value: optionElement.value" +
" });" +
" }" +
" var isMultiple = !!this.flutterInAppWebViewSelect.multiple;" +
" window." + JavaScriptBridgeInterface.name + ".callHandler('flutterInAppWebViewDropDownWorkaroud', getIndexSelectValues(this.flutterInAppWebViewSelect), isMultiple, options).then(function(result) {" +
" if (result != null && result.values != null) {" +
" if (!isMultiple) {" +
" if (result.values.length > 0) {" +
" self.flutterInAppWebViewSelect.value = result.values[0];" +
" }" +
" } else {" +
" setMultipleValues(self.flutterInAppWebViewSelect, result.values);" +
" }" +
" }" +
" self.flutterInAppWebViewSelect.blur();" +
" });" +
" };" +
" divElement.addEventListener('click', clickEventListener);" +
" divWithEventListeners.push({" +
" divElement: divElement," +
" clickEvent: clickEventListener" +
" });" +
" document.body.appendChild(divElement);" +
" }" +
" function removeDivWrapper(selectElement) {" +
" if (selectElement.flutterInAppWebViewDivWrapper) {" +
" divWithEventListeners.splice(divWithEventListeners.indexOf(selectElement.flutterInAppWebViewDivWrapper), 1);" +
" document.body.removeChild(selectElement.flutterInAppWebViewDivWrapper);" +
" }" +
" }" +
" function updateBoundingClientRectDivWrapper(selectElement) {" +
" selectElement.flutterInAppWebViewDivWrapper.style.width = selectElement.getBoundingClientRect().width + 'px';" +
" selectElement.flutterInAppWebViewDivWrapper.style.height = selectElement.getBoundingClientRect().height + 'px';" +
" selectElement.flutterInAppWebViewDivWrapper.style.top = selectElement.getBoundingClientRect().y + 'px';" +
" selectElement.flutterInAppWebViewDivWrapper.style.left = selectElement.getBoundingClientRect().x + 'px';" +
" }" +
" var selectElements = document.querySelectorAll('select');" +
" var divWithEventListeners = [];" +
" for(var selectElement of selectElements) {" +
" addDivWrapper(selectElement);" +
" }" +
" window.addEventListener('resize', function(event) {" +
" for(var divWithEventListener of divWithEventListeners) {" +
" var divElement = divWithEventListener.divElement;" +
" var selectElement = divElement.flutterInAppWebViewSelect;" +
" divElement.style.width = selectElement.getBoundingClientRect().width + 'px';" +
" divElement.style.height = selectElement.getBoundingClientRect().height + 'px';" +
" divElement.style.top = selectElement.getBoundingClientRect().y + 'px';" +
" divElement.style.left = selectElement.getBoundingClientRect().x + 'px';" +
" }" +
" });" +
" var mutationObserver = new MutationObserver(function(mutations) {" +
" mutations.forEach(function(mutation) {" +
" for(var nodeElement of mutation.addedNodes) {" +
" if (nodeElement.tagName == 'SELECT') {" +
" addDivWrapper(nodeElement);" +
" }" +
" }" +
" for(var nodeElement of mutation.removedNodes) {" +
" if (nodeElement.tagName == 'SELECT') {" +
" removeDivWrapper(nodeElement);" +
" if (nodeElement.mutationObserver) {" +
" nodeElement.mutationObserver.disconnect();" +
" }" +
" }" +
" }" +
" });" +
" });" +
" mutationObserver.observe(document.body, {" +
" childList: true" +
" });" +
"})();";


public InAppWebView(Context context) {
super(context);
Expand Down Expand Up @@ -1341,6 +1470,72 @@ public void printCurrentPage() {
new PrintAttributes.Builder().build());
}

public void showDropDownWorkaroud(final List<Integer> selectedValues, final List<List<String>> values, final boolean isMultiSelect, final DropDownWorkaroudCallback callback) {
FrameLayout layout = new FrameLayout(getContext());

final List<String> listViewValues = new ArrayList<String>();
for(List<String> value : values) {
listViewValues.add(value.get(0));
}

ListView listView = new ListView(registrar.activeContext());
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(registrar.activeContext(), (!isMultiSelect) ? android.R.layout.simple_list_item_1 : android.R.layout.simple_list_item_multiple_choice, listViewValues);
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_list_item_multiple_choice);
listView.setAdapter(spinnerArrayAdapter);


AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(registrar.activeContext(), R.style.Theme_AppCompat_Dialog_Alert);
final AlertDialog alertDialog = alertDialogBuilder.create();

final List<String> result = new ArrayList<>();

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String value = values.get(position).get(1);
if (!isMultiSelect) {
result.add(value);
//callback.result(result);
alertDialog.dismiss();
} else {
if (!result.contains(value)) {
result.add(value);
} else {
result.remove(value);
}
}
}
});

if (isMultiSelect) {
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
listView.setItemsCanFocus(false);

for(Integer selectedValueIndex : selectedValues) {
listView.setItemChecked(selectedValueIndex, true);
String value = values.get(selectedValueIndex).get(1);
result.add(value);
}
}

alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
callback.result(result);
}
});

layout.addView(listView);
alertDialog.setView(layout);
alertDialog.show();
}

public static class DropDownWorkaroudCallback {
public void result(List<String> value) {

}
}

@Override
public void dispose() {
super.dispose();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.pichillilorenzo.flutter_inappwebview.InAppWebView;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.net.http.SslCertificate;
Expand Down Expand Up @@ -227,6 +226,10 @@ public void onPageFinished(WebView view, String url) {

String js = InAppWebView.platformReadyJS.replaceAll("[\r\n]+", "");

if (webView.options.dropDownWorkaroudEnabled) {
js += InAppWebView.dropDownWorkaroundJS.replaceAll("[\r\n]+", "");
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(js, (ValueCallback<String>) null);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public class InAppWebViewOptions extends Options {
public Boolean hardwareAcceleration = true;
public Boolean supportMultipleWindows = false;
public String regexToCancelSubFramesLoading;
public Boolean dropDownWorkaroudEnabled = false;

@Override
public Object onParse(Map.Entry<String, Object> pair) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@

import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.flutter.plugin.common.MethodChannel;
Expand Down Expand Up @@ -251,7 +257,7 @@ else if (obj instanceof FlutterWebView)
}

@JavascriptInterface
public void _callHandler(final String handlerName, final String _callHandlerID, String args) {
public void _callHandler(final String handlerName, final String _callHandlerID, final String args) {
final InAppWebView webView = (inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView;

final Map<String, Object> obj = new HashMap<>();
Expand All @@ -266,6 +272,53 @@ public void _callHandler(final String handlerName, final String _callHandlerID,
handler.post(new Runnable() {
@Override
public void run() {

// workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/182
if (handlerName.equals("flutterInAppWebViewDropDownWorkaroud")) {
try {
JSONArray jsonArray = new JSONArray(args);

List<Integer> selectedValues = new ArrayList<>();
JSONArray jsonSelectedValues = jsonArray.getJSONArray(0);
for(int i = 0; i < jsonSelectedValues.length(); i++) {
Integer selectedValue = jsonSelectedValues.getInt(i);
selectedValues.add(selectedValue);
}

boolean isMultiSelect = jsonArray.getBoolean(1);

List<List<String>> values = new ArrayList<>();
JSONArray options = jsonArray.getJSONArray(2);

Log.d(LOG_TAG, options.toString());
for(int i = 0; i < options.length(); i++) {
JSONObject option = options.getJSONObject(i);

List<String> value = new ArrayList<>();
value.add(option.getString("key"));
value.add(option.getString("value"));

values.add(value);
}

webView.showDropDownWorkaroud(selectedValues, values, isMultiSelect, new InAppWebView.DropDownWorkaroudCallback() {
@Override
public void result(List<String> values) {
String value = "{values: " + (new JSONArray(values)) + "}";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("if(window." + name + "[" + _callHandlerID + "] != null) {window." + name + "[" + _callHandlerID + "](" + value + "); delete window." + name + "[" + _callHandlerID + "];}", (ValueCallback<String>) null);
}
else {
webView.loadUrl("javascript:if(window." + name + "[" + _callHandlerID + "] != null) {window." + name + "[" + _callHandlerID + "](" + value + "); delete window." + name + "[" + _callHandlerID + "];}");
}
}
});
} catch (JSONException e) {
e.printStackTrace();
}
return;
}

if (handlerName.equals("onPrint") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.printCurrentPage();
}
Expand Down
1 change: 1 addition & 0 deletions lib/src/in_app_webview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const javaScriptHandlerForbiddenNames = [
"onAjaxProgress",
"shouldInterceptFetchRequest",
"onPrint",
"flutterInAppWebViewDropDownWorkaroud"
];

///InAppWebView Widget class.
Expand Down
Loading

0 comments on commit ebfd521

Please sign in to comment.