From e6befb69eb16e4c633623df508bfb9de370e204f Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Wed, 20 Mar 2024 11:26:35 -0700 Subject: [PATCH] Implement selecting bookmark favorite icons from the file system. https://redmine.stoutner.com/issues/41 --- app/src/main/assets/de/about_licenses.html | 54 +- app/src/main/assets/en/about_licenses.html | 44 +- app/src/main/assets/es/about_licenses.html | 86 +- app/src/main/assets/fr/about_licenses.html | 50 +- app/src/main/assets/it/about_licenses.html | 1420 ++++++++--------- .../main/assets/pt-rBR/about_licenses.html | 50 +- app/src/main/assets/ru/about_licenses.html | 50 +- .../assets/shared_images/arrow_forward.svg | 6 +- ...rk_rounded_fill0_weight400_grade0_24px.svg | 30 + .../main/assets/shared_images/bookmarks.svg | 6 +- ...er_rounded_fill0_weight400_grade0_24px.svg | 30 + app/src/main/assets/tr/about_licenses.html | 50 +- .../main/assets/zh-rCN/about_licenses.html | 53 +- .../activities/BookmarksActivity.kt | 124 +- .../BookmarksDatabaseViewActivity.kt | 91 +- .../activities/MainWebViewActivity.kt | 65 +- .../dialogs/CreateBookmarkDialog.kt | 170 +- .../dialogs/CreateBookmarkFolderDialog.kt | 110 +- .../dialogs/EditBookmarkDatabaseViewDialog.kt | 165 +- .../dialogs/EditBookmarkDialog.kt | 162 +- .../EditBookmarkFolderDatabaseViewDialog.kt | 188 ++- .../dialogs/EditBookmarkFolderDialog.kt | 132 +- .../privacybrowser/dialogs/SaveDialog.kt | 12 +- .../views/NestedScrollWebView.kt | 6 +- .../res/drawable-hdpi/folder_blue_bitmap.png | Bin 700 -> 4706 bytes .../res/drawable-mdpi/folder_blue_bitmap.png | Bin 480 -> 4511 bytes .../res/drawable-xhdpi/folder_blue_bitmap.png | Bin 895 -> 4899 bytes .../drawable-xxhdpi/folder_blue_bitmap.png | Bin 1288 -> 5247 bytes .../drawable-xxxhdpi/folder_blue_bitmap.png | Bin 1708 -> 5627 bytes app/src/main/res/drawable/bookmark.xml | 13 + app/src/main/res/drawable/bookmarks.xml | 4 +- app/src/main/res/drawable/create_bookmark.xml | 8 +- app/src/main/res/drawable/folder.xml | 13 + .../res/layout/create_bookmark_dialog.xml | 93 +- .../layout/create_bookmark_folder_dialog.xml | 56 +- .../edit_bookmark_databaseview_dialog.xml | 42 +- .../main/res/layout/edit_bookmark_dialog.xml | 46 +- ...it_bookmark_folder_databaseview_dialog.xml | 50 +- .../layout/edit_bookmark_folder_dialog.xml | 52 +- app/src/main/res/values/strings.xml | 3 + build.gradle | 2 +- 41 files changed, 2168 insertions(+), 1368 deletions(-) create mode 100644 app/src/main/assets/shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg create mode 100644 app/src/main/assets/shared_images/folder_rounded_fill0_weight400_grade0_24px.svg create mode 100644 app/src/main/res/drawable/bookmark.xml create mode 100644 app/src/main/res/drawable/folder.xml diff --git a/app/src/main/assets/de/about_licenses.html b/app/src/main/assets/de/about_licenses.html index 80a88003..ae111a14 100644 --- a/app/src/main/assets/de/about_licenses.html +++ b/app/src/main/assets/de/about_licenses.html @@ -115,6 +115,7 @@

aod_tablet_rounded_grade200.

arrow_back.

arrow_forward.

+

bookmark_rounded_fill0_weight400_grade0_24px.

bookmarks.

bug_report.

call_to_action.

@@ -134,6 +135,7 @@

file_download.

find_in_page.

folder.

+

folder_rounded_fill0_weight400_grade0_24px.

home.

image.

import_contacts.

@@ -180,9 +182,7 @@

GNU General Public License

-

Offizielle deutsche Übersetzung der GNU General Public License

- -

Version 3, 29 June 2007

+

Version 3, 29 June 2007

Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>

@@ -822,18 +822,18 @@ state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.

-
<one line to give the program’s name
+        
<one line to give the program’s name
 and a brief idea of what it does.>
 Copyright (C) <year>  <name of
 author>
 
 This program 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.
+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.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -846,7 +846,8 @@ details.
 
 You should have received a copy of
 the GNU General Public License
-along with this program.  If not, see
+along with this program.  If not,
+see
 <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

@@ -854,12 +855,12 @@ along with this program. If not, see

If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:

-
<program>  Copyright (C) <year>
+        
<program>  Copyright (C) <year>
 <name of author>
-This program comes with ABSOLUTELY NO
-WARRANTY; for details type `show w'.
-This is free software, and you are
-welcome to redistribute it under
+This program comes with ABSOLUTELY
+NO WARRANTY; for details type `show
+w'. This is free software, and you
+are welcome to redistribute it under
 certain conditions; type `show c'
 for details.
@@ -1527,19 +1528,20 @@ for details.
state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

-
<one line to give the program's name
-and a brief idea of what it does.>
+        
<one line to give the program's
+name and a brief idea of what it
+does.>
 Copyright (C) <year>  <name of
 author>
 
 This program is free software: you
 can redistribute it and/or modify
-it under the terms of the GNU Affero
-General Public License as published
-by the Free Software Foundation,
-either version 3 of the License,
-or (at your option) any later
-version.
+it under the terms of the GNU
+Affero General Public License as
+published by the Free Software
+Foundation, either version 3 of the
+License, or (at your option) any
+later version.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -1766,15 +1768,15 @@ If not, see
             on the same “printed page” as the copyright notice for easier
             identification within third-party archives.

-
Copyright [yyyy] [name of copyright
+        
Copyright [yyyy] [name of copyright
 owner]
 
 Licensed under the Apache License,
 Version 2.0 (the “License”);
 you may not use this file except
 in compliance with the License.
-You may obtain a copy of the License
-at
+You may obtain a copy of the
+License at
 
 http://www.apache.org/licenses/
 LICENSE-2.0
diff --git a/app/src/main/assets/en/about_licenses.html b/app/src/main/assets/en/about_licenses.html
index 57bdc70d..6b53802f 100644
--- a/app/src/main/assets/en/about_licenses.html
+++ b/app/src/main/assets/en/about_licenses.html
@@ -111,6 +111,7 @@
         

aod_tablet_rounded_grade200.

arrow_back.

arrow_forward.

+

bookmark_rounded_fill0_weight400_grade0_24px.

bookmarks.

bug_report.

call_to_action.

@@ -130,6 +131,7 @@

file_download.

find_in_page.

folder.

+

folder_rounded_fill0_weight400_grade0_24px.

home.

image.

import_contacts.

@@ -823,11 +825,11 @@ author> This program 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. +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. This program is distributed in the hope that it will be useful, but @@ -840,7 +842,8 @@ details. You should have received a copy of the GNU General Public License -along with this program. If not, see +along with this program. If not, +see <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

@@ -850,10 +853,10 @@ along with this program. If not, see
<program>  Copyright (C) <year>
 <name of author>
-This program comes with ABSOLUTELY NO
-WARRANTY; for details type `show w'.
-This is free software, and you are
-welcome to redistribute it under
+This program comes with ABSOLUTELY
+NO WARRANTY; for details type `show
+w'. This is free software, and you
+are welcome to redistribute it under
 certain conditions; type `show c'
 for details.
@@ -1521,19 +1524,20 @@ for details.
state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

-
<one line to give the program's name
-and a brief idea of what it does.>
+
<one line to give the program's
+name and a brief idea of what it
+does.>
 Copyright (C) <year>  <name of
 author>
 
 This program is free software: you
 can redistribute it and/or modify
-it under the terms of the GNU Affero
-General Public License as published
-by the Free Software Foundation,
-either version 3 of the License,
-or (at your option) any later
-version.
+it under the terms of the GNU
+Affero General Public License as
+published by the Free Software
+Foundation, either version 3 of the
+License, or (at your option) any
+later version.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -1767,8 +1771,8 @@ Licensed under the Apache License,
 Version 2.0 (the “License”);
 you may not use this file except
 in compliance with the License.
-You may obtain a copy of the License
-at
+You may obtain a copy of the
+License at
 
 http://www.apache.org/licenses/
 LICENSE-2.0
diff --git a/app/src/main/assets/es/about_licenses.html b/app/src/main/assets/es/about_licenses.html
index 8e6a6803..b2a3c2ec 100644
--- a/app/src/main/assets/es/about_licenses.html
+++ b/app/src/main/assets/es/about_licenses.html
@@ -115,6 +115,7 @@
         

aod_tablet_rounded_grade200.

arrow_back.

arrow_forward.

+

bookmark_rounded_fill0_weight400_grade0_24px.

bookmarks.

bug_report.

call_to_action.

@@ -134,6 +135,7 @@

file_download.

find_in_page.

folder.

+

folder_rounded_fill0_weight400_grade0_24px.

home.

image.

import_contacts.

@@ -196,7 +198,7 @@

The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to - share and change all versions of a program--to make sure it remains free + share and change all versions of a program—to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to @@ -224,16 +226,16 @@ (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.

-

For the developers' and authors' protection, the GPL clearly explains - that there is no warranty for this free software. For both users' and - authors' sake, the GPL requires that modified versions be marked as +

For the developers’ and authors’ protection, the GPL clearly explains + that there is no warranty for this free software. For both users’ and + authors’ sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.

Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of - protecting users' freedom to change the software. The systematic + protecting users’ freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those @@ -317,7 +319,7 @@

The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to - control those activities. However, it does not include the work's + control those activities. However, it does not include the work’s System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source @@ -359,7 +361,7 @@ the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.

-

3. Protecting Users' Legal Rights From Anti-Circumvention Law.

+

3. Protecting Users’ Legal Rights From Anti-Circumvention Law.

No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article @@ -371,13 +373,13 @@ circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or - modification of the work as a means of enforcing, against the work's - users, your or third parties' legal rights to forbid circumvention of + modification of the work as a means of enforcing, against the work’s + users, your or third parties’ legal rights to forbid circumvention of technological measures.

4. Conveying Verbatim Copies.

-

You may convey verbatim copies of the Program's source code as you +

You may convey verbatim copies of the Program’s source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any @@ -422,7 +424,7 @@ and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not - used to limit the access or legal rights of the compilation's users + used to limit the access or legal rights of the compilation’s users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.

@@ -644,7 +646,7 @@ organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever - licenses to the work the party's predecessor in interest had or could + licenses to the work the party’s predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.

@@ -661,9 +663,9 @@

A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The - work thus licensed is called the contributor's “contributor version”.

+ work thus licensed is called the contributor’s “contributor version”.

-

A contributor's “essential patent claims” are all patent claims +

A contributor’s “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, @@ -674,7 +676,7 @@ this License.

Each contributor grants you a non-exclusive, worldwide, royalty-free - patent license under the contributor's essential patent claims, to + patent license under the contributor’s essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.

@@ -695,7 +697,7 @@ consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the - covered work in a country, or your recipient's use of the covered work + covered work in a country, or your recipient’s use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.

@@ -726,7 +728,7 @@ any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.

-

12. No Surrender of Others' Freedom.

+

12. No Surrender of Others’ Freedom.

If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not @@ -766,7 +768,7 @@ by the Free Software Foundation.

If the Program specifies that a proxy can decide which future - versions of the GNU General Public License can be used, that proxy's + versions of the GNU General Public License can be used, that proxy’s public statement of acceptance of a version permanently authorizes you to choose that version for the Program.

@@ -820,18 +822,18 @@ state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.

-
<one line to give the program’s name
+        
<one line to give the program’s name
 and a brief idea of what it does.>
 Copyright (C) <year>  <name of
 author>
 
 This program 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.
+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.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -844,7 +846,8 @@ details.
 
 You should have received a copy of
 the GNU General Public License
-along with this program.  If not, see
+along with this program.  If not,
+see
 <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

@@ -854,15 +857,15 @@ along with this program. If not, see
<program>  Copyright (C) <year>
 <name of author>
-This program comes with ABSOLUTELY NO
-WARRANTY; for details type `show w'.
-This is free software, and you are
-welcome to redistribute it under
+This program comes with ABSOLUTELY
+NO WARRANTY; for details type `show
+w'. This is free software, and you
+are welcome to redistribute it under
 certain conditions; type `show c'
 for details.

The hypothetical commands `show w' and `show c' should show the appropriate - parts of the General Public License. Of course, your program's commands + parts of the General Public License. Of course, your program’s commands might be different; for a GUI interface, you would use an “about box”.

You should also get your employer (if you work as a programmer) or school, @@ -1525,19 +1528,20 @@ for details.

state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

-
<one line to give the program's name
-and a brief idea of what it does.>
+        
<one line to give the program's
+name and a brief idea of what it
+does.>
 Copyright (C) <year>  <name of
 author>
 
 This program is free software: you
 can redistribute it and/or modify
-it under the terms of the GNU Affero
-General Public License as published
-by the Free Software Foundation,
-either version 3 of the License,
-or (at your option) any later
-version.
+it under the terms of the GNU
+Affero General Public License as
+published by the Free Software
+Foundation, either version 3 of the
+License, or (at your option) any
+later version.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -1764,15 +1768,15 @@ If not, see
             on the same “printed page” as the copyright notice for easier
             identification within third-party archives.

-
Copyright [yyyy] [name of copyright
+        
Copyright [yyyy] [name of copyright
 owner]
 
 Licensed under the Apache License,
 Version 2.0 (the “License”);
 you may not use this file except
 in compliance with the License.
-You may obtain a copy of the License
-at
+You may obtain a copy of the
+License at
 
 http://www.apache.org/licenses/
 LICENSE-2.0
diff --git a/app/src/main/assets/fr/about_licenses.html b/app/src/main/assets/fr/about_licenses.html
index 1e80e640..3877b686 100644
--- a/app/src/main/assets/fr/about_licenses.html
+++ b/app/src/main/assets/fr/about_licenses.html
@@ -117,6 +117,7 @@
         

aod_tablet_rounded_grade200.

arrow_back.

arrow_forward.

+

bookmark_rounded_fill0_weight400_grade0_24px.

bookmarks.

bug_report.

call_to_action.

@@ -136,6 +137,7 @@

file_download.

find_in_page.

folder.

+

folder_rounded_fill0_weight400_grade0_24px.

home.

image.

import_contacts.

@@ -822,18 +824,18 @@ state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.

-
<one line to give the program’s name
+        
<one line to give the program’s name
 and a brief idea of what it does.>
 Copyright (C) <year>  <name of
 author>
 
 This program 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.
+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.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -846,7 +848,8 @@ details.
 
 You should have received a copy of
 the GNU General Public License
-along with this program.  If not, see
+along with this program.  If not,
+see
 <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

@@ -854,12 +857,12 @@ along with this program. If not, see

If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:

-
<program>  Copyright (C) <year>
+        
<program>  Copyright (C) <year>
 <name of author>
-This program comes with ABSOLUTELY NO
-WARRANTY; for details type `show w'.
-This is free software, and you are
-welcome to redistribute it under
+This program comes with ABSOLUTELY
+NO WARRANTY; for details type `show
+w'. This is free software, and you
+are welcome to redistribute it under
 certain conditions; type `show c'
 for details.
@@ -1527,19 +1530,20 @@ for details.
state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

-
<one line to give the program's name
-and a brief idea of what it does.>
+        
<one line to give the program's
+name and a brief idea of what it
+does.>
 Copyright (C) <year>  <name of
 author>
 
 This program is free software: you
 can redistribute it and/or modify
-it under the terms of the GNU Affero
-General Public License as published
-by the Free Software Foundation,
-either version 3 of the License,
-or (at your option) any later
-version.
+it under the terms of the GNU
+Affero General Public License as
+published by the Free Software
+Foundation, either version 3 of the
+License, or (at your option) any
+later version.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -1766,15 +1770,15 @@ If not, see
             on the same “printed page” as the copyright notice for easier
             identification within third-party archives.

-
Copyright [yyyy] [name of copyright
+        
Copyright [yyyy] [name of copyright
 owner]
 
 Licensed under the Apache License,
 Version 2.0 (the “License”);
 you may not use this file except
 in compliance with the License.
-You may obtain a copy of the License
-at
+You may obtain a copy of the
+License at
 
 http://www.apache.org/licenses/
 LICENSE-2.0
diff --git a/app/src/main/assets/it/about_licenses.html b/app/src/main/assets/it/about_licenses.html
index 5ffebc36..159bf75d 100644
--- a/app/src/main/assets/it/about_licenses.html
+++ b/app/src/main/assets/it/about_licenses.html
@@ -1,7 +1,7 @@
 
+
+
+
+    
+
diff --git a/app/src/main/assets/shared_images/bookmarks.svg b/app/src/main/assets/shared_images/bookmarks.svg
index c1f21298..0b636ad7 100644
--- a/app/src/main/assets/shared_images/bookmarks.svg
+++ b/app/src/main/assets/shared_images/bookmarks.svg
@@ -1,11 +1,11 @@
 
 
 
+
+
+
+    
+
diff --git a/app/src/main/assets/tr/about_licenses.html b/app/src/main/assets/tr/about_licenses.html
index 43a4a065..01adbba5 100644
--- a/app/src/main/assets/tr/about_licenses.html
+++ b/app/src/main/assets/tr/about_licenses.html
@@ -112,6 +112,7 @@
         

aod_tablet_rounded_grade200.

arrow_back.

arrow_forward.

+

bookmark_rounded_fill0_weight400_grade0_24px.

bookmarks.

bug_report.

call_to_action.

@@ -131,6 +132,7 @@

file_download.

find_in_page.

folder.

+

folder_rounded_fill0_weight400_grade0_24px.

home.

image.

import_contacts.

@@ -817,18 +819,18 @@ state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.

-
<one line to give the program’s name
+        
<one line to give the program’s name
 and a brief idea of what it does.>
 Copyright (C) <year>  <name of
 author>
 
 This program 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.
+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.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -841,7 +843,8 @@ details.
 
 You should have received a copy of
 the GNU General Public License
-along with this program.  If not, see
+along with this program.  If not,
+see
 <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

@@ -849,12 +852,12 @@ along with this program. If not, see

If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:

-
<program>  Copyright (C) <year>
+        
<program>  Copyright (C) <year>
 <name of author>
-This program comes with ABSOLUTELY NO
-WARRANTY; for details type `show w'.
-This is free software, and you are
-welcome to redistribute it under
+This program comes with ABSOLUTELY
+NO WARRANTY; for details type `show
+w'. This is free software, and you
+are welcome to redistribute it under
 certain conditions; type `show c'
 for details.
@@ -1522,19 +1525,20 @@ for details.
state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

-
<one line to give the program's name
-and a brief idea of what it does.>
+        
<one line to give the program's
+name and a brief idea of what it
+does.>
 Copyright (C) <year>  <name of
 author>
 
 This program is free software: you
 can redistribute it and/or modify
-it under the terms of the GNU Affero
-General Public License as published
-by the Free Software Foundation,
-either version 3 of the License,
-or (at your option) any later
-version.
+it under the terms of the GNU
+Affero General Public License as
+published by the Free Software
+Foundation, either version 3 of the
+License, or (at your option) any
+later version.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -1761,15 +1765,15 @@ If not, see
             on the same “printed page” as the copyright notice for easier
             identification within third-party archives.

-
Copyright [yyyy] [name of copyright
+        
Copyright [yyyy] [name of copyright
 owner]
 
 Licensed under the Apache License,
 Version 2.0 (the “License”);
 you may not use this file except
 in compliance with the License.
-You may obtain a copy of the License
-at
+You may obtain a copy of the
+License at
 
 http://www.apache.org/licenses/
 LICENSE-2.0
diff --git a/app/src/main/assets/zh-rCN/about_licenses.html b/app/src/main/assets/zh-rCN/about_licenses.html
index d5f4a6b2..8855c8c2 100644
--- a/app/src/main/assets/zh-rCN/about_licenses.html
+++ b/app/src/main/assets/zh-rCN/about_licenses.html
@@ -112,6 +112,7 @@
         

aod_tablet_rounded_grade200.

arrow_back.

arrow_forward.

+

bookmark_rounded_fill0_weight400_grade0_24px.

bookmarks.

bug_report.

call_to_action.

@@ -131,6 +132,7 @@

file_download.

find_in_page.

folder.

+

folder_rounded_fill0_weight400_grade0_24px.

home.

image.

import_contacts.

@@ -817,18 +819,19 @@ state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.

-
<one line to give the program’s name
-and a brief idea of what it does.>
+
<one line to give the program’s
+name and a brief idea of what it
+does.>
 Copyright (C) <year>  <name of
 author>
 
 This program 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.
+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.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -841,7 +844,8 @@ details.
 
 You should have received a copy of
 the GNU General Public License
-along with this program.  If not, see
+along with this program.  If not,
+see
 <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

@@ -849,12 +853,12 @@ along with this program. If not, see

If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:

-
<program>  Copyright (C) <year>
+        
<program>  Copyright (C) <year>
 <name of author>
-This program comes with ABSOLUTELY NO
-WARRANTY; for details type `show w'.
-This is free software, and you are
-welcome to redistribute it under
+This program comes with ABSOLUTELY
+NO WARRANTY; for details type `show
+w'. This is free software, and you
+are welcome to redistribute it under
 certain conditions; type `show c'
 for details.
@@ -1522,19 +1526,20 @@ for details.
state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

-
<one line to give the program's name
-and a brief idea of what it does.>
+        
<one line to give the program's
+name and a brief idea of what it
+does.>
 Copyright (C) <year>  <name of
 author>
 
 This program is free software: you
 can redistribute it and/or modify
-it under the terms of the GNU Affero
-General Public License as published
-by the Free Software Foundation,
-either version 3 of the License,
-or (at your option) any later
-version.
+it under the terms of the GNU
+Affero General Public License as
+published by the Free Software
+Foundation, either version 3 of the
+License, or (at your option) any
+later version.
 
 This program is distributed in the
 hope that it will be useful, but
@@ -1761,15 +1766,15 @@ If not, see
             on the same “printed page” as the copyright notice for easier
             identification within third-party archives.

-
Copyright [yyyy] [name of copyright
+        
Copyright [yyyy] [name of copyright
 owner]
 
 Licensed under the Apache License,
 Version 2.0 (the “License”);
 you may not use this file except
 in compliance with the License.
-You may obtain a copy of the License
-at
+You may obtain a copy of the
+License at
 
 http://www.apache.org/licenses/
 LICENSE-2.0
diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.kt
index 737d6393..fe7d0808 100644
--- a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.kt
+++ b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2023 Soren Stoutner .
+ * Copyright 2016-2024 Soren Stoutner .
  *
  * This file is part of Privacy Browser Android .
  *
@@ -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
@@ -706,17 +707,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(R.id.create_bookmark_name_edittext)
-        val createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext)
+        // Get handles for the views from the dialog fragment.
+        val webpageFavoriteIconRadioButton = dialog.findViewById(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById(R.id.custom_icon_imageview)
+        val bookmarkNameEditText = dialog.findViewById(R.id.bookmark_name_edittext)
+        val bookmarkUrlEditText = dialog.findViewById(R.id.bookmark_url_edittext)
+
+        // Get the strings from the edit texts.
+        val bookmarkNameString = bookmarkNameEditText.text.toString()
+        val bookmarkUrlString = bookmarkUrlEditText.text.toString()
 
-        // Extract the strings from the edit texts.
-        val bookmarkNameString = createBookmarkNameEditText.text.toString()
-        val bookmarkUrlString = createBookmarkUrlEditText.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
+
+        // 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()
@@ -743,32 +756,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(R.id.default_folder_icon_radiobutton)
+        val defaultFolderIconImageView = dialog.findViewById(R.id.default_folder_icon_imageview)
+        val webpageFavoriteIconRadioButton = dialog.findViewById(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById(R.id.custom_icon_imageview)
         val folderNameEditText = dialog.findViewById(R.id.folder_name_edittext)
-        val defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton)
-        val defaultIconImageView = dialog.findViewById(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()
@@ -798,16 +813,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(R.id.current_icon_radiobutton)
+        val webpageFavoriteIconRadioButton = dialog.findViewById(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById(R.id.custom_icon_imageview)
         val bookmarkNameEditText = dialog.findViewById(R.id.bookmark_name_edittext)
         val bookmarkUrlEditText = dialog.findViewById(R.id.bookmark_url_edittext)
-        val currentIconRadioButton = dialog.findViewById(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 +833,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()
 
@@ -838,40 +865,39 @@ class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookma
         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(R.id.current_icon_radiobutton)
-        val defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton)
-        val defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview)
-        val editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext)
+        val defaultFolderIconRadioButton = dialog.findViewById(R.id.default_folder_icon_radiobutton)
+        val defaultFolderIconImageView = dialog.findViewById(R.id.default_folder_icon_imageview)
+        val webpageFavoriteIconRadioButton = dialog.findViewById(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById(R.id.custom_icon_imageview)
+        val folderNameEditText = dialog.findViewById(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).
diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.kt
index 227c4149..b9f161e8 100644
--- a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.kt
+++ b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2023 Soren Stoutner .
+ * Copyright 2016-2024 Soren Stoutner .
  *
  * This file is part of Privacy Browser Android .
  *
@@ -26,7 +26,6 @@ import android.database.MergeCursor
 import android.graphics.Bitmap
 import android.graphics.BitmapFactory
 import android.graphics.Typeface
-import android.graphics.drawable.BitmapDrawable
 import android.os.Bundle
 import android.view.ActionMode
 import android.view.Menu
@@ -49,6 +48,7 @@ import androidx.appcompat.app.ActionBar
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.appcompat.widget.Toolbar
+import androidx.core.graphics.drawable.toBitmap
 import androidx.cursoradapter.widget.CursorAdapter
 import androidx.cursoradapter.widget.ResourceCursorAdapter
 import androidx.fragment.app.DialogFragment
@@ -73,8 +73,6 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
 
 import java.io.ByteArrayOutputStream
 
-import java.util.Arrays
-
 // Define the public class constants.
 const val HOME_FOLDER_DATABASE_ID = -1
 const val HOME_FOLDER_ID = 0L
@@ -176,15 +174,6 @@ class BookmarksDatabaseViewActivity : AppCompatActivity(), EditBookmarkDatabaseV
             // Combine the matrix cursor and the folders cursor.
             val foldersMergeCursor = MergeCursor(arrayOf(matrixCursor, foldersCursor))
 
-            // Get the default folder bitmap.
-            val defaultFolderDrawable = AppCompatResources.getDrawable(this, R.drawable.folder_blue_bitmap)
-
-            // Cast the default folder drawable to a bitmap drawable.
-            val defaultFolderBitmapDrawable = (defaultFolderDrawable as BitmapDrawable)
-
-            // Convert the default folder bitmap drawable to a bitmap.
-            val defaultFolderBitmap = defaultFolderBitmapDrawable.bitmap
-
             // Create a resource cursor adapter for the spinner.
             val foldersCursorAdapter: ResourceCursorAdapter = object : ResourceCursorAdapter(this, R.layout.bookmarks_databaseview_appbar_spinner_item, foldersMergeCursor, 0) {
                 override fun bindView(view: View, context: Context, cursor: Cursor) {
@@ -207,29 +196,14 @@ class BookmarksDatabaseViewActivity : AppCompatActivity(), EditBookmarkDatabaseV
 
                     // Set the folder icon according to the type.
                     if (foldersMergeCursor.position > 1) {  // Set a user folder icon.
-                        // Initialize a default folder icon byte array output stream.
-                        val defaultFolderIconByteArrayOutputStream = ByteArrayOutputStream()
-
-                        // Covert the default folder bitmap to a PNG and store it in the output stream.  `0` is for lossless compression (the only option for a PNG).
-                        defaultFolderBitmap.compress(Bitmap.CompressFormat.PNG, 0, defaultFolderIconByteArrayOutputStream)
-
-                        // Convert the default folder icon output stream to a byte array.
-                        val defaultFolderIconByteArray = defaultFolderIconByteArrayOutputStream.toByteArray()
-
                         // Get the folder icon byte array from the cursor.
                         val folderIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(FAVORITE_ICON))
 
                         // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
                         val folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.size)
 
-                        // Set the icon according to the type.
-                        if (Arrays.equals(folderIconByteArray, defaultFolderIconByteArray)) {  // The default folder icon is used.
-                            // Set a smaller and darker folder icon, which works well with the spinner.
-                            folderIconImageView.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.folder_dark_blue))
-                        } else {  // A custom folder icon is uses.
-                            // Set the folder image stored in the cursor.
-                            folderIconImageView.setImageBitmap(folderIconBitmap)
-                        }
+                        // Set the folder image stored in the cursor.
+                        folderIconImageView.setImageBitmap(folderIconBitmap)
                     } else {  // Set the `All Folders` or `Home Folder` icon.
                         // Set the gray folder image.
                         folderIconImageView.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.folder_gray))
@@ -668,12 +642,15 @@ class BookmarksDatabaseViewActivity : AppCompatActivity(), EditBookmarkDatabaseV
         savedInstanceState.putBoolean(SORT_BY_DISPLAY_ORDER, sortByDisplayOrder)
     }
 
-    override fun saveBookmark(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 dialog fragment.
         val currentIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton)
+        val webpageFavoriteIconRadioButton = dialog.findViewById(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById(R.id.custom_icon_imageview)
         val bookmarkNameEditText = dialog.findViewById(R.id.bookmark_name_edittext)
         val bookmarkUrlEditText = dialog.findViewById(R.id.bookmark_url_edittext)
         val folderSpinner = dialog.findViewById(R.id.bookmark_folder_spinner)
@@ -694,7 +671,16 @@ class BookmarksDatabaseViewActivity : AppCompatActivity(), EditBookmarkDatabaseV
         // Update the bookmark.
         if (currentIconRadioButton.isChecked) {  // Update the bookmark without changing the favorite icon.
             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, parentFolderId, displayOrderInt)
-        } else {  // Update the bookmark using the `WebView` favorite icon.
+        } 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 favorite 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()
 
@@ -712,47 +698,46 @@ class BookmarksDatabaseViewActivity : AppCompatActivity(), EditBookmarkDatabaseV
         updateBookmarksListView()
     }
 
-    override fun saveBookmarkFolder(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 dialog fragment.
         val currentIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton)
-        val defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton)
-        val defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview)
+        val defaultFolderIconRadioButton = dialog.findViewById(R.id.default_folder_icon_radiobutton)
+        val defaultFolderIconImageView = dialog.findViewById(R.id.default_folder_icon_imageview)
+        val webpageFavoriteIconRadioButton = dialog.findViewById(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById(R.id.custom_icon_imageview)
         val folderNameEditText = dialog.findViewById(R.id.folder_name_edittext)
         val parentFolderSpinner = dialog.findViewById(R.id.parent_folder_spinner)
         val displayOrderEditText = dialog.findViewById(R.id.display_order_edittext)
 
-        // Extract the folder information.
+        // Get the folder information.
         val newFolderNameString = folderNameEditText.text.toString()
         val parentFolderDatabaseId = parentFolderSpinner.selectedItemId.toInt()
         val displayOrderInt = displayOrderEditText.text.toString().toInt()
 
         // Set the parent folder ID.
-        val parentFolderId: Long = if (parentFolderDatabaseId == HOME_FOLDER_DATABASE_ID)  // The home folder is selected.
+        val parentFolderIdLong: Long = if (parentFolderDatabaseId == HOME_FOLDER_DATABASE_ID)  // The home folder is selected.
             HOME_FOLDER_ID
         else  // Get the parent folder name from the database.
             bookmarksDatabaseHelper.getFolderId(parentFolderDatabaseId)
 
         // Update the folder.
         if (currentIconRadioButton.isChecked) {  // Update the folder without changing the favorite icon.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderNameString, parentFolderId, displayOrderInt)
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderNameString, parentFolderIdLong, displayOrderInt)
         } else {  // Update the folder and the icon.
-            // Get the new folder icon bitmap.
-            val folderIconBitmap = if (defaultIconRadioButton.isChecked) {
-                // Get the default folder icon drawable.
-                val folderIconDrawable = defaultIconImageView.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.
-                // Get a copy of the favorite icon bitmap.
-                favoriteIconBitmap
-            }
+            // 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 folder icon byte array output stream.
             val newFolderIconByteArrayOutputStream = ByteArrayOutputStream()
@@ -764,7 +749,7 @@ class BookmarksDatabaseViewActivity : AppCompatActivity(), EditBookmarkDatabaseV
             val newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray()
 
             //  Update the folder and the icon.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderNameString, parentFolderId, displayOrderInt, newFolderIconByteArray)
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderNameString, parentFolderIdLong, displayOrderInt, newFolderIconByteArray)
         }
 
         // Update the list view.
diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
index 5036e86a..b56a02f0 100644
--- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
+++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
@@ -3130,7 +3130,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     nestedScrollWebView.previousWebpageTitle = tabTitleTextView.text.toString()
 
                     // Set the default favorite icon as the favorite icon for this tab.
-                    tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteIcon(), 64, 64, true))
+                    tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteIcon(), 128, 128, true))
 
                     // Set the loading title text.
                     tabTitleTextView.setText(R.string.loading)
@@ -3885,17 +3885,32 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         }
     }
 
-    override fun createBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
+    override fun createBookmark(dialogFragment: DialogFragment) {
         // Get the dialog.
         val dialog = dialogFragment.dialog!!
 
         // Get the views from the dialog fragment.
-        val createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext)
-        val createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext)
+        val webpageFavoriteIconRadioButton = dialog.findViewById(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById(R.id.custom_icon_imageview)
+        val bookmarkNameEditText = dialog.findViewById(R.id.bookmark_name_edittext)
+        val bookmarkUrlEditText = dialog.findViewById(R.id.bookmark_url_edittext)
 
         // Extract the strings from the edit texts.
-        val bookmarkNameString = createBookmarkNameEditText.text.toString()
-        val bookmarkUrlString = createBookmarkUrlEditText.text.toString()
+        val bookmarkNameString = bookmarkNameEditText.text.toString()
+        val bookmarkUrlString = bookmarkUrlEditText.text.toString()
+
+        // Get the selected favorite icon drawable.
+        val favoriteIconDrawable = if (webpageFavoriteIconRadioButton.isChecked)  // Use the webpage favorite icon.
+            webpageFavoriteIconImageView.drawable
+        else  // Use the custom icon.
+            customIconImageView.drawable
+
+        // Cast the favorite icon bitmap to a bitmap drawable
+        val favoriteIconBitmapDrawable = favoriteIconDrawable as BitmapDrawable
+
+        // Convert the favorite icon bitmap drawable to a bitmap.
+        val favoriteIconBitmap = favoriteIconBitmapDrawable.bitmap
 
         // Create a favorite icon byte array output stream.
         val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
@@ -3922,32 +3937,34 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         bookmarksListView.setSelection(newBookmarkDisplayOrder)
     }
 
-    override fun createBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
+    override fun createBookmarkFolder(dialogFragment: DialogFragment) {
         // Get the dialog.
         val dialog = dialogFragment.dialog!!
 
         // Get handles for the views in the dialog fragment.
+        val defaultFolderIconRadioButton = dialog.findViewById(R.id.default_folder_icon_radiobutton)
+        val defaultFolderIconImageView = dialog.findViewById(R.id.default_folder_icon_imageview)
+        val webpageFavoriteIconRadioButton = dialog.findViewById(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById(R.id.custom_icon_imageview)
         val folderNameEditText = dialog.findViewById(R.id.folder_name_edittext)
-        val defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton)
-        val defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview)
 
         // Get new folder name string.
         val folderNameString = folderNameEditText.text.toString()
 
         // Set the folder icon bitmap according to the dialog.
-        val folderIconBitmap: Bitmap = if (defaultIconRadioButton.isChecked) {  // Use the default folder icon.
-            // Get the default folder icon drawable.
-            val folderIconDrawable = defaultIconImageView.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
-        }
+        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
+
+        // Cast the folder icon bitmap to a bitmap drawable.
+        val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
+
+        // Convert the folder icon bitmap drawable to a bitmap.
+        val folderIconBitmap = folderIconBitmapDrawable.bitmap
 
         // Create a folder icon byte array output stream.
         val folderIconByteArrayOutputStream = ByteArrayOutputStream()
@@ -4907,7 +4924,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                             val tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview)
 
                             // Display the favorite icon in the tab.
-                            tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true))
+                            tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 128, 128, true))
                         }
                     }
                 }
@@ -5906,7 +5923,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             currentWebView!!.setFavoriteIcon(previousFavoriteIcon)
 
         // Display the previous favorite icon in the tab.
-        tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(currentWebView!!.getFavoriteIcon(), 64, 64, true))
+        tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(currentWebView!!.getFavoriteIcon(), 128, 128, true))
 
         // Load the history entry.
         currentWebView!!.goBackOrForward(steps)
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt
index 145c895b..60d199ca 100644
--- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt
+++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2023 Soren Stoutner .
+ * Copyright 2016-2024 Soren Stoutner .
  *
  * This file is part of Privacy Browser Android .
  *
@@ -24,26 +24,35 @@ import android.content.Context
 import android.content.DialogInterface
 import android.graphics.Bitmap
 import android.graphics.BitmapFactory
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
+import android.net.Uri
 import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
 import android.view.KeyEvent
 import android.view.View
 import android.view.WindowManager
+import android.widget.Button
 import android.widget.EditText
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.RadioButton
 
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.content.res.AppCompatResources
 import androidx.fragment.app.DialogFragment
 import androidx.preference.PreferenceManager
 
+import com.google.android.material.snackbar.Snackbar
+
 import com.stoutner.privacybrowser.R
 
 import java.io.ByteArrayOutputStream
 
 // Define the class constants.
-private const val URL_STRING = "url_string"
-private const val TITLE = "title"
-private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
+private const val URL_STRING = "A"
+private const val TITLE = "B"
+private const val FAVORITE_ICON_BYTE_ARRAY = "C"
 
 class CreateBookmarkDialog : DialogFragment() {
     companion object {
@@ -76,12 +85,53 @@ class CreateBookmarkDialog : DialogFragment() {
         }
     }
 
+    private val browseActivityResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { imageUri: Uri? ->
+        // Only do something if the user didn't press back from the file picker.
+        if (imageUri != null) {
+            // Get a handle for the content resolver.
+            val contentResolver = requireContext().contentResolver
+
+            // Get the image MIME type.
+            val mimeType = contentResolver.getType(imageUri)
+
+            // Decode the image according to the type.
+            if (mimeType == "image/svg+xml") {  // The image is an SVG.
+                // Display a snackbar.
+                Snackbar.make(bookmarkNameEditText, getString(R.string.cannot_use_svg), Snackbar.LENGTH_LONG).show()
+            } else {  // The image is not an SVG.
+                // Get an input stream for the image URI.
+                val inputStream = contentResolver.openInputStream(imageUri)
+
+                // Get the bitmap from the URI.
+                // `ImageDecoder.decodeBitmap` can't be used, because when running `Drawable.toBitmap` later the `Software rendering doesn't support hardware bitmaps` error message might be produced.
+                var imageBitmap = BitmapFactory.decodeStream(inputStream)
+
+                // Scale the image down if it is greater than 128 pixels in either direction.
+                if ((imageBitmap != null) && ((imageBitmap.height > 128) || (imageBitmap.width > 128)))
+                    imageBitmap = Bitmap.createScaledBitmap(imageBitmap, 128, 128, true)
+
+                // Display the new custom favorite icon.
+                customIconImageView.setImageBitmap(imageBitmap)
+
+                // Select the custom icon radio button.
+                customIconLinearLayout.performClick()
+            }
+        }
+    }
+
+    // Declare the class views.
+    private lateinit var bookmarkNameEditText: EditText
+    private lateinit var bookmarkUrlEditText: EditText
+    private lateinit var createButton: Button
+    private lateinit var customIconImageView: ImageView
+    private lateinit var customIconLinearLayout: LinearLayout
+
     // Declare the class variables
     private lateinit var createBookmarkListener: CreateBookmarkListener
 
     // The public interface is used to send information back to the parent activity.
     interface CreateBookmarkListener {
-        fun createBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap)
+        fun createBookmark(dialogFragment: DialogFragment)
     }
 
     override fun onAttach(context: Context) {
@@ -110,11 +160,8 @@ class CreateBookmarkDialog : DialogFragment() {
         // Set the title.
         dialogBuilder.setTitle(R.string.create_bookmark)
 
-        // Create a drawable version of the favorite icon.
-        val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap)
-
         // Set the icon.
-        dialogBuilder.setIcon(favoriteIconDrawable)
+        dialogBuilder.setIcon(R.drawable.bookmark)
 
         // Set the view.
         dialogBuilder.setView(R.layout.create_bookmark_dialog)
@@ -125,7 +172,7 @@ class CreateBookmarkDialog : DialogFragment() {
         // Set a listener on the create button.
         dialogBuilder.setPositiveButton(R.string.create) { _: DialogInterface, _: Int ->
             // Return the dialog fragment and the favorite icon bitmap to the parent activity.
-            createBookmarkListener.createBookmark(this, favoriteIconBitmap)
+            createBookmarkListener.createBookmark(this)
         }
 
         // Create an alert dialog from the builder.
@@ -147,19 +194,88 @@ class CreateBookmarkDialog : DialogFragment() {
         alertDialog.show()
 
         // Get a handle for the edit texts.
-        val createBookmarkNameEditText = alertDialog.findViewById(R.id.create_bookmark_name_edittext)!!
-        val createBookmarkUrlEditText = alertDialog.findViewById(R.id.create_bookmark_url_edittext)!!
+        val webpageFavoriteIconLinearLayout = alertDialog.findViewById(R.id.webpage_favorite_icon_linearlayout)!!
+        val webpageFavoriteIconRadioButton = alertDialog.findViewById(R.id.webpage_favorite_icon_radiobutton)!!
+        val webpageFavoriteIconImageView = alertDialog.findViewById(R.id.webpage_favorite_icon_imageview)!!
+        customIconLinearLayout = alertDialog.findViewById(R.id.custom_icon_linearlayout)!!
+        val customIconRadioButton = alertDialog.findViewById(R.id.custom_icon_radiobutton)!!
+        customIconImageView = alertDialog.findViewById(R.id.custom_icon_imageview)!!
+        val browseButton = alertDialog.findViewById