]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.kt
Bump the target API to 36. https://redmine.stoutner.com/issues/1283
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / BookmarksActivity.kt
index 737d6393be75dadd936d94968a211f58ab41f52a..62004a9a29a6d075b8b2377677bfdb1c635468a3 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * Copyright 2016-2023 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2016-2024 Soren Stoutner <soren@stoutner.com>.
  *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
  *
  * Privacy Browser Android is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -14,7 +14,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
+ * along with Privacy Browser Android.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 package com.stoutner.privacybrowser.activities
@@ -47,6 +47,7 @@ import androidx.activity.OnBackPressedCallback
 import androidx.appcompat.app.ActionBar
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.widget.Toolbar
+import androidx.core.graphics.drawable.toBitmap
 import androidx.cursoradapter.widget.CursorAdapter
 import androidx.fragment.app.DialogFragment
 import androidx.preference.PreferenceManager
@@ -72,12 +73,12 @@ import java.io.ByteArrayOutputStream
 import java.util.function.Consumer
 
 // Define the public constants.
-const val CURRENT_FAVORITE_ICON_BYTE_ARRAY = "A"
-const val CURRENT_FOLDER_ID = "B"
-const val CURRENT_TITLE = "C"
+const val CURRENT_FAVORITE_ICON_BYTE_ARRAY = "current_favorite_icon_byte_array"
+const val CURRENT_FOLDER_ID = "current_folder_id"
+const val CURRENT_TITLE = "current_title"
 
 // Define the private constants.
-private const val CHECKED_BOOKMARKS_ARRAY_LIST = "D"
+private const val CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list"
 
 class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener,
     EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
@@ -93,6 +94,7 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
     private var checkingManyBookmarks = false
     private var closeActivityAfterDismissingSnackbar = false
     private var contextualActionMode: ActionMode? = null
+    private var sortBookmarksAlphabetically = false
 
     // Declare the class variables.
     private lateinit var appBar: ActionBar
@@ -103,6 +105,8 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
     private lateinit var currentFavoriteIconByteArray: ByteArray
     private lateinit var moveBookmarkDownMenuItem: MenuItem
     private lateinit var moveBookmarkUpMenuItem: MenuItem
+    private lateinit var moveToTopMenuItem: MenuItem
+    private lateinit var moveToBottomMenuItem: MenuItem
     private lateinit var moveToFolderMenuItem: MenuItem
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -112,6 +116,7 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         // Get the preferences.
         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
         val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
+        sortBookmarksAlphabetically = sharedPreferences.getBoolean(getString(R.string.sort_bookmarks_alphabetically_key), false)
 
         // Disable screenshots if not allowed.
         if (!allowScreenshots) {
@@ -228,11 +233,21 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
                 // Get handles for menu items that need to be selectively disabled.
                 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up)
                 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down)
+                moveToTopMenuItem = menu.findItem(R.id.move_to_top)
+                moveToBottomMenuItem = menu.findItem(R.id.move_to_bottom)
                 moveToFolderMenuItem = menu.findItem(R.id.move_to_folder)
                 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark)
                 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark)
                 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks)
 
+                // Hide the move up and down menu items if bookmarks are sorted alphabetically.
+                if (sortBookmarksAlphabetically) {
+                    moveBookmarkUpMenuItem.isVisible = false
+                    moveBookmarkDownMenuItem.isVisible = false
+                    moveToTopMenuItem.isVisible = false
+                    moveToBottomMenuItem.isVisible = false
+                }
+
                 // Disable the delete bookmarks menu item if a delete is pending.
                 deleteBookmarksMenuItem.isEnabled = !deletingBookmarks
 
@@ -261,20 +276,42 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
                     if (numberOfSelectedBookmarks > 0) {
                         // Adjust the action mode and the menu according to the number of selected bookmarks.
                         if (numberOfSelectedBookmarks == 1) {  // One bookmark is selected.
-                            // Show the applicable menu items.
-                            moveBookmarkUpMenuItem.isVisible = true
-                            moveBookmarkDownMenuItem.isVisible = true
-                            editBookmarkMenuItem.isVisible = true
+                            // Update the move menu items if the bookmarks are not sorted alphabetically.
+                            if (!sortBookmarksAlphabetically) {
+                                moveBookmarkUpMenuItem.isVisible = true
+                                moveBookmarkDownMenuItem.isVisible = true
+                            }
 
-                            // Update the enabled status of the move icons.
-                            updateMoveIcons()
+                            // Show the edit bookmark menu item.
+                            editBookmarkMenuItem.isVisible = true
                         } else {  // More than one bookmark is selected.
-                            // Hide non-applicable `MenuItems`.
-                            moveBookmarkUpMenuItem.isVisible = false
-                            moveBookmarkDownMenuItem.isVisible = false
+                            // Update the move menu items if the bookmarks are not sorted alphabetically.
+                            if (!sortBookmarksAlphabetically) {
+                                moveBookmarkUpMenuItem.isVisible = false
+                                moveBookmarkDownMenuItem.isVisible = false
+                            }
+
+                            // Hide the edit bookmark menu item.
                             editBookmarkMenuItem.isVisible = false
                         }
 
+                        //  Update the move icons if not sorting alphabetically.
+                        if (!sortBookmarksAlphabetically) {
+                            // Adjust the visibility of the move to top and bottom menu items.
+                            if ((numberOfSelectedBookmarks == bookmarksListView.count)) {  // All the bookmarks are selected.
+                                // Hide the move to top and bottom menu items.
+                                moveToTopMenuItem.isVisible = false
+                                moveToBottomMenuItem.isVisible = false
+                            } else {  // Not all the bookmarks are selected.
+                                // Show the move to top and bottom menu item.
+                                moveToTopMenuItem.isVisible = true
+                                moveToBottomMenuItem.isVisible = true
+                            }
+
+                            // Update the move icons.
+                            updateMoveIcons()
+                        }
+
                         // Display the move to folder menu item if at least one other folder exists.
                         moveToFolderMenuItem.isVisible = bookmarksDatabaseHelper.hasFoldersExceptDatabaseId(bookmarksListView.checkedItemIds)
 
@@ -347,7 +384,7 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
 
                     // Scroll to the new bookmark position.
-                    scrollBookmarks(checkedBookmarkNewPosition)
+                    bookmarksListView.setSelection(checkedBookmarkNewPosition)
 
                     // Update the enabled status of the move icons.
                     updateMoveIcons()
@@ -401,6 +438,122 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
 
                     // Update the enabled status of the move icons.
                     updateMoveIcons()
+                } else if (menuItemId == R.id.move_to_top) {  // Move to top.
+                    // Get a list of selected bookmark IDs by display order.
+                    val selectedBookmarkIdsList = getSelectedBookmarkIdsByDisplayOrder()
+
+                    // Update the display order of the bookmarks that are selected.  `newDisplayOrder` is the index, which auto-increments.
+                    for ((newSelectedBookmarkDisplayOrder, bookmarkDatabaseId) in selectedBookmarkIdsList.withIndex()) {
+                        // Set the new bookmark position.
+                        bookmarksDatabaseHelper.updateDisplayOrder(bookmarkDatabaseId, newSelectedBookmarkDisplayOrder)
+                    }
+
+                    // Get the number of bookmarks.
+                    val numberOfSelectedBookmarks = selectedBookmarkIdsList.size
+                    val totalNumberOfBookmarksMinusOne = bookmarksListView.count - 1
+
+                    // Initialize the new unselected bookmark display order.
+                    var newUnselectedBookmarkDisplayOrder = numberOfSelectedBookmarks
+
+                    // Increment the display order of the other bookmarks by the number of selected bookmarks.
+                    for (i in 0..totalNumberOfBookmarksMinusOne) {
+                        // Get the bookmark database ID long at the indicated position.
+                        val bookmarkDatabaseIdLong = bookmarksListView.getItemIdAtPosition(i)
+
+                        // Increment the display order if it isn't one of the selected bookmarks.
+                        if (!selectedBookmarkIdsList.contains(bookmarkDatabaseIdLong.toInt())) {
+                            // Move the bookmarks cursor to the current bookmark position.
+                            bookmarksCursor.moveToPosition(i)
+
+                            // Update the unselected bookmark display order if it has changed.
+                            if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(DISPLAY_ORDER)) != newUnselectedBookmarkDisplayOrder)
+                                bookmarksDatabaseHelper.updateDisplayOrder(bookmarkDatabaseIdLong.toInt(), newUnselectedBookmarkDisplayOrder)
+
+                            // Increment the new unselected bookmark display order.
+                            ++newUnselectedBookmarkDisplayOrder
+                        }
+                    }
+
+                    // Update the bookmarks cursor with the current contents of the bookmarks database.
+                    bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
+
+                    // Update the list view.
+                    bookmarksCursorAdapter.changeCursor(bookmarksCursor)
+
+                    // Update the enabled status of the move icons.
+                    updateMoveIcons()
+
+                    // Reselect the original bookmarks (the system only automatically does so for those that were visible before the move).
+                    for (i in 0..totalNumberOfBookmarksMinusOne) {
+                        // Reelect the originally selected bookmarks.  Deselect all the other bookmarks.
+                        bookmarksListView.setItemChecked(i, i < numberOfSelectedBookmarks)
+                    }
+
+                    // Scroll to the top.
+                    scrollBookmarks(0)
+                } else if (menuItemId == R.id.move_to_bottom) {  // Move to bottom.
+                    // Get a list of selected bookmark IDs by display order.
+                    val selectedBookmarkIdsList = getSelectedBookmarkIdsByDisplayOrder()
+
+                    // Get the number of bookmarks.
+                    val numberOfSelectedBookmarks = selectedBookmarkIdsList.size
+                    val totalNumberOfBookmarks = bookmarksListView.count
+                    val totalNumberOfBookmarksMinusOne = totalNumberOfBookmarks - 1
+
+                    // Initialize the new selected bookmark display order.
+                    var newSelectedBookmarkDisplayOrder = totalNumberOfBookmarks - numberOfSelectedBookmarks
+
+                    // Update the display order of the bookmarks that are selected.
+                    for (bookmarkDatabaseId in selectedBookmarkIdsList) {
+                        // Set the new bookmark position.
+                        bookmarksDatabaseHelper.updateDisplayOrder(bookmarkDatabaseId, newSelectedBookmarkDisplayOrder)
+
+                        // Increment the new selected bookmark display order.
+                        ++newSelectedBookmarkDisplayOrder
+                    }
+
+                    // Initialize the new unselected bookmark display order.
+                    var newUnselectedBookmarkDisplayOrder = 0
+
+                    // Increment the display order of the bookmarks that are not selected.
+                    for (i in 0..totalNumberOfBookmarksMinusOne) {
+                        // Get the bookmark database ID long at the indicated position.
+                        val bookmarkDatabaseIdLong = bookmarksListView.getItemIdAtPosition(i)
+
+                        // Adjust the display order if it isn't one of the selected bookmarks.
+                        if (!selectedBookmarkIdsList.contains(bookmarkDatabaseIdLong.toInt())) {
+                            // Move the bookmarks cursor to the current bookmark position.
+                            bookmarksCursor.moveToPosition(i)
+
+                            // Update the unselected bookmark display order if it has changed.
+                            if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(DISPLAY_ORDER)) != newUnselectedBookmarkDisplayOrder)
+                                bookmarksDatabaseHelper.updateDisplayOrder(bookmarkDatabaseIdLong.toInt(), newUnselectedBookmarkDisplayOrder)
+
+                            // Increment the new unselected bookmark display order.
+                            ++newUnselectedBookmarkDisplayOrder
+                        }
+                    }
+
+                    // Update the bookmarks cursor with the current contents of the bookmarks database.
+                    bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
+
+                    // Update the list view.
+                    bookmarksCursorAdapter.changeCursor(bookmarksCursor)
+
+                    // Update the enabled status of the move icons.
+                    updateMoveIcons()
+
+                    // Calculate th
+                    val firstSelectedBookmarkAtEnd = totalNumberOfBookmarks - numberOfSelectedBookmarks
+
+                    // Reselect the original bookmarks (the system only automatically does so for those that were visible before the move).
+                    for (i in 0..totalNumberOfBookmarksMinusOne) {
+                        // Reelect the originally selected bookmarks.  Deselect all the other bookmarks.
+                        bookmarksListView.setItemChecked(i, i >= firstSelectedBookmarkAtEnd)
+                    }
+
+                    // Scroll to the bottom.
+                    scrollBookmarks(totalNumberOfBookmarksMinusOne)
                 } else if (menuItemId == R.id.move_to_folder) {  // Move to folder.
                     // Instantiate the move to folder alert dialog.
                     val moveToFolderDialog = MoveToFolderDialog.moveBookmarks(currentFolderId, bookmarksListView.checkedItemIds)
@@ -469,7 +622,10 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
                     checkedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions.clone()
 
                     // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
-                    bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(checkedBookmarksIdsLongArray, currentFolderId)
+                    bookmarksCursor = if (sortBookmarksAlphabetically)
+                        bookmarksDatabaseHelper.getBookmarksSortedAlphabeticallyExcept(checkedBookmarksIdsLongArray, currentFolderId)
+                    else
+                        bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(checkedBookmarksIdsLongArray, currentFolderId)
 
                     // Update the list view.
                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
@@ -481,7 +637,10 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
                             override fun onDismissed(snackbar: Snackbar, event: Int) {
                                 if (event == DISMISS_EVENT_ACTION) {  // The user pushed the undo button.
                                     // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
-                                    bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
+                                    bookmarksCursor = if (sortBookmarksAlphabetically)
+                                        bookmarksDatabaseHelper.getBookmarksSortedAlphabetically(currentFolderId)
+                                    else
+                                        bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
 
                                     // Update the list view.
                                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
@@ -515,18 +674,8 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
                                         bookmarksDatabaseHelper.deleteBookmark(databaseIdInt)
                                     }
 
-                                    // Update the display order.
-                                    for (i in 0 until bookmarksListView.count) {
-                                        // Get the database ID for the current bookmark.
-                                        val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
-
-                                        // Move bookmarks cursor to the current bookmark position.
-                                        bookmarksCursor.moveToPosition(i)
-
-                                        // Update the display order only if it is not correct in the database.
-                                        if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(DISPLAY_ORDER)) != i)
-                                            bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
-                                    }
+                                    // Recalculate the display order of the current folder.
+                                    bookmarksDatabaseHelper.recalculateFolderContentsDisplayOrder(currentFolderId)
                                 }
 
                                 // Reset the deleting bookmarks flag.
@@ -626,9 +775,9 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         }
     }
 
-    public override fun onSaveInstanceState(savedInstanceState: Bundle) {
+    public override fun onSaveInstanceState(outState: Bundle) {
         // Run the default commands.
-        super.onSaveInstanceState(savedInstanceState)
+        super.onSaveInstanceState(outState)
 
         // Get the sparse boolean array of the checked items.
         val checkedBookmarksSparseBooleanArray = bookmarksListView.checkedItemPositions
@@ -645,9 +794,9 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
             }
         }
 
-        // Store the variables in the saved instance state.
-        savedInstanceState.putLong(CURRENT_FOLDER_ID, currentFolderId)
-        savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList)
+        // Store the variables in the out state.
+        outState.putLong(CURRENT_FOLDER_ID, currentFolderId)
+        outState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList)
     }
 
     override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -706,17 +855,29 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         return true
     }
 
-    override fun createBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
+    override fun createBookmark(dialogFragment: DialogFragment) {
         // Get the alert dialog from the fragment.
         val dialog = dialogFragment.dialog!!
 
-        // Get the views from the dialog fragment.
-        val createBookmarkNameEditText = dialog.findViewById<EditText>(R.id.create_bookmark_name_edittext)
-        val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
+        // Get handles for the views from the dialog fragment.
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
+        val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
+        val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
+
+        // Get the strings from the edit texts.
+        val bookmarkNameString = bookmarkNameEditText.text.toString()
+        val bookmarkUrlString = bookmarkUrlEditText.text.toString()
+
+        // Get the selected favorite icon drawable.
+        val favoriteIconDrawable = if (webpageFavoriteIconRadioButton.isChecked)  // The webpage favorite icon is checked.
+            webpageFavoriteIconImageView.drawable
+        else  // The custom favorite icon is checked.
+            customIconImageView.drawable
 
-        // Extract the strings from the edit texts.
-        val bookmarkNameString = createBookmarkNameEditText.text.toString()
-        val bookmarkUrlString = createBookmarkUrlEditText.text.toString()
+        // Convert the favorite icon drawable to a bitmap.  Once the minimum API >= 33, this can use Bitmap.Config.RGBA_1010102.
+        val favoriteIconBitmap = favoriteIconDrawable.toBitmap(128, 128, Bitmap.Config.ARGB_8888)
 
         // Create a favorite icon byte array output stream.
         val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
@@ -734,7 +895,10 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolderId, newBookmarkDisplayOrder, favoriteIconByteArray)
 
         // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
+        bookmarksCursor = if (sortBookmarksAlphabetically)
+            bookmarksDatabaseHelper.getBookmarksSortedAlphabetically(currentFolderId)
+        else
+            bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
 
         // Update the list view.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
@@ -743,32 +907,34 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         bookmarksListView.setSelection(newBookmarkDisplayOrder)
     }
 
-    override fun createBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
+    override fun createBookmarkFolder(dialogFragment: DialogFragment) {
         // Get the dialog from the dialog fragment.
         val dialog = dialogFragment.dialog!!
 
         // Get handles for the views in the dialog fragment.
+        val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_folder_icon_radiobutton)
+        val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_folder_icon_imageview)
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
         val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
-        val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
-        val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
 
-        // Get new folder name string.
+        // Get the folder name string.
         val folderNameString = folderNameEditText.text.toString()
 
-        // Set the folder icon bitmap according to the dialog.
-        val folderIconBitmap = if (defaultIconRadioButton.isChecked) {  // Use the default folder icon.
-            // Get the default folder icon drawable.
-            val folderIconDrawable = defaultIconImageView.drawable
+        // Get the selected folder icon drawable.
+        val folderIconDrawable = if (defaultFolderIconRadioButton.isChecked)  // Use the default folder icon.
+            defaultFolderIconImageView.drawable
+        else if (webpageFavoriteIconRadioButton.isChecked)  // Use the webpage favorite icon.
+            webpageFavoriteIconImageView.drawable
+        else  // Use the custom icon.
+            customIconImageView.drawable
 
-            // Convert the folder icon drawable to a bitmap drawable.
-            val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
+        // Cast the folder icon bitmap to a bitmap drawable.
+        val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
 
-            // Convert the folder icon bitmap drawable to a bitmap.
-            folderIconBitmapDrawable.bitmap
-        } else {  // Use the WebView favorite icon.
-            // Copy the favorite icon bitmap to the folder icon bitmap.
-            favoriteIconBitmap
-        }
+        // Convert the folder icon bitmap drawable to a bitmap.
+        val folderIconBitmap = folderIconBitmapDrawable.bitmap
 
         // Create a folder icon byte array output stream.
         val folderIconByteArrayOutputStream = ByteArrayOutputStream()
@@ -789,7 +955,10 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         bookmarksDatabaseHelper.createFolder(folderNameString, currentFolderId, displayOrder = 0, folderIconByteArray)
 
         // Update the bookmarks cursor with the contents of the current folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
+        bookmarksCursor = if (sortBookmarksAlphabetically)
+            bookmarksDatabaseHelper.getBookmarksSortedAlphabetically(currentFolderId)
+        else
+            bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
 
         // Update the list view.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
@@ -798,16 +967,19 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         bookmarksListView.setSelection(0)
     }
 
-    override fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap) {
+    override fun saveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int) {
         // Get the dialog from the dialog fragment.
         val dialog = dialogFragment.dialog!!
 
         // Get handles for the views from the dialog fragment.
+        val currentIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
         val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
         val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
-        val currentIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
 
-        // Get the bookmark strings.
+        // Get the strings from the edit texts.
         val bookmarkNameString = bookmarkNameEditText.text.toString()
         val bookmarkUrlString = bookmarkUrlEditText.text.toString()
 
@@ -815,6 +987,15 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         if (currentIconRadioButton.isChecked) {  // Update the bookmark without changing the favorite icon.
             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString)
         } else {  // Update the bookmark using the WebView favorite icon.
+            // Get the selected favorite icon drawable.
+            val favoriteIconDrawable = if (webpageFavoriteIconRadioButton.isChecked)  // The webpage favorite icon is checked.
+                webpageFavoriteIconImageView.drawable
+            else  // The custom icon is checked.
+                customIconImageView.drawable
+
+            // Convert the favorite icon drawable to a bitmap.  Once the minimum API >= 33, this can use Bitmap.Config.RGBA_1010102.
+            val favoriteIconBitmap = favoriteIconDrawable.toBitmap(128, 128, Bitmap.Config.ARGB_8888)
+
             // Create a favorite icon byte array output stream.
             val newFavoriteIconByteArrayOutputStream = ByteArrayOutputStream()
 
@@ -832,46 +1013,48 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         contextualActionMode?.finish()
 
         // Update the bookmarks cursor with the contents of the current folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
+        bookmarksCursor = if (sortBookmarksAlphabetically)
+            bookmarksDatabaseHelper.getBookmarksSortedAlphabetically(currentFolderId)
+        else
+            bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
 
         // Update the list view.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
     }
 
-    override fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap) {
+    override fun saveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int) {
         // Get the dialog from the dialog fragment.
         val dialog = dialogFragment.dialog!!
 
         // Get handles for the views from the dialog fragment.
         val currentFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
-        val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
-        val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
-        val editFolderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
+        val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_folder_icon_radiobutton)
+        val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_folder_icon_imageview)
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
+        val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
 
         // Get the new folder name.
-        val newFolderName = editFolderNameEditText.text.toString()
+        val newFolderName = folderNameEditText.text.toString()
 
-        // Check if the favorite icon has changed.
+        // Check if the folder icon has changed.
         if (currentFolderIconRadioButton.isChecked) {  // Only the name has changed.
             // Update the name in the database.
             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderName)
         } else {  // The icon has changed.
-            // Populate the new folder icon bitmap.
-            val folderIconBitmap: Bitmap = if (defaultFolderIconRadioButton.isChecked) {
-                // Get the default folder icon drawable.
-                val folderIconDrawable = defaultFolderIconImageView.drawable
-
-                // Convert the folder icon drawable to a bitmap drawable.
-                val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
-
-                // Convert the folder icon bitmap drawable to a bitmap.
-                folderIconBitmapDrawable.bitmap
-            } else {  // Use the WebView favorite icon.
-                // Copy the favorite icon bitmap to the folder icon bitmap.
-                favoriteIconBitmap
-            }
-
-            // Create a folder icon byte array output stream.
+            // Get the selected folder icon drawable.
+            val folderIconDrawable = if (defaultFolderIconRadioButton.isChecked)  // The default folder icon is checked.
+                defaultFolderIconImageView.drawable
+            else if (webpageFavoriteIconRadioButton.isChecked)  // The webpage favorite icon is checked.
+                webpageFavoriteIconImageView.drawable
+            else  // The custom icon is checked.
+                customIconImageView.drawable
+
+            // Convert the folder icon drawable to a bitmap.  Once the minimum API >= 33, this can use Bitmap.Config.RGBA_1010102.
+            val folderIconBitmap = folderIconDrawable.toBitmap(128, 128, Bitmap.Config.ARGB_8888)
+
+            // Create a new folder icon byte array output stream.
             val newFolderIconByteArrayOutputStream = ByteArrayOutputStream()
 
             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
@@ -885,7 +1068,10 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         }
 
         // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
+        bookmarksCursor = if (sortBookmarksAlphabetically)
+            bookmarksDatabaseHelper.getBookmarksSortedAlphabetically(currentFolderId)
+        else
+            bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
 
         // Update the list view.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
@@ -927,8 +1113,14 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
             bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderId)
         }
 
+        // Recalculate the display order of the current folder.
+        bookmarksDatabaseHelper.recalculateFolderContentsDisplayOrder(currentFolderId)
+
         // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
+        bookmarksCursor = if (sortBookmarksAlphabetically)
+            bookmarksDatabaseHelper.getBookmarksSortedAlphabetically(currentFolderId)
+        else
+            bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
 
         // Update the list view.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
@@ -991,6 +1183,35 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         }
     }
 
+    private fun getSelectedBookmarkIdsByDisplayOrder(): List<Int> {
+        // Create the lists.
+        val selectedBookmarkPositionsList = mutableListOf<Int>()
+        val selectedBookmarkIdsList = mutableListOf<Int>()
+
+        // Get the array of checked bookmark positions.
+        val checkedBookmarkPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
+
+        // Get the checked bookmarks positions sparse boolean array size.
+        val checkedBookmarkPositionsSparseBooleanArraySize = checkedBookmarkPositionsSparseBooleanArray.size()
+
+        // Get the position of the bookmarks that are selected.  If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
+        for (i in 0 until checkedBookmarkPositionsSparseBooleanArraySize) {
+            // Check to see if the value for the bookmark is true, meaning it is currently selected.
+            if (checkedBookmarkPositionsSparseBooleanArray.valueAt(i)) {
+                // Add the selected bookmarks positions to the list.
+                selectedBookmarkPositionsList.add(checkedBookmarkPositionsSparseBooleanArray.keyAt(i))
+            }
+        }
+
+        // Get the selected bookmark IDs from their positions.  The selected bookmark positions list will already be sorted by position.
+        for (selectedBookmarkPosition in selectedBookmarkPositionsList) {
+            selectedBookmarkIdsList.add(bookmarksListView.getItemIdAtPosition(selectedBookmarkPosition).toInt())
+        }
+
+        // Return the selected bookmark IDs list.
+        return selectedBookmarkIdsList
+    }
+
     private fun prepareFinish() {
         // 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.
         if (bookmarksDeletedSnackbar != null && bookmarksDeletedSnackbar!!.isShown) {  // Close the bookmarks deleted snackbar before going home.
@@ -1012,23 +1233,23 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
     }
 
     private fun updateMoveIcons() {
-        // Get a long array of the selected bookmarks.
-        val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
+        // Get a long array of the selected bookmarks IDs.
+        val selectedBookmarkIdsLongArray = bookmarksListView.checkedItemIds
 
         // Get the database IDs for the first, last, and selected bookmarks.
         val firstBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(0).toInt()
         val lastBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(bookmarksListView.count - 1).toInt()  // The bookmarks list view is 0 indexed.
-        val selectedBookmarkDatabaseId = selectedBookmarksLongArray[0].toInt()
+        val firstSelectedBookmarkDatabaseId = selectedBookmarkIdsLongArray[0].toInt()
 
         // Update the move bookmark up menu item.
-        if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) {  // The selected bookmark is in the first position.
-            // Disable the move bookmark up menu item.
+        if (firstSelectedBookmarkDatabaseId == firstBookmarkDatabaseId) {  // The selected bookmark is in the first position.
+            // Disable the menu item.
             moveBookmarkUpMenuItem.isEnabled = false
 
             //  Set the icon.
             moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled)
         } else {  // The selected bookmark is not in the first position.
-            // Enable the move bookmark up menu item.
+            // Enable the menu item.
             moveBookmarkUpMenuItem.isEnabled = true
 
             // Set the icon according to the theme.
@@ -1036,19 +1257,82 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         }
 
         // Update the move bookmark down menu item.
-        if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) {  // The selected bookmark is in the last position.
-            // Disable the move bookmark down menu item.
+        if (firstSelectedBookmarkDatabaseId == lastBookmarkDatabaseId) {  // The selected bookmark is in the last position.
+            // Disable the menu item.
             moveBookmarkDownMenuItem.isEnabled = false
 
             // Set the icon.
             moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled)
         } else {  // The selected bookmark is not in the last position.
-            // Enable the move bookmark down menu item.
+            // Enable the menu item.
             moveBookmarkDownMenuItem.isEnabled = true
 
             // Set the icon.
             moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled)
         }
+
+        // Create a list of selected bookmark positions.
+        val selectedBookmarkPositionsList = mutableListOf<Int>()
+
+        // Get the array of checked bookmark positions.
+        val checkedBookmarkPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
+
+        // Get the checked bookmarks positions sparse boolean array size.
+        val checkedBookmarkPositionsSparseBooleanArraySize = checkedBookmarkPositionsSparseBooleanArray.size()
+
+        // Get the position of the bookmarks that are selected.  If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
+        for (i in 0 until checkedBookmarkPositionsSparseBooleanArraySize) {
+            // Check to see if the value for the bookmark is true, meaning it is currently selected.
+            if (checkedBookmarkPositionsSparseBooleanArray.valueAt(i)) {
+                // Add the selected bookmarks positions to the list.
+                selectedBookmarkPositionsList.add(checkedBookmarkPositionsSparseBooleanArray.keyAt(i))
+            }
+        }
+
+        // Get the selected bookmark positions list size.
+        val selectedBookmarkPositionsListSize = selectedBookmarkPositionsList.size
+
+        // Create a contiguous selected bookmarks tracker.
+        var selectedBookmarksAreContiguous = true
+
+        for (i in 0 until selectedBookmarkPositionsListSize) {
+            // Check all the items after the first one.
+            if (i > 0) {
+                // Mark the list as not contiguous if any of the bookmark positions jump by more than 1.
+                if (selectedBookmarkPositionsList.elementAt(i) - selectedBookmarkPositionsList.elementAt(i - 1) != 1)
+                    selectedBookmarksAreContiguous = false
+            }
+        }
+
+        // Update the move to top menu item.
+        if (selectedBookmarkIdsLongArray.contains(firstBookmarkDatabaseId.toLong()) && selectedBookmarksAreContiguous) {  // The selected bookmarks contains the first bookmark and they are contiguous.
+            // Disable the menu item.
+            moveToTopMenuItem.isEnabled = false
+
+            // Set the icon.
+            moveToTopMenuItem.setIcon(R.drawable.move_to_top_disabled)
+        } else {  // The selected bookmarks do not contain the first bookmark.
+            // Enable the menu item.
+            moveToTopMenuItem.isEnabled = true
+
+            // Set the icon.
+            moveToTopMenuItem.setIcon(R.drawable.move_to_top_enabled)
+        }
+
+        // Update the move to bottom menu item.
+        if (selectedBookmarkIdsLongArray.contains(lastBookmarkDatabaseId.toLong()) && selectedBookmarksAreContiguous) {  // The selected bookmarks contains the last bookmark and they are contiguous.
+            // Disable the menu item.
+            moveToBottomMenuItem.isEnabled = false
+
+            // Set the icon.
+            moveToBottomMenuItem.setIcon(R.drawable.move_to_bottom_disabled)
+        } else {  // The selected bookmarks do not contain the last bookmark.
+            // Enable the menu item.
+            moveToBottomMenuItem.isEnabled = true
+
+            // Set the icon.
+            moveToBottomMenuItem.setIcon(R.drawable.move_to_bottom_enabled)
+        }
     }
 
     private fun scrollBookmarks(selectedBookmarkPosition: Int) {
@@ -1072,7 +1356,10 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
 
     private fun loadFolder() {
         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
+        bookmarksCursor = if (sortBookmarksAlphabetically)
+            bookmarksDatabaseHelper.getBookmarksSortedAlphabetically(currentFolderId)
+        else
+            bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
 
         // Setup a cursor adapter.
         bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) {