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
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
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
companion object {
// Define the public static variables.
- var currentBookmarksFolder = ""
+ var currentBookmarksFolderId = 0L
val executorService = Executors.newFixedThreadPool(4)!!
var orbotStatus = "unknown"
val pendingDialogsArrayList = ArrayList<PendingDialogDataClass>()
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
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
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
tabLayout = findViewById(R.id.tablayout)
swipeRefreshLayout = findViewById(R.id.swiperefreshlayout)
webViewViewPager2 = findViewById(R.id.webview_viewpager2)
- val navigationView = findViewById<NavigationView>(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)
// 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)
}
} 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
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)
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.
// 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)
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)
// 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.
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)
)
}
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
// Load the previous website in the history.
currentWebView!!.goBack()
+
+ // Update the URL edit text after a delay.
+ updateUrlEditTextAfterDelay()
}
}
// Load the next website in the history.
currentWebView!!.goForward()
+
+ // Update the URL edit text after a delay.
+ updateUrlEditTextAfterDelay()
}
}
// 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) {
}
}
- // 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.
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.
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)
// 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()
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)
}
// 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)
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.
// 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.
val createBookmarkFolderFab = findViewById<FloatingActionButton>(R.id.create_bookmark_folder_fab)
val createBookmarkFab = findViewById<FloatingActionButton>(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 2 seconds since the last restart as deep restarts sometimes end up selecting a tab twice.
+ if (millisecondsSinceLastRestart > 2000) {
+ // 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))
+ }
+ }
}
})
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)
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)
// 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()
// 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()
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()
// 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)
+ }
}
}
})
// 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.
// 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)
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) {
val bookmarkNameTextView = view.findViewById<TextView>(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)
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
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) {
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://") ||
// Load the history entry.
currentWebView!!.goBackOrForward(steps)
+
+ // Update the URL edit text after a delay.
+ updateUrlEditTextAfterDelay()
}
override fun openFile(dialogFragment: DialogFragment) {
// Go back.
currentWebView!!.goBack()
+
+ // Update the URL edit text after a delay.
+ updateUrlEditTextAfterDelay()
}
private fun sanitizeUrl(urlString: String): String {
// 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.
// 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())
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)
}
}
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)
+ }
}