]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.kt
Convert the flavor specific Java classes to Kotlin. https://redmine.stoutner.com...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / SaveWebpageDialog.kt
1 /*
2  * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs
21
22 import android.annotation.SuppressLint
23 import android.app.Dialog
24 import android.content.Context
25 import android.content.DialogInterface
26 import android.content.Intent
27 import android.content.res.Configuration
28 import android.net.Uri
29 import android.os.AsyncTask
30 import android.os.Bundle
31 import android.text.Editable
32 import android.text.InputType
33 import android.text.TextWatcher
34 import android.view.View
35 import android.view.WindowManager
36 import android.widget.Button
37 import android.widget.EditText
38 import android.widget.TextView
39
40 import androidx.appcompat.app.AlertDialog
41 import androidx.fragment.app.DialogFragment
42 import androidx.preference.PreferenceManager
43
44 import com.google.android.material.textfield.TextInputLayout
45
46 import com.stoutner.privacybrowser.R
47 import com.stoutner.privacybrowser.activities.MainWebViewActivity
48 import com.stoutner.privacybrowser.asynctasks.GetUrlSize
49
50 // Define the class constants.
51 private const val SAVE_TYPE = "save_type"
52 private const val URL_STRING = "url_string"
53 private const val FILE_SIZE_STRING = "file_size_string"
54 private const val FILE_NAME_STRING = "file_name_string"
55 private const val USER_AGENT_STRING = "user_agent_string"
56 private const val COOKIES_ENABLED = "cookies_enabled"
57
58 class SaveWebpageDialog : DialogFragment() {
59     // Declare the class variables.
60     private lateinit var saveWebpageListener: SaveWebpageListener
61
62     // Define the class variables.
63     private var getUrlSize: AsyncTask<*, *, *>? = null
64
65     // The public interface is used to send information back to the parent activity.
66     interface SaveWebpageListener {
67         fun onSaveWebpage(saveType: Int, originalUrlString: String, dialogFragment: DialogFragment)
68     }
69
70     override fun onAttach(context: Context) {
71         // Run the default commands.
72         super.onAttach(context)
73
74         // Get a handle for the save webpage listener from the launching context.
75         saveWebpageListener = context as SaveWebpageListener
76     }
77
78     companion object {
79         // Define the companion object constants.  These can be moved to class constants once all of the code has transitioned to Kotlin.
80         const val SAVE_URL = 0
81         const val SAVE_ARCHIVE = 1
82         const val SAVE_IMAGE = 2
83
84         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
85         @JvmStatic
86         fun saveWebpage(saveType: Int, urlString: String, fileSizeString: String?, fileNameString: String?, userAgentString: String?, cookiesEnabled: Boolean): SaveWebpageDialog {
87             // Create an arguments bundle.
88             val argumentsBundle = Bundle()
89
90             // Store the arguments in the bundle.
91             argumentsBundle.putInt(SAVE_TYPE, saveType)
92             argumentsBundle.putString(URL_STRING, urlString)
93             argumentsBundle.putString(FILE_SIZE_STRING, fileSizeString)
94             argumentsBundle.putString(FILE_NAME_STRING, fileNameString)
95             argumentsBundle.putString(USER_AGENT_STRING, userAgentString)
96             argumentsBundle.putBoolean(COOKIES_ENABLED, cookiesEnabled)
97
98             // Create a new instance of the save webpage dialog.
99             val saveWebpageDialog = SaveWebpageDialog()
100
101             // Add the arguments bundle to the new dialog.
102             saveWebpageDialog.arguments = argumentsBundle
103
104             // Return the new dialog.
105             return saveWebpageDialog
106         }
107     }
108
109     // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog.
110     @SuppressLint("InflateParams")
111     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
112         // Get the arguments from the bundle.
113         val saveType = requireArguments().getInt(SAVE_TYPE)
114         val originalUrlString = requireArguments().getString(URL_STRING)!!
115         val fileSizeString = requireArguments().getString(FILE_SIZE_STRING)
116         var fileNameString = requireArguments().getString(FILE_NAME_STRING)
117         val userAgentString = requireArguments().getString(USER_AGENT_STRING)
118         val cookiesEnabled = requireArguments().getBoolean(COOKIES_ENABLED)
119
120         // Use an alert dialog builder to create the alert dialog.
121         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
122
123         // Get the current theme status.
124         val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
125
126         // Configure the dialog according to the save type.
127         when (saveType) {
128             SAVE_URL -> {
129                 // Set the title.
130                 dialogBuilder.setTitle(R.string.save_url)
131
132                 // Set the icon according to the theme.
133                 dialogBuilder.setIconAttribute(R.attr.copyBlueIcon)
134             }
135
136             SAVE_ARCHIVE -> {
137                 // Set the title.
138                 dialogBuilder.setTitle(R.string.save_archive)
139
140                 // Set the icon according to the theme.
141                 dialogBuilder.setIconAttribute(R.attr.domStorageBlueIcon)
142
143                 // Convert the URL to a URI.
144                 val uri = Uri.parse(originalUrlString)
145
146                 // Build a file name string based on the host from the URI.
147                 fileNameString = uri.host + ".mht"
148             }
149
150             SAVE_IMAGE -> {
151                 // Set the title.
152                 dialogBuilder.setTitle(R.string.save_image)
153
154                 // Set the icon according to the theme.
155                 dialogBuilder.setIconAttribute(R.attr.imagesBlueIcon)
156
157                 // Convert the URL to a URI.
158                 val uri = Uri.parse(originalUrlString)
159
160                 // Build a file name string based on the host from the URI.
161                 fileNameString = uri.host + ".png"
162             }
163         }
164
165         // Set the view.  The parent view is null because it will be assigned by the alert dialog.
166         dialogBuilder.setView(layoutInflater.inflate(R.layout.save_webpage_dialog, null))
167
168         // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
169         dialogBuilder.setNegativeButton(R.string.cancel, null)
170
171         // Set the save button listener.
172         dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface, _: Int ->
173             // Return the dialog fragment to the parent activity.
174             saveWebpageListener.onSaveWebpage(saveType, originalUrlString, this)
175         }
176
177         // Create an alert dialog from the builder.
178         val alertDialog = dialogBuilder.create()
179
180         // Get a handle for the shared preferences.
181         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
182
183         // Get the screenshot preference.
184         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
185
186         // Disable screenshots if not allowed.
187         if (!allowScreenshots) {
188             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
189         }
190
191         // The alert dialog must be shown before items in the layout can be modified.
192         alertDialog.show()
193
194         // Get handles for the layout items.
195         val urlTextInputLayout = alertDialog.findViewById<TextInputLayout>(R.id.url_textinputlayout)!!
196         val urlEditText = alertDialog.findViewById<EditText>(R.id.url_edittext)!!
197         val fileNameEditText = alertDialog.findViewById<EditText>(R.id.file_name_edittext)!!
198         val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
199         val fileSizeTextView = alertDialog.findViewById<TextView>(R.id.file_size_textview)!!
200         val saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
201
202         // Set the file size text view.
203         fileSizeTextView.text = fileSizeString
204
205         // Modify the layout based on the save type.
206         if (saveType == SAVE_URL) {  // A URL is being saved.
207             // 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.
208             if (originalUrlString.startsWith("data:")) {  // The URL contains the entire data of an image.
209                 // Get a substring of the data URL with the first 100 characters.  Otherwise, the user interface will freeze while trying to layout the edit text.
210                 val urlSubstring = originalUrlString.substring(0, 100) + "…"
211
212                 // Populate the URL edit text with the truncated URL.
213                 urlEditText.setText(urlSubstring)
214
215                 // Disable the editing of the URL edit text.
216                 urlEditText.inputType = InputType.TYPE_NULL
217             } else {  // The URL contains a reference to the location of the data.
218                 // Populate the URL edit text with the full URL.
219                 urlEditText.setText(originalUrlString)
220             }
221
222             // Update the file size and the status of the save button when the URL changes.
223             urlEditText.addTextChangedListener(object : TextWatcher {
224                 override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
225                     // Do nothing.
226                 }
227
228                 override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
229                     // Do nothing.
230                 }
231
232                 override fun afterTextChanged(editable: Editable) {
233                     // Cancel the get URL size AsyncTask if it is running.
234                     if (getUrlSize != null) {
235                         getUrlSize!!.cancel(true)
236                     }
237
238                     // Get the current URL to save.
239                     val urlToSave = urlEditText.text.toString()
240
241                     // Wipe the file size text view.
242                     fileSizeTextView.text = ""
243
244                     // Get the file size for the current URL.
245                     getUrlSize = GetUrlSize(context, alertDialog, userAgentString, cookiesEnabled).execute(urlToSave)
246
247                     // Enable the save button if the URL and file name are populated.
248                     saveButton.isEnabled = urlToSave.isNotEmpty() && fileNameEditText.text.toString().isNotEmpty()
249                 }
250             })
251         } else {  // An archive or an image is being saved.
252             // Hide the URL edit text and the file size text view.
253             urlTextInputLayout.visibility = View.GONE
254             fileSizeTextView.visibility = View.GONE
255         }
256
257         // Initially disable the save button.
258         saveButton.isEnabled = false
259
260         // Update the status of the save button when the file name changes.
261         fileNameEditText.addTextChangedListener(object : TextWatcher {
262             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
263                 // Do nothing.
264             }
265
266             override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
267                 // Do nothing.
268             }
269
270             override fun afterTextChanged(s: Editable) {
271                 // Enable the save button based on the save type.
272                 if (saveType == SAVE_URL) {  // A URL is being saved.
273                     // Enable the save button if the file name and the URL are populated.
274                     saveButton.isEnabled = fileNameEditText.text.toString().isNotEmpty() && urlEditText.text.toString().isNotEmpty()
275                 } else {  // An archive or an image is being saved.
276                     // Enable the save button if the file name is populated.
277                     saveButton.isEnabled = fileNameEditText.text.toString().isNotEmpty()
278                 }
279             }
280         })
281
282         // Handle clicks on the browse button.
283         browseButton.setOnClickListener {
284             // Create the file picker intent.
285             val browseIntent = Intent(Intent.ACTION_CREATE_DOCUMENT)
286
287             // Set the intent MIME type to include all files so that everything is visible.
288             browseIntent.type = "*/*"
289
290             // Set the initial file name according to the type.
291             browseIntent.putExtra(Intent.EXTRA_TITLE, fileNameString)
292
293             // Request a file that can be opened.
294             browseIntent.addCategory(Intent.CATEGORY_OPENABLE)
295
296             // Start the file picker.  This must be started under `activity` so that the request code is returned correctly.
297             requireActivity().startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_SAVE_WEBPAGE_REQUEST_CODE)
298         }
299
300         // Return the alert dialog.
301         return alertDialog
302     }
303 }