]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.kt
Release 3.8.
[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         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
119             dialogBuilder.setIcon(R.drawable.move_to_folder_blue_day)
120         } else {
121             dialogBuilder.setIcon(R.drawable.move_to_folder_blue_night)
122         }
123
124         // Set the title.
125         dialogBuilder.setTitle(R.string.move_to_folder)
126
127         // Set the view.  The parent view is `null` because it will be assigned by the alert dialog.
128         dialogBuilder.setView(layoutInflater.inflate(R.layout.move_to_folder_dialog, null))
129
130         // Set the listener for the cancel button.  Using `null` as the listener closes the dialog without doing anything else.
131         dialogBuilder.setNegativeButton(R.string.cancel, null)
132
133         // Set the listener fo the move button.
134         dialogBuilder.setPositiveButton(R.string.move) { _: DialogInterface?, _: Int ->
135             // Return the dialog fragment to the parent activity on move.
136             moveToFolderListener.onMoveToFolder(this)
137         }
138
139         // Create an alert dialog from the alert dialog builder.
140         val alertDialog = dialogBuilder.create()
141
142         // Get a handle for the shared preferences.
143         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
144
145         // Get the screenshot preference.
146         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
147
148         // Disable screenshots if not allowed.
149         if (!allowScreenshots) {
150             // Disable screenshots.
151             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
152         }
153
154         // The alert dialog must be shown before items in the layout can be modified.
155         alertDialog.show()
156
157         // Get a handle for the positive button.
158         val moveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
159
160         // Initially disable the positive button.
161         moveButton.isEnabled = false
162
163         // Initialize the except folders string builder.
164         exceptFolders = StringBuilder()
165
166         // Declare the cursor variables.
167         val foldersCursor: Cursor
168         val foldersCursorAdapter: CursorAdapter
169
170         // Check to see if the bookmark is currently in the home folder.
171         if (currentFolder.isEmpty()) {  // The bookmark is currently in the home folder.  Don't display `Home Folder` at the top of the list view.
172             // If a folder is selected, add it and all children to the list of folders not to display.
173             for (databaseIdLong in selectedBookmarksLongArray) {
174                 // Get the database ID int for each selected bookmark.
175                 val databaseIdInt = databaseIdLong.toInt()
176
177                 // Check to see if the bookmark is a folder.
178                 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
179                     // Add the folder to the list of folders not to display.
180                     addFolderToExceptFolders(databaseIdInt)
181                 }
182             }
183
184             // Get a cursor containing the folders to display.
185             foldersCursor = bookmarksDatabaseHelper.getFoldersExcept(exceptFolders.toString())
186
187             // Populate the folders cursor adapter.
188             foldersCursorAdapter = populateFoldersCursorAdapter(requireContext(), foldersCursor)
189         } else {  // The current folder is not directly in the home folder.  Display `Home Folder` at the top of the list view.
190             // Get the home folder icon drawable.
191             val homeFolderIconDrawable = ContextCompat.getDrawable(requireActivity().applicationContext, R.drawable.folder_gray_bitmap)
192
193             // Convert the home folder icon drawable to a bitmap drawable.
194             val homeFolderIconBitmapDrawable = homeFolderIconDrawable as BitmapDrawable
195
196             // Convert the home folder bitmap drawable to a bitmap.
197             val homeFolderIconBitmap = homeFolderIconBitmapDrawable.bitmap
198
199             // Create a home folder icon byte array output stream.
200             val homeFolderIconByteArrayOutputStream = ByteArrayOutputStream()
201
202             // Convert the home folder bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
203             homeFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, homeFolderIconByteArrayOutputStream)
204
205             // Convert the home folder icon byte array output stream to a byte array.
206             val homeFolderIconByteArray = homeFolderIconByteArrayOutputStream.toByteArray()
207
208             // Setup the home folder matrix cursor column names.
209             val homeFolderMatrixCursorColumnNames = arrayOf(BookmarksDatabaseHelper._ID, BookmarksDatabaseHelper.BOOKMARK_NAME, BookmarksDatabaseHelper.FAVORITE_ICON)
210
211             // Setup a matrix cursor for the `Home Folder`.
212             val homeFolderMatrixCursor = MatrixCursor(homeFolderMatrixCursorColumnNames)
213
214             // Add the home folder to the home folder matrix cursor.
215             homeFolderMatrixCursor.addRow(arrayOf<Any>(0, getString(R.string.home_folder), homeFolderIconByteArray))
216
217             // Add the parent folder to the list of folders not to display.
218             exceptFolders.append(DatabaseUtils.sqlEscapeString(currentFolder))
219
220             // If a folder is selected, add it and all children to the list of folders not to display.
221             for (databaseIdLong in selectedBookmarksLongArray) {
222                 // Get the database ID int for each selected bookmark.
223                 val databaseIdInt = databaseIdLong.toInt()
224
225                 // Check to see if the bookmark is a folder.
226                 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
227                     // Add the folder to the list of folders not to display.
228                     addFolderToExceptFolders(databaseIdInt)
229                 }
230             }
231
232             // Get a cursor containing the folders to display.
233             foldersCursor = bookmarksDatabaseHelper.getFoldersExcept(exceptFolders.toString())
234
235             // Combine the home folder matrix cursor and the folders cursor.
236             val foldersMergeCursor = MergeCursor(arrayOf(homeFolderMatrixCursor, foldersCursor))
237
238             // Populate the folders cursor adapter.
239             foldersCursorAdapter = populateFoldersCursorAdapter(requireContext(), foldersMergeCursor)
240         }
241
242         // Get a handle for the folders list view.
243         val foldersListView = alertDialog.findViewById<ListView>(R.id.move_to_folder_listview)!!
244
245         // Set the folder list view adapter.
246         foldersListView.adapter = foldersCursorAdapter
247
248         // Enable the move button when a folder is selected.
249         foldersListView.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
250             // Enable the move button.
251             moveButton.isEnabled = true
252         }
253
254         // Return the alert dialog.
255         return alertDialog
256     }
257
258     private fun addFolderToExceptFolders(databaseIdInt: Int) {
259         // Get the name of the selected folder.
260         val folderName = bookmarksDatabaseHelper.getFolderName(databaseIdInt)
261
262         // Populate the list of folders not to get.
263         if (exceptFolders.isEmpty()) {
264             // Add the selected folder to the list of folders not to display.
265             exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName))
266         } else {
267             // Add the selected folder to the end of the list of folders not to display.
268             exceptFolders.append(",")
269             exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName))
270         }
271
272         // Add the selected folder's subfolders to the list of folders not to display.
273         addSubfoldersToExceptFolders(folderName)
274     }
275
276     private fun addSubfoldersToExceptFolders(folderName: String) {
277         // Get a cursor with all the immediate subfolders.
278         val subfoldersCursor = bookmarksDatabaseHelper.getSubfolders(folderName)
279
280         // Add each subfolder to the list of folders not to display.
281         for (i in 0 until subfoldersCursor.count) {
282             // Move the subfolder cursor to the current item.
283             subfoldersCursor.moveToPosition(i)
284
285             // Get the name of the subfolder.
286             val subfolderName = subfoldersCursor.getString(subfoldersCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME))
287
288             // Add the subfolder to except folders.
289             exceptFolders.append(",")
290             exceptFolders.append(DatabaseUtils.sqlEscapeString(subfolderName))
291
292             // Run the same tasks for any subfolders of the subfolder.
293             addSubfoldersToExceptFolders(subfolderName)
294         }
295     }
296
297     private fun populateFoldersCursorAdapter(context: Context, cursor: Cursor): CursorAdapter {
298         // Return the folders cursor adapter.
299         return object : CursorAdapter(context, cursor, false) {
300             override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
301                 // Inflate the individual item layout.
302                 return requireActivity().layoutInflater.inflate(R.layout.move_to_folder_item_linearlayout, parent, false)
303             }
304
305             override fun bindView(view: View, context: Context, cursor: Cursor) {
306                 // Get the data from the cursor.
307                 val folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON))
308                 val folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME))
309
310                 // Get handles for the views.
311                 val folderIconImageView = view.findViewById<ImageView>(R.id.move_to_folder_icon)
312                 val folderNameTextView = view.findViewById<TextView>(R.id.move_to_folder_name_textview)
313
314                 // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
315                 val folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.size)
316
317                 // Display the folder icon bitmap.
318                 folderIconImageView.setImageBitmap(folderIconBitmap)
319
320                 // Display the folder name.
321                 folderNameTextView.text = folderName
322             }
323         }
324     }
325 }