]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.kt
Standardize suggested file names. https://redmine.stoutner.com/issues/951
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / EditBookmarkDialog.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 EditBookmarkDialog : DialogFragment() {
53     companion object {
54         fun bookmarkDatabaseId(databaseId: Int, favoriteIconBitmap: Bitmap): EditBookmarkDialog {
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 editBookmarkDialog = EditBookmarkDialog()
73
74             // Add the arguments bundle to the dialog.
75             editBookmarkDialog.arguments = argumentsBundle
76
77             // Return the new dialog.
78             return editBookmarkDialog
79         }
80     }
81
82     // Declare the class variables.
83     private lateinit var editBookmarkListener: EditBookmarkListener
84
85     // Declare the class views.
86     private lateinit var webpageFavoriteIconRadioButton: RadioButton
87     private lateinit var nameEditText: EditText
88     private lateinit var urlEditText: EditText
89     private lateinit var saveButton: Button
90
91     // The public interface is used to send information back to the parent activity.
92     interface EditBookmarkListener {
93         fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap)
94     }
95
96     override fun onAttach(context: Context) {
97         // Run the default commands.
98         super.onAttach(context)
99
100         // Get a handle for the edit bookmark listener from the launching context.
101         editBookmarkListener = context as EditBookmarkListener
102     }
103
104     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
105         // Get the arguments.
106         val arguments = requireArguments()
107
108         // Get the variables from the arguments.
109         val selectedBookmarkDatabaseId = arguments.getInt(DATABASE_ID)
110         val favoriteIconByteArray = arguments.getByteArray(FAVORITE_ICON_BYTE_ARRAY)!!
111
112         // Convert the favorite icon byte array to a bitmap.
113         val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
114
115         // Initialize the bookmarks database helper.
116         val bookmarksDatabaseHelper = BookmarksDatabaseHelper(requireContext())
117
118         // Get a cursor with the selected bookmark.
119         val bookmarkCursor = bookmarksDatabaseHelper.getBookmark(selectedBookmarkDatabaseId)
120
121         // Move the cursor to the first position.
122         bookmarkCursor.moveToFirst()
123
124         // Use an alert dialog builder to create the alert dialog.
125         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
126
127         // Set the title.
128         dialogBuilder.setTitle(R.string.edit_bookmark)
129
130         // Set the view.
131         dialogBuilder.setView(R.layout.edit_bookmark_dialog)
132
133         // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
134         dialogBuilder.setNegativeButton(R.string.cancel, null)
135
136         // Set the save button listener.
137         dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface?, _: Int ->
138             // Return the dialog fragment to the parent activity.
139             editBookmarkListener.onSaveBookmark(this, selectedBookmarkDatabaseId, favoriteIconBitmap)
140         }
141
142         // Create an alert dialog from the builder.
143         val alertDialog = dialogBuilder.create()
144
145         // Get a handle for the shared preferences.
146         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
147
148         // Get the screenshot preference.
149         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
150
151         // Disable screenshots if not allowed.
152         if (!allowScreenshots) {
153             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
154         }
155
156         // The alert dialog must be shown before items in the layout can be modified.
157         alertDialog.show()
158
159         // Get handles for the layout items.
160         val currentIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.current_icon_linearlayout)!!
161         val currentIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)!!
162         val currentIconImageView = alertDialog.findViewById<ImageView>(R.id.current_icon_imageview)!!
163         val webpageFavoriteIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.webpage_favorite_icon_linearlayout)!!
164         webpageFavoriteIconRadioButton = alertDialog.findViewById(R.id.webpage_favorite_icon_radiobutton)!!
165         val webpageFavoriteIconImageView = alertDialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)!!
166         nameEditText = alertDialog.findViewById(R.id.bookmark_name_edittext)!!
167         urlEditText = alertDialog.findViewById(R.id.bookmark_url_edittext)!!
168         saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
169
170         // Get the current favorite icon byte array from the cursor.
171         val currentIconByteArray = bookmarkCursor.getBlob(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON))
172
173         // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
174         val currentIconBitmap = BitmapFactory.decodeByteArray(currentIconByteArray, 0, currentIconByteArray.size)
175
176         // Display the current icon bitmap.
177         currentIconImageView.setImageBitmap(currentIconBitmap)
178
179         // Set the webpage favorite icon bitmap.
180         webpageFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
181
182         // Store the current bookmark name and URL.
183         val currentName = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
184         val currentUrl = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL))
185
186         // Populate the edit texts.
187         nameEditText.setText(currentName)
188         urlEditText.setText(currentUrl)
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         webpageFavoriteIconRadioButton.setOnClickListener { webpageFavoriteIconLinearLayout.performClick() }
196
197         // Set the current icon linear layout click listener.
198         currentIconLinearLayout.setOnClickListener {
199             // Check the current icon radio button.
200             currentIconRadioButton.isChecked = true
201
202             // Uncheck the webpage favorite icon radio button.
203             webpageFavoriteIconRadioButton.isChecked = false
204
205             // Update the save button.
206             updateSaveButton(currentName, currentUrl)
207         }
208
209         // Set the webpage favorite icon linear layout click listener.
210         webpageFavoriteIconLinearLayout.setOnClickListener {
211             // Check the webpage favorite icon radio button.
212             webpageFavoriteIconRadioButton.isChecked = true
213
214             // Uncheck the current icon radio button.
215             currentIconRadioButton.isChecked = false
216
217             // Update the save button.
218             updateSaveButton(currentName, currentUrl)
219         }
220
221         // Update the save button if the bookmark name changes.
222         nameEditText.addTextChangedListener(object: TextWatcher {
223             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
224                 // Do nothing.
225             }
226
227             override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
228                 // Do nothing.
229             }
230
231             override fun afterTextChanged(s: Editable) {
232                 // Update the save button.
233                 updateSaveButton(currentName, currentUrl)
234             }
235         })
236
237         // Update the save button if the URL changes.
238         urlEditText.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 edit button.
249                 updateSaveButton(currentName, currentUrl)
250             }
251         })
252
253         // Allow the enter key on the keyboard to save the bookmark from the bookmark name edit text.
254         nameEditText.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                 editBookmarkListener.onSaveBookmark(this, selectedBookmarkDatabaseId, favoriteIconBitmap)
259
260                 // Manually dismiss 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         // Allow the enter key on the keyboard to save the bookmark from the URL edit text.
271         urlEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
272             // Check the key code, event, and button status.
273             if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && saveButton.isEnabled) {  // The enter key was pressed and the save button is enabled.
274                 // Trigger the listener and return the dialog fragment to the parent activity.
275                 editBookmarkListener.onSaveBookmark(this, selectedBookmarkDatabaseId, favoriteIconBitmap)
276
277                 // Manually dismiss the alert dialog.
278                 alertDialog.dismiss()
279
280                 // Consume the event.
281                 return@setOnKeyListener true
282             } else { // If any other key was pressed, or if the save button is currently disabled, do not consume the event.
283                 return@setOnKeyListener false
284             }
285         }
286
287         // Return the alert dialog.
288         return alertDialog
289     }
290
291     private fun updateSaveButton(currentName: String, currentUrl: String) {
292         // Get the text from the edit texts.
293         val newName = nameEditText.text.toString()
294         val newUrl = urlEditText.text.toString()
295
296         // Has the favorite icon changed?
297         val iconChanged = webpageFavoriteIconRadioButton.isChecked
298
299         // Has the name changed?
300         val nameChanged = newName != currentName
301
302         // Has the URL changed?
303         val urlChanged = newUrl != currentUrl
304
305         // Update the enabled status of the save button.
306         saveButton.isEnabled = iconChanged || nameChanged || urlChanged
307     }
308 }