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