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