diff --git a/modules/web/src/main/AndroidManifest.xml b/modules/web/src/main/AndroidManifest.xml index 0625d6e6..09483aef 100644 --- a/modules/web/src/main/AndroidManifest.xml +++ b/modules/web/src/main/AndroidManifest.xml @@ -7,6 +7,12 @@ + + + + + + result.confirm()) + .show(); + return true; + } + + @Override + public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { + Context ctx = view.getContext(); + ActivityDelegate.get(ctx).createDialogBuilder(ctx) + .setMessage(message) + .setNegativeButton(android.R.string.cancel, (d, w) -> result.cancel()) + .setPositiveButton(android.R.string.ok, (d, w) -> result.confirm()) + .show(); + return true; + } + + @Override + public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { + Context ctx = view.getContext(); + ActivityDelegate a = ActivityDelegate.get(ctx); + EditText text = a.createEditText(ctx); + text.setSingleLine(); + text.setText(defaultValue); + a.createDialogBuilder(ctx) + .setTitle(message).setView(text) + .setNegativeButton(android.R.string.cancel, (d, i) -> result.cancel()) + .setPositiveButton(android.R.string.ok, (d, i) -> result.confirm(text.getText().toString())).show(); + return true; + } + + @Override + public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) { + return onJsConfirm(view, url, message, result); + } + @Override public void onShowCustomView(View view, CustomViewCallback callback) { if (view instanceof ViewGroup) { @@ -197,4 +251,59 @@ public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermiss }); } } + + @Override + public void onPermissionRequest(PermissionRequest request) { + Log.d("Permissions requested: ", Arrays.toString(request.getResources())); + Map perms = new HashMap<>(); + + for (String p : request.getResources()) { + switch (p) { + case PermissionRequest.RESOURCE_AUDIO_CAPTURE: + perms.put(Manifest.permission.RECORD_AUDIO, p); + break; + case PermissionRequest.RESOURCE_VIDEO_CAPTURE: + perms.put(Manifest.permission.CAMERA, p); + break; + } + } + + if (perms.isEmpty()) { + Log.d("No permissions granted"); + request.deny(); + return; + } + + MainActivityDelegate a = MainActivityDelegate.get(getWebView().getContext()); + + if (BuildConfig.AUTO && a.isCarActivity()) { + // Activity.checkPermissions() is not supported by AA + Log.d("Granted permissions: ", perms.values()); + request.grant(perms.values().toArray(new String[0])); + return; + } + + String[] keys = perms.keySet().toArray(new String[0]); + FutureSupplier perm = a.getAppActivity().checkPermissions(keys); + perm.onCompletion((r, err) -> { + if (err != null) { + Log.e(err, "Permission request failed"); + request.deny(); + } else { + List granted = new ArrayList<>(r.length); + + for (int i = 0; i < r.length; i++) { + if (r[i] == PERMISSION_GRANTED) granted.add(perms.get(keys[i])); + } + + if (granted.isEmpty()) { + Log.d("No permissions granted"); + request.deny(); + } else { + Log.d("Granted permissions: ", granted); + request.grant(granted.toArray(new String[0])); + } + } + }); + } } diff --git a/modules/web/src/main/java/me/aap/fermata/addon/web/FermataWebView.java b/modules/web/src/main/java/me/aap/fermata/addon/web/FermataWebView.java index f6ef3516..fe327b53 100644 --- a/modules/web/src/main/java/me/aap/fermata/addon/web/FermataWebView.java +++ b/modules/web/src/main/java/me/aap/fermata/addon/web/FermataWebView.java @@ -18,6 +18,8 @@ import androidx.webkit.WebSettingsCompat; import androidx.webkit.WebViewFeature; +import java.util.List; + import me.aap.fermata.BuildConfig; import me.aap.fermata.ui.activity.FermataActivity; import me.aap.fermata.ui.activity.MainActivityDelegate; @@ -35,7 +37,7 @@ * @author Andrey Pavlenko */ public class FermataWebView extends WebView implements TextChangedListener, - TextView.OnEditorActionListener { + TextView.OnEditorActionListener, PreferenceStore.Listener { private final boolean isCar; private WebBrowserAddon addon; private FermataChromeClient chrome; @@ -60,22 +62,55 @@ public void init(WebBrowserAddon addon, FermataWebClient webClient, FermataChrom setWebViewClient(webClient); setWebChromeClient(chromeClient); WebSettings s = getSettings(); - s.setJavaScriptEnabled(true); s.setSupportZoom(true); + s.setBuiltInZoomControls(true); + s.setDisplayZoomControls(false); s.setDatabaseEnabled(true); s.setDomStorageEnabled(true); + s.setAllowFileAccess(true); s.setLoadWithOverviewMode(true); - s.setAllowUniversalAccessFromFileURLs(true); + s.setJavaScriptEnabled(true); + s.setJavaScriptCanOpenWindowsAutomatically(true); + + setDesktopMode(addon.isDesktopVersion(), false); + addon.getPreferenceStore().addBroadcastListener(this); addJavascriptInterface(createJsInterface(), FermataJsInterface.NAME); + CookieManager.getInstance().setAcceptThirdPartyCookies(this, true); if (WebViewFeature.isFeatureSupported(FORCE_DARK)) { - PreferenceStore store = addon.getPreferenceStore(); - if (store.getBooleanPref(addon.getForceDarkPref())) { + if (addon.isForceDark()) { WebSettingsCompat.setForceDark(s, WebSettingsCompat.FORCE_DARK_ON); } } } + @Override + public void onPreferenceChanged(PreferenceStore store, List> prefs) { + WebBrowserAddon addon = getAddon(); + + if ((addon != null) && prefs.contains(addon.getDesktopVersionPref())) { + setDesktopMode(store.getBooleanPref(addon.getDesktopVersionPref()), true); + } + } + + private void setDesktopMode(boolean v, boolean reload) { + WebSettings s = getSettings(); + s.setUseWideViewPort(v); + + if (v) { + String ua = s.getUserAgentString(); + int i1 = ua.indexOf('(') + 1; + int i2 = ua.indexOf(')', i1); + ua = ua.substring(0, i1) + "X11; Linux x86_64" + ua.substring(i2).replace("Mobile ", ""); + Log.d("Changing UserAgent to " + ua); + s.setUserAgentString(ua); + } else { + s.setUserAgentString(null); + } + + if (reload) reload(); + } + protected FermataJsInterface createJsInterface() { return new FermataJsInterface(this); } @@ -112,7 +147,13 @@ protected void pageLoaded(String uri) { if (f == null) return; ToolBarView.Mediator m = f.getToolBarMediator(); - if (m instanceof WebToolBarMediator) ((WebToolBarMediator) m).setAddress(a.getToolBar(), uri); + + if (m instanceof WebToolBarMediator) { + WebToolBarMediator wm = (WebToolBarMediator) m; + ToolBarView tb = a.getToolBar(); + wm.setAddress(tb, uri); + wm.setButtonsVisibility(tb, canGoBack(), canGoForward()); + } CookieManager.getInstance().flush(); } @@ -151,21 +192,33 @@ private void setTextInput(CharSequence text) { Log.d(text); loadUrl("javascript:\n" + "var e = document.activeElement;\n" + - "if (e.isContentEditable) e.innerText = '" + text + "';\n" + - "else e.value = '" + text + "';\n" + - "e.dispatchEvent(new KeyboardEvent('keyup'));" + "var text = '" + text + "';\n" + + "if (e.isContentEditable) e.innerText = text;\n" + + "else e.value = text;\n" + + "e.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true }));\n" + + "e.dispatchEvent(new KeyboardEvent('keypress', { bubbles: true }));\n" + + "e.dispatchEvent(new InputEvent('input', { bubbles: true, data: text, inputType: 'insertText' }));\n" + + "e.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true }));\n" + + "e.dispatchEvent(new Event('change', { bubbles: true }));" ); } - protected void sendEnterEvent() { + + protected void submitForm() { if (!BuildConfig.AUTO) return; loadUrl("javascript:\n" + - "var e = new KeyboardEvent('keydown',\n" + - "{ code: 'Enter', key: 'Enter', charKode: 13, keyCode: 13, view: window });\n" + - "document.activeElement.dispatchEvent(e);\n" + - "e = new KeyboardEvent('keyup',\n" + - "{ code: 'Enter', key: 'Enter', charKode: 13, keyCode: 13, view: window });\n" + - "document.activeElement.dispatchEvent(e);"); + "var ae = document.activeElement;\n" + + "if (ae.form != null) {\n" + + " ae.form.submit();\n" + + "} else {\n" + + " var e = new KeyboardEvent('keydown',\n" + + " { code: 'Enter', key: 'Enter', keyCode: 13, view: window, bubbles: true });\n" + + " ae.dispatchEvent(e);\n" + + " e = new KeyboardEvent('keyup',\n" + + " { code: 'Enter', key: 'Enter', keyCode: 13, view: window, bubbles: true });\n" + + " ae.dispatchEvent(e);\n" + + "}" + ); } public void showKeyboard(String text) { @@ -213,7 +266,7 @@ public boolean onEditorAction(TextView v, int actionId, @Nullable KeyEvent event case EditorInfo.IME_ACTION_SEND: case EditorInfo.IME_ACTION_NEXT: case EditorInfo.IME_ACTION_DONE: - sendEnterEvent(); + submitForm(); hideKeyboard(); } @@ -223,7 +276,18 @@ public boolean onEditorAction(TextView v, int actionId, @Nullable KeyEvent event @Override public boolean onInterceptTouchEvent(MotionEvent ev) { FermataChromeClient chrome = getWebChromeClient(); - if ((chrome != null) && chrome.isFullScreen()) chrome.onTouchEvent(this, ev); + + if ((chrome != null) && chrome.isFullScreen()) { + chrome.onTouchEvent(this, ev); + } else if (BuildConfig.AUTO) { + FermataActivity a = MainActivityDelegate.get(getContext()).getAppActivity(); + + if (a.isInputActive()) { + a.stopInput(this); + return true; + } + } + return super.onInterceptTouchEvent(ev); } } diff --git a/modules/web/src/main/java/me/aap/fermata/addon/web/WebBrowserAddon.java b/modules/web/src/main/java/me/aap/fermata/addon/web/WebBrowserAddon.java index 9c35090c..3e1a29e6 100644 --- a/modules/web/src/main/java/me/aap/fermata/addon/web/WebBrowserAddon.java +++ b/modules/web/src/main/java/me/aap/fermata/addon/web/WebBrowserAddon.java @@ -28,6 +28,7 @@ public class WebBrowserAddon implements FermataAddon { private static final Pref> LAST_URL = Pref.s("LAST_URL", "http://google.com"); private static final Pref FORCE_DARK = Pref.b("FORCE_DARK", false); + private static final Pref DESKTOP_VERSION = Pref.b("DESKTOP_VERSION", false); private static final Pref> BOOKMARKS = Pref.sa("BOOKMARKS"); private final SharedPreferenceStore preferenceStore; @@ -64,6 +65,22 @@ public Pref getForceDarkPref() { return FORCE_DARK; } + public boolean isForceDark() { + return getPreferenceStore().getBooleanPref(getForceDarkPref()); + } + + public Pref getDesktopVersionPref() { + return DESKTOP_VERSION; + } + + public boolean isDesktopVersion() { + return getPreferenceStore().getBooleanPref(getDesktopVersionPref()); + } + + public void setDesktopVersion(boolean v) { + getPreferenceStore().applyBooleanPref(DESKTOP_VERSION, v); + } + Map getBookmarks() { String[] p = getPreferenceStore().getStringArrayPref(BOOKMARKS); if (p.length == 0) return Collections.emptyMap(); diff --git a/modules/web/src/main/java/me/aap/fermata/addon/web/WebBrowserFragment.java b/modules/web/src/main/java/me/aap/fermata/addon/web/WebBrowserFragment.java index 5fb1d76e..5f187755 100644 --- a/modules/web/src/main/java/me/aap/fermata/addon/web/WebBrowserFragment.java +++ b/modules/web/src/main/java/me/aap/fermata/addon/web/WebBrowserFragment.java @@ -152,8 +152,9 @@ public void contributeToNavBarMenu(OverlayMenu.Builder b) { b.addItem(me.aap.fermata.R.id.refresh, me.aap.fermata.R.drawable.refresh, me.aap.fermata.R.string.refresh).setHandler(this); - if (v.canGoForward()) { - b.addItem(R.id.browser_forward, R.drawable.forward, R.string.go_forward).setHandler(this); + if (isDesktopVersionSupported()) { + b.addItem(R.id.desktop_version, R.drawable.desktop, R.string.desktop_version) + .setChecked(getAddon().isDesktopVersion()).setHandler(this); } FermataChromeClient chrome = v.getWebChromeClient(); @@ -171,6 +172,10 @@ public void contributeToNavBarMenu(OverlayMenu.Builder b) { me.aap.fermata.R.string.bookmarks).setSubmenu(this::bookmarksMenu); } + protected boolean isDesktopVersionSupported() { + return true; + } + @Override public boolean menuItemSelected(OverlayMenuItem item) { FermataWebView v = getWebView(); @@ -182,8 +187,9 @@ public boolean menuItemSelected(OverlayMenuItem item) { case me.aap.fermata.R.id.refresh: v.reload(); return true; - case R.id.browser_forward: - v.goForward(); + case R.id.desktop_version: + WebBrowserAddon addon = getAddon(); + addon.setDesktopVersion(!addon.isDesktopVersion()); return true; case R.id.fullscreen: case R.id.fullscreen_exit: @@ -197,7 +203,7 @@ public boolean menuItemSelected(OverlayMenuItem item) { return false; } - private void bookmarksMenu(OverlayMenu.Builder b) { + public void bookmarksMenu(OverlayMenu.Builder b) { WebBrowserAddon a = getAddon(); if (a == null) return; diff --git a/modules/web/src/main/java/me/aap/fermata/addon/web/WebToolBarMediator.java b/modules/web/src/main/java/me/aap/fermata/addon/web/WebToolBarMediator.java index fca15b67..c8ee55a1 100644 --- a/modules/web/src/main/java/me/aap/fermata/addon/web/WebToolBarMediator.java +++ b/modules/web/src/main/java/me/aap/fermata/addon/web/WebToolBarMediator.java @@ -13,7 +13,8 @@ import static android.view.KeyEvent.KEYCODE_DPAD_CENTER; import static android.view.KeyEvent.KEYCODE_ENTER; import static android.view.KeyEvent.KEYCODE_NUMPAD_ENTER; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.LEFT; import static me.aap.utils.ui.UiUtils.toPx; @@ -31,23 +32,39 @@ public static WebToolBarMediator getInstance() { @Override public void enable(ToolBarView tb, ActivityFragment f) { WebBrowserFragment b = (WebBrowserFragment) f; + FermataWebView wv = b.getWebView(); EditText t = createAddress(tb, b); String url = b.getUrl(); if (url != null) t.setText(url); addView(tb, t, R.id.browser_addr, LEFT); + addButton(tb, R.drawable.forward, v -> wv.goForward(), R.id.browser_forward, LEFT); + addButton(tb, me.aap.utils.R.drawable.back, v -> wv.goBack(), me.aap.utils.R.id.tool_bar_back_button, LEFT); + addButton(tb, R.drawable.clear, v -> t.setText(""), R.id.browser_addr_clear); + addButton(tb, me.aap.fermata.R.drawable.bookmark_filled, v -> onBookmarksButtonClick(b), me.aap.fermata.R.id.bookmarks); + setButtonsVisibility(tb, wv.canGoBack(), wv.canGoForward()); ToolBarView.Mediator.super.enable(tb, f); } + private void onBookmarksButtonClick(WebBrowserFragment f) { + f.getActivityDelegate().getToolBarMenu().show(b -> f.bookmarksMenu(b)); + } + public void setAddress(ToolBarView tb, String addr) { EditText et = tb.findViewById(R.id.browser_addr); if (et != null) et.setText(addr); } + public void setButtonsVisibility(ToolBarView tb, boolean back, boolean forward) { + tb.findViewById(me.aap.utils.R.id.tool_bar_back_button).setVisibility(back ? VISIBLE : GONE); + tb.findViewById(R.id.browser_forward).setVisibility(forward ? VISIBLE : GONE); + } + private EditText createAddress(ToolBarView tb, WebBrowserFragment f) { Context ctx = tb.getContext(); int p = (int) toPx(ctx, 2); EditText t = createEditText(tb); - ConstraintLayout.LayoutParams lp = setLayoutParams(t, MATCH_PARENT, WRAP_CONTENT); + ConstraintLayout.LayoutParams lp = setLayoutParams(t, 0, WRAP_CONTENT); + lp.horizontalWeight = 2; t.setBackgroundResource(me.aap.utils.R.drawable.tool_bar_edittext_bg); t.setOnKeyListener((v, keyCode, event) -> onKey(f, t, keyCode, event)); t.setMaxLines(1); diff --git a/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeAddon.java b/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeAddon.java index 310d7f61..552feb96 100644 --- a/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeAddon.java +++ b/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeAddon.java @@ -16,6 +16,7 @@ @SuppressWarnings("unused") public class YoutubeAddon extends WebBrowserAddon { private static final Pref YT_FORCE_DARK = Pref.b("YT_FORCE_DARK", false); + private static final Pref YT_DESKTOP_VERSION = Pref.b("YT_DESKTOP_VERSION", false); private static final Pref> VIDEO_SCALE = Pref.s("VIDEO_SCALE", VideoScale.CONTAIN::prefName); @Override @@ -34,6 +35,11 @@ public Pref getForceDarkPref() { return YT_FORCE_DARK; } + @Override + public Pref getDesktopVersionPref() { + return YT_DESKTOP_VERSION; + } + VideoScale getScale() { switch (getPreferenceStore().getStringPref(VIDEO_SCALE)) { case "fill": diff --git a/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeFragment.java b/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeFragment.java index fb8fe68d..c478b938 100644 --- a/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeFragment.java +++ b/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeFragment.java @@ -131,4 +131,8 @@ protected YoutubeWebView getWebView() { View v = getView(); return (v != null) ? v.findViewById(R.id.ytWebView) : null; } + + protected boolean isDesktopVersionSupported() { + return false; + } } diff --git a/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeWebView.java b/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeWebView.java index a20b1116..901a08e0 100644 --- a/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeWebView.java +++ b/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeWebView.java @@ -54,6 +54,17 @@ protected void pageLoaded(String uri) { CookieManager.getInstance().flush(); } + protected void submitForm() { + if (!me.aap.fermata.BuildConfig.AUTO) return; + loadUrl("javascript:\n" + + "var e = new KeyboardEvent('keydown',\n" + + "{ code: 'Enter', key: 'Enter', keyCode: 13, view: window, bubbles: true });\n" + + "document.activeElement.dispatchEvent(e);\n" + + "e = new KeyboardEvent('keyup',\n" + + "{ code: 'Enter', key: 'Enter', keyCode: 13, view: window, bubbles: true });\n" + + "document.activeElement.dispatchEvent(e);"); + } + void attachListeners() { String debug = BuildConfig.DEBUG ? JS_EVENT + "(" + JS_VIDEO_FOUND + ", null);\n" : ""; String scale = getAddon().getScale().prefName(); diff --git a/modules/web/src/main/res/drawable/clear.xml b/modules/web/src/main/res/drawable/clear.xml new file mode 100644 index 00000000..2cca6e3d --- /dev/null +++ b/modules/web/src/main/res/drawable/clear.xml @@ -0,0 +1,9 @@ + + + diff --git a/modules/web/src/main/res/drawable/desktop.xml b/modules/web/src/main/res/drawable/desktop.xml new file mode 100644 index 00000000..beb7bc05 --- /dev/null +++ b/modules/web/src/main/res/drawable/desktop.xml @@ -0,0 +1,9 @@ + + + diff --git a/modules/web/src/main/res/values-ru/strings.xml b/modules/web/src/main/res/values-ru/strings.xml index d4b5c667..0bffcaac 100644 --- a/modules/web/src/main/res/values-ru/strings.xml +++ b/modules/web/src/main/res/values-ru/strings.xml @@ -1,7 +1,7 @@ Форсировать темный стиль, если поддерживается - Вперед + Версия для ПК Полноэкранный режим Выйти из полноэкранного режима Заполнить пропорционально diff --git a/modules/web/src/main/res/values/idx.xml b/modules/web/src/main/res/values/idx.xml index 08d58d48..d5a1171c 100644 --- a/modules/web/src/main/res/values/idx.xml +++ b/modules/web/src/main/res/values/idx.xml @@ -1,7 +1,9 @@ + + diff --git a/modules/web/src/main/res/values/strings.xml b/modules/web/src/main/res/values/strings.xml index fc4fa5f0..7138c6e5 100644 --- a/modules/web/src/main/res/values/strings.xml +++ b/modules/web/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ Force dark mode, if supported - Go forward + Desktop version Full screen Exit full screen URL