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