]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt
First wrong button text in View Headers in night theme. https://redmine.stoutner...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / SaveDialog.kt
index 1aa31dd00c7ce94fc117dde1fcb5f64d1b24ae32..dca20947c0ed69f644a917d75bca8cab0d0e9603 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2019-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2019-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
  *
@@ -22,60 +22,46 @@ package com.stoutner.privacybrowser.dialogs
 import android.app.Dialog
 import android.content.Context
 import android.content.DialogInterface
-import android.os.AsyncTask
 import android.os.Bundle
 import android.text.Editable
 import android.text.InputType
 import android.text.TextWatcher
+import android.view.View
 import android.view.WindowManager
 import android.widget.EditText
+import android.widget.LinearLayout
 import android.widget.TextView
 
 import androidx.appcompat.app.AlertDialog
+import androidx.core.view.isVisible
 import androidx.fragment.app.DialogFragment
 import androidx.preference.PreferenceManager
 
 import com.stoutner.privacybrowser.R
-import com.stoutner.privacybrowser.asynctasks.GetUrlSize
+import com.stoutner.privacybrowser.helpers.UrlHelper
 
-// Define the class constants.
-private const val URL_STRING = "url_string"
-private const val FILE_SIZE_STRING = "file_size_string"
-private const val FILE_NAME_STRING = "file_name_string"
-private const val USER_AGENT_STRING = "user_agent_string"
-private const val COOKIES_ENABLED = "cookies_enabled"
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
-class SaveDialog : DialogFragment() {
-    // Declare the class variables.
-    private lateinit var saveListener: SaveListener
-
-    // Define the class variables.
-    private var getUrlSize: AsyncTask<*, *, *>? = null
-
-    // The public interface is used to send information back to the parent activity.
-    interface SaveListener {
-        fun onSaveUrl(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment)
-    }
-
-    override fun onAttach(context: Context) {
-        // Run the default commands.
-        super.onAttach(context)
-
-        // Get a handle for the save webpage listener from the launching context.
-        saveListener = context as SaveListener
-    }
+// Define the private class constants.
+private const val URL_STRING = "A"
+private const val FILE_SIZE_STRING = "B"
+private const val FILE_NAME_STRING = "C"
+private const val USER_AGENT_STRING = "D"
+private const val COOKIES_ENABLED = "E"
 
+class SaveDialog : DialogFragment() {
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
-        @JvmStatic
-        fun saveUrl(urlString: String, fileSizeString: String, fileNameString: String, userAgentString: String, cookiesEnabled: Boolean): SaveDialog {
+        fun saveUrl(urlString: String, fileNameString: String, fileSizeString: String, userAgentString: String, cookiesEnabled: Boolean): SaveDialog {
             // Create an arguments bundle.
             val argumentsBundle = Bundle()
 
             // Store the arguments in the bundle.
             argumentsBundle.putString(URL_STRING, urlString)
-            argumentsBundle.putString(FILE_SIZE_STRING, fileSizeString)
             argumentsBundle.putString(FILE_NAME_STRING, fileNameString)
+            argumentsBundle.putString(FILE_SIZE_STRING, fileSizeString)
             argumentsBundle.putString(USER_AGENT_STRING, userAgentString)
             argumentsBundle.putBoolean(COOKIES_ENABLED, cookiesEnabled)
 
@@ -90,13 +76,49 @@ class SaveDialog : DialogFragment() {
         }
     }
 
+    // Declare the class variables.
+    private lateinit var saveListener: SaveListener
+
+    // The public interface is used to send information back to the parent activity.
+    interface SaveListener {
+        // Save with Android's download manager.
+        fun saveWithAndroidDownloadManager(dialogFragment: DialogFragment)
+
+        // Save with Privacy Browser.
+        fun saveWithPrivacyBrowser(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment)
+    }
+
+    override fun onAttach(context: Context) {
+        // Run the default commands.
+        super.onAttach(context)
+
+        // Get a handle for the save webpage listener from the launching context.
+        saveListener = context as SaveListener
+    }
+
     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // Get the arguments
+        val arguments = requireArguments()
+
         // Get the arguments from the bundle.
-        val originalUrlString = requireArguments().getString(URL_STRING)!!
-        val fileSizeString = requireArguments().getString(FILE_SIZE_STRING)!!
-        val fileNameString = requireArguments().getString(FILE_NAME_STRING)!!
-        val userAgentString = requireArguments().getString(USER_AGENT_STRING)!!
-        val cookiesEnabled = requireArguments().getBoolean(COOKIES_ENABLED)
+        val originalUrlString = arguments.getString(URL_STRING)!!
+        var fileNameString = arguments.getString(FILE_NAME_STRING)!!
+        val fileSizeString = arguments.getString(FILE_SIZE_STRING)!!
+        val userAgentString = arguments.getString(USER_AGENT_STRING)!!
+        val cookiesEnabled = arguments.getBoolean(COOKIES_ENABLED)
+
+        // Get the download provider entry values string array.
+        val downloadProviderEntryValuesStringArray = resources.getStringArray(R.array.download_provider_entry_values)
+
+        // Get a handle for the shared preferences.
+        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
+
+        // Get the preference.
+        val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
+        val downloadProvider = sharedPreferences.getString(getString(R.string.download_provider_key), getString(R.string.download_provider_default_value))!!
+
+        // Determine the download provider.
+        val privacyBrowserDownloadProvider = downloadProvider == downloadProviderEntryValuesStringArray[0]
 
         // Use an alert dialog builder to create the alert dialog.
         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
@@ -104,8 +126,8 @@ class SaveDialog : DialogFragment() {
         // Set the title.
         dialogBuilder.setTitle(R.string.save_url)
 
-        // Set the icon according to the theme.
-        dialogBuilder.setIconAttribute(R.attr.copyBlueIcon)
+        // Set the icon.
+        dialogBuilder.setIcon(R.drawable.download)
 
         // Set the view.
         dialogBuilder.setView(R.layout.save_dialog)
@@ -115,19 +137,16 @@ class SaveDialog : DialogFragment() {
 
         // Set the save button listener.
         dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface, _: Int ->
-            // Return the dialog fragment to the parent activity.
-            saveListener.onSaveUrl(originalUrlString, fileNameString, this)
+            // Save the URL with the selected download provider.
+            if (privacyBrowserDownloadProvider)  // Download with Privacy Browser.
+                saveListener.saveWithPrivacyBrowser(originalUrlString, fileNameString, this)
+            else  // Download with Android's download manager.
+                saveListener.saveWithAndroidDownloadManager(this)
         }
 
         // Create an alert dialog from the builder.
         val alertDialog = dialogBuilder.create()
 
-        // Get a handle for the shared preferences.
-        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
-
-        // Get the screenshot preference.
-        val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
-
         // Disable screenshots if not allowed.
         if (!allowScreenshots) {
             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
@@ -139,10 +158,19 @@ class SaveDialog : DialogFragment() {
         // Get handles for the layout items.
         val urlEditText = alertDialog.findViewById<EditText>(R.id.url_edittext)!!
         val fileSizeTextView = alertDialog.findViewById<TextView>(R.id.file_size_textview)!!
+        val blobUrlWarningTextView = alertDialog.findViewById<TextView>(R.id.blob_url_warning_textview)!!
+        val dataUrlWarningTextView = alertDialog.findViewById<TextView>(R.id.data_url_warning_textview)!!
+        val androidDownloadManagerLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.android_download_manager_linearlayout)!!
+        val fileNameEditText = alertDialog.findViewById<TextView>(R.id.file_name_edittext)!!
         val saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
 
-        // Set the file size text view.
+        // Display the extra views if using Android's download manager.
+        if (!privacyBrowserDownloadProvider)
+            androidDownloadManagerLinearLayout.visibility = View.VISIBLE
+
+        // Populate the views.
         fileSizeTextView.text = fileSizeString
+        fileNameEditText.text = fileNameString
 
         // Populate the URL edit text according to the type.  This must be done before the text change listener is created below so that the file size isn't requested again.
         if (originalUrlString.startsWith("data:")) {  // The URL contains the entire data of an image.
@@ -154,42 +182,105 @@ class SaveDialog : DialogFragment() {
 
             // Disable the editing of the URL edit text.
             urlEditText.inputType = InputType.TYPE_NULL
+
+            // Display the warning if using Android's download manager.
+            if (!privacyBrowserDownloadProvider) {
+                // Display the data URL warning.
+                dataUrlWarningTextView.visibility = View.VISIBLE
+
+                // Disable the save button.
+                saveButton.isEnabled = false
+            }
         } else {  // The URL contains a reference to the location of the data.
             // Populate the URL edit text with the full URL.
             urlEditText.setText(originalUrlString)
         }
 
-        // Update the file size when the URL changes.
+        // Handle blob URLs.
+        if (originalUrlString.startsWith("blob:")) {
+            // Display the blob URL warning.
+            blobUrlWarningTextView.visibility = View.VISIBLE
+
+            // Disable the save button.
+            saveButton.isEnabled = false
+        }
+
+        // Update the UI when the URL changes.
         urlEditText.addTextChangedListener(object : TextWatcher {
-            override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(editable: Editable) {
-                // Cancel the get URL size AsyncTask if it is running.
-                if (getUrlSize != null) {
-                    getUrlSize!!.cancel(true)
+            override fun afterTextChanged(editable: Editable?) {
+                // Get the contents of the edit texts.
+                val urlToSave = urlEditText.text.toString()
+                val fileName = fileNameEditText.text.toString()
+
+                // Determine if this is a blob URL.
+                val blobUrl = urlToSave.startsWith("blob:")
+
+                // Set the display status of the blob warning.
+                if (blobUrl)
+                    blobUrlWarningTextView.visibility = View.VISIBLE
+                else
+                    blobUrlWarningTextView.visibility = View.GONE
+
+                // Enable the save button if the edit texts are populated and this isn't a blob URL.
+                saveButton.isEnabled = urlToSave.isNotBlank() && fileName.isNotBlank() && !blobUrl
+
+                // Determine if this is a data URL.
+                val dataUrl = urlToSave.startsWith("data:")
+
+                // Only process the URL if it is not a data URL.
+                if (!dataUrl) {
+                    CoroutineScope(Dispatchers.Main).launch {
+                        // Create a URL size string.
+                        var fileNameAndSize: Pair<String, String>
+
+                        // Get the URL size on the IO thread.
+                        withContext(Dispatchers.IO) {
+                            // Get the updated file name and size.
+                            fileNameAndSize = UrlHelper.getNameAndSize(requireContext(), urlToSave, userAgentString, cookiesEnabled)
+
+                            // Save the updated file name.
+                            fileNameString = fileNameAndSize.first
+                        }
+
+                        // Display the updated file size.
+                        fileSizeTextView.text = fileNameAndSize.second
+                    }
                 }
+            }
+        })
 
-                // Get the current URL to save.
-                val urlToSave = urlEditText.text.toString()
+        // Update the UI when the file name changes.
+        fileNameEditText.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
+                // Do nothing.
+            }
 
-                // Wipe the file size text view.
-                fileSizeTextView.text = ""
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
+                // Do nothing.
+            }
+
+            override fun afterTextChanged(editable: Editable?) {
+                // Get the contents of the edit texts.
+                val urlToSave = urlEditText.text.toString()
+                val fileName = fileNameEditText.text.toString()
 
-                // Get the file size for the current URL.
-                getUrlSize = GetUrlSize(context, alertDialog, userAgentString, cookiesEnabled).execute(urlToSave)
+                // Determine if this is a blob URL.
+                val blobUrl = urlToSave.startsWith("blob:")
 
-                // Enable the save button if the URL is populated.
-                saveButton.isEnabled = urlToSave.isNotEmpty()
+                // Enable the save button if the edit texts are populated and this isn't a blob URL (or a data URL using Android's download manager).
+                saveButton.isEnabled = urlToSave.isNotBlank() && fileName.isNotBlank() && !blobUrl && !dataUrlWarningTextView.isVisible
             }
         })
 
         // Return the alert dialog.
         return alertDialog
     }
-}
\ No newline at end of file
+}