]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.kt
Release 3.8.1.
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / MoveToFolderDialog.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.annotation.SuppressLint
23 import android.app.Dialog
24 import android.content.Context
25 import android.content.DialogInterface
26 import android.database.Cursor
27 import android.database.DatabaseUtils
28 import android.database.MatrixCursor
29 import android.database.MergeCursor
30 import android.graphics.Bitmap
31 import android.graphics.BitmapFactory
32 import android.graphics.drawable.BitmapDrawable
33 import android.os.Bundle
34 import android.view.View
35 import android.view.ViewGroup
36 import android.view.WindowManager
37 import android.widget.AdapterView
38 import android.widget.AdapterView.OnItemClickListener
39 import android.widget.ImageView
40 import android.widget.ListView
41 import android.widget.TextView
42
43 import androidx.appcompat.app.AlertDialog
44 import androidx.core.content.ContextCompat
45 import androidx.cursoradapter.widget.CursorAdapter
46 import androidx.fragment.app.DialogFragment
47 import androidx.preference.PreferenceManager
48
49 import com.stoutner.privacybrowser.R
50 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
51
52 import java.io.ByteArrayOutputStream
53 import java.lang.StringBuilder
54
55 // Define the class constants.
56 private const val CURRENT_FOLDER = "current_folder"
57 private const val SELECTED_BOOKMARKS_LONG_ARRAY = "selected_bookmarks_long_array"
58
59 class MoveToFolderDialog : DialogFragment() {
60     // Declare the class variables.
61     private lateinit var moveToFolderListener: MoveToFolderListener
62     private lateinit var bookmarksDatabaseHelper: BookmarksDatabaseHelper
63     private lateinit var exceptFolders: StringBuilder
64
65     // The public interface is used to send information back to the parent activity.
66     interface MoveToFolderListener {
67         fun onMoveToFolder(dialogFragment: DialogFragment)
68     }
69
70     override fun onAttach(context: Context) {
71         // Run the default commands.
72         super.onAttach(context)
73
74         // Get a handle for the move to folder listener from the launching context.
75         moveToFolderListener = context as MoveToFolderListener
76     }
77
78     companion object {
79         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
80         @JvmStatic
81         fun moveBookmarks(currentFolder: String, selectedBookmarksLongArray: LongArray): MoveToFolderDialog {
82             // Create an arguments bundle.
83             val argumentsBundle = Bundle()
84
85             // Store the arguments in the bundle.
86             argumentsBundle.putString(CURRENT_FOLDER, currentFolder)
87             argumentsBundle.putLongArray(SELECTED_BOOKMARKS_LONG_ARRAY, selectedBookmarksLongArray)
88
89             // Create a new instance of the dialog.
90             val moveToFolderDialog = MoveToFolderDialog()
91
92             // And the bundle to the dialog.
93             moveToFolderDialog.arguments = argumentsBundle
94
95             // Return the new dialog.
96             return moveToFolderDialog
97         }
98     }
99
100     // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog.
101     @SuppressLint("InflateParams")
102     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
103         // Get the data from the arguments.
104         val currentFolder = requireArguments().getString(CURRENT_FOLDER)!!
105         val selectedBookmarksLongArray = requireArguments().getLongArray(SELECTED_BOOKMARKS_LONG_ARRAY)!!
106
107         // Initialize the database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in the bookmarks database helper.
108         bookmarksDatabaseHelper = BookmarksDatabaseHelper(context, null, null, 0)
109
110         // Use an alert dialog builder to create the alert dialog.
111         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
112
113         // Set the icon according to the theme.
114         dialogBuilder.setIconAttribute(R.attr.moveToFolderBlueIcon)
115
116         // Set the title.
117         dialogBuilder.setTitle(R.string.move_to_folder)
118
119         // Set the view.  The parent view is `null` because it will be assigned by the alert dialog.
120         dialogBuilder.setView(layoutInflater.inflate(R.layout.move_to_folder_dialog, null))
121
122         // Set the listener for the cancel button.  Using `null` as the listener closes the dialog without doing anything else.
123         dialogBuilder.setNegativeButton(R.string.cancel, null)
124
125         // Set the listener fo the move button.
126         dialogBuilder.setPositiveButton(R.string.move) { _: DialogInterface?, _: Int ->
127             // Return the dialog fragment to the parent activity on move.
128             moveToFolderListener.onMoveToFolder(this)
129         }
130
131         // Create an alert dialog from the alert dialog builder.
132         val alertDialog = dialogBuilder.create()
133
134         // Get a handle for the shared preferences.
135         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
136
137         // Get the screenshot preference.
138         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
139
140         // Disable screenshots if not allowed.
141         if (!allowScreenshots) {
142             // Disable screenshots.
143             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
144         }
145
146         // The alert dialog must be shown before items in the layout can be modified.
147         alertDialog.show()
148
149         // Get a handle for the positive button.
150         val moveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
151
152         // Initially disable the positive button.
153         moveButton.isEnabled = false
154
155         // Initialize the except folders string builder.
156         exceptFolders = StringBuilder()
157
158         // Declare the cursor variables.
159         val foldersCursor: Cursor
160         val foldersCursorAdapter: CursorAdapter
161
162         // Check to see if the bookmark is currently in the home folder.
163         if (currentFolder.isEmpty()) {  // The bookmark is currently in the home folder.  Don't display `Home Folder` at the top of the list view.
164             // If a folder is selected, add it and all children to the list of folders not to display.
165             for (databaseIdLong in selectedBookmarksLongArray) {
166                 // Get the database ID int for each selected bookmark.
167                 val databaseIdInt = databaseIdLong.toInt()
168
169                 // Check to see if the bookmark is a folder.
170                 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
171                     // Add the folder to the list of folders not to display.
172                     addFolderToExceptFolders(databaseIdInt)
173                 }
174             }
175
176             // Get a cursor containing the folders to display.
177             foldersCursor = bookmarksDatabaseHelper.getFoldersExcept(exceptFolders.toString())
178
179             // Populate the folders cursor adapter.
180             foldersCursorAdapter = populateFoldersCursorAdapter(requireContext(), foldersCursor)
181         } else {  // The current folder is not directly in the home folder.  Display `Home Folder` at the top of the list view.
182             // Get the home folder icon drawable.
183             val homeFolderIconDrawable = ContextCompat.getDrawable(requireActivity().applicationContext, R.drawable.folder_gray_bitmap)
184
185             // Convert the home folder icon drawable to a bitmap drawable.
186             val homeFolderIconBitmapDrawable = homeFolderIconDrawable as BitmapDrawable
187
188             // Convert the home folder bitmap drawable to a bitmap.
189             val homeFolderIconBitmap = homeFolderIconBitmapDrawable.bitmap
190
191             // Create a home folder icon byte array output stream.
192             val homeFolderIconByteArrayOutputStream = ByteArrayOutputStream()
193
194             // Convert the home folder bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
195             homeFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, homeFolderIconByteArrayOutputStream)
196
197             // Convert the home folder icon byte array output stream to a byte array.
198             val homeFolderIconByteArray = homeFolderIconByteArrayOutputStream.toByteArray()
199
200             // Setup the home folder matrix cursor column names.
201             val homeFolderMatrixCursorColumnNames = arrayOf(BookmarksDatabaseHelper._ID, BookmarksDatabaseHelper.BOOKMARK_NAME, BookmarksDatabaseHelper.FAVORITE_ICON)
202
203             // Setup a matrix cursor for the `Home Folder`.
204             val homeFolderMatrixCursor = MatrixCursor(homeFolderMatrixCursorColumnNames)
205
206             // Add the home folder to the home folder matrix cursor.
207             homeFolderMatrixCursor.addRow(arrayOf<Any>(0, getString(R.string.home_folder), homeFolderIconByteArray))
208
209             // Add the parent folder to the list of folders not to display.
210             exceptFolders.append(DatabaseUtils.sqlEscapeString(currentFolder))
211
212             // If a folder is selected, add it and all children to the list of folders not to display.
213             for (databaseIdLong in selectedBookmarksLongArray) {
214                 // Get the database ID int for each selected bookmark.
215                 val databaseIdInt = databaseIdLong.toInt()
216
217                 // Check to see if the bookmark is a folder.
218                 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
219                     // Add the folder to the list of folders not to display.
220                     addFolderToExceptFolders(databaseIdInt)
221                 }
222             }
223
224             // Get a cursor containing the folders to display.
225             foldersCursor = bookmarksDatabaseHelper.getFoldersExcept(exceptFolders.toString())
226
227             // Combine the home folder matrix cursor and the folders cursor.
228             val foldersMergeCursor = MergeCursor(arrayOf(homeFolderMatrixCursor, foldersCursor))
229
230             // Populate the folders cursor adapter.
231             foldersCursorAdapter = populateFoldersCursorAdapter(requireContext(), foldersMergeCursor)
232         }
233
234         // Get a handle for the folders list view.
235         val foldersListView = alertDialog.findViewById<ListView>(R.id.move_to_folder_listview)!!
236
237         // Set the folder list view adapter.
238         foldersListView.adapter = foldersCursorAdapter
239
240         // Enable the move button when a folder is selected.
241         foldersListView.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
242             // Enable the move button.
243             moveButton.isEnabled = true
244         }
245
246         // Return the alert dialog.
247         return alertDialog
248     }
249
250     private fun addFolderToExceptFolders(databaseIdInt: Int) {
251         // Get the name of the selected folder.
252         val folderName = bookmarksDatabaseHelper.getFolderName(databaseIdInt)
253
254         // Populate the list of folders not to get.
255         if (exceptFolders.isEmpty()) {
256             // Add the selected folder to the list of folders not to display.
257             exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName))
258         } else {
259             // Add the selected folder to the end of the list of folders not to display.
260             exceptFolders.append(",")
261             exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName))
262         }
263
264         // Add the selected folder's subfolders to the list of folders not to display.
265         addSubfoldersToExceptFolders(folderName)
266     }
267
268     private fun addSubfoldersToExceptFolders(folderName: String) {
269         // Get a cursor with all the immediate subfolders.
270         val subfoldersCursor = bookmarksDatabaseHelper.getSubfolders(folderName)
271
272         // Add each subfolder to the list of folders not to display.
273         for (i in 0 until subfoldersCursor.count) {
274             // Move the subfolder cursor to the current item.
275             subfoldersCursor.moveToPosition(i)
276
277             // Get the name of the subfolder.
278             val subfolderName = subfoldersCursor.getString(subfoldersCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME))
279
280             // Add the subfolder to except folders.
281             exceptFolders.append(",")
282             exceptFolders.append(DatabaseUtils.sqlEscapeString(subfolderName))
283
284             // Run the same tasks for any subfolders of the subfolder.
285             addSubfoldersToExceptFolders(subfolderName)
286         }
287     }
288
289     private fun populateFoldersCursorAdapter(context: Context, cursor: Cursor): CursorAdapter {
290         // Return the folders cursor adapter.
291         return object : CursorAdapter(context, cursor, false) {
292             override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
293                 // Inflate the individual item layout.
294                 return requireActivity().layoutInflater.inflate(R.layout.move_to_folder_item_linearlayout, parent, false)
295             }
296
297             override fun bindView(view: View, context: Context, cursor: Cursor) {
298                 // Get the data from the cursor.
299                 val folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON))
300                 val folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME))
301
302                 // Get handles for the views.
303                 val folderIconImageView = view.findViewById<ImageView>(R.id.move_to_folder_icon)
304                 val folderNameTextView = view.findViewById<TextView>(R.id.move_to_folder_name_textview)
305
306                 // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
307                 val folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.size)
308
309                 // Display the folder icon bitmap.
310                 folderIconImageView.setImageBitmap(folderIconBitmap)
311
312                 // Display the folder name.
313                 folderNameTextView.text = folderName
314             }
315         }
316     }
317 }