]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
Add share entries to the WebView context menus. https://redmine.stoutner.com/issues...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.kt
index de608492aade9b22fe3e09a62d0b1990155dc7df..5235003a9ef563c51d7d4fb372ba31301fa32356 100644 (file)
@@ -99,6 +99,7 @@ import androidx.appcompat.app.AppCompatDelegate
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.appcompat.widget.Toolbar
 import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.content.ContextCompat
 import androidx.core.view.GravityCompat
 import androidx.cursoradapter.widget.CursorAdapter
 import androidx.drawerlayout.widget.DrawerLayout
@@ -282,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
@@ -384,7 +386,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
     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
@@ -605,6 +606,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)
 
@@ -725,6 +727,14 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         // Run the default commands.
         super.onNewIntent(intent)
 
+        // Close the navigation drawer if it is open.
+        if (drawerLayout.isDrawerVisible(GravityCompat.START))
+            drawerLayout.closeDrawer(GravityCompat.START)
+
+        // Close the bookmarks drawer if it is open.
+        if (drawerLayout.isDrawerVisible(GravityCompat.END))
+            drawerLayout.closeDrawer(GravityCompat.END)
+
         // Get the information from the intent.
         val intentAction = intent.action
         val intentUriData = intent.data
@@ -772,21 +782,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     // Make it so.
                     loadUrl(currentWebView!!, url!!)
                 }
-
-                // Close the navigation drawer if it is open.
-                if (drawerLayout.isDrawerVisible(GravityCompat.START))
-                    drawerLayout.closeDrawer(GravityCompat.START)
-
-                // Close the bookmarks drawer if it is open.
-                if (drawerLayout.isDrawerVisible(GravityCompat.END))
-                    drawerLayout.closeDrawer(GravityCompat.END)
             }
         } 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 new tab will be the tab position that is restored.
-            if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch)
-                savedTabPosition = savedStateArrayList!!.size
-
             // Replace the intent that started the app with this one.  This will load the tab after the others have been restored.
             setIntent(intent)
         }
@@ -2271,6 +2268,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)
@@ -2539,6 +2545,27 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     true
                 }
 
+                // Add a Share URL entry.
+                contextMenu.add(R.string.share_url).setOnMenuItemClickListener {
+                    // Create the share intent.
+                    val shareUrlIntent = Intent(Intent.ACTION_SEND)
+
+                    // Add the URL to the intent.
+                    shareUrlIntent.putExtra(Intent.EXTRA_TEXT, linkUrl)
+
+                    // Set the MIME type.
+                    shareUrlIntent.type = "text/plain"
+
+                    // Set the intent to open in a new task.
+                    shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+                    //Make it so.
+                    startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)))
+
+                    // Consume the event.
+                    true
+                }
+
                 // Add an empty cancel entry, which by default closes the context menu.
                 contextMenu.add(R.string.cancel)
             }
@@ -2615,6 +2642,27 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     true
                 }
 
+                // Add a Share URL entry.
+                contextMenu.add(R.string.share_url).setOnMenuItemClickListener {
+                    // Create the share intent.
+                    val shareUrlIntent = Intent(Intent.ACTION_SEND)
+
+                    // Add the URL to the intent.
+                    shareUrlIntent.putExtra(Intent.EXTRA_TEXT, imageUrl)
+
+                    // Set the MIME type.
+                    shareUrlIntent.type = "text/plain"
+
+                    // Set the intent to open in a new task.
+                    shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+                    //Make it so.
+                    startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)))
+
+                    // Consume the event.
+                    true
+                }
+
                 // Add an empty cancel entry, which by default closes the context menu.
                 contextMenu.add(R.string.cancel)
             }
@@ -2705,6 +2753,27 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     true
                 }
 
+                // Add a Share Image entry.
+                contextMenu.add(R.string.share_image).setOnMenuItemClickListener {
+                    // Create the share intent.
+                    val shareUrlIntent = Intent(Intent.ACTION_SEND)
+
+                    // Add the URL to the intent.
+                    shareUrlIntent.putExtra(Intent.EXTRA_TEXT, imageUrl)
+
+                    // Set the MIME type.
+                    shareUrlIntent.type = "text/plain"
+
+                    // Set the intent to open in a new task.
+                    shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+                    //Make it so.
+                    startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)))
+
+                    // Consume the event.
+                    true
+                }
+
                 // Add a copy URL entry.
                 contextMenu.add(R.string.copy_url).setOnMenuItemClickListener {
                     // Save the link URL in a clip data.
@@ -2729,6 +2798,27 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     true
                 }
 
+                // Add a Share URL entry.
+                contextMenu.add(R.string.share_url).setOnMenuItemClickListener {
+                    // Create the share intent.
+                    val shareUrlIntent = Intent(Intent.ACTION_SEND)
+
+                    // Add the URL to the intent.
+                    shareUrlIntent.putExtra(Intent.EXTRA_TEXT, linkUrl)
+
+                    // Set the MIME type.
+                    shareUrlIntent.type = "text/plain"
+
+                    // Set the intent to open in a new task.
+                    shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+                    //Make it so.
+                    startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)))
+
+                    // Consume the event.
+                    true
+                }
+
                 // Add an empty cancel entry, which by default closes the context menu.
                 contextMenu.add(R.string.cancel)
             }
@@ -3480,8 +3570,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     // Get the package manager.
                     val packageManager = packageManager
 
-                    // Check to see if Orbot is in the list.  This will throw an error and drop to the catch section if it isn't installed.  The deprecated method must be used until the minimum API >= 33.
-                    @Suppress("DEPRECATION")
+                    // Check to see if Orbot is in the list.  This will throw an error and drop to the catch section if it isn't installed.
                     packageManager.getPackageInfo("org.torproject.android", 0)
 
                     // Check to see if the proxy is ready.
@@ -3529,14 +3618,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                 // Check to see if I2P is installed.
                 try {
                     // Check to see if the F-Droid flavor is installed.  This will throw an error and drop to the catch section if it isn't installed.
-                    // The deprecated method must be used until the minimum API >= 33.
-                    @Suppress("DEPRECATION")
                     packageManager.getPackageInfo("net.i2p.android.router", 0)
                 } catch (fdroidException: PackageManager.NameNotFoundException) {  // The F-Droid flavor is not installed.
                     try {
                         // Check to see if the Google Play flavor is installed.  This will throw an error and drop to the catch section if it isn't installed.
-                        // The deprecated method must be used until the minimum API >= 33.
-                        @Suppress("DEPRECATION")
                         packageManager.getPackageInfo("net.i2p.android", 0)
                     } catch (googlePlayException: PackageManager.NameNotFoundException) {  // The Google Play flavor is not installed.
                         // Sow the I2P not installed dialog if it is not already displayed.
@@ -4007,9 +4092,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             // Add the first tab.
             addNewTab("", false)
         } 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.
@@ -4029,21 +4111,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             savedStateArrayList = null
             savedNestedScrollWebViewStateArrayList = null
 
-            // Restore the selected tab position.
-            if (savedTabPosition == 0) {  // The first tab is selected.
-                // Set the first page as the current WebView.
-                setCurrentWebView(0)
-            } else {  // The first tab is not selected.
-                // 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 intent = intent
 
@@ -4059,7 +4126,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             val isWebSearch = (intentAction != null) && (intentAction == Intent.ACTION_WEB_SEARCH)
 
             // Only process the URI if it contains data or it is a web search.  If the user pressed the desktop icon after the app was already running the URI will be null.
-            if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch) {
+            if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch) {  // A new tab is being loaded.
                 // Get the URL string.
                 val urlString = if (isWebSearch) {  // The intent is a web search.
                     // Sanitize the search input.
@@ -4087,6 +4154,21 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     // Make it so.
                     loadUrl(currentWebView!!, urlString)
                 }
+            } else {  // A new tab is not being loaded.
+                // Restore the selected tab position.
+                if (savedTabPosition == 0) {  // The first tab is selected.
+                    // Set the first page as the current WebView.
+                    setCurrentWebView(0)
+                } else {  // The first tab is not selected.
+                    // 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()
+                    }
+                }
             }
         }
     }
@@ -4192,8 +4274,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             }
         }
 
-        // Register the Orbot status broadcast receiver.
-        registerReceiver(orbotStatusBroadcastReceiver, IntentFilter("org.torproject.android.intent.action.STATUS"))
+        // Register the Orbot status broadcast receiver.  `ContextCompat` must be used until the minimum API >= 34.
+        ContextCompat.registerReceiver(this, orbotStatusBroadcastReceiver, IntentFilter("org.torproject.android.intent.action.STATUS"), ContextCompat.RECEIVER_EXPORTED)
 
         // Get handles for views that need to be modified.
         val bookmarksHeaderLinearLayout = findViewById<LinearLayout>(R.id.bookmarks_header_linearlayout)
@@ -4207,11 +4289,14 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                 // Close the find on page bar if it is open.
                 closeFindOnPage(null)
 
-                // Select the same page in the view pager.
-                webViewViewPager2.currentItem = tab.position
+                // Update the view pager when it has quiesced.  Otherwise, if a page launched by a new intent on restart has not yet been created, the view pager will not be updated to match the tab layout.
+                webViewViewPager2.post {
+                    // Select the same page in the view pager.
+                    webViewViewPager2.currentItem = tab.position
 
-                // Set the current WebView.
-                setCurrentWebView(tab.position)
+                    // Set the current WebView.
+                    setCurrentWebView(tab.position)
+                }
             }
 
             override fun onTabUnselected(tab: TabLayout.Tab) {}
@@ -4220,17 +4305,11 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                 // 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())
+                    // 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))
-                    }
+                    // Display the View SSL Certificate dialog.
+                    viewSslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.view_ssl_certificate))
                 }
             }
         })
@@ -4436,9 +4515,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).
@@ -4668,9 +4766,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                 }
             }
 
-            override fun onFling(motionEvent1: MotionEvent, motionEvent2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
+            override fun onFling(motionEvent1: MotionEvent?, motionEvent2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
                 // Scroll the bottom app bar if enabled.
-                if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning) {
+                if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning && (motionEvent1 != null)) {
                     // Calculate the Y change.
                     val motionY = motionEvent2.y - motionEvent1.y
 
@@ -5660,10 +5758,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             } else {  // Load the URL.
                 loadUrl(nestedScrollWebView, urlToLoadString!!)
             }
-
-            // Reset the intent.  This prevents a duplicate tab from being created on a subsequent restart if loading an link from a new intent on restart.
-            // For example, this prevents a duplicate tab if a link is loaded from the Guide after changing the theme in the guide and then changing the theme again in the main activity.
-            intent = Intent()
         } else {  // This is not the first tab.
             // Load the URL.
             loadUrl(nestedScrollWebView, urlString)