]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Show the bottom app bar when opening a new tab. https://redmine.stoutner.com/issues/729
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index ed25a37d28b3d7689ec18fa45285ff4ebe85de80..d2881d7ebe8887c6d299ca1ec9b1b55cdc6b7e44 100644 (file)
@@ -107,6 +107,7 @@ import androidx.core.content.res.ResourcesCompat;
 import androidx.core.view.GravityCompat;
 import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 import androidx.viewpager.widget.ViewPager;
 import androidx.webkit.WebSettingsCompat;
@@ -126,6 +127,7 @@ import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
 import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
+import com.stoutner.privacybrowser.definitions.PendingDialog;
 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
@@ -158,12 +160,16 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+
 import java.text.NumberFormat;
+
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -191,6 +197,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
     public static boolean restartFromBookmarksActivity;
 
+    // Define the public static variables.
+    public static ArrayList<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
+
     // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     public static String currentBookmarksFolder;
@@ -247,21 +256,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private ArrayList<List<String[]>> ultraList;
     private ArrayList<List<String[]>> ultraPrivacy;
 
-    // Declare the class variables
-    private BroadcastReceiver orbotStatusBroadcastReceiver;
-    private String webViewDefaultUserAgent;
-    private boolean incognitoModeEnabled;
-    private boolean fullScreenBrowsingModeEnabled;
-    private boolean inFullScreenBrowsingMode;
-    private boolean downloadWithExternalApp;
-    private boolean hideAppBar;
-    private boolean scrollAppBar;
-    private boolean loadingNewIntent;
-    private boolean reapplyDomainSettingsOnRestart;
-    private boolean reapplyAppSettingsOnRestart;
-    private boolean displayingFullScreenVideo;
-    private boolean waitingForProxy;
-
     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
     private ActionBarDrawerToggle actionBarDrawerToggle;
 
@@ -296,6 +290,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private boolean sanitizeFacebookClickIds;
     private boolean sanitizeTwitterAmpRedirects;
 
+    // Declare the class variables
+    private BroadcastReceiver orbotStatusBroadcastReceiver;
+    private String webViewDefaultUserAgent;
+    private boolean incognitoModeEnabled;
+    private boolean fullScreenBrowsingModeEnabled;
+    private boolean inFullScreenBrowsingMode;
+    private boolean downloadWithExternalApp;
+    private boolean hideAppBar;
+    private boolean scrollAppBar;
+    private boolean bottomAppBar;
+    private boolean loadingNewIntent;
+    private boolean reapplyDomainSettingsOnRestart;
+    private boolean reapplyAppSettingsOnRestart;
+    private boolean displayingFullScreenVideo;
+    private boolean waitingForProxy;
+
+    // Define the class variables.
+    private long lastScrollUpdate = 0;
+
     // Declare the class views.
     private FrameLayout rootFrameLayout;
     private DrawerLayout drawerLayout;
@@ -385,9 +398,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Get the screenshot preference.
+        // Get the preferences.
         String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
         boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
+        bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
 
         // Get the theme entry values string array.
         String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
@@ -423,7 +437,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         setTheme(R.style.PrivacyBrowser);
 
         // Set the content view.
-        setContentView(R.layout.main_framelayout);
+        if (bottomAppBar) {
+            setContentView(R.layout.main_framelayout_bottom_appbar);
+        } else {
+            setContentView(R.layout.main_framelayout_top_appbar);
+        }
 
         // Get handles for the views.
         rootFrameLayout = findViewById(R.id.root_framelayout);
@@ -690,6 +708,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Resume the ad.
             AdHelper.resumeAd(adView);
         }
+
+        // Show any pending dialogs.
+        for (int i = 0; i < pendingDialogsArrayList.size(); i++) {
+            // Get the pending dialog from the array list.
+            PendingDialog pendingDialog = pendingDialogsArrayList.get(i);
+
+            // Show the pending dialog.
+            pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
+        }
+
+        // Clear the pending dialogs array list.
+        pendingDialogsArrayList.clear();
     }
 
     // `onStop()` runs after `onPause()`.  It is used instead of `onPause()` so the commands are not called every time the screen is partially hidden.
@@ -1392,7 +1422,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Update the menu checkbox.
             menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
 
-            // Update the staus of Fanboy's Social Blocking List.
+            // Update the status of Fanboy's Social Blocking List.
             optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
 
             // Reload the current WebView.
@@ -2115,7 +2145,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
 
             // Add the blocklist versions to the intent.
-            aboutIntent.putExtra("blocklist_versions", blocklistVersions);
+            aboutIntent.putExtra(AboutActivity.BLOCKLIST_VERSIONS, blocklistVersions);
 
             // Make it so.
             startActivity(aboutIntent);
@@ -3196,12 +3226,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Reset the waiting for proxy status.
                     waitingForProxy = false;
 
-                    // Get a handle for the waiting for proxy dialog.
-                    DialogFragment waitingForProxyDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog));
+                    // Get a list of the current fragments.
+                    List<Fragment> fragmentList = getSupportFragmentManager().getFragments();
+
+                    // Check each fragment to see if it is a waiting for proxy dialog.  Sometimes more than one is displayed.
+                    for (int i = 0; i < fragmentList.size(); i++) {
+                        // Get the fragment tag.
+                        String fragmentTag = fragmentList.get(i).getTag();
 
-                    // Dismiss the waiting for proxy dialog if it is displayed.
-                    if (waitingForProxyDialogFragment != null) {
-                        waitingForProxyDialogFragment.dismiss();
+                        // Check to see if it is the waiting for proxy dialog.
+                        if ((fragmentTag!= null) && fragmentTag.equals(getString(R.string.waiting_for_proxy_dialog))) {
+                            // Dismiss the waiting for proxy dialog.
+                            ((DialogFragment) fragmentList.get(i)).dismiss();
+                        }
                     }
 
                     // Reload existing URLs and load any URLs that are waiting for the proxy.
@@ -3395,7 +3432,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         });
 
         // Implement swipe to refresh.
-        swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
+        swipeRefreshLayout.setOnRefreshListener(() -> {
+            // Check the visibility of the bottom app bar.  Sometimes it is hidden if the WebView is the same size as the visible screen.
+            if (bottomAppBar && scrollAppBar && (appBarLayout.getVisibility() == View.GONE)) {  // The bottom app bar is currently hidden.
+                // Show the app bar.
+                appBarLayout.setVisibility(View.VISIBLE);
+
+                // Disable the refreshing animation.
+                swipeRefreshLayout.setRefreshing(false);
+            } else {  // A bottom app bar is not currently hidden.
+                // Reload the website.
+                currentWebView.reload();
+            }
+        });
 
         // Store the default progress view offsets for use later in `initializeWebView()`.
         defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
@@ -3587,51 +3636,48 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Apply the proxy.
         applyProxy(false);
 
-        // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
-        CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-        AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
-        AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
-        AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
-
-        // Add the scrolling behavior to the layout parameters.
-        if (scrollAppBar) {
-            // Enable scrolling of the app bar.
-            swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
-            toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-            findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-            tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-        } else {
-            // Disable scrolling of the app bar.
-            swipeRefreshLayoutParams.setBehavior(null);
-            toolbarLayoutParams.setScrollFlags(0);
-            findOnPageLayoutParams.setScrollFlags(0);
-            tabsLayoutParams.setScrollFlags(0);
-
-            // Expand the app bar if it is currently collapsed.
-            appBarLayout.setExpanded(true);
-        }
-
-        // Apply the modified layout parameters.
-        swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
-        toolbar.setLayoutParams(toolbarLayoutParams);
-        findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
-        tabsLinearLayout.setLayoutParams(tabsLayoutParams);
+        // Adjust the layout and scrolling parameters if the app bar is at the top of the screen.
+        if (!bottomAppBar) {
+            // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
+            CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+            AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
+            AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
+            AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
+
+            // Add the scrolling behavior to the layout parameters.
+            if (scrollAppBar) {
+                // Enable scrolling of the app bar.
+                swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+                toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+                findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+                tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+            } else {
+                // Disable scrolling of the app bar.
+                swipeRefreshLayoutParams.setBehavior(null);
+                toolbarLayoutParams.setScrollFlags(0);
+                findOnPageLayoutParams.setScrollFlags(0);
+                tabsLayoutParams.setScrollFlags(0);
+
+                // Expand the app bar if it is currently collapsed.
+                appBarLayout.setExpanded(true);
+            }
 
-        // Set the app bar scrolling for each WebView.
-        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
-            // Get the WebView tab fragment.
-            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+            // Set the app bar scrolling for each WebView.
+            for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                // Get the WebView tab fragment.
+                WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
+                // Get the fragment view.
+                View fragmentView = webViewTabFragment.getView();
 
-            // Only modify the WebViews if they exist.
-            if (fragmentView != null) {
-                // Get the nested scroll WebView from the tab fragment.
-                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+                // Only modify the WebViews if they exist.
+                if (fragmentView != null) {
+                    // Get the nested scroll WebView from the tab fragment.
+                    NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-                // Set the app bar scrolling.
-                nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
+                    // Set the app bar scrolling.
+                    nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
+                }
             }
         }
 
@@ -4282,8 +4328,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Get a handle for the waiting for proxy alert dialog.
                             DialogFragment waitingForProxyDialogFragment = new WaitingForProxyDialog();
 
-                            // Display the waiting for proxy alert dialog.
-                            waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
+                            // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
+                            try {
+                                // Show the waiting for proxy alert dialog.
+                                waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
+                            } catch (Exception waitingForTorException) {
+                                // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                                pendingDialogsArrayList.add(new PendingDialog(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
+                            }
                         }
                     }
                 } catch (PackageManager.NameNotFoundException exception) {  // Orbot is not installed.
@@ -4292,8 +4344,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Get a handle for the Orbot not installed alert dialog.
                         DialogFragment orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
 
-                        // Display the Orbot not installed alert dialog.
-                        orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
+                        try {
+                            // Display the Orbot not installed alert dialog.
+                            orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        } catch (Exception orbotNotInstalledException) {
+                            // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                            pendingDialogsArrayList.add(new PendingDialog(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                        }
                     }
                 }
                 break;
@@ -4319,8 +4377,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Get a handle for the waiting for proxy alert dialog.
                         DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
 
-                        // Display the I2P not installed alert dialog.
-                        i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
+                        try {
+                            // Display the I2P not installed alert dialog.
+                            i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        } catch (Exception i2pNotInstalledException) {
+                            // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                            pendingDialogsArrayList.add(new PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                        }
                     }
                 }
                 break;
@@ -4741,6 +4805,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Add the new WebView page.
         webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab);
+
+        // Show the app bar if it is at the bottom of the screen and the new tab is taking focus.
+        if (bottomAppBar && moveToTab) {
+            appBarLayout.setVisibility(View.VISIBLE);
+        }
     }
 
     public void closeTab(View view) {
@@ -5238,19 +5307,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Hide the action bar.
                             actionBar.hide();
 
-                            // Check to see if the app bar is normally scrolled.
-                            if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
-                                // Get the swipe refresh layout parameters.
-                                CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-
-                                // Remove the off-screen scrolling layout.
-                                swipeRefreshLayoutParams.setBehavior(null);
-                            } else {  // The app bar is not scrolled when it is displayed.
-                                // Remove the padding from the top of the swipe refresh layout.
-                                swipeRefreshLayout.setPadding(0, 0, 0, 0);
-
-                                // The swipe refresh circle must be moved above the now removed status bar location.
-                                swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
+                            // Set layout and scrolling parameters if the app bar is at the top of the screen.
+                            if (!bottomAppBar) {
+                                // Check to see if the app bar is normally scrolled.
+                                if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
+                                    // Get the swipe refresh layout parameters.
+                                    CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+
+                                    // Remove the off-screen scrolling layout.
+                                    swipeRefreshLayoutParams.setBehavior(null);
+                                } else {  // The app bar is not scrolled when it is displayed.
+                                    // Remove the padding from the top of the swipe refresh layout.
+                                    swipeRefreshLayout.setPadding(0, 0, 0, 0);
+
+                                    // The swipe refresh circle must be moved above the now removed status bar location.
+                                    swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
+                                }
                             }
                         }
 
@@ -5280,19 +5352,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Show the action bar.
                             actionBar.show();
 
-                            // Check to see if the app bar is normally scrolled.
-                            if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
-                                // Get the swipe refresh layout parameters.
-                                CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-
-                                // Add the off-screen scrolling layout.
-                                swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
-                            } else {  // The app bar is not scrolled when it is displayed.
-                                // The swipe refresh layout must be manually moved below the app bar layout.
-                                swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
-
-                                // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
-                                swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                            // Set layout and scrolling parameters if the app bar is at the top of the screen.
+                            if (!bottomAppBar) {
+                                // Check to see if the app bar is normally scrolled.
+                                if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
+                                    // Get the swipe refresh layout parameters.
+                                    CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+
+                                    // Add the off-screen scrolling layout.
+                                    swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+                                } else {  // The app bar is not scrolled when it is displayed.
+                                    // The swipe refresh layout must be manually moved below the app bar layout.
+                                    swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
+
+                                    // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+                                    swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                                }
                             }
                         }
 
@@ -5350,23 +5425,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get the file name from the content disposition.
                 String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
 
-                // Prevent the dialog from displaying if the app window is not visible.
-                // The download listener continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
-                while (!activity.getWindow().isActive()) {
-                    try {
-                        // The window is not active.  Wait 1 second.
-                        wait(1000);
-                    } catch (InterruptedException e) {
-                        // Do nothing.
-                    }
-                }
-
                 // Instantiate the save dialog.
                 DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
                         nestedScrollWebView.getAcceptCookies());
 
-                // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
-                saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                // Try to show the dialog.  The download listener continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
+                try {
+                    // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
+                    saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                } catch (Exception exception) {  // The dialog could not be shown.
+                    // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                    pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, getString(R.string.save_dialog)));
+                }
             }
         });
 
@@ -5396,7 +5466,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.  Also reinforce full screen browsing mode.
         // 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) -> {
+            nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                // Set the swipe to refresh status.
                 if (nestedScrollWebView.getSwipeToRefresh()) {
                     // Only enable swipe to refresh if the WebView is scrolled to the top.
                     swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
@@ -5405,6 +5476,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     swipeRefreshLayout.setEnabled(false);
                 }
 
+                //  Set the visibility of the bottom app bar.
+                if (bottomAppBar && scrollAppBar && (Calendar.getInstance().getTimeInMillis() - lastScrollUpdate > 100)) {
+                    if (scrollY - oldScrollY > 25) {  // The WebView was scrolled down.
+                        appBarLayout.setVisibility(View.GONE);
+                    } else if (scrollY - oldScrollY < -25) {  // The WebView was scrolled up.
+                        appBarLayout.setVisibility(View.VISIBLE);
+                    }
+
+                    // Update the last scroll update variable.  This prevents the app bar from flashing on and off at the bottom of the screen.
+                    lastScrollUpdate = Calendar.getInstance().getTimeInMillis();
+                }
+
                 // 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) {
@@ -5428,7 +5511,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     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) {
@@ -5806,7 +5888,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList);
 
                     // Process the UltraList results.
-                    if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraLists's blacklist.
+                    if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraList's blacklist.
                         // Add the result to the resource requests.
                         nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
 
@@ -6060,25 +6142,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             @Override
             public void onPageStarted(WebView view, String url, Bitmap favicon) {
-                // Get the preferences.
-                boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
-
-                // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.  This can't be done in `appAppSettings()` because the app bar is not yet populated there.
-                if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {
-                    // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
-                    swipeRefreshLayout.setPadding(0, 0, 0, 0);
-
-                    // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
-                    swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
-                } else {
-                    // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
-                    appBarHeight = appBarLayout.getHeight();
-
-                    // The swipe refresh layout must be manually moved below the app bar layout.
-                    swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
-
-                    // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
-                    swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                // Set the padding and layout settings if the app bar is at the top of the screen.
+                if (!bottomAppBar) {
+                    // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.  This can't be done in `appAppSettings()` because the app bar is not yet populated there.
+                    if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {
+                        // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
+                        swipeRefreshLayout.setPadding(0, 0, 0, 0);
+
+                        // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+                        swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
+                    } else {
+                        // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
+                        appBarHeight = appBarLayout.getHeight();
+
+                        // The swipe refresh layout must be manually moved below the app bar layout.
+                        swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
+
+                        // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+                        swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                    }
                 }
 
                 // Reset the list of resource requests.
@@ -6095,7 +6177,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Display the formatted URL text.
                     urlEditText.setText(url);
 
-                    // Apply text highlighting to `urlTextBox`.
+                    // Apply text highlighting to the URL text box.
                     highlightUrlText();
 
                     // Hide the keyboard.
@@ -6306,22 +6388,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Store the SSL error handler.
                     nestedScrollWebView.setSslErrorHandler(handler);
 
-                    // Prevent the dialog from displaying if the app window is not visible.
-                    // The SSL error handler continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
-                    while (!activity.getWindow().isActive()) {
-                        try {
-                            // The window is not active.  Wait 1 second.
-                            wait(1000);
-                        } catch (InterruptedException e) {
-                            // Do nothing.
-                        }
-                    }
-
                     // Instantiate an SSL certificate error alert dialog.
                     DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());
 
-                    // Show the SSL certificate error dialog.
-                    sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+                    // Try to show the dialog.  The SSL error handler continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
+                    try {
+                        // Show the SSL certificate error dialog.
+                        sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+                    } catch (Exception exception) {
+                        // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                        pendingDialogsArrayList.add(new PendingDialog(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
+                    }
                 }
             }
         });