]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Add an option to sort bookmarks alphabetically. https://redmine.stoutner.com/issues... master
authorSoren Stoutner <soren@stoutner.com>
Mon, 9 Sep 2024 20:29:58 +0000 (13:29 -0700)
committerSoren Stoutner <soren@stoutner.com>
Mon, 9 Sep 2024 20:29:58 +0000 (13:29 -0700)
29 files changed:
app/src/main/assets/de/about_licenses.html
app/src/main/assets/en/about_licenses.html
app/src/main/assets/es/about_licenses.html
app/src/main/assets/fr/about_licenses.html
app/src/main/assets/it/about_licenses.html
app/src/main/assets/pt-rBR/about_licenses.html
app/src/main/assets/ru/about_licenses.html
app/src/main/assets/shared_images/menu_rounded_weight400_grade0_24px.svg
app/src/main/assets/shared_images/question_answer.svg
app/src/main/assets/shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg [new file with mode: 0644]
app/src/main/assets/tr/about_licenses.html
app/src/main/assets/zh-rCN/about_licenses.html
app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.kt
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
app/src/main/java/com/stoutner/privacybrowser/activities/SettingsActivity.kt
app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.kt
app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.kt
app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.kt
app/src/main/res/drawable/more_disabled.xml
app/src/main/res/drawable/more_enabled.xml
app/src/main/res/drawable/move_down_disabled.xml
app/src/main/res/drawable/move_down_enabled.xml
app/src/main/res/drawable/move_to_folder_blue.xml
app/src/main/res/drawable/move_up_disabled.xml
app/src/main/res/drawable/move_up_enabled.xml
app/src/main/res/drawable/sort_by_alpha_disabled.xml [new file with mode: 0644]
app/src/main/res/drawable/sort_by_alpha_enabled.xml [new file with mode: 0644]
app/src/main/res/values/strings.xml
app/src/main/res/xml/preferences.xml

index ae111a14467c97f8ec5d196a2a660ab0ea2dff99..0eca2bfc789be724c47b5ae0dbe002c1b22e0762 100644 (file)
@@ -5,7 +5,7 @@
 
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to 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
         <p><svg class="icon"><use href="../shared_images/share.svg#icon"/></svg> share.</p>
         <p><svg class="icon"><use href="../shared_images/smartphone.svg#icon"/></svg> smartphone.</p>
         <p><svg class="icon"><use href="../shared_images/sort.svg#icon"/></svg> sort.</p>
+        <p><svg class="icon"><use href="../shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> sort_<wbr>by_<wbr>alpha_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/style.svg#icon"/></svg> style.</p>
         <p><svg class="icon"><use href="../shared_images/subheader_rounded_weight400_grade0_48px.svg#icon"/></svg> subheader_<wbr>rounded_<wbr>weight400_<wbr>grade0_<wbr>48px.</p>
         <p><svg class="icon"><use href="../shared_images/tab.svg#icon"/></svg> tab.</p>
index 6b53802f69c0bca61a3ab64cec74220a4770bf9e..36a2c057997dce625e2cfb7354ec7a2e6358d7f0 100644 (file)
@@ -1,7 +1,7 @@
 <!--
   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
         <p><svg class="icon"><use href="../shared_images/share.svg#icon"/></svg> share.</p>
         <p><svg class="icon"><use href="../shared_images/smartphone.svg#icon"/></svg> smartphone.</p>
         <p><svg class="icon"><use href="../shared_images/sort.svg#icon"/></svg> sort.</p>
+        <p><svg class="icon"><use href="../shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> sort_<wbr>by_<wbr>alpha_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/style.svg#icon"/></svg> style.</p>
         <p><svg class="icon"><use href="../shared_images/subheader_rounded_weight400_grade0_48px.svg#icon"/></svg> subheader_<wbr>rounded_<wbr>weight400_<wbr>grade0_<wbr>48px.</p>
         <p><svg class="icon"><use href="../shared_images/tab.svg#icon"/></svg> tab.</p>
index b2a3c2ecec1886ff9792385964a1825879330714..2c5c2e442896da803ad925d8b1eddbbe9af839c9 100644 (file)
@@ -3,7 +3,7 @@
 
   Translation 2017-2020,2023 Jose A. León.  Copyright assigned to 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
         <p><svg class="icon"><use href="../shared_images/share.svg#icon"/></svg> share.</p>
         <p><svg class="icon"><use href="../shared_images/smartphone.svg#icon"/></svg> smartphone.</p>
         <p><svg class="icon"><use href="../shared_images/sort.svg#icon"/></svg> sort.</p>
+        <p><svg class="icon"><use href="../shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> sort_<wbr>by_<wbr>alpha_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/style.svg#icon"/></svg> style.</p>
         <p><svg class="icon"><use href="../shared_images/subheader_rounded_weight400_grade0_48px.svg#icon"/></svg> subheader_<wbr>rounded_<wbr>weight400_<wbr>grade0_<wbr>48px.</p>
         <p><svg class="icon"><use href="../shared_images/tab.svg#icon"/></svg> tab.</p>
index 3877b6869a8e86bbde8d612d0984499b819aa916..f3fecb820a744da05854d90d1ebc6534b1aca682 100644 (file)
@@ -3,7 +3,7 @@
 
   Translation 2019-2023 Kévin L. <kevinliste@framalistes.org>.  Copyright assigned to 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
         <p><svg class="icon"><use href="../shared_images/share.svg#icon"/></svg> share.</p>
         <p><svg class="icon"><use href="../shared_images/smartphone.svg#icon"/></svg> smartphone.</p>
         <p><svg class="icon"><use href="../shared_images/sort.svg#icon"/></svg> sort.</p>
+        <p><svg class="icon"><use href="../shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> sort_<wbr>by_<wbr>alpha_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/style.svg#icon"/></svg> style.</p>
         <p><svg class="icon"><use href="../shared_images/subheader_rounded_weight400_grade0_48px.svg#icon"/></svg> subheader_<wbr>rounded_<wbr>weight400_<wbr>grade0_<wbr>48px.</p>
         <p><svg class="icon"><use href="../shared_images/tab.svg#icon"/></svg> tab.</p>
index 159bf75d469f0886bf916878f6290e0c8a9c7403..2647b3ce6e1e8036106d8ab6112b9312d145836d 100644 (file)
@@ -3,7 +3,7 @@
 
   Translation 2017-2020, 2022-2023 Francesco Buratti.  Copyright assigned to 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
         <p><svg class="icon"><use href="../shared_images/share.svg#icon"/></svg> share.</p>
         <p><svg class="icon"><use href="../shared_images/smartphone.svg#icon"/></svg> smartphone.</p>
         <p><svg class="icon"><use href="../shared_images/sort.svg#icon"/></svg> sort.</p>
+        <p><svg class="icon"><use href="../shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> sort_<wbr>by_<wbr>alpha_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/style.svg#icon"/></svg> style.</p>
         <p><svg class="icon"><use href="../shared_images/subheader_rounded_weight400_grade0_48px.svg#icon"/></svg> subheader_<wbr>rounded_<wbr>weight400_<wbr>grade0_<wbr>48px.</p>
         <p><svg class="icon"><use href="../shared_images/tab.svg#icon"/></svg> tab.</p>
index cc93f346dad0cca5451590f9f167d9314b9eb68a..fc52407357b7758446acd369533fd7e2d65d46a3 100644 (file)
@@ -3,7 +3,7 @@
 
   Translation 2021-2022 Thiago Nazareno Conceição Silva de Jesus <mochileiro2006-trilhas@yahoo.com.br>.  Copyright assigned to 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
         <p><svg class="icon"><use href="../shared_images/share.svg#icon"/></svg> share.</p>
         <p><svg class="icon"><use href="../shared_images/smartphone.svg#icon"/></svg> smartphone.</p>
         <p><svg class="icon"><use href="../shared_images/sort.svg#icon"/></svg> sort.</p>
+        <p><svg class="icon"><use href="../shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> sort_<wbr>by_<wbr>alpha_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/style.svg#icon"/></svg> style.</p>
         <p><svg class="icon"><use href="../shared_images/subheader_rounded_weight400_grade0_48px.svg#icon"/></svg> subheader_<wbr>rounded_<wbr>weight400_<wbr>grade0_<wbr>48px.</p>
         <p><svg class="icon"><use href="../shared_images/tab.svg#icon"/></svg> tab.</p>
index 4c99e7ba0d4ce6c81a9b793a91de3260734beb94..6e3cac232f83799615e96be2dc64fb26b3d42721 100644 (file)
@@ -1,7 +1,7 @@
 <!--
   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
         <p><svg class="icon"><use href="../shared_images/share.svg#icon"/></svg> share.</p>
         <p><svg class="icon"><use href="../shared_images/smartphone.svg#icon"/></svg> smartphone.</p>
         <p><svg class="icon"><use href="../shared_images/sort.svg#icon"/></svg> sort.</p>
+        <p><svg class="icon"><use href="../shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> sort_<wbr>by_<wbr>alpha_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/style.svg#icon"/></svg> style.</p>
         <p><svg class="icon"><use href="../shared_images/subheader_rounded_weight400_grade0_48px.svg#icon"/></svg> subheader_<wbr>rounded_<wbr>weight400_<wbr>grade0_<wbr>48px.</p>
         <p><svg class="icon"><use href="../shared_images/tab.svg#icon"/></svg> tab.</p>
index 044514eb42473eca881e420292b09cd6e87ae97c..16ac974ae7cf10cdc70f1456eb096b6e45a5f666 100644 (file)
@@ -3,9 +3,9 @@
 <!--
   Copyright 2023 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/>.
 
-  It is a modified version of `map`, which is part of the Android Material icon set and is released under the Apache License 2.0.
+  It is a modified version of `map`, which is part of the Android Material icon set and is released under the Apache License 2.0 <https://fonts.google.com/icons>.
 
   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
index 3c6d4ba2a8bd391e2dcb640cef31c3480e387e74..796eb47266aa256ef4d60fe7b78cf9a6839771df 100644 (file)
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 
 <!--
-  Copyright 2020,2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2020, 2022 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/>.
 
-  It is a modified version of `question_answer`, which is part of the Android Material icon set and is released under the Apache License 2.0.
+  It is a modified version of `question_answer`, which is part of the Android Material icon set and is released under the Apache License 2.0 <https://fonts.google.com/icons>.
 
   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
diff --git a/app/src/main/assets/shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg b/app/src/main/assets/shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg
new file mode 100644 (file)
index 0000000..bb98ae3
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<!--
+  Copyright 2024 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
+
+  It is a modified version of `sort_by_alpha`, which is part of the Android Material icon set and is released under the Apache License 2.0 <https://fonts.google.com/icons>.
+
+  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
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser Android is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  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/>. -->
+
+<svg
+    xmlns="http://www.w3.org/2000/svg"
+    viewBox="0 -960 960 960"
+    id="icon">
+
+   <path
+       d="m196-376-23 70q-4 11-14 18.5t-22 7.5q-20 0-32.5-16.5T100-333l120-321q5-12 15-19t23-7h30q13 0 23 7t15 19l121 323q7 19-4.5 35T411-280q-12 0-22-7.5T375-306l-25-70H196Zm24-68h104l-48-150h-6l-50 150Zm418 92h166q15 0 25.5 10.5T840-316q0 15-10.5 25.5T804-280H572q-10 0-17-7t-7-17v-38q0-7 2-13.5t7-11.5l193-241H592q-15 0-25.5-10.5T556-644q0-15 10.5-25.5T592-680h222q10 0 17 7t7 17v38q0 7-2 13.5t-7 11.5L638-352ZM384-760q-7 0-9.5-6t2.5-11l89-89q6-6 14-6t14 6l89 89q5 5 2.5 11t-9.5 6H384Zm82 666-89-89q-5-5-2.5-11t9.5-6h192q7 0 9.5 6t-2.5 11l-89 89q-6 6-14 6t-14-6Z" />
+</svg>
index 01adbba5d7e310758fd329747cf0ba4a5c8e41af..158b8bb8b866dbc85527659927a5c1d59d42aaee 100644 (file)
@@ -1,7 +1,7 @@
 <!--
   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
         <p><svg class="icon"><use href="../shared_images/share.svg#icon"/></svg> share.</p>
         <p><svg class="icon"><use href="../shared_images/smartphone.svg#icon"/></svg> smartphone.</p>
         <p><svg class="icon"><use href="../shared_images/sort.svg#icon"/></svg> sort.</p>
+        <p><svg class="icon"><use href="../shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> sort_<wbr>by_<wbr>alpha_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/style.svg#icon"/></svg> style.</p>
         <p><svg class="icon"><use href="../shared_images/subheader_rounded_weight400_grade0_48px.svg#icon"/></svg> subheader_<wbr>rounded_<wbr>weight400_<wbr>grade0_<wbr>48px.</p>
         <p><svg class="icon"><use href="../shared_images/tab.svg#icon"/></svg> tab.</p>
index 8855c8c2dcc32b8c18c77f5161a29ea380dffbc7..858649a524f8f3cb205282d2f4617894eb1d51c3 100644 (file)
@@ -3,7 +3,7 @@
 
   Translation 2023 Xin.  Copyright assigned to 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
         <p><svg class="icon"><use href="../shared_images/share.svg#icon"/></svg> share.</p>
         <p><svg class="icon"><use href="../shared_images/smartphone.svg#icon"/></svg> smartphone.</p>
         <p><svg class="icon"><use href="../shared_images/sort.svg#icon"/></svg> sort.</p>
+        <p><svg class="icon"><use href="../shared_images/sort_by_alpha_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> sort_<wbr>by_<wbr>alpha_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/style.svg#icon"/></svg> style.</p>
         <p><svg class="icon"><use href="../shared_images/subheader_rounded_weight400_grade0_48px.svg#icon"/></svg> subheader_<wbr>rounded_<wbr>weight400_<wbr>grade0_<wbr>48px.</p>
         <p><svg class="icon"><use href="../shared_images/tab.svg#icon"/></svg> tab.</p>
index 343f8e9af0aa57b57c733b9d18ed0e1a10cfd571..794f7e5dbf41b268908631b9a0a282e8d88f1b65 100644 (file)
@@ -73,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 {
@@ -94,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
@@ -113,6 +114,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) {
@@ -234,6 +236,12 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
                 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
+                }
+
                 // Disable the delete bookmarks menu item if a delete is pending.
                 deleteBookmarksMenuItem.isEnabled = !deletingBookmarks
 
@@ -262,17 +270,26 @@ 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
+                            // Update the move menu items if the bookmarks are not sorted alphabetically.
+                            if (!sortBookmarksAlphabetically) {
+                                moveBookmarkUpMenuItem.isVisible = true
+                                moveBookmarkDownMenuItem.isVisible = true
+                            }
+
+                            // Show the edit bookmark menu item.
                             editBookmarkMenuItem.isVisible = true
 
-                            // Update the enabled status of the move icons.
-                            updateMoveIcons()
+                            // Update the enabled status of the move icons if the bookmarks are not sorted alphabetically.
+                            if (!sortBookmarksAlphabetically)
+                                updateMoveIcons()
                         } 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
                         }
 
@@ -470,7 +487,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)
@@ -482,7 +502,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)
@@ -516,18 +539,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.
@@ -747,7 +760,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)
@@ -804,7 +820,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)
@@ -859,7 +878,10 @@ 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)
@@ -911,7 +933,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)
@@ -953,8 +978,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)
@@ -1098,7 +1129,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) {
index 1e31f8f2d1c1d372e287448a31e4a480dc919913..8c44ac15ee55417b5bb972f8a21c099443a01781 100644 (file)
@@ -390,6 +390,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
     private var savedStateArrayList: ArrayList<Bundle>? = null
     private var savedTabPosition = 0
     private var scrollAppBar = false
+    private var sortBookmarksAlphabetically = false
     private var ultraPrivacy: ArrayList<List<Array<String>>>? = null
     private var waitingForProxy = false
 
@@ -2944,6 +2945,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         hideAppBar = sharedPreferences.getBoolean(getString(R.string.hide_app_bar_key), true)
         val downloadProvider = sharedPreferences.getString(getString(R.string.download_provider_key), getString(R.string.download_provider_default_value))!!
         scrollAppBar = sharedPreferences.getBoolean(getString(R.string.scroll_app_bar_key), false)
+        sortBookmarksAlphabetically = sharedPreferences.getBoolean(getString(R.string.sort_bookmarks_alphabetically_key), false)
 
         // Determine if downloading should be handled by an external app.
         downloadWithExternalApp = (downloadProvider == downloadProviderEntryValuesStringArray[2])
@@ -3073,6 +3075,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             @Suppress("DEPRECATION")
             rootFrameLayout.systemUiVisibility = 0
         }
+
+        // Load the bookmarks folder.
+        loadBookmarksFolder()
     }
 
     // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
@@ -3923,7 +3928,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         bookmarksDatabaseHelper!!.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolderId, newBookmarkDisplayOrder, favoriteIconByteArray)
 
         // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
+        bookmarksCursor = if (sortBookmarksAlphabetically)
+            bookmarksDatabaseHelper!!.getBookmarksSortedAlphabetically(currentBookmarksFolderId)
+        else
+            bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
 
         // Update the list view.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
@@ -3983,7 +3991,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         bookmarksDatabaseHelper!!.createFolder(folderNameString, currentBookmarksFolderId, displayOrder = 0, folderIconByteArray)
 
         // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
+        bookmarksCursor = if (sortBookmarksAlphabetically)
+            bookmarksDatabaseHelper!!.getBookmarksSortedAlphabetically(currentBookmarksFolderId)
+        else
+            bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
 
         // Update the list view.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
@@ -4396,9 +4407,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer))
         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks))
 
-        // Load the bookmarks folder.
-        loadBookmarksFolder()
-
         // Handle clicks on bookmarks.
         bookmarksListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
             // Convert the id from long to int to match the format of the bookmarks database.
@@ -5768,7 +5776,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
 
     private fun loadBookmarksFolder() {
         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
-        bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
+        bookmarksCursor = if (sortBookmarksAlphabetically)
+            bookmarksDatabaseHelper!!.getBookmarksSortedAlphabetically(currentBookmarksFolderId)
+        else
+            bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
 
         // Populate the bookmarks cursor adapter.
         bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) {
index 35cb1f6223eac3a6bee20c0158b753a9ef49d7f1..80c1f6f3464abc8e8a22278bf87deb5cf27b170e 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright 2016-2022 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
index 957eb10bcfd55543598d2c7c845c2355b862b2b3..7333aff1233ad4755613b42eb597665822e0f09b 100644 (file)
@@ -91,6 +91,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
     private lateinit var searchCustomURLPreference: Preference
     private lateinit var searchPreference: Preference
     private lateinit var sharedPreferenceChangeListener: OnSharedPreferenceChangeListener
+    private lateinit var sortBookmarksAlphabeticallyPreference: Preference
     private lateinit var swipeToRefreshPreference: Preference
     private lateinit var trackingQueriesPreference: Preference
     private lateinit var translatedUserAgentNamesArray: Array<String>
@@ -157,6 +158,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
         scrollAppBarPreference = findPreference(getString(R.string.scroll_app_bar_key))!!
         bottomAppBarPreference = findPreference(getString(R.string.bottom_app_bar_key))!!
         displayAdditionalAppBarIconsPreference = findPreference(getString(R.string.display_additional_app_bar_icons_key))!!
+        sortBookmarksAlphabeticallyPreference = findPreference(getString(R.string.sort_bookmarks_alphabetically_key))!!
         appThemePreference = findPreference(getString(R.string.app_theme_key))!!
         webViewThemePreference = findPreference(getString(R.string.webview_theme_key))!!
         wideViewportPreference = findPreference(getString(R.string.wide_viewport_key))!!
@@ -512,6 +514,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
         else
             displayAdditionalAppBarIconsPreference.setIcon(R.drawable.more_disabled)
 
+        // Set the sort bookmarks alphabetically icon.
+        if (sharedPreferences.getBoolean(getString(R.string.sort_bookmarks_alphabetically_key), false))
+            sortBookmarksAlphabeticallyPreference.setIcon(R.drawable.sort_by_alpha_enabled)
+        else
+            sortBookmarksAlphabeticallyPreference.setIcon(R.drawable.sort_by_alpha_disabled)
+
         // Set the WebView theme icon.
         if (webViewThemePreference.isEnabled) {  // The WebView theme preference is enabled.
             when (webViewThemeEntryNumber) {
@@ -1055,6 +1063,14 @@ class SettingsFragment : PreferenceFragmentCompat() {
                     restartPrivacyBrowser()
                 }
 
+                getString(R.string.sort_bookmarks_alphabetically_key) -> {
+                    // Update the icon.
+                    if (sharedPreferences.getBoolean(getString(R.string.sort_bookmarks_alphabetically_key), false))
+                        sortBookmarksAlphabeticallyPreference.setIcon(R.drawable.sort_by_alpha_enabled)
+                    else
+                        sortBookmarksAlphabeticallyPreference.setIcon(R.drawable.sort_by_alpha_disabled)
+                }
+
                 getString(R.string.app_theme_key) -> {
                     // Get the app theme entry number that matches the current app theme.
                     val appThemeEntryNumber: Int = when (sharedPreferences.getString(getString(R.string.app_theme_key), getString(R.string.app_theme_default_value))) {
index 1d1988408d03abfc7a0c202fb97949f86a966c14..929ce15ce1211bad05a51f00727cce522607e696 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
@@ -372,8 +372,7 @@ class BookmarksDatabaseHelper(context: Context) : SQLiteOpenHelper(context, BOOK
             idsNotToGetStringBuilder.append(databaseIdLong)
         }
 
-        // Return a cursor with all the bookmarks in the specified folder except for those database IDs specified ordered by display order.
-        // The cursor cannot be closed because it will be used in the parent activity.
+        // Return a cursor with all the bookmarks in the specified folder except for those database IDs specified ordered by display order.  The cursor cannot be closed because it will be used in the parent activity.
         return bookmarksDatabase.rawQuery("SELECT * FROM $BOOKMARKS_TABLE WHERE $PARENT_FOLDER_ID = $parentFolderId AND $ID NOT IN ($idsNotToGetStringBuilder) ORDER BY $DISPLAY_ORDER ASC", null)
     }
 
@@ -401,6 +400,51 @@ class BookmarksDatabaseHelper(context: Context) : SQLiteOpenHelper(context, BOOK
         return bookmarksDatabase.rawQuery("SELECT * FROM $BOOKMARKS_TABLE WHERE $PARENT_FOLDER_ID = $parentFolderId AND $ID NOT IN ($idsNotToGetStringBuilder)", null)
     }
 
+    fun getBookmarksSortedAlphabetically(parentFolderId: Long): Cursor {
+        // Get a readable database handle.
+        val bookmarksDatabase = this.readableDatabase
+
+        // Get the folders sorted alphabetically.
+        val foldersCursor = bookmarksDatabase.rawQuery("SELECT * FROM $BOOKMARKS_TABLE WHERE $PARENT_FOLDER_ID = $parentFolderId AND $IS_FOLDER = 1 ORDER BY $BOOKMARK_NAME ASC", null)
+
+        // Get the bookmarks sorted alphabetically.
+        val bookmarksCursor = bookmarksDatabase.rawQuery("SELECT * FROM $BOOKMARKS_TABLE WHERE $PARENT_FOLDER_ID = $parentFolderId AND $IS_FOLDER = 0 ORDER BY $BOOKMARK_NAME ASC", null)
+
+        // Return the merged cursors.
+        return MergeCursor(arrayOf(foldersCursor, bookmarksCursor))
+    }
+
+    fun getBookmarksSortedAlphabeticallyExcept(exceptIdLongArray: LongArray, parentFolderId: Long): Cursor {
+        // Get a readable database handle.
+        val bookmarksDatabase = this.readableDatabase
+
+        // Prepare a string builder to contain the comma-separated list of IDs not to get.
+        val idsNotToGetStringBuilder = StringBuilder()
+
+        // Extract the array of IDs not to get to the string builder.
+        for (databaseIdLong in exceptIdLongArray) {
+            // Check to see if there is already a number in the builder.
+            if (idsNotToGetStringBuilder.isNotEmpty()) {
+                // This is not the first number, so place a `,` before the new number.
+                idsNotToGetStringBuilder.append(",")
+            }
+
+            // Add the new number to the builder.
+            idsNotToGetStringBuilder.append(databaseIdLong)
+        }
+
+        // Get the folders sorted alphabetically except for those database IDs specified, ordered by name.
+        val foldersCursor = bookmarksDatabase.rawQuery(
+            "SELECT * FROM $BOOKMARKS_TABLE WHERE $PARENT_FOLDER_ID = $parentFolderId AND $IS_FOLDER = 1 AND $ID NOT IN ($idsNotToGetStringBuilder) ORDER BY $BOOKMARK_NAME ASC", null)
+
+        // Get the bookmarks sorted alphabetically except for those database IDs specified, ordered by name.
+        val bookmarksCursor = bookmarksDatabase.rawQuery(
+            "SELECT * FROM $BOOKMARKS_TABLE WHERE $PARENT_FOLDER_ID = $parentFolderId AND $IS_FOLDER = 0 AND $ID NOT IN ($idsNotToGetStringBuilder) ORDER BY $BOOKMARK_NAME ASC", null)
+
+        // Return the merged cursors.
+        return MergeCursor(arrayOf(foldersCursor, bookmarksCursor))
+    }
+
     fun getFolderBookmarks(parentFolderId: Long): Cursor {
         // Get a readable database handle.
         val bookmarksDatabase = this.readableDatabase
@@ -722,6 +766,37 @@ class BookmarksDatabaseHelper(context: Context) : SQLiteOpenHelper(context, BOOK
         bookmarksDatabase.close()
     }
 
+    fun recalculateFolderContentsDisplayOrder(folderId: Long) {
+        // Get a readable database.
+        val bookmarksDatabase = this.readableDatabase
+
+        // Get a cursor with the current content of the folder.
+        val folderContentsCursor = bookmarksDatabase.rawQuery("SELECT $ID, $DISPLAY_ORDER FROM $BOOKMARKS_TABLE WHERE $PARENT_FOLDER_ID = $folderId ORDER BY $DISPLAY_ORDER ASC", null)
+
+        // Get the count of the folder contents.
+        val folderContentsCount = folderContentsCursor.count
+
+        // Get the query columns.
+        val databaseIdColumnInt = folderContentsCursor.getColumnIndexOrThrow(ID)
+        val displayOrderColumnInt = folderContentsCursor.getColumnIndexOrThrow(DISPLAY_ORDER)
+
+        // Move to the first entry.
+        folderContentsCursor.moveToFirst()
+
+        // Update the display order if it isn't currently correct.
+        for (i in 0 until folderContentsCount) {
+            // Use the current value for `i` as the display order if it isn't currently so.
+            if (i != folderContentsCursor.getInt(displayOrderColumnInt))
+                updateDisplayOrder(folderContentsCursor.getInt(databaseIdColumnInt), i)
+
+            // Move to the next entry.
+            folderContentsCursor.moveToNext()
+        }
+
+        // Close the cursor.
+        folderContentsCursor.close()
+    }
+
     // Update the bookmark name and URL.
     fun updateBookmark(databaseId: Int, bookmarkName: String, bookmarkUrl: String) {
         // Initialize a content values.
index 0b040748dbfad1112dd3e29c80c51ac0c1f16516..9f1d17f96667b7795d044c54eea0b83a971cbf9a 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright 2018-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
@@ -38,9 +38,9 @@ import java.io.OutputStream
 import java.util.Date
 
 // Define the public constants.
-const val IMPORT_EXPORT_SCHEMA_VERSION = 19
-const val EXPORT_SUCCESSFUL = "A"
-const val IMPORT_SUCCESSFUL = "B"
+const val IMPORT_EXPORT_SCHEMA_VERSION = 20
+const val EXPORT_SUCCESSFUL = "export_successful"
+const val IMPORT_SUCCESSFUL = "import_successful"
 
 // Define the private class constants.
 private const val ALLOW_SCREENSHOTS = "allow_screenshots"
@@ -77,6 +77,7 @@ private const val PROXY_CUSTOM_URL = "proxy_custom_url"
 private const val SEARCH = "search"
 private const val SEARCH_CUSTOM_URL = "search_custom_url"
 private const val SCROLL_APP_BAR = "scroll_app_bar"
+private const val SORT_BOOKMARKS_ALPHABETICALLY = "sort_bookmarks_alphabetically"
 private const val PREFERENCES_SWIPE_TO_REFRESH = "swipe_to_refresh"
 private const val TRACKING_QUERIES = "tracking_queries"
 private const val ULTRAPRIVACY = "ultraprivacy"
@@ -604,6 +605,23 @@ class ImportExportDatabaseHelper {
             // This upgrade removed `enableformdata` from the Domains table and `save_form_data` and `clear_form_data` from the Preferences table.
             // There is no need to delete the columns as they will simply be ignored by the import.
 
+            // Upgrade from schema version 19, first used in Privacy Browser 3.18, to schema version 20, first used in Privacy Browser 3.19.
+            if (importDatabaseVersion < 20) {
+                // Create the new sort bookmarks alphabetically column.
+                importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $SORT_BOOKMARKS_ALPHABETICALLY BOOLEAN")
+
+                // Get the current sort bookmarks alphabetically column.
+                val sortBookmarksAlphabetically = sharedPreferences.getBoolean(SORT_BOOKMARKS_ALPHABETICALLY, false)
+
+                // Populate the preferences table with the current sort bookmarks alphabetically value.
+                // This can switch to using the variables directly once the minimum API >= 30.  <https://www.sqlite.org/datatype3.html#boolean_datatype>
+                // <https://developer.android.com/reference/android/database/sqlite/package-summary>
+                if (sortBookmarksAlphabetically)
+                    importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $SORT_BOOKMARKS_ALPHABETICALLY = 1")
+                else
+                    importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $SORT_BOOKMARKS_ALPHABETICALLY = 0")
+            }
+
 
             /* End of database upgrade logic. */
 
@@ -788,6 +806,7 @@ class ImportExportDatabaseHelper {
                 .putBoolean(SCROLL_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(SCROLL_APP_BAR)) == 1)
                 .putBoolean(BOTTOM_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(BOTTOM_APP_BAR)) == 1)
                 .putBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DISPLAY_ADDITIONAL_APP_BAR_ICONS)) == 1)
+                .putBoolean(SORT_BOOKMARKS_ALPHABETICALLY, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(SORT_BOOKMARKS_ALPHABETICALLY)) == 1)
                 .putString(APP_THEME, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(APP_THEME)))
                 .putString(WEBVIEW_THEME, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(WEBVIEW_THEME)))
                 .putBoolean(WIDE_VIEWPORT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(WIDE_VIEWPORT)) == 1)
@@ -996,6 +1015,7 @@ class ImportExportDatabaseHelper {
                     "$SCROLL_APP_BAR BOOLEAN, " +
                     "$BOTTOM_APP_BAR BOOLEAN, " +
                     "$DISPLAY_ADDITIONAL_APP_BAR_ICONS BOOLEAN, " +
+                    "$SORT_BOOKMARKS_ALPHABETICALLY BOOLEAN, " +
                     "$APP_THEME TEXT, " +
                     "$WEBVIEW_THEME TEXT, " +
                     "$WIDE_VIEWPORT BOOLEAN, " +
@@ -1047,6 +1067,7 @@ class ImportExportDatabaseHelper {
             preferencesContentValues.put(SCROLL_APP_BAR, sharedPreferences.getBoolean(SCROLL_APP_BAR, true))
             preferencesContentValues.put(BOTTOM_APP_BAR, sharedPreferences.getBoolean(BOTTOM_APP_BAR, false))
             preferencesContentValues.put(DISPLAY_ADDITIONAL_APP_BAR_ICONS, sharedPreferences.getBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, false))
+            preferencesContentValues.put(SORT_BOOKMARKS_ALPHABETICALLY, sharedPreferences.getBoolean(SORT_BOOKMARKS_ALPHABETICALLY, false))
             preferencesContentValues.put(APP_THEME, sharedPreferences.getString(APP_THEME, context.getString(R.string.app_theme_default_value)))
             preferencesContentValues.put(WEBVIEW_THEME, sharedPreferences.getString(WEBVIEW_THEME, context.getString(R.string.webview_theme_default_value)))
             preferencesContentValues.put(WIDE_VIEWPORT, sharedPreferences.getBoolean(WIDE_VIEWPORT, true))
index 8aa482ed2b65f07b65661f89b511edc64b21f6b0..bf32f21eadf2bc2a086fa2ab566a64055d35bb26 100644 (file)
@@ -1,4 +1,4 @@
-<!-- This file comes from the Android Material icon set, where it is called `more`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `more`.  It is released under the Apache License 2.0 <https://fonts.google.com/icons>. -->
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
index 0a591973795891a98763b63feec771aa4c1f37d8..362d4da7b54183eca51a66ce1c4508b1461668b2 100644 (file)
@@ -1,4 +1,4 @@
-<!-- This file comes from the Android Material icon set, where it is called `more`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `more`.  It is released under the Apache License 2.0 <https://fonts.google.com/icons>. -->
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
index 9cc4a9d730abe8c1e3518a198ee9b3e9af50ba93..c8afc9b72b0f1662d63965483132cf9ae7445a45 100644 (file)
@@ -1,4 +1,4 @@
-<!-- This file comes from the Android Material icon set, where it is called `vertical_align_bottom`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `vertical_align_bottom`.  It is released under the Apache License 2.0 <https://fonts.google.com/icons>. -->
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
index c7a80cf9273d8a5ad86b87628e83eed99d262e45..e957ae015fb7ab8dbc39613ba9cb09b2fc72e2d3 100644 (file)
@@ -1,4 +1,4 @@
-<!-- This file comes from the Android Material icon set, where it is called `vertical_align_bottom`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `vertical_align_bottom`.  It is released under the Apache License 2.0 <https://fonts.google.com/icons>. -->
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
index 704fa1536665f921f9861514493177a3fb02def8..2187503e9125ea6dc932e7b89223cf5e5372ccca 100644 (file)
@@ -1,7 +1,7 @@
 <!--
   Copyright 2017,2022 Soren Stoutner <soren@stoutner.com>.
 
-  This file is derived from elements of `folder` and `exit_to_app`, which are part of the Android Material icon set.  They are released under the Apache License 2.0.
+  This file is derived from elements of `folder` and `exit_to_app`, which are part of the Android Material icon set.  They are released under the Apache License 2.0 <https://fonts.google.com/icons>.
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
 
index 1c78ff7b495badd95ef1f175624f2434d23046c1..6aa24cc35ab8c60cdde948ad516306a918aed893 100644 (file)
@@ -1,4 +1,4 @@
-<!-- This file comes from the Android Material icon set, where it is called `vertical_align_top`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `vertical_align_top`.  It is released under the Apache License 2.0 <https://fonts.google.com/icons>. -->
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
index 2cb79084563e703f2e389050014067cd5b5ec49c..89f66c67a4a5b291616bc63c993e25820b6f1d3c 100644 (file)
@@ -1,4 +1,4 @@
-<!-- This file comes from the Android Material icon set, where it is called `vertical_align_top`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `vertical_align_top`.  It is released under the Apache License 2.0 <https://fonts.google.com/icons>. -->
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/app/src/main/res/drawable/sort_by_alpha_disabled.xml b/app/src/main/res/drawable/sort_by_alpha_disabled.xml
new file mode 100644 (file)
index 0000000..4c5a308
--- /dev/null
@@ -0,0 +1,13 @@
+<!-- This file comes from the Android Material icon set, where it is called `sort_by_alpha_rounded_fill0_weight400_grade0_24px`.  It is released under the Apache License 2.0 <https://fonts.google.com/icons>. -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="960"
+    android:viewportWidth="960" >
+      
+    <path
+        android:fillColor="@color/disabled_icon"
+        android:pathData="m196,584 l-23,70q-4,11 -14,18.5t-22,7.5q-20,0 -32.5,-16.5T100,627l120,-321q5,-12 15,-19t23,-7h30q13,0 23,7t15,19l121,323q7,19 -4.5,35T411,680q-12,0 -22,-7.5T375,654l-25,-70L196,584ZM220,516h104l-48,-150h-6l-50,150ZM638,608h166q15,0 25.5,10.5T840,644q0,15 -10.5,25.5T804,680L572,680q-10,0 -17,-7t-7,-17v-38q0,-7 2,-13.5t7,-11.5l193,-241L592,352q-15,0 -25.5,-10.5T556,316q0,-15 10.5,-25.5T592,280h222q10,0 17,7t7,17v38q0,7 -2,13.5t-7,11.5L638,608ZM384,200q-7,0 -9.5,-6t2.5,-11l89,-89q6,-6 14,-6t14,6l89,89q5,5 2.5,11t-9.5,6L384,200ZM466,866 L377,777q-5,-5 -2.5,-11t9.5,-6h192q7,0 9.5,6t-2.5,11l-89,89q-6,6 -14,6t-14,-6Z" />
+</vector>
diff --git a/app/src/main/res/drawable/sort_by_alpha_enabled.xml b/app/src/main/res/drawable/sort_by_alpha_enabled.xml
new file mode 100644 (file)
index 0000000..e041189
--- /dev/null
@@ -0,0 +1,13 @@
+<!-- This file comes from the Android Material icon set, where it is called `sort_by_alpha_rounded_fill0_weight400_grade0_24px`.  It is released under the Apache License 2.0 <https://fonts.google.com/icons>. -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="960"
+    android:viewportWidth="960" >
+      
+    <path
+        android:fillColor="@color/blue_icon"
+        android:pathData="m196,584 l-23,70q-4,11 -14,18.5t-22,7.5q-20,0 -32.5,-16.5T100,627l120,-321q5,-12 15,-19t23,-7h30q13,0 23,7t15,19l121,323q7,19 -4.5,35T411,680q-12,0 -22,-7.5T375,654l-25,-70L196,584ZM220,516h104l-48,-150h-6l-50,150ZM638,608h166q15,0 25.5,10.5T840,644q0,15 -10.5,25.5T804,680L572,680q-10,0 -17,-7t-7,-17v-38q0,-7 2,-13.5t7,-11.5l193,-241L592,352q-15,0 -25.5,-10.5T556,316q0,-15 10.5,-25.5T592,280h222q10,0 17,7t7,17v38q0,7 -2,13.5t-7,11.5L638,608ZM384,200q-7,0 -9.5,-6t2.5,-11l89,-89q6,-6 14,-6t14,6l89,89q5,5 2.5,11t-9.5,6L384,200ZM466,866 L377,777q-5,-5 -2.5,-11t9.5,-6h192q7,0 9.5,6t-2.5,11l-89,89q-6,6 -14,6t-14,-6Z" />
+</vector>
index a45b63149edfcf25ca1af43868e73bbc3808d98c..230916f600734d43a73f1e12e693e19f2ab6b1f0 100644 (file)
         <string name="display_additional_app_bar_icons">Display additional app bar icons</string>
         <string name="display_additional_app_bar_icons_summary">Display icons in the app bar for refreshing the WebView and, if there is room, for opening the bookmarks drawer and toggling cookies.
             Changing this setting will restart Privacy Browser.</string>
+        <string name="sort_bookmarks_alphabetically">Sort bookmarks alphabetically</string>
+        <string name="sort_bookmarks_alphabetically_summary">Sort the bookmarks alphabetically, with the folders displayed above the bookmarks. This disables manual ordering of bookmarks.</string>
         <string name="app_theme">App theme</string>
         <string-array name="app_theme_entries">
             <item>System default</item>
     <string name="scroll_app_bar_key" translatable="false">scroll_app_bar</string>
     <string name="search_custom_url_key" translatable="false">search_custom_url</string>
     <string name="search_key" translatable="false">search</string>
+    <string name="sort_bookmarks_alphabetically_key" translatable="false">sort_bookmarks_alphabetically</string>
     <string name="swipe_to_refresh_key" translatable="false">swipe_to_refresh</string>
     <string name="tracking_queries_key" translatable="false">tracking_queries</string>
     <string name="ultralist_key" translatable="false">ultralist</string>
index d26549635c6e90274de61ee065d0b0e1a8d20b01..1b21b9daa6df84ac8a008f040b65f5cbf8d4c6d1 100644 (file)
             app:summary="@string/display_additional_app_bar_icons_summary"
             app:defaultValue="false" />
 
+        <SwitchPreferenceCompat
+            app:key="@string/sort_bookmarks_alphabetically_key"
+            app:title="@string/sort_bookmarks_alphabetically"
+            app:summary="@string/sort_bookmarks_alphabetically_summary"
+            app:defaultValue="false" />
+
         <ListPreference
             app:key="@string/app_theme_key"
             app:title="@string/app_theme"