Fix the ViewPager not always moving to new tabs. https://redmine.stoutner.com/issues/798
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / SaveDialog.kt
1 /*
2  * Copyright © 2019-2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
6  * Privacy Browser Android 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 Android 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 Android.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs
21
22 import android.app.Dialog
23 import android.content.Context
24 import android.content.DialogInterface
25 import android.os.AsyncTask
26 import android.os.Bundle
27 import android.text.Editable
28 import android.text.InputType
29 import android.text.TextWatcher
30 import android.view.WindowManager
31 import android.widget.EditText
32 import android.widget.TextView
33
34 import androidx.appcompat.app.AlertDialog
35 import androidx.fragment.app.DialogFragment
36 import androidx.preference.PreferenceManager
37
38 import com.stoutner.privacybrowser.R
39 import com.stoutner.privacybrowser.asynctasks.GetUrlSize
40
41 // Define the class constants.
42 private const val URL_STRING = "url_string"
43 private const val FILE_SIZE_STRING = "file_size_string"
44 private const val FILE_NAME_STRING = "file_name_string"
45 private const val USER_AGENT_STRING = "user_agent_string"
46 private const val COOKIES_ENABLED = "cookies_enabled"
47
48 class SaveDialog : DialogFragment() {
49     // Declare the class variables.
50     private lateinit var saveListener: SaveListener
51
52     // Define the class variables.
53     private var getUrlSize: AsyncTask<*, *, *>? = null
54
55     // The public interface is used to send information back to the parent activity.
56     interface SaveListener {
57         fun onSaveUrl(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment)
58     }
59
60     override fun onAttach(context: Context) {
61         // Run the default commands.
62         super.onAttach(context)
63
64         // Get a handle for the save webpage listener from the launching context.
65         saveListener = context as SaveListener
66     }
67
68     companion object {
69         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
70         @JvmStatic
71         fun saveUrl(urlString: String, fileSizeString: String, fileNameString: String, userAgentString: String, cookiesEnabled: Boolean): SaveDialog {
72             // Create an arguments bundle.
73             val argumentsBundle = Bundle()
74
75             // Store the arguments in the bundle.
76             argumentsBundle.putString(URL_STRING, urlString)
77             argumentsBundle.putString(FILE_SIZE_STRING, fileSizeString)
78             argumentsBundle.putString(FILE_NAME_STRING, fileNameString)
79             argumentsBundle.putString(USER_AGENT_STRING, userAgentString)
80             argumentsBundle.putBoolean(COOKIES_ENABLED, cookiesEnabled)
81
82             // Create a new instance of the save webpage dialog.
83             val saveDialog = SaveDialog()
84
85             // Add the arguments bundle to the new dialog.
86             saveDialog.arguments = argumentsBundle
87
88             // Return the new dialog.
89             return saveDialog
90         }
91     }
92
93     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
94         // Get the arguments from the bundle.
95         val originalUrlString = requireArguments().getString(URL_STRING)!!
96         val fileSizeString = requireArguments().getString(FILE_SIZE_STRING)!!
97         val fileNameString = requireArguments().getString(FILE_NAME_STRING)!!
98         val userAgentString = requireArguments().getString(USER_AGENT_STRING)!!
99         val cookiesEnabled = requireArguments().getBoolean(COOKIES_ENABLED)
100
101         // Use an alert dialog builder to create the alert dialog.
102         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
103
104         // Set the title.
105         dialogBuilder.setTitle(R.string.save_url)
106
107         // Set the icon according to the theme.
108         dialogBuilder.setIconAttribute(R.attr.copyBlueIcon)
109
110         // Set the view.
111         dialogBuilder.setView(R.layout.save_dialog)
112
113         // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
114         dialogBuilder.setNegativeButton(R.string.cancel, null)
115
116         // Set the save button listener.
117         dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface, _: Int ->
118             // Return the dialog fragment to the parent activity.
119             saveListener.onSaveUrl(originalUrlString, fileNameString, this)
120         }
121
122         // Create an alert dialog from the builder.
123         val alertDialog = dialogBuilder.create()
124
125         // Get a handle for the shared preferences.
126         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
127
128         // Get the screenshot preference.
129         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
130
131         // Disable screenshots if not allowed.
132         if (!allowScreenshots) {
133             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
134         }
135
136         // The alert dialog must be shown before items in the layout can be modified.
137         alertDialog.show()
138
139         // Get handles for the layout items.
140         val urlEditText = alertDialog.findViewById<EditText>(R.id.url_edittext)!!
141         val fileSizeTextView = alertDialog.findViewById<TextView>(R.id.file_size_textview)!!
142         val saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
143
144         // Set the file size text view.
145         fileSizeTextView.text = fileSizeString
146
147         // 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.
148         if (originalUrlString.startsWith("data:")) {  // The URL contains the entire data of an image.
149             // 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.
150             val urlSubstring = originalUrlString.substring(0, 100) + "…"
151
152             // Populate the URL edit text with the truncated URL.
153             urlEditText.setText(urlSubstring)
154
155             // Disable the editing of the URL edit text.
156             urlEditText.inputType = InputType.TYPE_NULL
157         } else {  // The URL contains a reference to the location of the data.
158             // Populate the URL edit text with the full URL.
159             urlEditText.setText(originalUrlString)
160         }
161
162         // Update the file size when the URL changes.
163         urlEditText.addTextChangedListener(object : TextWatcher {
164             override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
165                 // Do nothing.
166             }
167
168             override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
169                 // Do nothing.
170             }
171
172             override fun afterTextChanged(editable: Editable) {
173                 // Cancel the get URL size AsyncTask if it is running.
174                 if (getUrlSize != null) {
175                     getUrlSize!!.cancel(true)
176                 }
177
178                 // Get the current URL to save.
179                 val urlToSave = urlEditText.text.toString()
180
181                 // Wipe the file size text view.
182                 fileSizeTextView.text = ""
183
184                 // Get the file size for the current URL.
185                 getUrlSize = GetUrlSize(context, alertDialog, userAgentString, cookiesEnabled).execute(urlToSave)
186
187                 // Enable the save button if the URL is populated.
188                 saveButton.isEnabled = urlToSave.isNotEmpty()
189             }
190         })
191
192         // Return the alert dialog.
193         return alertDialog
194     }
195 }