From 99d687b50a4f750f0ca1f865b665931eecf511b5 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Wed, 25 Oct 2023 12:47:26 -0700 Subject: [PATCH] Add a scroll to top/bottom navigation view entry. https://redmine.stoutner.com/issues/144 --- .../activities/MainWebViewActivity.kt | 30 +++++++++++++++++++ .../activities/ViewHeadersActivity.kt | 25 +++++++++------- .../privacybrowser/helpers/UrlHelper.kt | 16 +++++----- .../res/layout/view_headers_bottom_appbar.xml | 2 +- .../res/layout/view_headers_top_appbar.xml | 2 +- .../webview_navigation_menu_bottom_appbar.xml | 16 ++++++---- .../webview_navigation_menu_top_appbar.xml | 30 +++++++++++-------- app/src/main/res/values-es/strings.xml | 8 +++++ app/src/main/res/values-it/strings.xml | 11 ++++++- app/src/main/res/values/strings.xml | 2 ++ 10 files changed, 104 insertions(+), 38 deletions(-) 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 2c7560d6..b0d20e59 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt @@ -283,6 +283,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook private lateinit var navigationForwardMenuItem: MenuItem private lateinit var navigationHistoryMenuItem: MenuItem private lateinit var navigationRequestsMenuItem: MenuItem + private lateinit var navigationScrollToBottomMenuItem: MenuItem private lateinit var navigationView: NavigationView private lateinit var optionsAddOrEditDomainMenuItem: MenuItem private lateinit var optionsBlockAllThirdPartyRequestsMenuItem: MenuItem @@ -606,6 +607,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Get handles for the navigation menu items. navigationBackMenuItem = navigationMenu.findItem(R.id.back) navigationForwardMenuItem = navigationMenu.findItem(R.id.forward) + navigationScrollToBottomMenuItem = navigationMenu.findItem(R.id.scroll_to_bottom) navigationHistoryMenuItem = navigationMenu.findItem(R.id.history) navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests) @@ -2272,6 +2274,15 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook } } + R.id.scroll_to_bottom -> { // Scroll to Bottom. + // Check if the WebView is scrolled to the top. + if (currentWebView!!.scrollY == 0) { // The WebView is at the top; scroll to the bottom. Using a large Y number is more efficient than trying to calculate the exact WebView length. + currentWebView!!.scrollTo(0, 1_000_000_000) + } else { // The WebView is not at the top; scroll to the top. + currentWebView!!.scrollTo(0, 0) + } + } + R.id.history -> { // History. // Instantiate the URL history dialog. val urlHistoryDialogFragment: DialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView!!.webViewFragmentId) @@ -4435,9 +4446,28 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook if (newState == DrawerLayout.STATE_SETTLING || newState == DrawerLayout.STATE_DRAGGING) { // A drawer is opening or closing. // Update the navigation menu items if the WebView is not null. if (currentWebView != null) { + // Set the enabled status of the menu items. navigationBackMenuItem.isEnabled = currentWebView!!.canGoBack() navigationForwardMenuItem.isEnabled = currentWebView!!.canGoForward() + navigationScrollToBottomMenuItem.isEnabled = (currentWebView!!.canScrollVertically(-1) || currentWebView!!.canScrollVertically(1)) navigationHistoryMenuItem.isEnabled = currentWebView!!.canGoBack() || currentWebView!!.canGoForward() + + // Update the scroll menu item. + if (currentWebView!!.scrollY == 0) { // The WebView is scrolled to the top. + // Set the title. + navigationScrollToBottomMenuItem.title = getString(R.string.scroll_to_bottom) + + // Set the icon. + navigationScrollToBottomMenuItem.icon = AppCompatResources.getDrawable(applicationContext, R.drawable.move_down_enabled) + } else { // The WebView is not scrolled to the top. + // Set the title. + navigationScrollToBottomMenuItem.title = getString(R.string.scroll_to_top) + + // Set the icon. + navigationScrollToBottomMenuItem.icon = AppCompatResources.getDrawable(applicationContext, R.drawable.move_up_enabled) + } + + // Display the number of blocked requests. navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + currentWebView!!.getRequestsCount(BLOCKED_REQUESTS) // Hide the keyboard (if displayed). diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewHeadersActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewHeadersActivity.kt index c8878c5f..145ced5d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewHeadersActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewHeadersActivity.kt @@ -146,27 +146,23 @@ class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener responseBodyTitleTextView = findViewById(R.id.response_body_title_textview) val responseBodyTextView = findViewById(R.id.response_body_textview) - // Populate the URL text box. - urlEditText.setText(currentUrl) - // Initialize the gray foreground color spans for highlighting the URLs. initialGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500)) finalGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500)) redColorSpan = ForegroundColorSpan(getColor(R.color.red_text)) - // Apply text highlighting to the URL. - UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan) - // Get a handle for the input method manager, which is used to hide the keyboard. val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) // Remove the formatting from the URL when the user is editing the text. urlEditText.onFocusChangeListener = OnFocusChangeListener { _: View?, hasFocus: Boolean -> if (hasFocus) { // The user is editing the URL text box. - // Remove the highlighting. - urlEditText.text.removeSpan(redColorSpan) - urlEditText.text.removeSpan(initialGrayColorSpan) - urlEditText.text.removeSpan(finalGrayColorSpan) + // Get the foreground color spans. + val foregroundColorSpans: Array = urlEditText.text.getSpans(0, urlEditText.text.length, ForegroundColorSpan::class.java) + + // Remove each foreground color span that highlights the text. + for (foregroundColorSpan in foregroundColorSpans) + urlEditText.text.removeSpan(foregroundColorSpan) } else { // The user has stopped editing the URL text box. // Hide the soft keyboard. inputMethodManager.hideSoftInputFromWindow(urlEditText.windowToken, 0) @@ -174,11 +170,20 @@ class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener // Move to the beginning of the string. urlEditText.setSelection(0) + // Store the URL text in the intent, so update layout uses the new text if the app is restarted. + intent.putExtra(CURRENT_URL, urlEditText.text.toString()) + // Reapply the highlighting. UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan) } } + // Populate the URL text box. + urlEditText.setText(currentUrl) + + // Apply the initial text highlighting to the URL. + UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan) + // Set the refresh color scheme according to the theme. swipeRefreshLayout.setColorSchemeResources(R.color.blue_text) diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/UrlHelper.kt b/app/src/main/java/com/stoutner/privacybrowser/helpers/UrlHelper.kt index a375e0a9..f98b918f 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/helpers/UrlHelper.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/UrlHelper.kt @@ -202,7 +202,7 @@ object UrlHelper { // Highlight the URL according to the protocol. if (urlString.startsWith("file://") || urlString.startsWith("content://")) { // This is a file or content URL. // De-emphasize everything before the file name. - urlEditText.text.setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + urlEditText.text.setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } else { // This is a web URL. // Get the index of the `/` immediately after the domain name. val endOfDomainName = urlString.indexOf("/", urlString.indexOf("//") + 2) @@ -222,29 +222,29 @@ object UrlHelper { // Markup the beginning of the URL. if (urlString.startsWith("http://")) { // The protocol is not encrypted. // Highlight the protocol in red. - urlEditText.text.setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + urlEditText.text.setSpan(redColorSpan, 0, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) // De-emphasize subdomains. if (penultimateDotIndex > 0) // There is more than one subdomain in the domain name. - urlEditText.text.setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + urlEditText.text.setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } else if (urlString.startsWith("https://") || urlString.startsWith("view-source:https://")) { // The protocol is encrypted. // De-emphasize the protocol of connections that are encrypted. if (penultimateDotIndex > 0) // There is more than one subdomain in the domain name. De-emphasize the protocol and the additional subdomains. - urlEditText.text.setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + urlEditText.text.setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) else // There is only one subdomain in the domain name. De-emphasize only the protocol. - urlEditText.text.setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + urlEditText.text.setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } else if (urlString.startsWith("view-source:http://")) { // An insecure source is being viewed. // Check to see if subdomains should be de-emphasized. if (penultimateDotIndex > 0) { // There are subdomains that should be de-emphasized. // De-emphasize the `view-source:` text. - urlEditText.text.setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + urlEditText.text.setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } else { // There are no subdomains that need to be de-emphasized. // De-emphasize the `view-source:` text. - urlEditText.text.setSpan(initialGrayColorSpan, 0, 11, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + urlEditText.text.setSpan(initialGrayColorSpan, 0, 11, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } // Highlight the protocol in red. - urlEditText.text.setSpan(redColorSpan, 12, 19, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + urlEditText.text.setSpan(redColorSpan, 12, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } // De-emphasize the text after the domain name. diff --git a/app/src/main/res/layout/view_headers_bottom_appbar.xml b/app/src/main/res/layout/view_headers_bottom_appbar.xml index 0b896c71..287b982d 100644 --- a/app/src/main/res/layout/view_headers_bottom_appbar.xml +++ b/app/src/main/res/layout/view_headers_bottom_appbar.xml @@ -77,7 +77,6 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="horizontal" - android:layout_marginBottom="16dp" android:layout_gravity="center_horizontal" >