import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager.widget.ViewPager;
+import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import java.util.Map;
import java.util.Set;
-// TODO. New tabs are white in dark mode.
-// TODO. Find on page.
-
// AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
// `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
private boolean inFullScreenBrowsingMode;
- // The hide app bar tracker is used in `applyAppSettings()` and `initializeWebView()`.
+ // The app bar trackers are set in `applyAppSettings()` and used in `initializeWebView()`.
private boolean hideAppBar;
+ private boolean scrollAppBar;
+
+ // The loading new intent tracker is set in `onNewIntent()` and used in `setCurrentWebView()`.
+ private boolean loadingNewIntent;
// `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
private boolean reapplyDomainSettingsOnRestart;
// `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
private ValueCallback<Uri[]> fileChooserCallback;
+ // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
+ private int defaultProgressViewStartOffset;
+ private int defaultProgressViewEndOffset;
+
+ // The swipe refresh layout top padding is used when exiting full screen browsing mode. It is used in an inner class in `initializeWebView()`.
+ private int swipeRefreshLayoutPaddingTop;
+
// The download strings are used in `onCreate()`, `onRequestPermissionResult()` and `initializeWebView()`.
private String downloadUrl;
private String downloadContentDisposition;
@Override
public void onPageSelected(int position) {
+ // Close the find on page bar if it is open.
+ closeFindOnPage(null);
+
// Set the current WebView.
setCurrentWebView(position);
@Override
public void afterTextChanged(Editable s) {
- // Search for the text in `mainWebView`.
- currentWebView.findAllAsync(findOnPageEditText.getText().toString());
+ // Search for the text in the WebView if it is not null. Sometimes on resume after a period of non-use the WebView will be null.
+ if (currentWebView != null) {
+ currentWebView.findAllAsync(findOnPageEditText.getText().toString());
+ }
}
});
// Implement swipe to refresh.
swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
- // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
- swipeRefreshLayout.setProgressViewOffset(false, swipeRefreshLayout.getProgressViewStartOffset() - 10, swipeRefreshLayout.getProgressViewEndOffset());
+ // Store the default progress view offsets for use later in `initializeWebView()`.
+ defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
+ defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
// Set the swipe to refresh color according to the theme.
if (darkTheme) {
// Sets the new intent as the activity intent, which replaces the one that originally started the app.
setIntent(intent);
- // Add a new tab.
- addTab(null);
+ // Get the shared preferences.
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+ // Add a new tab if specified in the preferences.
+ if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {
+ // Set the loading new intent flag.
+ loadingNewIntent = true;
+
+ // Add a new tab.
+ addTab(null);
+ }
// Create a URL string.
String url;
// Reset the current domain name so the domain settings will be reapplied.
nestedScrollWebView.resetCurrentDomainName();
- // Reapply the domain settings.
- applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
+ // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
+ if (nestedScrollWebView.getUrl() != null) {
+ applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
+ }
}
}
}
// Only show Ad Consent if this is the free flavor.
adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
- // Get the shared preference values.
+ // Get the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
// Get the dark theme and app bar preferences..
LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
+ // Set the minimum height of the find on page linear layout to match the toolbar.
+ findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
+
// Hide the toolbar.
toolbar.setVisibility(View.GONE);
// Delete the contents of `find_on_page_edittext`.
findOnPageEditText.setText(null);
- // Clear the highlighted phrases.
- currentWebView.clearMatches();
+ // Clear the highlighted phrases if the WebView is not null.
+ if (currentWebView != null) {
+ currentWebView.clearMatches();
+ }
// Hide the find on page linear layout.
findOnPageLinearLayout.setVisibility(View.GONE);
proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
+ scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
// Get handles for the views that need to be modified.
FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+ AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
ActionBar actionBar = getSupportActionBar();
LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
+ SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
// Remove the incorrect lint warning below that the action bar might be null.
assert actionBar != null;
customHeaders.remove("DNT");
}
+ // Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command.
+ CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+
+ // Add the scrolling behavior to the layout parameters.
+ if (scrollAppBar) {
+ // Enable scrolling of the app bar.
+ layoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+ } else {
+ // Disable scrolling of the app bar.
+ layoutParams.setBehavior(null);
+
+ // Expand the app bar if it is currently collapsed.
+ appBarLayout.setExpanded(true);
+ }
+
+ // Apply the modified layout parameters to the swipe refresh layout.
+ swipeRefreshLayout.setLayoutParams(layoutParams);
+
// Set the app bar scrolling for each WebView.
for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
// Get the WebView tab fragment.
NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
// Set the app bar scrolling.
- nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
+ nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
}
}
// Get the corresponding tab.
TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
- // Remove the warning below that the tab might be null.
- assert tab != null;
-
- // Get the tab custom view.
- View tabCustomView = tab.getCustomView();
+ // Update the tab if it isn't null, which sometimes happens when restarting from the background.
+ if (tab != null) {
+ // Get the tab custom view.
+ View tabCustomView = tab.getCustomView();
- // Remove the warning below that the tab custom view might be null.
- assert tabCustomView != null;
+ // Remove the warning below that the tab custom view might be null.
+ assert tabCustomView != null;
- // Get the tab views.
- ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
- TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
+ // Get the tab views.
+ ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
+ TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
- // Set the default favorite icon as the favorite icon for this tab.
- tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
+ // Set the default favorite icon as the favorite icon for this tab.
+ tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
- // Set the loading title text.
- tabTitleTextView.setText(R.string.loading);
+ // Set the loading title text.
+ tabTitleTextView.setText(R.string.loading);
+ }
}
// Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
- // Get a handle for the action bar. `getSupportActionBar()` must be used until the minimum API >= 21.
- ActionBar actionBar = getSupportActionBar();
-
- // Remove the incorrect lint warning later that the action bar might be null.
- assert actionBar != null;
+ // Get a handle for the app bar layout.
+ AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
// Set the homepage, search, and proxy options.
if (proxyThroughOrbot) { // Set the Tor options.
// Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
- // Set the `appBar` background to indicate proxying through Orbot is enabled.
+ // Set the app bar background to indicate proxying through Orbot is enabled.
if (darkTheme) {
- actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
+ appBarLayout.setBackgroundResource(R.color.dark_blue_30);
} else {
- actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
+ appBarLayout.setBackgroundResource(R.color.blue_50);
}
// Check to see if Orbot is ready.
// Reset the proxy to default. The host is `""` and the port is `"0"`.
OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
- // Set the default `appBar` background.
+ // Set the default app bar layout background.
if (darkTheme) {
- actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
+ appBarLayout.setBackgroundResource(R.color.gray_900);
} else {
- actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
+ appBarLayout.setBackgroundResource(R.color.gray_100);
}
// Reset `waitingForOrbot.
// Update the privacy icons. `true` redraws the icons in the app bar.
updatePrivacyIcons(true);
- // Clear the focus from the URL text box.
- urlEditText.clearFocus();
-
// Get a handle for the input method manager.
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// Remove the lint warning below that the input method manager might be null.
assert inputMethodManager != null;
- // Hide the soft keyboard.
- inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+ // Get the current URL.
+ String url = currentWebView.getUrl();
+
+ // Update the URL edit text if not loading a new intent. Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`.
+ if (!loadingNewIntent) { // A new intent is not being loaded.
+ if ((url == null) || url.equals("about:blank")) { // The WebView is blank.
+ // Display the hint in the URL edit text.
+ urlEditText.setText("");
+
+ // Request focus for the URL text box.
+ urlEditText.requestFocus();
- // Display the current URL in the URL text box.
- urlEditText.setText(currentWebView.getUrl());
+ // Display the keyboard.
+ inputMethodManager.showSoftInput(urlEditText, 0);
+ } else { // The WebView has a loaded URL.
+ // Clear the focus from the URL text box.
+ urlEditText.clearFocus();
+
+ // Hide the soft keyboard.
+ inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
- // Highlight the URL text.
- highlightUrlText();
+ // Display the current URL in the URL text box.
+ urlEditText.setText(url);
+
+ // Highlight the URL text.
+ highlightUrlText();
+ }
+ } else { // A new intent is being loaded.
+ // Reset the loading new intent tracker.
+ loadingNewIntent = false;
+ }
// Set the background to indicate the domain settings status.
if (currentWebView.getDomainSettingsApplied()) {
// Toggle the full screen browsing mode.
if (inFullScreenBrowsingMode) { // Switch to full screen mode.
+ // Store the swipe refresh layout top padding.
+ swipeRefreshLayoutPaddingTop = swipeRefreshLayout.getPaddingTop();
+
// Hide the app bar if specified.
if (hideAppBar) {
+ // Close the find on page bar if it is visible.
+ closeFindOnPage(null);
+
// Hide the tab linear layout.
tabsLinearLayout.setVisibility(View.GONE);
// Hide the action bar.
actionBar.hide();
+
+ // Check to see if app bar scrolling is disabled.
+ if (!scrollAppBar) {
+ // Remove the padding from the top of the swipe refresh layout.
+ swipeRefreshLayout.setPadding(0, 0, 0, 0);
+ }
}
// Hide the banner ad in the free flavor.
// Show the action bar.
actionBar.show();
+ // Check to see if app bar scrolling is disabled.
+ if (!scrollAppBar) {
+ // Add the padding from the top of the swipe refresh layout.
+ swipeRefreshLayout.setPadding(0, swipeRefreshLayoutPaddingTop, 0, 0);
+ }
+
// Show the banner ad in the free flavor.
if (BuildConfig.FLAVOR.contentEquals("free")) {
// Reload the ad.
}
});
- if (Build.VERSION.SDK_INT >= 23) {
- nestedScrollWebView.setOnScrollChangeListener((View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) -> {
- // Update the status of swipe to refresh if it is enabled.
- if (nestedScrollWebView.getSwipeToRefresh()) {
- // Only enable swipe to refresh if the WebView is scrolled to the top.
- swipeRefreshLayout.setEnabled(scrollY == 0);
- }
- });
- }
+ // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.
+ // 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);
+ }
+ });
// Set the web chrome client.
nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int progress) {
// Inject the night mode CSS if night mode is enabled.
- if (nestedScrollWebView.getNightMode()) {
+ if (nestedScrollWebView.getNightMode()) { // Night mode is enabled.
// `background-color: #212121` sets the background to be dark gray. `color: #BDBDBD` sets the text color to be light gray. `box-shadow: none` removes a lower underline on links
// used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
// `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
// Display the WebView after 500 milliseconds.
displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
});
+ } else { // Night mode is disabled.
+ // Display the nested scroll WebView if night mode is disabled.
+ // Because of a race condition between `applyDomainSettings` and `onPageStarted`,
+ // when night mode is set by domain settings the WebView may be hidden even if night mode is not currently enabled.
+ nestedScrollWebView.setVisibility(View.VISIBLE);
}
// Update the progress bar.
// Hide the progress bar.
progressBar.setVisibility(View.GONE);
- // Display the nested scroll WebView if night mode is disabled.
- // Because of a race condition between `applyDomainSettings` and `onPageStarted`,
- // when night mode is set by domain settings the WebView may be hidden even if night mode is not currently enabled.
- if (!nestedScrollWebView.getNightMode()) {
- nestedScrollWebView.setVisibility(View.VISIBLE);
- }
-
//Stop the swipe to refresh indicator if it is running
swipeRefreshLayout.setRefreshing(false);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
- // Get the theme preference.
+ // Get the preferences.
+ boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
+ // Get a handler for the app bar layout.
+ AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
+
+ // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.
+ if (scrollAppBar) {
+ // 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.
+ int 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.
nestedScrollWebView.clearResourceRequests();
// If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
if (nestedScrollWebView.getNightMode()) {
nestedScrollWebView.setVisibility(View.INVISIBLE);
+ } else {
+ nestedScrollWebView.setVisibility(View.VISIBLE);
}
// Hide the keyboard.
// Update the URL text bar if the page is currently selected.
if (tabLayout.getSelectedTabPosition() == currentPagePosition) {
+ // Clear the focus from the URL edit text.
+ urlEditText.clearFocus();
+
// Display the formatted URL text.
urlEditText.setText(url);
// Get the current page position.
int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
- // Update the URL text bar if the page is currently selected.
- if (tabLayout.getSelectedTabPosition() == currentPagePosition) {
+ // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
+ if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() &&
+ !nestedScrollWebView.ignorePinnedDomainInformation()) {
+ CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
+ }
+
+ // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
+ if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus()) {
// Check to see if the URL is `about:blank`.
- if (url.equals("about:blank")) { // The WebView is blank.
+ if (nestedScrollWebView.getUrl().equals("about:blank")) { // The WebView is blank.
// Display the hint in the URL edit text.
urlEditText.setText("");
// Display the keyboard.
inputMethodManager.showSoftInput(urlEditText, 0);
- // Hide the WebView, which causes the default background color to be displayed according to the theme. // TODO
- nestedScrollWebView.setVisibility(View.GONE);
+ // Hide the WebView, which causes the default background color to be displayed according to the theme.
+ nestedScrollWebView.setVisibility(View.INVISIBLE);
// Apply the domain settings. This clears any settings from the previous domain.
applyDomainSettings(nestedScrollWebView, "", true, false);
} else { // The WebView has loaded a webpage.
- // Only update the URL text box if the user is not typing in it.
- if (!urlEditText.hasFocus()) {
- // Display the final URL. Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
- urlEditText.setText(nestedScrollWebView.getUrl());
+ // Display the final URL. Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
+ urlEditText.setText(nestedScrollWebView.getUrl());
- // Apply text highlighting to the URL.
- highlightUrlText();
- }
+ // Apply text highlighting to the URL.
+ highlightUrlText();
}
}
-
- // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
- if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() &&
- !nestedScrollWebView.ignorePinnedDomainInformation()) {
- CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
- }
}
}