X-Git-Url: https://gitweb.stoutner.com/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.kt;h=ac27dcd657c4315e254ab1db442094f8963f0e1e;hb=f8486dc082d82deead08a6747670e22ce7263c97;hp=16b40d855ab46df94eab92f0e5f33589a5f7aaec;hpb=09795c801b5e5d85beb63259a2d9cb39f756fa61;p=PrivacyBrowserAndroid.git diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt index 16b40d85..ac27dcd6 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt @@ -104,6 +104,8 @@ import androidx.cursoradapter.widget.CursorAdapter import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.DialogFragment import androidx.preference.PreferenceManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.viewpager2.widget.ViewPager2 import androidx.webkit.WebSettingsCompat @@ -137,6 +139,8 @@ import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog import com.stoutner.privacybrowser.fragments.WebViewTabFragment +import com.stoutner.privacybrowser.helpers.BOOKMARK_NAME +import com.stoutner.privacybrowser.helpers.BOOKMARK_URL import com.stoutner.privacybrowser.helpers.COOKIES import com.stoutner.privacybrowser.helpers.DARK_THEME import com.stoutner.privacybrowser.helpers.DISABLED @@ -151,9 +155,12 @@ import com.stoutner.privacybrowser.helpers.ENABLE_FORM_DATA import com.stoutner.privacybrowser.helpers.ENABLE_JAVASCRIPT import com.stoutner.privacybrowser.helpers.ENABLE_ULTRAPRIVACY import com.stoutner.privacybrowser.helpers.ENABLED +import com.stoutner.privacybrowser.helpers.FAVORITE_ICON +import com.stoutner.privacybrowser.helpers.FOLDER_ID import com.stoutner.privacybrowser.helpers.FONT_SIZE import com.stoutner.privacybrowser.helpers.ID import com.stoutner.privacybrowser.helpers.IP_ADDRESSES +import com.stoutner.privacybrowser.helpers.IS_FOLDER import com.stoutner.privacybrowser.helpers.LIGHT_THEME import com.stoutner.privacybrowser.helpers.PINNED_IP_ADDRESSES import com.stoutner.privacybrowser.helpers.PINNED_SSL_CERTIFICATE @@ -234,7 +241,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook companion object { // Define the public static variables. - var currentBookmarksFolder = "" + var currentBookmarksFolderId = 0L val executorService = Executors.newFixedThreadPool(4)!! var orbotStatus = "unknown" val pendingDialogsArrayList = ArrayList() @@ -275,6 +282,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook private lateinit var navigationForwardMenuItem: MenuItem private lateinit var navigationHistoryMenuItem: MenuItem private lateinit var navigationRequestsMenuItem: MenuItem + private lateinit var navigationView: NavigationView private lateinit var optionsAddOrEditDomainMenuItem: MenuItem private lateinit var optionsBlockAllThirdPartyRequestsMenuItem: MenuItem private lateinit var optionsClearCookiesMenuItem: MenuItem @@ -316,6 +324,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook private lateinit var optionsUserAgentSafariOnIosMenuItem: MenuItem private lateinit var optionsUserAgentSafariOnMacosMenuItem: MenuItem private lateinit var optionsUserAgentWebViewDefaultMenuItem: MenuItem + private lateinit var optionsViewSourceMenuItem: MenuItem private lateinit var optionsWideViewportMenuItem: MenuItem private lateinit var proxyHelper: ProxyHelper private lateinit var redColorSpan: ForegroundColorSpan @@ -369,11 +378,13 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook private var inFullScreenBrowsingMode = false private var incognitoModeEnabled = false private var loadingNewIntent = false + private var navigationDrawerFirstView = true private var objectAnimator = ObjectAnimator() private var optionsMenu: Menu? = null private var orbotStatusBroadcastReceiver: BroadcastReceiver? = null private var reapplyAppSettingsOnRestart = false private var reapplyDomainSettingsOnRestart = false + private var restartTime = Date(0) private var sanitizeAmpRedirects = false private var sanitizeTrackingQueries = false private var savedProxyMode: String? = null @@ -582,7 +593,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook tabLayout = findViewById(R.id.tablayout) swipeRefreshLayout = findViewById(R.id.swiperefreshlayout) webViewViewPager2 = findViewById(R.id.webview_viewpager2) - val navigationView = findViewById(R.id.navigationview) + navigationView = findViewById(R.id.navigationview) bookmarksListView = findViewById(R.id.bookmarks_drawer_listview) bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview) bookmarksDrawerPinnedImageView = findViewById(R.id.bookmarks_drawer_pinned_imageview) @@ -680,6 +691,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Go back. currentWebView!!.goBack() + + // Update the URL edit text after a delay. + updateUrlEditTextAfterDelay() } else { // Close the current tab. // A view is required because the method is also called by an XML `onClick`. closeTab(null) @@ -769,7 +783,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook } } else { // The app has been restarted. // If the new intent will open a new tab, set the saved tab position to be the size of the saved state array list. - // The tab position is 0 based, meaning the at the new tab will be the tab position that is restored. + // The tab position is 0 based, meaning the new tab will be the tab position that is restored. if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch) savedTabPosition = savedStateArrayList!!.size @@ -994,6 +1008,29 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook super.onDestroy() } + override fun onConfigurationChanged(newConfig: Configuration) { + // Run the default commands. + super.onConfigurationChanged(newConfig) + + // Reset the navigation drawer first view flag. + navigationDrawerFirstView = true + + // Get the current page. + val currentPage = webViewViewPager2.currentItem + + // Toggle the pages if there is more than one so that the view pager will recalculate their size. + if (currentPage > 0) { + // Switch to the previous page after 25 milliseconds. + webViewViewPager2.postDelayed ({ webViewViewPager2.currentItem = (currentPage - 1) }, 25) + + // Switch back to the current page after the view pager has quiesced (which we are deciding should be 25 milliseconds). + webViewViewPager2.postDelayed ({ webViewViewPager2.currentItem = currentPage }, 25) + } + + // Scroll to the current tab position after 25 milliseconds. + tabLayout.postDelayed ({ tabLayout.setScrollPosition(currentPage, 0F, false, false) }, 25) + } + override fun onCreateOptionsMenu(menu: Menu): Boolean { // Inflate the menu. This adds items to the app bar if it is present. menuInflater.inflate(R.menu.webview_options_menu, menu) @@ -1041,6 +1078,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook optionsDisplayImagesMenuItem = menu.findItem(R.id.display_images) optionsDarkWebViewMenuItem = menu.findItem(R.id.dark_webview) optionsFontSizeMenuItem = menu.findItem(R.id.font_size) + optionsViewSourceMenuItem = menu.findItem(R.id.view_source) optionsAddOrEditDomainMenuItem = menu.findItem(R.id.add_or_edit_domain) // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step. @@ -1053,9 +1091,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly. optionsClearFormDataMenuItem.isEnabled = Build.VERSION.SDK_INT < 26 - // Only display the dark WebView menu item if the API >= 29. - optionsDarkWebViewMenuItem.isVisible = Build.VERSION.SDK_INT >= 29 - // Set the status of the additional app bar icons. Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones. if (displayAdditionalAppBarIcons) { // Display the additional icons. optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) @@ -1118,7 +1153,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook optionsWideViewportMenuItem.isChecked = currentWebView!!.settings.useWideViewPort optionsDisplayImagesMenuItem.isChecked = currentWebView!!.settings.loadsImagesAutomatically - // Initialize the display names for the filter lists with the number of blocked requests. + // Set the display names for the filter lists with the number of blocked requests. optionsFilterListsMenuItem.title = getString(R.string.filterlists) + " - " + currentWebView!!.getRequestsCount(BLOCKED_REQUESTS) optionsEasyListMenuItem.title = currentWebView!!.getRequestsCount(EASYLIST).toString() + " - " + getString(R.string.easylist) optionsEasyPrivacyMenuItem.title = currentWebView!!.getRequestsCount(EASYPRIVACY).toString() + " - " + getString(R.string.easyprivacy) @@ -1137,9 +1172,15 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Enable dark WebView if night mode is enabled. optionsDarkWebViewMenuItem.isEnabled = (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) - // Set the checkbox status for dark WebView if the device is running API >= 29 and algorithmic darkening is supported. - if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) + // Set the checkbox status for dark WebView if algorithmic darkening is supported. + if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) optionsDarkWebViewMenuItem.isChecked = WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView!!.settings) + + // Set the view source title according to the current URL. + if (currentWebView!!.currentUrl.startsWith("view-source:")) + optionsViewSourceMenuItem.title = getString(R.string.view_rendered_website) + else + optionsViewSourceMenuItem.title = getString(R.string.view_source) } // Set the cookies menu item checked status. @@ -1896,7 +1937,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook R.id.dark_webview -> { // Dark WebView. // Toggle dark WebView if supported. - if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) + if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) WebSettingsCompat.setAlgorithmicDarkeningAllowed(currentWebView!!.settings, !WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView!!.settings) ) @@ -1984,15 +2025,29 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook } R.id.view_source -> { // View source. - // Create an intent to launch the view source activity. - val viewSourceIntent = Intent(this, ViewSourceActivity::class.java) + // Open a new tab according to the current URL. + if (currentWebView!!.currentUrl.startsWith("view-source:")) { // The source is currently viewed. + // Open the rendered website in a new tab. + addNewTab(currentWebView!!.currentUrl.substring(12, currentWebView!!.currentUrl.length), true) + } else { // The rendered website is currently viewed. + // Open the source in a new tab. + addNewTab("view-source:${currentWebView!!.currentUrl}", true) + } + + // Consume the event. + true + } + + R.id.view_headers -> { // View headers. + // Create an intent to launch the view headers activity. + val viewHeadersIntent = Intent(this, ViewHeadersActivity::class.java) // Add the variables to the intent. - viewSourceIntent.putExtra(CURRENT_URL, currentWebView!!.url) - viewSourceIntent.putExtra(USER_AGENT, currentWebView!!.settings.userAgentString) + viewHeadersIntent.putExtra(CURRENT_URL, currentWebView!!.url) + viewHeadersIntent.putExtra(USER_AGENT, currentWebView!!.settings.userAgentString) // Make it so. - startActivity(viewSourceIntent) + startActivity(viewHeadersIntent) // Consume the event. true @@ -2190,6 +2245,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Load the previous website in the history. currentWebView!!.goBack() + + // Update the URL edit text after a delay. + updateUrlEditTextAfterDelay() } } @@ -2207,6 +2265,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Load the next website in the history. currentWebView!!.goForward() + + // Update the URL edit text after a delay. + updateUrlEditTextAfterDelay() } } @@ -2742,8 +2803,11 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Set a custom view on the new tab. newTab.setCustomView(R.layout.tab_custom_view) + // Scroll to the new tab position. + tabLayout.post { tabLayout.setScrollPosition(newTabNumber, 0F, false, false) } + // Add the new WebView page. - webViewStateAdapter!!.addPage(newTabNumber, webViewViewPager2, urlString, moveToTab) + webViewStateAdapter!!.addPage(newTabNumber, newTab, urlString, 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.translationY != 0f) { @@ -3216,8 +3280,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook } } - // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported. - if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { + // Set the WebView theme if algorithmic darkening is supported. + if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // Set the WebView theme. when (webViewThemeInt) { // Set the WebView theme. @@ -3336,8 +3400,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook else -> nestedScrollWebView.settings.userAgentString = userAgentDataArray[userAgentArrayPosition] } - // Set the WebView theme if the device is running API >= 29 and algorithmic darkening is supported. - if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { + // Set the WebView theme if algorithmic darkening is supported. + if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // Set the WebView theme. when (defaultWebViewTheme) { // The light theme is selected. Turn off algorithmic darkening. @@ -3375,6 +3439,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook if (reloadWebsite) nestedScrollWebView.reload() + // Disable the wide viewport if the source is being viewed. + if (url.startsWith("view-source:")) + nestedScrollWebView.settings.useWideViewPort = false + // Load the URL if directed. This makes sure that the domain settings are properly loaded before the URL. By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent. if (loadUrl) nestedScrollWebView.loadUrl(url) @@ -3518,12 +3586,12 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // The view parameter cannot be removed because it is called from the layout onClick. fun bookmarksBack(@Suppress("UNUSED_PARAMETER")view: View?) { - if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed. + if (currentBookmarksFolderId == HOME_FOLDER_ID) { // The home folder is displayed. // close the bookmarks drawer. drawerLayout.closeDrawer(GravityCompat.END) } else { // A subfolder is displayed. // Set the former parent folder as the current folder. - currentBookmarksFolder = bookmarksDatabaseHelper!!.getParentFolderName(currentBookmarksFolder) + currentBookmarksFolderId = bookmarksDatabaseHelper!!.getParentFolderId(currentBookmarksFolderId) // Load the new folder. loadBookmarksFolder() @@ -3782,10 +3850,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook val newBookmarkDisplayOrder = bookmarksListView.count // Create the bookmark. - bookmarksDatabaseHelper!!.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray) + bookmarksDatabaseHelper!!.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolderId, newBookmarkDisplayOrder, favoriteIconByteArray) // Update the bookmarks cursor with the current contents of this folder. - bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolder) + bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId) // Update the list view. bookmarksCursorAdapter.changeCursor(bookmarksCursor) @@ -3840,10 +3908,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook } // Create the folder, which will be placed at the top of the list view. - bookmarksDatabaseHelper!!.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray) + bookmarksDatabaseHelper!!.createFolder(folderNameString, currentBookmarksFolderId, folderIconByteArray) // Update the bookmarks cursor with the current contents of this folder. - bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolder) + bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId) // Update the list view. bookmarksCursorAdapter.changeCursor(bookmarksCursor) @@ -3938,7 +4006,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook if ((savedStateArrayList == null) || (savedStateArrayList!!.size == 0)) { // The activity has not been restarted or it was restarted on start to change the theme. // Add the first tab. addNewTab("", false) - } else { // The activity has been restarted. + } else { // The activity has been restarted with a saved state. + // Set the current restart time. + restartTime = Date() + // Restore each tab. for (i in savedStateArrayList!!.indices) { // Add a new tab. @@ -3963,8 +4034,14 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Set the first page as the current WebView. setCurrentWebView(0) } else { // The first tab is not selected. - // Move to the selected tab. - webViewViewPager2.currentItem = savedTabPosition + // Select the tab when the layout has finished populating. + tabLayout.post { + // Get a handle for the tab. + val tab = tabLayout.getTabAt(savedTabPosition)!! + + // Select the tab. + tab.select() + } } // Get the intent that started the app. @@ -4124,44 +4201,37 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook val createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab) val createBookmarkFab = findViewById(R.id.create_bookmark_fab) - // Update the WebView pager every time a tab is modified. - webViewViewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - // Close the find on page bar if it is open. - closeFindOnPage(null) - - // Set the current WebView. - setCurrentWebView(position) - - // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled by creating a new tab. - if (tabLayout.selectedTabPosition != position) { - // Wait until the new tab has been created. - tabLayout.post { - // Get a handle for the tab. - val tab = tabLayout.getTabAt(position)!! - - // Select the tab. - tab.select() - } - } - } - }) - // Handle tab selections. tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab) { + // Close the find on page bar if it is open. + closeFindOnPage(null) + // Select the same page in the view pager. webViewViewPager2.currentItem = tab.position + + // Set the current WebView. + setCurrentWebView(tab.position) } override fun onTabUnselected(tab: TabLayout.Tab) {} override fun onTabReselected(tab: TabLayout.Tab) { - // Instantiate the View SSL Certificate dialog. - val viewSslCertificateDialogFragment: DialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView!!.webViewFragmentId, currentWebView!!.getFavoriteIcon()) - - // Display the View SSL Certificate dialog. - viewSslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.view_ssl_certificate)) + // Only display the view SSL certificate dialog if the current WebView is not null. + // This can happen if the tab is programmatically reselected while the app is being restarted and is not yet populated. + if (currentWebView != null) { + // Calculate the milliseconds since the last restart. This can be replaced by the simpler LocalDateTime once the minimum API >= 26. + val millisecondsSinceLastRestart = Date().time - restartTime.time + + // Only display the SSL certificate dialog if it has been at least 1 second since the last restart as deep restarts sometimes end up selecting a tab twice. + if (millisecondsSinceLastRestart > 1000) { + // Instantiate the View SSL Certificate dialog. + val viewSslCertificateDialogFragment: DialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView!!.webViewFragmentId, currentWebView!!.getFavoriteIcon()) + + // Display the View SSL Certificate dialog. + viewSslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.view_ssl_certificate)) + } + } } }) @@ -4186,7 +4256,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook val bookmarksIntent = Intent(applicationContext, BookmarksActivity::class.java) // Add the extra information to the intent. - bookmarksIntent.putExtra(CURRENT_FOLDER, currentBookmarksFolder) + bookmarksIntent.putExtra(CURRENT_FOLDER_ID, currentBookmarksFolderId) bookmarksIntent.putExtra(CURRENT_TITLE, currentWebView!!.title) bookmarksIntent.putExtra(CURRENT_URL, currentWebView!!.url) bookmarksIntent.putExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY, currentFavoriteIconByteArray) @@ -4283,15 +4353,15 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook bookmarkCursor.moveToFirst() // Act upon the bookmark according to the type. - if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder. - // Store the folder name. - currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)) + if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(IS_FOLDER)) == 1) { // The selected bookmark is a folder. + // Store the folder ID. + currentBookmarksFolderId = bookmarkCursor.getLong(bookmarkCursor.getColumnIndexOrThrow(FOLDER_ID)) // Load the new folder. loadBookmarksFolder() } else { // The selected bookmark is not a folder. // Load the bookmark URL. - loadUrl(currentWebView!!, bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL))) + loadUrl(currentWebView!!, bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_URL))) // Close the bookmarks drawer if it is not pinned. if (!bookmarksDrawerPinned) @@ -4309,8 +4379,11 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Run the commands associated with the type. if (bookmarksDatabaseHelper!!.isFolder(databaseId)) { // The bookmark is a folder. + // Get the folder ID. + val folderId = bookmarksDatabaseHelper!!.getFolderId(databaseId) + // Get a cursor of all the bookmarks in the folder. - val bookmarksCursor = bookmarksDatabaseHelper!!.getFolderBookmarks(databaseId) + val bookmarksCursor = bookmarksDatabaseHelper!!.getFolderBookmarks(folderId) // Move to the first entry in the cursor. bookmarksCursor.moveToFirst() @@ -4318,7 +4391,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Open each bookmark for (i in 0 until bookmarksCursor.count) { // Load the bookmark in a new tab, moving to the tab for the first bookmark if the drawer is not pinned. - addNewTab(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), !bookmarksDrawerPinned && (i == 0)) + addNewTab(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BOOKMARK_URL)), !bookmarksDrawerPinned && (i == 0)) // Move to the next bookmark. bookmarksCursor.moveToNext() @@ -4334,7 +4407,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook bookmarkCursor.moveToFirst() // Load the bookmark in a new tab and move to the tab if the drawer is not pinned. - addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), !bookmarksDrawerPinned) + addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_URL)), !bookmarksDrawerPinned) // Close the cursor. bookmarkCursor.close() @@ -4378,6 +4451,20 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Clear the focus from from the WebView if it is not null, which can happen if a user opens a drawer while the browser is being resumed. // Clearing the focus from the WebView removes any text selection markers and context menus, which otherwise draw above the open drawers. currentWebView?.clearFocus() + + if (bottomAppBar && navigationDrawerFirstView) { + // Reset the navigation drawer first view flag. + navigationDrawerFirstView = false + + // Get a handle for the navigation recycler view. + val navigationRecyclerView = navigationView.getChildAt(0) as RecyclerView + + // Get the navigation linear layout manager. + val navigationLinearLayoutManager = navigationRecyclerView.layoutManager as LinearLayoutManager + + // Scroll the navigation drawer to the bottom. + navigationLinearLayoutManager.scrollToPositionWithOffset(13, 0) + } } } }) @@ -4409,8 +4496,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Get the WebView theme entry values string array. val webViewThemeEntryValuesStringArray = resources.getStringArray(R.array.webview_theme_entry_values) - // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported. - if (Build.VERSION.SDK_INT >= 29 && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { + // Set the WebView theme if algorithmic darkening is supported. + if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // Set the WebView them. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. if (webViewTheme == webViewThemeEntryValuesStringArray[1]) { // The light theme is selected. // Turn off algorithmic darkening. @@ -5320,8 +5407,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Update the URL text bar if the page is currently selected and the URL edit text is not currently being edited. if ((tabLayout.selectedTabPosition == currentPagePosition) && !urlEditText.hasFocus()) { - // Display the formatted URL text. - urlEditText.setText(url) + // Display the formatted URL text. The nested scroll WebView current URL preserves any initial `view-source:`, and opposed to the method URL variable. + urlEditText.setText(nestedScrollWebView.currentUrl) // Highlight the URL syntax. UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan) @@ -5603,7 +5690,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook private fun loadBookmarksFolder() { // Update the bookmarks cursor with the contents of the bookmarks database for the current folder. - bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolder) + bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId) // Populate the bookmarks cursor adapter. bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) { @@ -5618,7 +5705,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook val bookmarkNameTextView = view.findViewById(R.id.bookmark_name) // Get the favorite icon byte array from the cursor. - val favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON)) + val favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(FAVORITE_ICON)) // Convert the byte array to a bitmap beginning at the first byte and ending at the last. val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size) @@ -5627,10 +5714,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap) // Display the bookmark name from the cursor in the bookmark name text view. - bookmarkNameTextView.text = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)) + bookmarkNameTextView.text = cursor.getString(cursor.getColumnIndexOrThrow(BOOKMARK_NAME)) // Make the font bold for folders. - if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) + if (cursor.getInt(cursor.getColumnIndexOrThrow(IS_FOLDER)) == 1) bookmarkNameTextView.typeface = Typeface.DEFAULT_BOLD else // Reset the font to default for normal bookmarks. bookmarkNameTextView.typeface = Typeface.DEFAULT @@ -5641,10 +5728,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook bookmarksListView.adapter = bookmarksCursorAdapter // Set the bookmarks drawer title. - if (currentBookmarksFolder.isEmpty()) + if (currentBookmarksFolderId == HOME_FOLDER_ID) // The current bookmarks folder is the home folder. bookmarksTitleTextView.setText(R.string.bookmarks) else - bookmarksTitleTextView.text = currentBookmarksFolder + bookmarksTitleTextView.text = bookmarksDatabaseHelper!!.getFolderName(currentBookmarksFolderId) } private fun loadUrl(nestedScrollWebView: NestedScrollWebView, url: String) { @@ -5663,7 +5750,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook var urlString = "" // Check to see if the unformatted URL string is a valid URL. Otherwise, convert it into a search. - if (unformattedUrlString.startsWith("content://")) { // This is a content URL. + if (unformattedUrlString.startsWith("content://") || unformattedUrlString.startsWith("view-source:")) { // This is a content or source URL. // Load the entire content URL. urlString = unformattedUrlString } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") || @@ -5727,6 +5814,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Load the history entry. currentWebView!!.goBackOrForward(steps) + + // Update the URL edit text after a delay. + updateUrlEditTextAfterDelay() } override fun openFile(dialogFragment: DialogFragment) { @@ -5837,6 +5927,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Go back. currentWebView!!.goBack() + + // Update the URL edit text after a delay. + updateUrlEditTextAfterDelay() } private fun sanitizeUrl(urlString: String): String { @@ -5879,16 +5972,16 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Stop the swipe to refresh indicator if it is running swipeRefreshLayout.isRefreshing = false - // Get the WebView tab fragment. - val webViewTabFragment = webViewStateAdapter!!.getPageFragment(pageNumber) + // Try to set the current WebView. This will fail if the WebView has not yet been populated. + try { + // Get the WebView tab fragment. + val webViewTabFragment = webViewStateAdapter!!.getPageFragment(pageNumber) - // Get the fragment view. - val webViewFragmentView = webViewTabFragment.view + // Get the fragment view. + val webViewFragmentView = webViewTabFragment.view - // Set the current WebView if the fragment view is not null. - if (webViewFragmentView != null) { // The fragment has been populated. // Store the current WebView. - currentWebView = webViewFragmentView.findViewById(R.id.nestedscroll_webview) + currentWebView = webViewFragmentView!!.findViewById(R.id.nestedscroll_webview) // Update the status of swipe to refresh. if (currentWebView!!.swipeToRefresh) { // Swipe to refresh is enabled. @@ -5948,8 +6041,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Remove any background on the URL relative layout. urlRelativeLayout.background = AppCompatResources.getDrawable(this, R.color.transparent) } - } else if ((pageNumber == savedTabPosition) || (pageNumber >= (webViewStateAdapter!!.itemCount - 1))) { // The tab has not been populated yet. - // Try again in 100 milliseconds if the app is being restored or the a new tab has been added (the last tab). + } catch (exception: Exception) { + // Try again in 100 milliseconds if the WebView has not yet been populated. // Create a handler to set the current WebView. val setCurrentWebViewHandler = Handler(Looper.getMainLooper()) @@ -5959,8 +6052,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook setCurrentWebView(pageNumber) } - // Try setting the current WebView again after 100 milliseconds. - setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100) + // Try setting the current WebView again after 50 milliseconds. + setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 50) } } @@ -6056,4 +6149,22 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook invalidateOptionsMenu() } } + + fun updateUrlEditTextAfterDelay() { + // Create a handler to update the URL edit box. + val urlEditTextUpdateHandler = Handler(Looper.getMainLooper()) + + // Create a runnable to update the URL edit box. + val urlEditTextUpdateRunnable = Runnable { + // Update the URL edit text. + urlEditText.setText(currentWebView!!.url) + + // Disable the wide viewport if the source is being viewed. + if (currentWebView!!.url!!.startsWith("view-source:")) + currentWebView!!.settings.useWideViewPort = false + } + + // Update the URL edit text after 50 milliseconds, so that the WebView has enough time to navigate to the new URL. + urlEditTextUpdateHandler.postDelayed(urlEditTextUpdateRunnable, 50) + } }