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