]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt
Convert a number of files to Kotlin. https://redmine.stoutner.com/issues/641
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / SaveDialog.kt
1 /*
2  * Copyright © 2016-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.Manifest
23 import android.annotation.SuppressLint
24 import android.os.Bundle
25 import android.app.Dialog
26 import android.content.Context
27 import android.content.DialogInterface
28 import android.content.Intent
29 import android.content.pm.PackageManager
30 import android.content.res.Configuration
31 import android.view.View
32 import android.view.WindowManager
33 import android.widget.Button
34 import android.widget.EditText
35 import android.widget.TextView
36 import android.text.TextWatcher
37 import android.text.Editable
38
39 import androidx.appcompat.app.AlertDialog
40 import androidx.core.content.ContextCompat
41 import androidx.fragment.app.DialogFragment
42 import androidx.preference.PreferenceManager
43
44 import com.stoutner.privacybrowser.R
45 import com.stoutner.privacybrowser.helpers.DownloadLocationHelper
46
47 import java.io.File
48
49 // Declare the class constants.
50 private const val SAVE_TYPE = "save_type"
51
52 class SaveDialog : DialogFragment() {
53     // Declare the class variables.
54     private lateinit var saveListener: SaveListener
55     private lateinit var fileName: String
56
57     // The public interface is used to send information back to the parent activity.
58     interface SaveListener {
59         fun onSave(saveType: Int, dialogFragment: DialogFragment)
60     }
61
62     override fun onAttach(context: Context) {
63         // Run the default commands.
64         super.onAttach(context)
65
66         // Get a handle for the save listener from the launching context.
67         saveListener = context as SaveListener
68     }
69
70     companion object {
71         // Declare the companion object constants.  These can be moved to class constants once all of the code has transitioned to Kotlin.
72         const val SAVE_LOGCAT = 0
73         const val SAVE_ABOUT_VERSION_TEXT = 1
74         const val SAVE_ABOUT_VERSION_IMAGE = 2
75
76         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
77         @JvmStatic
78         fun save(saveType: Int): SaveDialog {
79             // Create an arguments bundle.
80             val argumentsBundle = Bundle()
81
82             // Store the arguments in the bundle.
83             argumentsBundle.putInt(SAVE_TYPE, saveType)
84
85             // Create a new instance of the save dialog.
86             val saveDialog = SaveDialog()
87
88             // Add the arguments bundle to the dialog.
89             saveDialog.arguments = argumentsBundle
90
91             // Return the new dialog.
92             return saveDialog
93         }
94     }
95
96     // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog.
97     @SuppressLint("InflateParams")
98     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
99         // Get the arguments from the bundle.
100         val saveType = requireArguments().getInt(SAVE_TYPE)
101
102         // Use an alert dialog builder to create the alert dialog.
103         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
104
105         // Get the current theme status.
106         val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
107
108         // Set the title and the icon according to the save type.
109         when (saveType) {
110             SAVE_LOGCAT -> {
111                 // Set the title.
112                 dialogBuilder.setTitle(R.string.save_logcat)
113
114                 // Set the icon according to the theme.
115                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
116                     dialogBuilder.setIcon(R.drawable.save_dialog_day)
117                 } else {
118                     dialogBuilder.setIcon(R.drawable.save_dialog_night)
119                 }
120             }
121
122             SAVE_ABOUT_VERSION_TEXT -> {
123                 // Set the title.
124                 dialogBuilder.setTitle(R.string.save_text)
125
126                 // Set the icon according to the theme.
127                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
128                     dialogBuilder.setIcon(R.drawable.save_text_blue_day)
129                 } else {
130                     dialogBuilder.setIcon(R.drawable.save_text_blue_night)
131                 }
132             }
133
134             SAVE_ABOUT_VERSION_IMAGE -> {
135                 // Set the title.
136                 dialogBuilder.setTitle(R.string.save_image)
137
138                 // Set the icon according to the theme.
139                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
140                     dialogBuilder.setIcon(R.drawable.images_enabled_day)
141                 } else {
142                     dialogBuilder.setIcon(R.drawable.images_enabled_night)
143                 }
144             }
145         }
146
147         // Set the view.  The parent view is null because it will be assigned by the alert dialog.
148         dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.save_dialog, null))
149
150         // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
151         dialogBuilder.setNegativeButton(R.string.cancel, null)
152
153         // Set the save button listener.
154         dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface?, _: Int ->
155             // Return the dialog fragment to the parent activity.
156             saveListener.onSave(saveType, this)
157         }
158
159         // Create an alert dialog from the builder.
160         val alertDialog = dialogBuilder.create()
161
162         // Get a handle for the shared preferences.
163         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
164
165         // Get the screenshot preference.
166         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
167
168         // Disable screenshots if not allowed.
169         if (!allowScreenshots) {
170             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
171         }
172
173         // The alert dialog must be shown before items in the layout can be modified.
174         alertDialog.show()
175
176         // Get handles for the layout items.
177         val fileNameEditText = alertDialog.findViewById<EditText>(R.id.file_name_edittext)!!
178         val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
179         val fileExistsWarningTextView = alertDialog.findViewById<TextView>(R.id.file_exists_warning_textview)!!
180         val storagePermissionTextView = alertDialog.findViewById<TextView>(R.id.storage_permission_textview)!!
181         val saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
182
183         // Update the status of the save button when the file name changes.
184         fileNameEditText.addTextChangedListener(object : TextWatcher {
185             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
186                 // Do nothing.
187             }
188
189             override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
190                 // Do nothing.
191             }
192
193             override fun afterTextChanged(s: Editable) {
194                 // Get the current file name.
195                 val fileNameString = fileNameEditText.text.toString()
196
197                 // Convert the file name string to a file.
198                 val file = File(fileNameString)
199
200                 // Check to see if the file exists.
201                 if (file.exists()) {
202                     // Show the file exists warning.
203                     fileExistsWarningTextView.visibility = View.VISIBLE
204                 } else {
205                     // Hide the file exists warning.
206                     fileExistsWarningTextView.visibility = View.GONE
207                 }
208
209                 // Enable the save button if the file name is populated.
210                 saveButton.isEnabled = fileNameString.isNotEmpty()
211             }
212         })
213
214         // Set the file name according to the type.
215         when (saveType) {
216             SAVE_LOGCAT -> fileName = getString(R.string.privacy_browser_logcat_txt)
217             SAVE_ABOUT_VERSION_TEXT -> fileName = getString(R.string.privacy_browser_version_txt)
218             SAVE_ABOUT_VERSION_IMAGE -> fileName = getString(R.string.privacy_browser_version_png)
219         }
220
221         // Instantiate the download location helper.
222         val downloadLocationHelper = DownloadLocationHelper()
223
224         // Get the default file path.
225         val defaultFilePath = downloadLocationHelper.getDownloadLocation(context) + "/" + fileName
226
227         // Display the default file path.
228         fileNameEditText.setText(defaultFilePath)
229
230         // Hide the storage permission text view if the permission has already been granted.
231         if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
232             storagePermissionTextView.visibility = View.GONE
233         }
234
235         // Handle clicks on the browse button.
236         browseButton.setOnClickListener {
237             // Create the file picker intent.
238             val browseIntent = Intent(Intent.ACTION_CREATE_DOCUMENT)
239
240             // Set the intent MIME type to include all files so that everything is visible.
241             browseIntent.type = "*/*"
242
243             // Set the initial file name.
244             browseIntent.putExtra(Intent.EXTRA_TITLE, fileName)
245
246             // Request a file that can be opened.
247             browseIntent.addCategory(Intent.CATEGORY_OPENABLE)
248
249             // Launch the file picker.  There is only one `startActivityForResult()`, so the request code is simply set to 0, but it must be run under `activity` so the request code is correct.
250             requireActivity().startActivityForResult(browseIntent, 0)
251         }
252
253         // Return the alert dialog.
254         return alertDialog
255     }
256 }