]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDialog.kt
Standardize suggested file names. https://redmine.stoutner.com/issues/951
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / EditBookmarkFolderDialog.kt
1 /*
2  * Copyright © 2016-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.graphics.Bitmap
26 import android.graphics.BitmapFactory
27 import android.os.Bundle
28 import android.text.Editable
29 import android.text.TextWatcher
30 import android.view.KeyEvent
31 import android.view.View
32 import android.view.WindowManager
33 import android.widget.Button
34 import android.widget.EditText
35 import android.widget.ImageView
36 import android.widget.LinearLayout
37 import android.widget.RadioButton
38
39 import androidx.appcompat.app.AlertDialog
40 import androidx.fragment.app.DialogFragment
41 import androidx.preference.PreferenceManager
42
43 import com.stoutner.privacybrowser.R
44 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
45
46 import java.io.ByteArrayOutputStream
47
48 // Define the class constants.
49 private const val DATABASE_ID = "database_id"
50 private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
51
52 class EditBookmarkFolderDialog : DialogFragment() {
53     companion object {
54         fun folderDatabaseId(databaseId: Int, favoriteIconBitmap: Bitmap): EditBookmarkFolderDialog {
55             // Create a favorite icon byte array output stream.
56             val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
57
58             // Convert the favorite icon to a PNG and place it in the byte array output stream.  `0` is for lossless compression (the only option for a PNG).
59             favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream)
60
61             // Convert the byte array output stream to a byte array.
62             val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
63
64             // Create an arguments bundle
65             val argumentsBundle = Bundle()
66
67             // Store the variables in the bundle.
68             argumentsBundle.putInt(DATABASE_ID, databaseId)
69             argumentsBundle.putByteArray(FAVORITE_ICON_BYTE_ARRAY, favoriteIconByteArray)
70
71             // Create a new instance of the dialog.
72             val editBookmarkFolderDialog = EditBookmarkFolderDialog()
73
74             // Add the arguments bundle to the dialog.
75             editBookmarkFolderDialog.arguments = argumentsBundle
76
77             // Return the new dialog.
78             return editBookmarkFolderDialog
79         }
80     }
81
82     // Declare the class variables.
83     private lateinit var editBookmarkFolderListener: EditBookmarkFolderListener
84     private lateinit var bookmarksDatabaseHelper: BookmarksDatabaseHelper
85     private lateinit var currentFolderName: String
86
87     // Declare the class views.
88     private lateinit var currentIconRadioButton: RadioButton
89     private lateinit var folderNameEditText: EditText
90     private lateinit var saveButton: Button
91
92     // The public interface is used to send information back to the parent activity.
93     interface EditBookmarkFolderListener {
94         fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap)
95     }
96
97     override fun onAttach(context: Context) {
98         // Run the default commands.
99         super.onAttach(context)
100
101         // Get a handle for the edit bookmark folder listener from the launching context.
102         editBookmarkFolderListener = context as EditBookmarkFolderListener
103     }
104
105     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
106         // Get a handle for the arguments.
107         val arguments = requireArguments()
108
109         // Get the variables from the arguments.
110         val selectedFolderDatabaseId = arguments.getInt(DATABASE_ID)
111         val favoriteIconByteArray = arguments.getByteArray(FAVORITE_ICON_BYTE_ARRAY)!!
112
113         // Convert the favorite icon byte array to a bitmap.
114         val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
115
116         // Initialize the database helper.
117         bookmarksDatabaseHelper = BookmarksDatabaseHelper(requireContext())
118
119         // Get a cursor with the selected folder.
120         val folderCursor = bookmarksDatabaseHelper.getBookmark(selectedFolderDatabaseId)
121
122         // Move the cursor to the first position.
123         folderCursor.moveToFirst()
124
125         // Use an alert dialog builder to create the alert dialog.
126         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
127
128         // Set the title.
129         dialogBuilder.setTitle(R.string.edit_folder)
130
131         // Set the view.
132         dialogBuilder.setView(R.layout.edit_bookmark_folder_dialog)
133
134         // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
135         dialogBuilder.setNegativeButton(R.string.cancel, null)
136
137         // Set the save button listener.
138         dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface?, _: Int ->
139             // Return the dialog fragment to the parent activity on save.
140             editBookmarkFolderListener.onSaveBookmarkFolder(this, selectedFolderDatabaseId, favoriteIconBitmap)
141         }
142
143         // Create an alert dialog from the alert dialog builder.
144         val alertDialog = dialogBuilder.create()
145
146         // Get a handle for the shared preferences.
147         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
148
149         // Get the screenshot preference.
150         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
151
152         // Disable screenshots if not allowed.
153         if (!allowScreenshots) {
154             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
155         }
156
157         // The alert dialog must be shown before items in the layout can be modified.
158         alertDialog.show()
159
160         // Get handles for the views in the alert dialog.
161         val currentIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.current_icon_linearlayout)!!
162         currentIconRadioButton = alertDialog.findViewById(R.id.current_icon_radiobutton)!!
163         val currentIconImageView = alertDialog.findViewById<ImageView>(R.id.current_icon_imageview)!!
164         val defaultIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.default_icon_linearlayout)!!
165         val defaultIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)!!
166         val webpageFavoriteIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.webpage_favorite_icon_linearlayout)!!
167         val webpageFavoriteIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)!!
168         val webpageFavoriteIconImageView = alertDialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)!!
169         folderNameEditText = alertDialog.findViewById(R.id.folder_name_edittext)!!
170         saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
171
172         // Get the current favorite icon byte array from the cursor.
173         val currentIconByteArray = folderCursor.getBlob(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON))
174
175         // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
176         val currentIconBitmap = BitmapFactory.decodeByteArray(currentIconByteArray, 0, currentIconByteArray.size)
177
178         // Display the current icon bitmap.
179         currentIconImageView.setImageBitmap(currentIconBitmap)
180
181         // Set the webpage favorite icon bitmap.
182         webpageFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
183
184         // Get the current folder name.
185         currentFolderName = folderCursor.getString(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
186
187         // Display the current folder name.
188         folderNameEditText.setText(currentFolderName)
189
190         // Initially disable the save button.
191         saveButton.isEnabled = false
192
193         // Set the radio button listeners.  These perform a click on the linear layout, which contains the necessary logic.
194         currentIconRadioButton.setOnClickListener { currentIconLinearLayout.performClick() }
195         defaultIconRadioButton.setOnClickListener { defaultIconLinearLayout.performClick() }
196         webpageFavoriteIconRadioButton.setOnClickListener { webpageFavoriteIconLinearLayout.performClick() }
197
198         // Set the current icon linear layout click listener.
199         currentIconLinearLayout.setOnClickListener {
200             // Check the current icon radio button.
201             currentIconRadioButton.isChecked = true
202
203             // Uncheck the other radio buttons.
204             defaultIconRadioButton.isChecked = false
205             webpageFavoriteIconRadioButton.isChecked = false
206
207             // Update the save button.
208             updateSaveButton()
209         }
210
211         // Set the default icon linear layout click listener.
212         defaultIconLinearLayout.setOnClickListener {
213             // Check the default icon radio button.
214             defaultIconRadioButton.isChecked = true
215
216             // Uncheck the other radio buttons.
217             currentIconRadioButton.isChecked = false
218             webpageFavoriteIconRadioButton.isChecked = false
219
220             // Update the save button.
221             updateSaveButton()
222         }
223
224         // Set the webpage favorite icon linear layout click listener.
225         webpageFavoriteIconLinearLayout.setOnClickListener {
226             // Check the webpage favorite icon radio button.
227             webpageFavoriteIconRadioButton.isChecked = true
228
229             // Uncheck the other radio buttons.
230             currentIconRadioButton.isChecked = false
231             defaultIconRadioButton.isChecked = false
232
233             // Update the save button.
234             updateSaveButton()
235         }
236
237         // Update the status of the save button when the folder name is changed.
238         folderNameEditText.addTextChangedListener(object: TextWatcher {
239             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
240                 // Do nothing.
241             }
242
243             override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
244                 // Do nothing.
245             }
246
247             override fun afterTextChanged(s: Editable) {
248                 // Update the save button.
249                 updateSaveButton()
250             }
251         })
252
253         // Allow the enter key on the keyboard to save the bookmark from the edit bookmark name edit text.
254         folderNameEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
255             // Check the key code, event, and button status.
256             if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && saveButton.isEnabled) {  // The enter key was pressed and the save button is enabled.
257                 // Trigger the listener and return the dialog fragment to the parent activity.
258                 editBookmarkFolderListener.onSaveBookmarkFolder(this, selectedFolderDatabaseId, favoriteIconBitmap)
259
260                 // Manually dismiss the the alert dialog.
261                 alertDialog.dismiss()
262
263                 // Consume the event.
264                 return@setOnKeyListener true
265             } else {  // If any other key was pressed, or if the save button is currently disabled, do not consume the event.
266                 return@setOnKeyListener false
267             }
268         }
269
270         // Return the alert dialog.
271         return alertDialog
272     }
273
274     private fun updateSaveButton() {
275         // Get the new folder name.
276         val newFolderName = folderNameEditText.text.toString()
277
278         // Get a cursor for the new folder name if it exists.
279         val folderExistsCursor = bookmarksDatabaseHelper.getFolder(newFolderName)
280
281         // Is the new folder name empty?
282         val folderNameEmpty = newFolderName.isEmpty()
283
284         // Does the folder name already exist?
285         val folderNameAlreadyExists = (newFolderName != currentFolderName) && folderExistsCursor.count > 0
286
287         // Has the folder been renamed?
288         val folderRenamed = (newFolderName != currentFolderName) && !folderNameAlreadyExists
289
290         // Has the favorite icon changed?
291         val iconChanged = !currentIconRadioButton.isChecked && !folderNameAlreadyExists
292
293         // Enable the save button if something has been edited and the new folder name is valid.
294         saveButton.isEnabled = !folderNameEmpty && (folderRenamed || iconChanged)
295     }
296 }