import androidx.fragment.app.DialogFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager.widget.ViewPager;
+import androidx.webkit.WebSettingsCompat;
+import androidx.webkit.WebViewFeature;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
// `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
@SuppressLint("SetJavaScriptEnabled")
- private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) {
- // Store a copy of the current user agent to track changes for the return boolean.
- String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString();
-
+ private void applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) {
// Store the current URL.
nestedScrollWebView.setCurrentUrl(url);
// Store the swipe to refresh status in the nested scroll WebView.
nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
- // Apply swipe to refresh according to the default. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
- swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
+ // Update the swipe refresh layout.
+ if (defaultSwipeToRefresh) { // Swipe to refresh is enabled.
+ // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes.
+ swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+ } else { // Swipe to refresh is disabled.
+ // Disable the swipe refresh layout.
+ swipeRefreshLayout.setEnabled(false);
+ }
break;
case DomainsDatabaseHelper.ENABLED:
// Store the swipe to refresh status in the nested scroll WebView.
nestedScrollWebView.setSwipeToRefresh(true);
- // Enable swipe to refresh. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
- swipeRefreshLayout.setEnabled(true);
+ // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes.
+ swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
break;
case DomainsDatabaseHelper.DISABLED:
// Store the swipe to refresh status in the nested scroll WebView.
nestedScrollWebView.setSwipeToRefresh(false);
- // Disable swipe to refresh. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
+ // Disable swipe to refresh.
swipeRefreshLayout.setEnabled(false);
}
// Store the swipe to refresh status in the nested scroll WebView.
nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
- // Apply swipe to refresh according to the default.
- swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
+ // Update the swipe refresh layout.
+ if (defaultSwipeToRefresh) { // Swipe to refresh is enabled.
+ // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes.
+ swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+ } else { // Swipe to refresh is disabled.
+ // Disable the swipe refresh layout.
+ swipeRefreshLayout.setEnabled(false);
+ }
// Reset the pinned variables.
nestedScrollWebView.setDomainSettingsDatabaseId(-1);
if (reloadWebsite) {
nestedScrollWebView.reload();
}
-
- // Return the user agent changed status.
- return !nestedScrollWebView.getSettings().getUserAgentString().equals(initialUserAgent);
}
private void applyProxy(boolean reloadWebViews) {
});
// Update the status of swipe to refresh based on the scroll position of the nested scroll WebView. Also reinforce full screen browsing mode.
- // Once the minimum API >= 23 this can be replaced with `nestedScrollWebView.setOnScrollChangeListener()`.
- nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> {
- if (nestedScrollWebView.getSwipeToRefresh()) {
- // Only enable swipe to refresh if the WebView is scrolled to the top.
- swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
- }
+ // On API < 23, `getViewTreeObserver().addOnScrollChangedListener()` must be used, but it is a little bit buggy and appears to get garbage collected from time to time.
+ if (Build.VERSION.SDK_INT >= 23) {
+ nestedScrollWebView.setOnScrollChangeListener((view, i, i1, i2, i3) -> {
+ if (nestedScrollWebView.getSwipeToRefresh()) {
+ // Only enable swipe to refresh if the WebView is scrolled to the top.
+ swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
+ } else {
+ // Disable swipe to refresh.
+ swipeRefreshLayout.setEnabled(false);
+ }
+
+ // Reinforce the system UI visibility flags if in full screen browsing mode.
+ // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
+ if (inFullScreenBrowsingMode) {
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ }
+ });
+ } else {
+ nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> {
+ if (nestedScrollWebView.getSwipeToRefresh()) {
+ // Only enable swipe to refresh if the WebView is scrolled to the top.
+ swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
+ } else {
+ // Disable swipe to refresh.
+ swipeRefreshLayout.setEnabled(false);
+ }
- // Reinforce the system UI visibility flags if in full screen browsing mode.
- // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
- if (inFullScreenBrowsingMode) {
- /* Hide the system bars.
- * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
- rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
- }
- });
+
+ // Reinforce the system UI visibility flags if in full screen browsing mode.
+ // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
+ if (inFullScreenBrowsingMode) {
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ }
+ });
+ }
+
+ // TODO.
+ if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
+ WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_AUTO);
+ }
// Set the web chrome client.
nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
});
nestedScrollWebView.setWebViewClient(new WebViewClient() {
- // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
+ // `shouldOverrideUrlLoading` makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps.
// The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Sanitize the url.
url = sanitizeUrl(url);
+ // Handle the URL according to the type.
if (url.startsWith("http")) { // Load the URL in Privacy Browser.
// Apply the domain settings for the new URL. This doesn't do anything if the domain has not changed.
- boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
+ applyDomainSettings(nestedScrollWebView, url, true, false);
- // Check if the user agent has changed.
- if (userAgentChanged) {
- // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
- nestedScrollWebView.loadUrl(url, customHeaders);
+ // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
+ nestedScrollWebView.loadUrl(url, customHeaders);
- // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
- return true;
- } else {
- // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
- return false;
- }
+ // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
+ // Custom headers cannot be added if false is returned and the WebView handles the loading of the URL.
+ return true;
} else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
// Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);