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