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