]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.kt
Create a class domains settings set. https://redmine.stoutner.com/issues/990
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / BookmarksActivity.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.activities
21
22 import android.annotation.SuppressLint
23 import android.content.Context
24 import android.content.Intent
25 import android.database.Cursor
26 import android.graphics.Bitmap
27 import android.graphics.BitmapFactory
28 import android.graphics.Typeface
29 import android.graphics.drawable.BitmapDrawable
30 import android.os.Bundle
31 import android.util.SparseBooleanArray
32 import android.view.ActionMode
33 import android.view.Menu
34 import android.view.MenuItem
35 import android.view.View
36 import android.view.ViewGroup
37 import android.view.Window
38 import android.view.WindowManager
39 import android.widget.AbsListView.MultiChoiceModeListener
40 import android.widget.AdapterView
41 import android.widget.EditText
42 import android.widget.ImageView
43 import android.widget.ListView
44 import android.widget.RadioButton
45 import android.widget.TextView
46
47 import androidx.activity.OnBackPressedCallback
48 import androidx.appcompat.app.ActionBar
49 import androidx.appcompat.app.AppCompatActivity
50 import androidx.appcompat.widget.Toolbar
51 import androidx.cursoradapter.widget.CursorAdapter
52 import androidx.fragment.app.DialogFragment
53 import androidx.preference.PreferenceManager
54
55 import com.google.android.material.floatingactionbutton.FloatingActionButton
56 import com.google.android.material.snackbar.Snackbar
57
58 import com.stoutner.privacybrowser.R
59 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog.Companion.createBookmark
60 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog.CreateBookmarkListener
61 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog.Companion.createBookmarkFolder
62 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog.CreateBookmarkFolderListener
63 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog.Companion.bookmarkDatabaseId
64 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog.EditBookmarkListener
65 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog.Companion.folderDatabaseId
66 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog.EditBookmarkFolderListener
67 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog.Companion.moveBookmarks
68 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog.MoveToFolderListener
69 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
70
71 import java.io.ByteArrayOutputStream
72 import java.util.function.Consumer
73
74 // Define the public constants.
75 const val CURRENT_FOLDER = "current_folder"
76 const val CURRENT_TITLE = "current_title"
77 const val CURRENT_FAVORITE_ICON_BYTE_ARRAY = "current_favorite_icon_byte_array"
78
79 // Define the private constants.
80 private const val CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list"
81
82 class BookmarksActivity : AppCompatActivity(), CreateBookmarkListener, CreateBookmarkFolderListener, EditBookmarkListener, EditBookmarkFolderListener, MoveToFolderListener {
83     companion object {
84         // Define the public static variables, which are accessed from the bookmarks database view activity.
85         var currentFolder: String = ""
86         var restartFromBookmarksDatabaseViewActivity = false
87     }
88
89     // Define the class variables.
90     private var bookmarksDeletedSnackbar: Snackbar? = null
91     private var closeActivityAfterDismissingSnackbar = false
92     private var contextualActionMode: ActionMode? = null
93
94     // Declare the class variables.
95     private lateinit var appBar: ActionBar
96     private lateinit var bookmarksCursor: Cursor
97     private lateinit var bookmarksCursorAdapter: CursorAdapter
98     private lateinit var bookmarksDatabaseHelper: BookmarksDatabaseHelper
99     private lateinit var bookmarksListView: ListView
100     private lateinit var currentFavoriteIconByteArray: ByteArray
101     private lateinit var moveBookmarkDownMenuItem: MenuItem
102     private lateinit var moveBookmarkUpMenuItem: MenuItem
103     private lateinit var oldFolderNameString: String
104
105     override fun onCreate(savedInstanceState: Bundle?) {
106         // Get a handle for the shared preferences.
107         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
108
109         // Get the preferences.
110         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
111         val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
112
113         // Disable screenshots if not allowed.
114         if (!allowScreenshots) {
115             window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
116         }
117
118         // Run the default commands.
119         super.onCreate(savedInstanceState)
120
121         // Get the intent that launched the activity.
122         val launchingIntent = intent
123
124         // Populate the variables from the launching intent.
125         currentFolder = launchingIntent.getStringExtra(CURRENT_FOLDER)!!
126         val currentTitle = launchingIntent.getStringExtra(CURRENT_TITLE)!!
127         val currentUrl = launchingIntent.getStringExtra(CURRENT_URL)!!
128         currentFavoriteIconByteArray = launchingIntent.getByteArrayExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY)!!
129
130         // Convert the favorite icon byte array to a bitmap.
131         val currentFavoriteIconBitmap = BitmapFactory.decodeByteArray(currentFavoriteIconByteArray, 0, currentFavoriteIconByteArray.size)
132
133         // Set the content according to the app bar position.
134         if (bottomAppBar) {
135             // Set the content view.
136             setContentView(R.layout.bookmarks_bottom_appbar)
137         } else {
138             // `Window.FEATURE_ACTION_MODE_OVERLAY` makes the contextual action mode cover the support action bar.  It must be requested before the content is set.
139             supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY)
140
141             // Set the content view.
142             setContentView(R.layout.bookmarks_top_appbar)
143         }
144
145         // Get handles for the views.
146         val toolbar = findViewById<Toolbar>(R.id.bookmarks_toolbar)
147         bookmarksListView = findViewById(R.id.bookmarks_listview)
148         val createBookmarkFolderFab = findViewById<FloatingActionButton>(R.id.create_bookmark_folder_fab)
149         val createBookmarkFab = findViewById<FloatingActionButton>(R.id.create_bookmark_fab)
150
151         // Set the support action bar.
152         setSupportActionBar(toolbar)
153
154         // Get a handle for the app bar.
155         appBar = supportActionBar!!
156
157         // Display the home arrow on the app bar.
158         appBar.setDisplayHomeAsUpEnabled(true)
159
160         // Control what the system back command does.
161         val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
162             override fun handleOnBackPressed() {
163                 // Prepare to finish the activity.
164                 prepareFinish()
165             }
166         }
167
168         // Register the on back pressed callback.
169         onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
170
171         // Initialize the database helper.
172         bookmarksDatabaseHelper = BookmarksDatabaseHelper(this)
173
174         // Set a listener so that tapping a list item loads the URL or folder.
175         bookmarksListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
176             // Convert the id from long to int to match the format of the bookmarks database.
177             val databaseId = id.toInt()
178
179             // Get the bookmark cursor for this ID.
180             val bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId)
181
182             // Move the cursor to the first entry.
183             bookmarkCursor.moveToFirst()
184
185             // Act upon the bookmark according to the type.
186             if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
187                 // Update the current folder.
188                 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
189
190                 // Load the new folder.
191                 loadFolder()
192             } else {  // The selected bookmark is not a folder.
193                 // Instantiate the edit bookmark dialog.
194                 val editBookmarkDialog: DialogFragment = bookmarkDatabaseId(databaseId, currentFavoriteIconBitmap)
195
196                 // Make it so.
197                 editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark))
198             }
199
200             // Close the cursor.
201             bookmarkCursor.close()
202         }
203
204         // Handle long-presses on the list view.
205         bookmarksListView.setMultiChoiceModeListener(object : MultiChoiceModeListener {
206             // Define the object variables.
207             private var deletingBookmarks = false
208
209             // Declare the object variables.
210             private lateinit var editBookmarkMenuItem: MenuItem
211             private lateinit var deleteBookmarksMenuItem: MenuItem
212             private lateinit var selectAllBookmarksMenuItem: MenuItem
213
214             override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
215                 // Inflate the menu for the contextual app bar.
216                 menuInflater.inflate(R.menu.bookmarks_context_menu, menu)
217
218                 // Set the title.
219                 if (currentFolder.isEmpty()) {  // Use `R.string.bookmarks` if in the home folder.
220                     mode.setTitle(R.string.bookmarks)
221                 } else {  // Use the current folder name as the title.
222                     mode.title = currentFolder
223                 }
224
225                 // Get handles for menu items that need to be selectively disabled.
226                 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up)
227                 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down)
228                 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark)
229                 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark)
230                 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks)
231
232                 // Disable the delete bookmarks menu item if a delete is pending.
233                 deleteBookmarksMenuItem.isEnabled = !deletingBookmarks
234
235                 // Store a handle for the contextual action bar so it can be closed programatically.
236                 contextualActionMode = mode
237
238                 // Make it so.
239                 return true
240             }
241
242             override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
243                 // Get a handle for the move to folder menu item.
244                 val moveToFolderMenuItem = menu.findItem(R.id.move_to_folder)
245
246                 // Get a Cursor with all of the folders.
247                 val folderCursor = bookmarksDatabaseHelper.allFolders
248
249                 // Display the move to folder menu item if at least one folder exists.
250                 moveToFolderMenuItem.isVisible = folderCursor.count > 0
251
252                 // Make it so.
253                 return true
254             }
255
256             override fun onItemCheckedStateChanged(mode: ActionMode, position: Int, id: Long, checked: Boolean) {
257                 // Get the number of selected bookmarks.
258                 val numberOfSelectedBookmarks = bookmarksListView.checkedItemCount
259
260                 // Only process commands if at least one bookmark is selected.  Otherwise, a context menu with 0 selected bookmarks is briefly displayed.
261                 if (numberOfSelectedBookmarks > 0) {
262                     // Adjust the action mode and the menu according to the number of selected bookmarks.
263                     if (numberOfSelectedBookmarks == 1) {  // One bookmark is selected.
264                         // Show the applicable menu items.
265                         moveBookmarkUpMenuItem.isVisible = true
266                         moveBookmarkDownMenuItem.isVisible = true
267                         editBookmarkMenuItem.isVisible = true
268
269                         // Update the enabled status of the move icons.
270                         updateMoveIcons()
271                     } else {  // More than one bookmark is selected.
272                         // Hide non-applicable `MenuItems`.
273                         moveBookmarkUpMenuItem.isVisible = false
274                         moveBookmarkDownMenuItem.isVisible = false
275                         editBookmarkMenuItem.isVisible = false
276                     }
277
278                     // List the number of selected bookmarks in the subtitle.
279                     mode.subtitle = getString(R.string.selected, numberOfSelectedBookmarks)
280
281                     // Show the select all menu item if all the bookmarks are not selected.
282                     selectAllBookmarksMenuItem.isVisible = (numberOfSelectedBookmarks != bookmarksListView.count)
283                 }
284             }
285
286             override fun onActionItemClicked(actionMode: ActionMode, menuItem: MenuItem): Boolean {
287                 // Declare the variables.
288                 val selectedBookmarkNewPosition: Int
289                 val selectedBookmarksPositionsSparseBooleanArray: SparseBooleanArray
290
291                 // Initialize the selected bookmark position.
292                 var selectedBookmarkPosition = 0
293
294                 // Get the menu item ID.
295                 val menuItemId = menuItem.itemId
296
297                 // Run the commands according to the selected action item.
298                 if (menuItemId == R.id.move_bookmark_up) {  // Move the bookmark up.
299                     // Get the array of checked bookmark positions.
300                     selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
301
302                     // Get the position of the bookmark that is selected.  If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
303                     for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
304                         // Check to see if the value for the bookmark is true, meaning it is currently selected.
305                         if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
306                             // Only one bookmark should have a value of `true` when move bookmark up is enabled.
307                             selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
308                         }
309                     }
310
311                     // Calculate the new position of the selected bookmark.
312                     selectedBookmarkNewPosition = selectedBookmarkPosition - 1
313
314                     // Iterate through the bookmarks.
315                     for (i in 0 until bookmarksListView.count) {
316                         // Get the database ID for the current bookmark.
317                         val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
318
319                         // Update the display order for the current bookmark.
320                         if (i == selectedBookmarkPosition) {  // The current bookmark is the selected bookmark.
321                             // Move the current bookmark up one.
322                             bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1)
323                         } else if ((i + 1) == selectedBookmarkPosition) {  // The current bookmark is immediately above the selected bookmark.
324                             // Move the current bookmark down one.
325                             bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1)
326                         } else {  // The current bookmark is not changing positions.
327                             // Move the bookmarks cursor to the current bookmark position.
328                             bookmarksCursor.moveToPosition(i)
329
330                             // Update the display order only if it is not correct in the database.  This fixes problems where the display order somehow got out of sync.
331                             if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i)
332                                 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
333                         }
334                     }
335
336                     // Update the bookmarks cursor with the current contents of the bookmarks database.
337                     bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
338
339                     // Update the list view.
340                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
341
342                     // Scroll to the new bookmark position.
343                     scrollBookmarks(selectedBookmarkNewPosition)
344
345                     // Update the enabled status of the move icons.
346                     updateMoveIcons()
347                 } else if (menuItemId == R.id.move_bookmark_down) {  // Move the bookmark down.
348                     // Get the array of checked bookmark positions.
349                     selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
350
351                     // Get the position of the bookmark that is selected.  If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
352                     for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
353                         // Check to see if the value for the bookmark is true, meaning it is currently selected.
354                         if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
355                             // Only one bookmark should have a value of `true` when move bookmark down is enabled.
356                             selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
357                         }
358                     }
359
360                     // Calculate the new position of the selected bookmark.
361                     selectedBookmarkNewPosition = selectedBookmarkPosition + 1
362
363                     // Iterate through the bookmarks.
364                     for (i in 0 until bookmarksListView.count) {
365                         // Get the database ID for the current bookmark.
366                         val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
367
368                         // Update the display order for the current bookmark.
369                         if (i == selectedBookmarkPosition) {  // The current bookmark is the selected bookmark.
370                             // Move the current bookmark down one.
371                             bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1)
372                         } else if (i - 1 == selectedBookmarkPosition) {  // The current bookmark is immediately below the selected bookmark.
373                             // Move the bookmark below the selected bookmark up one.
374                             bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1)
375                         } else {  // The current bookmark is not changing positions.
376                             // Move the bookmarks cursor to the current bookmark position.
377                             bookmarksCursor.moveToPosition(i)
378
379                             // Update the display order only if it is not correct in the database.  This fixes problems where the display order somehow got out of sync.
380                             if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
381                                 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
382                             }
383                         }
384                     }
385
386                     // Update the bookmarks cursor with the current contents of the bookmarks database.
387                     bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
388
389                     // Update the list view.
390                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
391
392                     // Scroll to the new bookmark position.
393                     scrollBookmarks(selectedBookmarkNewPosition)
394
395                     // Update the enabled status of the move icons.
396                     updateMoveIcons()
397                 } else if (menuItemId == R.id.move_to_folder) {  // Move to folder.
398                     // Instantiate the move to folder alert dialog.
399                     val moveToFolderDialog: DialogFragment = moveBookmarks(currentFolder, bookmarksListView.checkedItemIds)
400
401                     // Show the move to folder alert dialog.
402                     moveToFolderDialog.show(supportFragmentManager, resources.getString(R.string.move_to_folder))
403                 } else if (menuItemId == R.id.edit_bookmark) {
404                     // Get the array of checked bookmark positions.
405                     selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
406
407                     // Get the position of the bookmark that is selected.  If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
408                     for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
409                         // Check to see if the value for the bookmark is true, meaning it is currently selected.
410                         if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
411                             // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
412                             selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
413                         }
414                     }
415
416                     // Move the cursor to the selected position.
417                     bookmarksCursor.moveToPosition(selectedBookmarkPosition)
418
419                     // Get the selected bookmark database ID.
420                     val databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
421
422                     // Show the edit bookmark or edit bookmark folder dialog.
423                     if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // A folder is selected.
424                         // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
425                         oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
426
427                         // Instantiate the edit bookmark folder dialog.
428                         val editFolderDialog: DialogFragment = folderDatabaseId(databaseId, currentFavoriteIconBitmap)
429
430                         // Make it so.
431                         editFolderDialog.show(supportFragmentManager, resources.getString(R.string.edit_folder))
432                     } else {  // A bookmark is selected.
433                         // Instantiate the edit bookmark dialog.
434                         val editBookmarkDialog: DialogFragment = bookmarkDatabaseId(databaseId, currentFavoriteIconBitmap)
435
436                         // Make it so.
437                         editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark))
438                     }
439                 } else if (menuItemId == R.id.delete_bookmark) {  // Delete bookmark.
440                     // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
441                     deletingBookmarks = true
442
443                     // Get an array of the selected row IDs.
444                     val selectedBookmarksIdsLongArray = bookmarksListView.checkedItemIds
445
446                     // Initialize a variable to count the number of bookmarks to delete.
447                     var numberOfBookmarksToDelete = 0
448
449                     // Count the number of bookmarks to delete.
450                     for (databaseIdLong in selectedBookmarksIdsLongArray) {
451                         // Convert the database ID long to an int.
452                         val databaseIdInt = databaseIdLong.toInt()
453
454                         // Count the contents of the folder if the selected bookmark is a folder.
455                         if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
456                             // Add the bookmarks from the folder to the running total.
457                             numberOfBookmarksToDelete += countBookmarkFolderContents(databaseIdInt)
458                         }
459
460                         // Increment the count of the number of bookmarks to delete.
461                         numberOfBookmarksToDelete++
462                     }
463
464                     // Get an array of checked bookmarks.  `.clone()` makes a copy that won't change if the list view is reloaded, which is needed for re-selecting the bookmarks on undelete.
465                     selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions.clone()
466
467                     // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
468                     bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder)
469
470                     // Update the list view.
471                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
472
473                     // Create a Snackbar with the number of deleted bookmarks.
474                     bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted, numberOfBookmarksToDelete), Snackbar.LENGTH_LONG)
475                         .setAction(R.string.undo) { }  // Do nothing because everything will be handled by `onDismissed()` below.
476                         .addCallback(object : Snackbar.Callback() {
477                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
478                             override fun onDismissed(snackbar: Snackbar, event: Int) {
479                                 if (event == DISMISS_EVENT_ACTION) {  // The user pushed the undo button.
480                                     // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
481                                     bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
482
483                                     // Update the list view.
484                                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
485
486                                     // Re-select the previously selected bookmarks.
487                                     for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size())
488                                         bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true)
489                                 } else {  // The snackbar was dismissed without the undo button being pushed.
490                                     // Delete each selected bookmark.
491                                     for (databaseIdLong in selectedBookmarksIdsLongArray) {
492                                         // Convert the database long ID to an int.
493                                         val databaseIdInt = databaseIdLong.toInt()
494
495                                         // Delete the contents of the folder if the selected bookmark is a folder.
496                                         if (bookmarksDatabaseHelper.isFolder(databaseIdInt))
497                                             deleteBookmarkFolderContents(databaseIdInt)
498
499                                         // Delete the selected bookmark.
500                                         bookmarksDatabaseHelper.deleteBookmark(databaseIdInt)
501                                     }
502
503                                     // Update the display order.
504                                     for (i in 0 until bookmarksListView.count) {
505                                         // Get the database ID for the current bookmark.
506                                         val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
507
508                                         // Move bookmarks cursor to the current bookmark position.
509                                         bookmarksCursor.moveToPosition(i)
510
511                                         // Update the display order only if it is not correct in the database.
512                                         if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i)
513                                             bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
514                                     }
515                                 }
516
517                                 // Reset the deleting bookmarks flag.
518                                 deletingBookmarks = false
519
520                                 // Enable the delete bookmarks menu item.
521                                 deleteBookmarksMenuItem.isEnabled = true
522
523                                 // Close the activity if back has been pressed.
524                                 if (closeActivityAfterDismissingSnackbar)
525                                     finish()
526                             }
527                         })
528
529                     // Show the Snackbar.
530                     bookmarksDeletedSnackbar!!.show()
531                 } else if (menuItemId == R.id.context_menu_select_all_bookmarks) {  // Select all.
532                     // Get the total number of bookmarks.
533                     val numberOfBookmarks = bookmarksListView.count
534
535                     // Select them all.
536                     for (i in 0 until numberOfBookmarks) {
537                         bookmarksListView.setItemChecked(i, true)
538                     }
539                 }
540
541                 // Consume the click.
542                 return true
543             }
544
545             override fun onDestroyActionMode(mode: ActionMode) {
546                 // Do nothing.
547             }
548         })
549
550         // Set the create new bookmark folder FAB to display the alert dialog.
551         createBookmarkFolderFab.setOnClickListener {
552             // Create a create bookmark folder dialog.
553             val createBookmarkFolderDialog: DialogFragment = createBookmarkFolder(currentFavoriteIconBitmap)
554
555             // Show the create bookmark folder dialog.
556             createBookmarkFolderDialog.show(supportFragmentManager, getString(R.string.create_folder))
557         }
558
559         // Set the create new bookmark FAB to display the alert dialog.
560         createBookmarkFab.setOnClickListener {
561             // Instantiate the create bookmark dialog.
562             val createBookmarkDialog: DialogFragment = createBookmark(currentUrl, currentTitle, currentFavoriteIconBitmap)
563
564             // Display the create bookmark dialog.
565             createBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.create_bookmark))
566         }
567
568         // Restore the state if the app has been restarted.
569         if (savedInstanceState != null) {
570             // Restore the current folder.
571             currentFolder = savedInstanceState.getString(CURRENT_FOLDER)!!
572
573             // Update the bookmarks list view after it has loaded.
574             bookmarksListView.post {
575                 // Get the checked bookmarks array list.
576                 val checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST)!!
577
578                 // Check each previously checked bookmark in the list view.
579                 checkedBookmarksArrayList.forEach(Consumer { position: Int -> bookmarksListView.setItemChecked(position, true) })
580             }
581         }
582
583         // Load the current folder.
584         loadFolder()
585     }
586
587     public override fun onRestart() {
588         // Run the default commands.
589         super.onRestart()
590
591         // Update the list view if returning from the bookmarks database view activity.
592         if (restartFromBookmarksDatabaseViewActivity) {
593             // Load the current folder in the list view.
594             loadFolder()
595
596             // Reset the restart from bookmarks database view activity flag.
597             restartFromBookmarksDatabaseViewActivity = false
598         }
599     }
600
601     public override fun onSaveInstanceState(savedInstanceState: Bundle) {
602         // Run the default commands.
603         super.onSaveInstanceState(savedInstanceState)
604
605         // Get the sparse boolean array of the checked items.
606         val checkedBookmarksSparseBooleanArray = bookmarksListView.checkedItemPositions
607
608         // Create a checked items array list.
609         val checkedBookmarksArrayList = ArrayList<Int>()
610
611         // Add each checked bookmark position to the array list.
612         for (i in 0 until checkedBookmarksSparseBooleanArray.size()) {
613             // Check to see if the bookmark is currently checked.  Bookmarks that have previously been checked but currently aren't will be populated in the sparse boolean array, but will return false.
614             if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
615                 // Add the bookmark position to the checked bookmarks array list.
616                 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i))
617             }
618         }
619
620         // Store the variables in the saved instance state.
621         savedInstanceState.putString(CURRENT_FOLDER, currentFolder)
622         savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList)
623     }
624
625     override fun onCreateOptionsMenu(menu: Menu): Boolean {
626         // Inflate the menu.
627         menuInflater.inflate(R.menu.bookmarks_options_menu, menu)
628
629         // Success.
630         return true
631     }
632
633     override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
634         // Get a handle for the menu item ID.
635         val menuItemId = menuItem.itemId
636
637         // Run the command according to the selected option.
638         if (menuItemId == android.R.id.home) {  // Home.  The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
639             if (currentFolder.isEmpty()) {  // Currently in the home folder.
640                 // Prepare to finish the activity.
641                 prepareFinish()
642             } else {  // Currently in a subfolder.
643                 // Set the former parent folder as the current folder.
644                 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder)
645
646                 // Load the new current folder.
647                 loadFolder()
648             }
649         } else if (menuItemId == R.id.options_menu_select_all_bookmarks) {  // Select all.
650             // Get the total number of bookmarks.
651             val numberOfBookmarks = bookmarksListView.count
652
653             // Select them all.
654             for (i in 0 until numberOfBookmarks) {
655                 bookmarksListView.setItemChecked(i, true)
656             }
657         } else if (menuItemId == R.id.bookmarks_database_view) {
658             // Close the contextual action bar if it is displayed.  This can happen if the bottom app bar is enabled.
659             contextualActionMode?.finish()
660
661             // Create an intent to launch the bookmarks database view activity.
662             val bookmarksDatabaseViewIntent = Intent(this, BookmarksDatabaseViewActivity::class.java)
663
664             // Include the favorite icon byte array to the intent.
665             bookmarksDatabaseViewIntent.putExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY, currentFavoriteIconByteArray)
666
667             // Make it so.
668             startActivity(bookmarksDatabaseViewIntent)
669         }
670         return true
671     }
672
673     override fun onCreateBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
674         // Get the alert dialog from the fragment.
675         val dialog = dialogFragment.dialog!!
676
677         // Get the views from the dialog fragment.
678         val createBookmarkNameEditText = dialog.findViewById<EditText>(R.id.create_bookmark_name_edittext)
679         val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
680
681         // Extract the strings from the edit texts.
682         val bookmarkNameString = createBookmarkNameEditText.text.toString()
683         val bookmarkUrlString = createBookmarkUrlEditText.text.toString()
684
685         // Create a favorite icon byte array output stream.
686         val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
687
688         // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
689         favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream)
690
691         // Convert the favorite icon byte array stream to a byte array.
692         val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
693
694         // Display the new bookmark below the current items in the (0 indexed) list.
695         val newBookmarkDisplayOrder = bookmarksListView.count
696
697         // Create the bookmark.
698         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray)
699
700         // Update the bookmarks cursor with the current contents of this folder.
701         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
702
703         // Update the list view.
704         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
705
706         // Scroll to the new bookmark.
707         bookmarksListView.setSelection(newBookmarkDisplayOrder)
708     }
709
710     override fun onCreateBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
711         // Get the dialog from the dialog fragment.
712         val dialog = dialogFragment.dialog!!
713
714         // Get handles for the views in the dialog fragment.
715         val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
716         val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
717         val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
718
719         // Get new folder name string.
720         val folderNameString = folderNameEditText.text.toString()
721
722         // Set the folder icon bitmap according to the dialog.
723         val folderIconBitmap = if (defaultIconRadioButton.isChecked) {  // Use the default folder icon.
724             // Get the default folder icon drawable.
725             val folderIconDrawable = defaultIconImageView.drawable
726
727             // Convert the folder icon drawable to a bitmap drawable.
728             val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
729
730             // Convert the folder icon bitmap drawable to a bitmap.
731             folderIconBitmapDrawable.bitmap
732         } else {  // Use the WebView favorite icon.
733             // Copy the favorite icon bitmap to the folder icon bitmap.
734             favoriteIconBitmap
735         }
736
737         // Create a folder icon byte array output stream.
738         val folderIconByteArrayOutputStream = ByteArrayOutputStream()
739
740         // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
741         folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream)
742
743         // Convert the folder icon byte array stream to a byte array.
744         val folderIconByteArray = folderIconByteArrayOutputStream.toByteArray()
745
746         // Move all the bookmarks down one in the display order.
747         for (i in 0 until bookmarksListView.count) {
748             val databaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
749             bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1)
750         }
751
752         // Create the folder, which will be placed at the top of the list view.
753         bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray)
754
755         // Update the bookmarks cursor with the contents of the current folder.
756         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
757
758         // Update the list view.
759         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
760
761         // Scroll to the new folder.
762         bookmarksListView.setSelection(0)
763     }
764
765     override fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap) {
766         // Get the dialog from the dialog fragment.
767         val dialog = dialogFragment.dialog!!
768
769         // Get handles for the views from the dialog fragment.
770         val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
771         val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
772         val currentIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
773
774         // Get the bookmark strings.
775         val bookmarkNameString = bookmarkNameEditText.text.toString()
776         val bookmarkUrlString = bookmarkUrlEditText.text.toString()
777
778         // Update the bookmark.
779         if (currentIconRadioButton.isChecked) {  // Update the bookmark without changing the favorite icon.
780             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString)
781         } else {  // Update the bookmark using the WebView favorite icon.
782             // Create a favorite icon byte array output stream.
783             val newFavoriteIconByteArrayOutputStream = ByteArrayOutputStream()
784
785             // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
786             favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream)
787
788             // Convert the favorite icon byte array stream to a byte array.
789             val newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray()
790
791             //  Update the bookmark and the favorite icon.
792             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray)
793         }
794
795         // Close the contextual action bar if it is displayed.
796         contextualActionMode?.finish()
797
798         // Update the bookmarks cursor with the contents of the current folder.
799         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
800
801         // Update the list view.
802         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
803     }
804
805     override fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap) {
806         // Get the dialog from the dialog fragment.
807         val dialog = dialogFragment.dialog!!
808
809         // Get handles for the views from the dialog fragment.
810         val currentFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
811         val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
812         val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
813         val editFolderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
814
815         // Get the new folder name.
816         val newFolderNameString = editFolderNameEditText.text.toString()
817
818         // Check if the favorite icon has changed.
819         if (currentFolderIconRadioButton.isChecked) {  // Only the name has changed.
820             // Update the name in the database.
821             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString)
822         } else {  // The icon has changed.
823             // Populate the new folder icon bitmap.
824             val folderIconBitmap: Bitmap = if (defaultFolderIconRadioButton.isChecked) {
825                 // Get the default folder icon drawable.
826                 val folderIconDrawable = defaultFolderIconImageView.drawable
827
828                 // Convert the folder icon drawable to a bitmap drawable.
829                 val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
830
831                 // Convert the folder icon bitmap drawable to a bitmap.
832                 folderIconBitmapDrawable.bitmap
833             } else {  // Use the WebView favorite icon.
834                 // Copy the favorite icon bitmap to the folder icon bitmap.
835                 favoriteIconBitmap
836             }
837
838             // Create a folder icon byte array output stream.
839             val newFolderIconByteArrayOutputStream = ByteArrayOutputStream()
840
841             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
842             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream)
843
844             // Convert the folder icon byte array stream to a byte array.
845             val newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray()
846
847             // Update the database.
848             if (!currentFolderIconRadioButton.isChecked && newFolderNameString == oldFolderNameString)  // Only the icon has changed.
849                 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray)
850             else  // The folder icon and the name have changed.
851                 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray)
852         }
853
854         // Update the bookmarks cursor with the current contents of this folder.
855         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
856
857         // Update the list view.
858         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
859
860         // Close the contextual action mode.
861         contextualActionMode!!.finish()
862     }
863
864     override fun onMoveToFolder(dialogFragment: DialogFragment) {
865         // Get the dialog from the dialog fragment.
866         val dialog = dialogFragment.dialog!!
867
868         // Get a handle for the folder list view from the dialog.
869         val folderListView = dialog.findViewById<ListView>(R.id.move_to_folder_listview)
870
871         // Store a long array of the selected folders.
872         val newFolderLongArray = folderListView.checkedItemIds
873
874         // Get the new folder database ID.  Only one folder will be selected so it will be the first one.
875         val newFolderDatabaseId = newFolderLongArray[0].toInt()
876
877         // Set the new folder name.
878         val newFolderName = if (newFolderDatabaseId == 0) {
879             // The new folder is the home folder, represented as `""` in the database.
880             ""
881         } else {
882             // Get the new folder name from the database.
883             bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId)
884         }
885
886         // Get a long array with the the database ID of the selected bookmarks.
887         val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
888
889         // Move each of the selected bookmarks to the new folder.
890         for (databaseIdLong in selectedBookmarksLongArray) {
891             // Convert the database long ID to an int for each selected bookmark.
892             val databaseIdInt = databaseIdLong.toInt()
893
894             // Move the selected bookmark to the new folder.
895             bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName)
896         }
897
898         // Update the bookmarks cursor with the current contents of this folder.
899         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
900
901         // Update the list view.
902         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
903
904         // Close the contextual app bar.
905         contextualActionMode!!.finish()
906     }
907
908     private fun countBookmarkFolderContents(folderDatabaseId: Int): Int {
909         // Get the name of the folder.
910         val folderName = bookmarksDatabaseHelper.getFolderName(folderDatabaseId)
911
912         // Get the contents of the folder.
913         val folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName)
914
915         // Initialize the bookmark counter.
916         var bookmarkCounter = 0
917
918         // Count each of the bookmarks in the folder.
919         for (i in 0 until folderCursor.count) {
920             // Move the folder cursor to the current row.
921             folderCursor.moveToPosition(i)
922
923             // Get the database ID of the item.
924             val itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
925
926             // If this is a folder, recursively count the contents first.
927             if (bookmarksDatabaseHelper.isFolder(itemDatabaseId))
928                 bookmarkCounter += countBookmarkFolderContents(itemDatabaseId)
929
930             // Add the bookmark to the running total.
931             bookmarkCounter++
932         }
933
934         // Return the bookmark counter.
935         return bookmarkCounter
936     }
937
938     private fun deleteBookmarkFolderContents(folderDatabaseId: Int) {
939         // Get the name of the folder.
940         val folderName = bookmarksDatabaseHelper.getFolderName(folderDatabaseId)
941
942         // Get the contents of the folder.
943         val folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName)
944
945         // Delete each of the bookmarks in the folder.
946         for (i in 0 until folderCursor.count) {
947             // Move the folder cursor to the current row.
948             folderCursor.moveToPosition(i)
949
950             // Get the database ID of the item.
951             val itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
952
953             // If this is a folder, recursively delete the contents first.
954             if (bookmarksDatabaseHelper.isFolder(itemDatabaseId))
955                 deleteBookmarkFolderContents(itemDatabaseId)
956
957             // Delete the bookmark.
958             bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId)
959         }
960     }
961
962     private fun prepareFinish() {
963         // Check to see if a snackbar is currently displayed.  If so, it must be closed before exiting so that a pending delete is completed before reloading the list view in the bookmarks drawer.
964         if (bookmarksDeletedSnackbar != null && bookmarksDeletedSnackbar!!.isShown) {  // Close the bookmarks deleted snackbar before going home.
965             // Set the close flag.
966             closeActivityAfterDismissingSnackbar = true
967
968             // Dismiss the snackbar.
969             bookmarksDeletedSnackbar!!.dismiss()
970         } else {  // Go home immediately.
971             // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
972             MainWebViewActivity.currentBookmarksFolder = currentFolder
973
974             // Close the bookmarks drawer and reload the bookmarks list view when returning to the main WebView activity.
975             MainWebViewActivity.restartFromBookmarksActivity = true
976
977             // Exit the bookmarks activity.
978             finish()
979         }
980     }
981
982     private fun updateMoveIcons() {
983         // Get a long array of the selected bookmarks.
984         val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
985
986         // Get the database IDs for the first, last, and selected bookmarks.
987         val firstBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(0).toInt()
988         val lastBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(bookmarksListView.count - 1).toInt()  // The bookmarks list view is 0 indexed.
989         val selectedBookmarkDatabaseId = selectedBookmarksLongArray[0].toInt()
990
991         // Update the move bookmark up menu item.
992         if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) {  // The selected bookmark is in the first position.
993             // Disable the move bookmark up menu item.
994             moveBookmarkUpMenuItem.isEnabled = false
995
996             //  Set the icon.
997             moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled)
998         } else {  // The selected bookmark is not in the first position.
999             // Enable the move bookmark up menu item.
1000             moveBookmarkUpMenuItem.isEnabled = true
1001
1002             // Set the icon according to the theme.
1003             moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled)
1004         }
1005
1006         // Update the move bookmark down menu item.
1007         if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) {  // The selected bookmark is in the last position.
1008             // Disable the move bookmark down menu item.
1009             moveBookmarkDownMenuItem.isEnabled = false
1010
1011             // Set the icon.
1012             moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled)
1013         } else {  // The selected bookmark is not in the last position.
1014             // Enable the move bookmark down menu item.
1015             moveBookmarkDownMenuItem.isEnabled = true
1016
1017             // Set the icon.
1018             moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled)
1019         }
1020     }
1021
1022     private fun scrollBookmarks(selectedBookmarkPosition: Int) {
1023         // Get the first and last visible bookmark positions.
1024         val firstVisibleBookmarkPosition = bookmarksListView.firstVisiblePosition
1025         val lastVisibleBookmarkPosition = bookmarksListView.lastVisiblePosition
1026
1027         // Calculate the number of bookmarks per screen.
1028         val numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition
1029
1030         // Scroll with the moved bookmark if necessary.
1031         if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) {  // The selected bookmark position is at or above the top of the screen.
1032             // Scroll to the selected bookmark position.
1033             bookmarksListView.setSelection(selectedBookmarkPosition)
1034         } else if (selectedBookmarkPosition >= lastVisibleBookmarkPosition - 1) {  // The selected bookmark is at or below the bottom of the screen.
1035             // The `-1` handles partial bookmarks displayed at the bottom of the list view.  This command scrolls to display the selected bookmark at the bottom of the screen.
1036             // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1037             bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1)
1038         }
1039     }
1040
1041     private fun loadFolder() {
1042         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
1043         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
1044
1045         // Setup a cursor adapter.
1046         bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) {
1047             override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
1048                 // Inflate the individual item layout.
1049                 return layoutInflater.inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false)
1050             }
1051
1052             override fun bindView(view: View, context: Context, cursor: Cursor) {
1053                 // Get handles for the views.
1054                 val bookmarkFavoriteIconImageView = view.findViewById<ImageView>(R.id.bookmark_favorite_icon)
1055                 val bookmarkNameTextView = view.findViewById<TextView>(R.id.bookmark_name)
1056
1057                 // Get the favorite icon byte array from the cursor.
1058                 val favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON))
1059
1060                 // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
1061                 val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
1062
1063                 // Display the bitmap in the bookmark favorite icon image view.
1064                 bookmarkFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
1065
1066                 // Get the bookmark name from the cursor.
1067                 val bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
1068
1069                 // Display the bookmark name.
1070                 bookmarkNameTextView.text = bookmarkNameString
1071
1072                 // Make the font bold for folders.
1073                 if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1)
1074                     bookmarkNameTextView.typeface = Typeface.DEFAULT_BOLD
1075                 else  // Reset the font to default for normal bookmarks.
1076                     bookmarkNameTextView.typeface = Typeface.DEFAULT
1077             }
1078         }
1079
1080         // Populate the list view with the adapter.
1081         bookmarksListView.adapter = bookmarksCursorAdapter
1082
1083         // Set the app bar title.
1084         if (currentFolder.isEmpty())
1085             appBar.setTitle(R.string.bookmarks)
1086         else
1087             appBar.title = currentFolder
1088     }
1089
1090     public override fun onDestroy() {
1091         // Close the bookmarks cursor and database.
1092         bookmarksCursor.close()
1093         bookmarksDatabaseHelper.close()
1094
1095         // Run the default commands.
1096         super.onDestroy()
1097     }
1098 }