]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Implement selecting bookmark favorite icons from the file system. https://redmine...
authorSoren Stoutner <soren@stoutner.com>
Wed, 20 Mar 2024 18:26:35 +0000 (11:26 -0700)
committerSoren Stoutner <soren@stoutner.com>
Wed, 20 Mar 2024 18:26:35 +0000 (11:26 -0700)
41 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/arrow_forward.svg
app/src/main/assets/shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg [new file with mode: 0644]
app/src/main/assets/shared_images/bookmarks.svg
app/src/main/assets/shared_images/folder_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/BookmarksDatabaseViewActivity.kt
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolderDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDatabaseViewDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt
app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.kt
app/src/main/res/drawable-hdpi/folder_blue_bitmap.png
app/src/main/res/drawable-mdpi/folder_blue_bitmap.png
app/src/main/res/drawable-xhdpi/folder_blue_bitmap.png
app/src/main/res/drawable-xxhdpi/folder_blue_bitmap.png
app/src/main/res/drawable-xxxhdpi/folder_blue_bitmap.png
app/src/main/res/drawable/bookmark.xml [new file with mode: 0644]
app/src/main/res/drawable/bookmarks.xml
app/src/main/res/drawable/create_bookmark.xml
app/src/main/res/drawable/folder.xml [new file with mode: 0644]
app/src/main/res/layout/create_bookmark_dialog.xml
app/src/main/res/layout/create_bookmark_folder_dialog.xml
app/src/main/res/layout/edit_bookmark_databaseview_dialog.xml
app/src/main/res/layout/edit_bookmark_dialog.xml
app/src/main/res/layout/edit_bookmark_folder_databaseview_dialog.xml
app/src/main/res/layout/edit_bookmark_folder_dialog.xml
app/src/main/res/values/strings.xml
build.gradle

index 80a880036bc00e7f654d3637a23e1f1aa217b8cf..ae111a14467c97f8ec5d196a2a660ab0ea2dff99 100644 (file)
         <p><svg class="icon"><use href="../shared_images/aod_tablet_rounded_grade200.svg#icon"/></svg> aod_tablet_rounded_grade200.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_back.svg#icon"/></svg> arrow_back.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_forward.svg#icon"/></svg> arrow_forward.</p>
+        <p><svg class="icon"><use href="../shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> bookmark_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/bookmarks.svg#icon"/></svg> bookmarks.</p>
         <p><svg class="icon"><use href="../shared_images/bug_report.svg#icon"/></svg> bug_report.</p>
         <p><svg class="icon"><use href="../shared_images/call_to_action.svg#icon"/></svg> call_to_action.</p>
         <p><svg class="icon"><use href="../shared_images/file_download.svg#icon"/></svg> file_download.</p>
         <p><svg class="icon"><use href="../shared_images/find_in_page.svg#icon"/></svg> find_in_page.</p>
         <p><svg class="icon"><use href="../shared_images/folder.svg#icon"/></svg> folder.</p>
+        <p><svg class="icon"><use href="../shared_images/folder_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> folder_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/home.svg#icon"/></svg> home.</p>
         <p><svg class="icon"><use href="../shared_images/image.svg#icon"/></svg> image.</p>
         <p><svg class="icon"><use href="../shared_images/import_contacts.svg#icon"/></svg> import_contacts.</p>
         <hr/>
 
         <h3 style="text-align: center;">GNU General Public License</h3>
-        <p style="text-align: center;"><a href="http://www.gnu.de/documents/gpl.de.html">Offizielle deutsche Übersetzung der GNU General Public License</a></p>
-
-        <p>Version 3, 29 June 2007</p>
+        <p style="text-align: center;">Version 3, 29 June 2007</p>
 
         <p>Copyright © 2007 Free Software Foundation, Inc.
             <<a href="http://fsf.org/">http://fsf.org/</a>></p>
             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.</p>
 
-<pre>&lt;one line to give the program’s name
+        <pre>&lt;one line to give the program’s name
 and a brief idea of what it does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
 &lt;http://www.gnu.org/licenses/&gt;.</pre>
 
         <p>Also add information on how to contact you by electronic and paper mail.</p>
@@ -854,12 +855,12 @@ along with this program.  If not, see
         <p>If the program does terminal interaction, make it output a short
             notice like this when it starts in an interactive mode:</p>
 
-<pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
+        <pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
 &lt;name of author&gt;
-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.</pre>
 
@@ -1527,19 +1528,20 @@ for details.</pre>
             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.</p>
 
-<pre>&lt;one line to give the program's name
-and a brief idea of what it does.&gt;
+        <pre>&lt;one line to give the program's
+name and a brief idea of what it
+does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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.</p>
 
-<pre>Copyright [yyyy] [name of copyright
+        <pre>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
index 57bdc70d3e487fb7b49a8cb0456a869d8976b587..6b53802f69c0bca61a3ab64cec74220a4770bf9e 100644 (file)
         <p><svg class="icon"><use href="../shared_images/aod_tablet_rounded_grade200.svg#icon"/></svg> aod_tablet_rounded_grade200.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_back.svg#icon"/></svg> arrow_back.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_forward.svg#icon"/></svg> arrow_forward.</p>
+        <p><svg class="icon"><use href="../shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> bookmark_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/bookmarks.svg#icon"/></svg> bookmarks.</p>
         <p><svg class="icon"><use href="../shared_images/bug_report.svg#icon"/></svg> bug_report.</p>
         <p><svg class="icon"><use href="../shared_images/call_to_action.svg#icon"/></svg> call_to_action.</p>
         <p><svg class="icon"><use href="../shared_images/file_download.svg#icon"/></svg> file_download.</p>
         <p><svg class="icon"><use href="../shared_images/find_in_page.svg#icon"/></svg> find_in_page.</p>
         <p><svg class="icon"><use href="../shared_images/folder.svg#icon"/></svg> folder.</p>
+        <p><svg class="icon"><use href="../shared_images/folder_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> folder_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/home.svg#icon"/></svg> home.</p>
         <p><svg class="icon"><use href="../shared_images/image.svg#icon"/></svg> image.</p>
         <p><svg class="icon"><use href="../shared_images/import_contacts.svg#icon"/></svg> import_contacts.</p>
@@ -823,11 +825,11 @@ author&gt;
 
 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
 &lt;http://www.gnu.org/licenses/&gt;.</pre>
 
         <p>Also add information on how to contact you by electronic and paper mail.</p>
@@ -850,10 +853,10 @@ along with this program.  If not, see
 
 <pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
 &lt;name of author&gt;
-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.</pre>
 
@@ -1521,19 +1524,20 @@ for details.</pre>
             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.</p>
 
-<pre>&lt;one line to give the program's name
-and a brief idea of what it does.&gt;
+<pre>&lt;one line to give the program's
+name and a brief idea of what it
+does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
index 8e6a68039fe3ebd52a6f7dc3e92bc41a549e6f1b..b2a3c2ecec1886ff9792385964a1825879330714 100644 (file)
         <p><svg class="icon"><use href="../shared_images/aod_tablet_rounded_grade200.svg#icon"/></svg> aod_tablet_rounded_grade200.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_back.svg#icon"/></svg> arrow_back.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_forward.svg#icon"/></svg> arrow_forward.</p>
+        <p><svg class="icon"><use href="../shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> bookmark_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/bookmarks.svg#icon"/></svg> bookmarks.</p>
         <p><svg class="icon"><use href="../shared_images/bug_report.svg#icon"/></svg> bug_report.</p>
         <p><svg class="icon"><use href="../shared_images/call_to_action.svg#icon"/></svg> call_to_action.</p>
         <p><svg class="icon"><use href="../shared_images/file_download.svg#icon"/></svg> file_download.</p>
         <p><svg class="icon"><use href="../shared_images/find_in_page.svg#icon"/></svg> find_in_page.</p>
         <p><svg class="icon"><use href="../shared_images/folder.svg#icon"/></svg> folder.</p>
+        <p><svg class="icon"><use href="../shared_images/folder_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> folder_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/home.svg#icon"/></svg> home.</p>
         <p><svg class="icon"><use href="../shared_images/image.svg#icon"/></svg> image.</p>
         <p><svg class="icon"><use href="../shared_images/import_contacts.svg#icon"/></svg> import_contacts.</p>
         <p>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 programto 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
             (1) assert copyright on the software, and (2) offer you this License
             giving you legal permission to copy, distribute and/or modify it.</p>
 
-        <p>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
+        <p>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.</p>
 
         <p>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
         <p>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 works
             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
             the conditions stated below.  Sublicensing is not allowed; section 10
             makes it unnecessary.</p>
 
-        <h4>3. Protecting Users' Legal Rights From Anti-Circumvention Law.</h4>
+        <h4>3. Protecting Users Legal Rights From Anti-Circumvention Law.</h4>
 
         <p>No covered work shall be deemed part of an effective technological
             measure under any applicable law fulfilling obligations under article
             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 works
+            users, your or third parties legal rights to forbid circumvention of
             technological measures.</p>
 
         <h4>4. Conveying Verbatim Copies.</h4>
 
-        <p>You may convey verbatim copies of the Program's source code as you
+        <p>You may convey verbatim copies of the Programs 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
             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 compilations 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.</p>
             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 partys 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.</p>
 
         <p>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”.</p>
+            work thus licensed is called the contributors “contributor version”.</p>
 
-        <p>A contributor's “essential patent claims” are all patent claims
+        <p>A contributors “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,
             this License.</p>
 
         <p>Each contributor grants you a non-exclusive, worldwide, royalty-free
-            patent license under the contributor's essential patent claims, to
+            patent license under the contributors essential patent claims, to
             make, use, sell, offer for sale, import and otherwise run, modify and
             propagate the contents of its contributor version.</p>
 
             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 recipients 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.</p>
 
             any implied license or other defenses to infringement that may
             otherwise be available to you under applicable patent law.</p>
 
-        <h4>12. No Surrender of Others' Freedom.</h4>
+        <h4>12. No Surrender of Others Freedom.</h4>
 
         <p>If conditions are imposed on you (whether by court order, agreement or
             otherwise) that contradict the conditions of this License, they do not
             by the Free Software Foundation.</p>
 
         <p>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 proxys
             public statement of acceptance of a version permanently authorizes you
             to choose that version for the Program.</p>
 
             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.</p>
 
-<pre>&lt;one line to give the program’s name
+        <pre>&lt;one line to give the program’s name
 and a brief idea of what it does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
 &lt;http://www.gnu.org/licenses/&gt;.</pre>
 
         <p>Also add information on how to contact you by electronic and paper mail.</p>
@@ -854,15 +857,15 @@ along with this program.  If not, see
 
         <pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
 &lt;name of author&gt;
-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.</pre>
 
         <p>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 programs commands
             might be different; for a GUI interface, you would use an “about box”.</p>
 
         <p>You should also get your employer (if you work as a programmer) or school,
@@ -1525,19 +1528,20 @@ for details.</pre>
             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.</p>
 
-<pre>&lt;one line to give the program's name
-and a brief idea of what it does.&gt;
+        <pre>&lt;one line to give the program's
+name and a brief idea of what it
+does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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.</p>
 
-<pre>Copyright [yyyy] [name of copyright
+        <pre>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
index 1e80e640fe3989bd741fa85ce93e588d9f48a9d4..3877b6869a8e86bbde8d612d0984499b819aa916 100644 (file)
         <p><svg class="icon"><use href="../shared_images/aod_tablet_rounded_grade200.svg#icon"/></svg> aod_tablet_rounded_grade200.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_back.svg#icon"/></svg> arrow_back.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_forward.svg#icon"/></svg> arrow_forward.</p>
+        <p><svg class="icon"><use href="../shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> bookmark_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/bookmarks.svg#icon"/></svg> bookmarks.</p>
         <p><svg class="icon"><use href="../shared_images/bug_report.svg#icon"/></svg> bug_report.</p>
         <p><svg class="icon"><use href="../shared_images/call_to_action.svg#icon"/></svg> call_to_action.</p>
         <p><svg class="icon"><use href="../shared_images/file_download.svg#icon"/></svg> file_download.</p>
         <p><svg class="icon"><use href="../shared_images/find_in_page.svg#icon"/></svg> find_in_page.</p>
         <p><svg class="icon"><use href="../shared_images/folder.svg#icon"/></svg> folder.</p>
+        <p><svg class="icon"><use href="../shared_images/folder_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> folder_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/home.svg#icon"/></svg> home.</p>
         <p><svg class="icon"><use href="../shared_images/image.svg#icon"/></svg> image.</p>
         <p><svg class="icon"><use href="../shared_images/import_contacts.svg#icon"/></svg> import_contacts.</p>
             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.</p>
 
-<pre>&lt;one line to give the program’s name
+        <pre>&lt;one line to give the program’s name
 and a brief idea of what it does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
 &lt;http://www.gnu.org/licenses/&gt;.</pre>
 
         <p>Also add information on how to contact you by electronic and paper mail.</p>
@@ -854,12 +857,12 @@ along with this program.  If not, see
         <p>If the program does terminal interaction, make it output a short
             notice like this when it starts in an interactive mode:</p>
 
-<pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
+        <pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
 &lt;name of author&gt;
-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.</pre>
 
@@ -1527,19 +1530,20 @@ for details.</pre>
             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.</p>
 
-<pre>&lt;one line to give the program's name
-and a brief idea of what it does.&gt;
+        <pre>&lt;one line to give the program's
+name and a brief idea of what it
+does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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.</p>
 
-<pre>Copyright [yyyy] [name of copyright
+        <pre>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
index 5ffebc36a23eb972e046b3641bf8ff524c3636c7..159bf75d469f0886bf916878f6290e0c8a9c7403 100644 (file)
@@ -1,7 +1,7 @@
 <!--
   Copyright 2017-2024 Soren Stoutner <soren@stoutner.com>.
 
-  Translation 2017-2020,2022-2023 Francesco Buratti.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+  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>.
 
         <p><svg class="icon"><use href="../shared_images/aod_tablet_rounded_grade200.svg#icon"/></svg> aod_tablet_rounded_grade200.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_back.svg#icon"/></svg> arrow_back.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_forward.svg#icon"/></svg> arrow_forward.</p>
+        <p><svg class="icon"><use href="../shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> bookmark_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/bookmarks.svg#icon"/></svg> bookmarks.</p>
         <p><svg class="icon"><use href="../shared_images/bug_report.svg#icon"/></svg> bug_report.</p>
         <p><svg class="icon"><use href="../shared_images/call_to_action.svg#icon"/></svg> call_to_action.</p>
         <p><svg class="icon"><use href="../shared_images/file_download.svg#icon"/></svg> file_download.</p>
         <p><svg class="icon"><use href="../shared_images/find_in_page.svg#icon"/></svg> find_in_page.</p>
         <p><svg class="icon"><use href="../shared_images/folder.svg#icon"/></svg> folder.</p>
+        <p><svg class="icon"><use href="../shared_images/folder_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> folder_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/home.svg#icon"/></svg> home.</p>
         <p><svg class="icon"><use href="../shared_images/image.svg#icon"/></svg> image.</p>
         <p><svg class="icon"><use href="../shared_images/import_contacts.svg#icon"/></svg> import_contacts.</p>
         <hr/>
 
         <h3 style="text-align: center;">GNU General Public License</h3>
-        <p style="text-align: center;">Versione 3, 29 Giugno 2007</p>
+        <p style="text-align: center;">Version 3, 29 June 2007</p>
 
         <p>Copyright © 2007 Free Software Foundation, Inc.
             <<a href="http://fsf.org/">http://fsf.org/</a>></p>
 
-        <p>A chiunque è permesso copiare e ridistribuire copie esatte di questo documento di licenza,
-            ma non è in alcun modo consentito apportarvi modifiche.
-        </p>
-
-        <h3>Premessa</h3>
-
-        <p>La GNU General Public License è una licenza libera, basata su copyleft per software e altri tipi di opere. </p>
-
-        <p>Le licenze della maggior parte del software e di altre opere materiali 
-            sono pensate per togliere la libertà di condividere e modificare tali opere.
-            Al contrario, la GNU General Public License ha l'obiettivo di garantire
-            la libertà di condividere e modificare tutte le versioni di un programma
-            e di fare in modo che esso rimanga software libero per tutti gli utenti.
-            Noi, Free Software Foundation, usiamo la GNU General Public License
-            per la maggior parte del nostro software; essa viene applicata anche a qualunque
-            altro software rilasciato dall'autore sotto questa licenza.
-            Chiunque può utilizzare questa licenza per i propri programmi. </p>
-
-        <p>Quando parliamo di software libero (free software), ci riferiamo al concetto di libertà, 
-            non al prezzo. Le nostre General Public License sono concepite per garantire
-            che chiunque abbia la libertà di distribuire copie di software libero
-            (anche dietro pagamento di un prezzo, se lo desidera), che chiunque riceva o possa ricevere
-            il codice sorgente se lo vuole, che chiunque possa apportare modifiche al software
-            o utilizzarne solo alcune porzioni in altri software liberi, e che chiunque sappia
-            che ha il diritto di fare tutte queste cose. </p>
-
-        <p>Per proteggere i vostri diritti, abbiamo la necessità di impedire che altri 
-            vi neghino questi diritti o vi obblighino a rinunciarvi.
-            Pertanto, chiunque distribuisce o modifica software rilasciato con questa licenza
-            si assume dei precisi doveri: il dovere di rispettare la libertà degli altri.</p>
-
-        <p>Per esempio, chi distribuisce copie di un programma rilasciato sotto questa licenza, 
-            sia a titolo gratuito che mediante pagamento di un prezzo,è obbligato a riconoscere
-            a chi riceve il software esattamente gli stessi diritti che ha ricevuto.
-            Deve garantire che chi riceva il software abbia o possa avere accesso al codice sorgente.
-            E deve far conoscere ai destinatari del software queste condizioni,
-            così che essi conoscano quali sono i loro diritti. </p>
-
-        <p>Gli sviluppatori che utilizzano la GNU GPL proteggono i vostri diritti in due modi: 
-            (1) Rivendicando il copyright sul software, e
-            (2) offrendovi questa licenza che vi garantisce il diritto legale di copiarlo e/o di modificarlo. </p>
-
-        <p>Al fine di proteggere gli sviluppatori e gli autori, la GPL spiega chiaramente 
-            che non esiste nessuna garanzia per questo software libero. Nell'interesse
-            sia degli utenti che degli autori, la GPL impone che le versioni modificate del software
-            vengano esplicitamente marcate come “modificate”, in modo tale che eventuali problemi
-            non vengano erroneamente attribuiti agli autori delle versioni precedenti. </p>
-
-        <p>Alcuni dispositivi sono progettati per negare agli utenti l'installazione 
-            o l'esecuzione di versioni modificate del software installato sugli stessi,
-            anche se il costruttore si riserva la possibilità di farlo.
-            Ciò è fondamentalmente incompatibile con l'obiettivo di garantire la libertà
-            degli utenti di modificare il software. Una ripetizione sistematica di tali abusi
-            avviene nel campo dei dispositivi per usi individuali, e ciò rende questi abusi
-            ancora più inaccettabili. Pertanto, abbiamo realizzato questa versione della GPL
-            al fine di proibire una tale pratica per questo tipo di prodotti. Se problemi simili
-            dovessero sorgere in altri ambiti, saremo pronti ad estendere queste misure
-            a questi nuovi ambiti in versioni future della GPL, nella maniera che si renderà necessaria
-            per difendere la libertà degli utenti. </p>
-
-        <p>In conclusione, tutti i programmi sono costantemente minacciati dai brevetti sul software. 
-            Gli Stati non dovrebbero permettere ai brevetti di limitare lo sviluppo
-            e l'utilizzo di software per computer, ma nei Paesi in cui ciò avviene noi vogliamo evitare
-            il pericolo che i brevetti applicati ad un programma libero possano renderlo, a tutti gli effetti,
-            proprietario. Per impedire ciò, la GPL assicura che non è possibile utilizzare
-            i brevetti sul software per rendere un programma non libero. </p>
-
-        <p>I termini e le condizioni esatte per la copia, la distribuzione e la modifica del software 
-            sono riportate di seguito. </p>
-
-        <h3>TERMINI E CONDIZIONI</h3>
-
-        <h4>0. Definizioni.</h4>
-
-        <p>“Questa Licenza”  fa riferimento alla versione 3 della GNU General Public License.</p>
-
-        <p>“Copyright” indica anche leggi simili al copyright che riguardano altri tipi di opere, 
-            come le maschere per la produzione di semiconduttori.</p>
-
-        <p>“Il Programma” indica qualunque opera che sia soggetta a copyright e che sia rilasciata 
-            sotto questa Licenza.   I detentori della licenza sono indicati come  “tu”.  “Licenziatari” e
-            “destinatari” possono essere individui o organizzazioni.</p>
-
-        <p>“Modificare”  un'opera significa copiare o adattare tutta o parte dell'opera 
-            in una maniera che richieda un permesso di copyright, e non indica la semplice azione
-            di fare una esatta copia dell'opera. L'opera risultante viene chiamata “versione modificata”
-            dell'opera precedente, oppure viene detta opera “basata sulla” opera precedente.</p>
-
-        <p>Un'“opera coperta da questa licenza”  indica il Programma originale non modificato 
-            oppure un'opera basata sul Programma.</p>
-
-        <p>“Propagare”  un'opera significa fare una qualunque cosa con essa che, 
-            in mancanza di un esplicito permesso, ti renda direttamente o indirettamente perseguibile
-            per violazione secondo le vigenti normative sul copyright,
-            ad eccezione della semplice esecuzione del Programma su un computer
-            o della modifica di una copia privata. La Propagazione include la copia,
-            la distribuzione (con o senza modifiche), la messa a disposizione al pubblico e,
-            in alcuni stati, altre attività simili e connesse.</p>
-
-        <p>“Distribuire” un'opera indica qualunque forma di propagazione 
-            che permetta a terze parti di effettuare o ricevere delle copie.
-            La mera interazione con un utente attraverso una rete di computer,
-            senza che ci sia alcun trasferimento di una copia, non è considerata “Distribuzione”.</p>
-
-        <p>Una interfaccia utente interattiva fornisce delle  “Adeguate Informazioni Legali”
-            soltanto nel caso in cui includa una apposita funzionalità, resa adeguatamente visibile, che 
-            (1) visualizzi un'adeguata informazione di copyright, e
-            (2) informi l'utente che non c'è alcuna garanzia sull'opera (eccetto nel caso in cui
-            delle garanzie sono espressamente fornite), dica che il licenziatario può distribuire
-            l'opera utilizzando questa Licenza, indichi come è possibile prendere visione di una copia
-            di questa Licenza.
-            Se l'interfaccia presenta una lista di comandi o di opzioni, come ad esempio un menù,
-            una delle opzioni fornite nella lista deve rispettare questa condizione. </p>
-
-        <h4>1. Codice Sorgente.</h4>
-
-        <p>Il “codice sorgente” di un'opera indica la forma più indicata
-            dell'opera per poter effettuare modifiche su di essa. Il “codice oggetto”
-            indica qualunque forma dell'opera che non sia codice sorgente.</p>
-
-        <p>Una “Interfaccia Standard” è una interfaccia che risponde ad uno
-            standard ufficiale definito da un ente di standardizzazione riconosciuto
-            o, nel caso di interfacce specifiche per un particolare linguaggio di
-            programmazione, una interfaccia che è largamente utilizzata dagli
-            sviluppatori per sviluppare in tale linguaggio.</p>
-
-        <p>Le “Librerie di Sistema” di un eseguibile includono qualunque cosa,
-            eccetto l'opera nel suo insieme, che (a) sia inclusa nella normale forma
-            di pacchettizzazione di un “Componente Principale”, ma che non è
-            parte di quel Componente Principale, e (b) che serva solo a consentire
-            l'uso dell'opera con quel Componente Principale, o per implementare una
-            Interfaccia Standard per la quale esista una implementazione disponibile
-            al pubblico in forma sorgente. Un “Componente Principale”, in questo
-            contesto, è un componente essenziale (kernel, gestore di finestre
-            eccetera) dello specifico sistema operativo (ammesso che ce ne sia uno)
-            sul quale l'eseguibile esegue, o un compilatore utilizzato per produrre
-            il programma, o un interprete di codice oggetto utilizzato per eseguire
-            il programma.</p>
-
-        <p>Il “Sorgente Corrispondente” per un'opera in forma di codice oggetto
-            è il codice sorgente necessario per generare, installare e (per un
-            programma eseguibile) eseguire il codice oggetto e per modificare
-            l'opera, inclusi gli script per controllare le suddette attività di
-            generazione, installazione ed esecuzione. Non sono incluse le Librerie
-            di Sistema usate dal programma, o gli strumenti di utilità generica o i
-            programmi liberamente accessibili che sono utilizzati, senza modifiche,
-            per portare a termine le suddette attività ma che non fanno parte
-            dell'opera. Per esempio, il sorgente corrispondente include i file con
-            le definizioni delle interfacce associati ai file sorgente dell'opera, e
-            il codice sorgente delle librerie condivise e sottoprogrammi collegati
-            dinamicamente specificatamente necessari per il programma, ad esempio a
-            causa di stretta comunicazione dati o di controllo di flusso tra questi
-            sottoprogrammi e altre parti del programma.</p>
-
-        <p>Il Sorgente Corrispondente non include nulla che l'utente possa
-            rigenerare automaticamente da altre parti del Sorgente Corrispondente
-            stesso.</p>
-
-        <p>Il Sorgente Corrispondente di un'opera in forma di codice sorgente è
-            l'opera stessa.</p>
-
-        <h4>2. Principali Diritti.</h4>
-
-        <p>Tutti i diritti garantiti da questa Licenza sono garantiti per la durata
-            del copyright sul Programma, e sono irrevocabili ammesso che le
-            condizioni qui riportate siano rispettate. Questa Licenza afferma esplicitamente il
-            tuo permesso illimitato di eseguire il Programma non modificato. Il
-            risultato dell'esecuzione di un programma coperto da questa Licenza è
-            a sua volta coperto da questa Licenza solo se il risultato stesso, a
-            causa del suo contenuto, è un'opera coperta da questa Licenza. Questa
-            Licenza riconosce il tuo diritto all'uso legittimo o altri diritti
-            equivalenti, come stabilito dalla legislazione sul copyright.</p>
-
-        <p>Puoi creare, eseguire e propagare programmi che tu non distribuisci 
-            coperti da questa Licenza, senza alcuna condizione fino a quando la tua
-            Licenza rimane valida. Puoi distribuire opere coperte da questa Licenza
-            ad altri al solo scopo di ottenere che essi facciano delle modifiche al
-            programma esclusivamente per te, o che ti forniscano dei servizi per
-            l'esecuzione di queste opere, ammesso che tu rispetti i termini di
-            questa Licenza nel distribuire tutto il materiale per il quale non
-            detieni il copyright. Coloro i quali creano o eseguono per conto tuo un
-            programma coperto da questa Licenza lo fanno esclusivamente in tua vece,
-            sotto la tua direzione e il tuo controllo, in maniera tale che sia
-            proibito a costoro effettuare copie di materiale di cui detieni il
-            copyright al di fuori della relazione che intrattengono nei tuoi
-            confronti.</p>
-
-        <p>Distribuire opere coperte da licenza in qualunque altra circostanza è
-            consentito soltanto alle condizioni espresse in seguito. Non è
-            consentito sottolicenziare le opere: la sezione 10 lo rende non
-            necessario.</p>
-
-        <h4>3. Protezione dei diritti legali degli utenti dalle leggi
-            anti-elusione.</h4>
-
-        <p>Nessun programma protetto da questa Licenza può essere considerato
-            parte di una misura tecnologica di restrizione che sottostia ad alcuna
-            delle leggi che soddisfano l'articolo 11 del “WIPO copyright treaty”
-            adottato il 20 Dicembre 1996, o a simili leggi che proibiscono o
-            limitano l'elusione di tali misure tecnologiche di restrizione.</p>
-
-        <p>Quando distribuisci un programma coperto da questa Licenza, rifiuti
-            tutti i poteri legali atti a proibire l'elusione di misure tecnologiche
-            di restrizione ammesso che tale elusione sia effettuata nell'esercizio
-            dei diritti garantiti da questa Licenza riguardo al programma coperto da
-            questa Licenza, e rinunci all'intenzione di limitare l'operatività o
-            la modifica del programma per far valere, contro i diritti degli utenti
-            del programma, diritti legali tuoi o di terze parti che impediscano
-            l'elusione di misure tecnologiche di restrizione.</p>
-
-        <h4>4. Distribuzione di Copie Esatte.</h4>
-
-        <p>Ti è permesso distribuire copie esatte del codice sorgente del
-            Programma come lo hai ricevuto, con qualunque mezzo, ammesso che tu
-            aggiunga in maniera appropriata su ciascuna copia una appropriata nota
-            di copyright; che tu lasci intatti tutti gli avvisi che affermano che
-            questa Licenza e tutte le clausole non-permissive aggiunte in accordo
-            con la sezione 7 sono valide per il codice che distribuisci; che tu
-            lasci intatti tutti gli avvisi circa l'assenza di garanzia; che tu
-            fornisca a tutti i destinatari una copia di questa Licenza assieme al
-            Programma.</p>
-
-        <p>Puoi richiedere il pagamento di un prezzo o di nessun prezzo per
-            ciascuna copia che distribuisci, e puoi offrire supporto o garanzia
-            a pagamento.</p>
-
-        <h4>5. Distribuzione di Versioni modificate del sorgente.</h4>
-
-        <p>Puoi distribuire un'opera basata sul Programma, o le modifiche per
-            produrla a partire dal Programma, nella forma di codice sorgente secondo
-            i termini della sezione 4, ammesso che tu rispetti anche tutte le
-            seguenti condizioni:</p>
+        <p>Everyone is permitted to copy and distribute verbatim copies
+            of this license document, but changing it is not allowed.</p>
+
+        <h3>Preamble</h3>
+
+        <p>The GNU General Public License is a free, copyleft license for
+            software and other kinds of works.</p>
+
+        <p>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
+            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
+            your programs, too.</p>
+
+        <p>When we speak of free software, we are referring to freedom, not
+            price.  Our General Public Licenses are designed to make sure that you
+            have the freedom to distribute copies of free software (and charge for
+            them if you wish), that you receive source code or can get it if you
+            want it, that you can change the software or use pieces of it in new
+            free programs, and that you know you can do these things.</p>
+
+        <p>To protect your rights, we need to prevent others from denying you
+            these rights or asking you to surrender the rights.  Therefore, you have
+            certain responsibilities if you distribute copies of the software, or if
+            you modify it: responsibilities to respect the freedom of others.</p>
+
+        <p>For example, if you distribute copies of such a program, whether
+            gratis or for a fee, you must pass on to the recipients the same
+            freedoms that you received.  You must make sure that they, too, receive
+            or can get the source code.  And you must show them these terms so they
+            know their rights.</p>
+
+        <p>Developers that use the GNU GPL protect your rights with two steps:
+            (1) assert copyright on the software, and (2) offer you this License
+            giving you legal permission to copy, distribute and/or modify it.</p>
+
+        <p>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.</p>
+
+        <p>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
+            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
+            products.  If such problems arise substantially in other domains, we
+            stand ready to extend this provision to those domains in future versions
+            of the GPL, as needed to protect the freedom of users.</p>
+
+        <p>Finally, every program is threatened constantly by software patents.
+            States should not allow patents to restrict development and use of
+            software on general-purpose computers, but in those that do, we wish to
+            avoid the special danger that patents applied to a free program could
+            make it effectively proprietary.  To prevent this, the GPL assures that
+            patents cannot be used to render the program non-free.</p>
+
+        <p>The precise terms and conditions for copying, distribution and
+            modification follow.</p>
+
+        <h3>TERMS AND CONDITIONS</h3>
+
+        <h4>0. Definitions.</h4>
+
+        <p>“This License” refers to version 3 of the GNU General Public License.</p>
+
+        <p>“Copyright” also means copyright-like laws that apply to other kinds of
+            works, such as semiconductor masks.</p>
+
+        <p>“The Program” refers to any copyrightable work licensed under this
+            License.  Each licensee is addressed as “you”.  “Licensees” and
+            “recipients” may be individuals or organizations.</p>
+
+        <p>To “modify” a work means to copy from or adapt all or part of the work
+            in a fashion requiring copyright permission, other than the making of an
+            exact copy.  The resulting work is called a “modified version” of the
+            earlier work or a work “based on” the earlier work.</p>
+
+        <p>A “covered work” means either the unmodified Program or a work based
+            on the Program.</p>
+
+        <p>To “propagate” a work means to do anything with it that, without
+            permission, would make you directly or secondarily liable for
+            infringement under applicable copyright law, except executing it on a
+            computer or modifying a private copy.  Propagation includes copying,
+            distribution (with or without modification), making available to the
+            public, and in some countries other activities as well.</p>
+
+        <p>To “convey” a work means any kind of propagation that enables other
+            parties to make or receive copies.  Mere interaction with a user through
+            a computer network, with no transfer of a copy, is not conveying.</p>
+
+        <p>An interactive user interface displays “Appropriate Legal Notices”
+            to the extent that it includes a convenient and prominently visible
+            feature that (1) displays an appropriate copyright notice, and (2)
+            tells the user that there is no warranty for the work (except to the
+            extent that warranties are provided), that licensees may convey the
+            work under this License, and how to view a copy of this License.  If
+            the interface presents a list of user commands or options, such as a
+            menu, a prominent item in the list meets this criterion.</p>
+
+        <h4>1. Source Code.</h4>
+
+        <p>The “source code” for a work means the preferred form of the work
+            for making modifications to it.  “Object code” means any non-source
+            form of a work.</p>
+
+        <p>A “Standard Interface” means an interface that either is an official
+            standard defined by a recognized standards body, or, in the case of
+            interfaces specified for a particular programming language, one that
+            is widely used among developers working in that language.</p>
+
+        <p>The “System Libraries” of an executable work include anything, other
+            than the work as a whole, that (a) is included in the normal form of
+            packaging a Major Component, but which is not part of that Major
+            Component, and (b) serves only to enable use of the work with that
+            Major Component, or to implement a Standard Interface for which an
+            implementation is available to the public in source code form.  A
+            “Major Component”, in this context, means a major essential component
+            (kernel, window system, and so on) of the specific operating system
+            (if any) on which the executable work runs, or a compiler used to
+            produce the work, or an object code interpreter used to run it.</p>
+
+        <p>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
+            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
+            includes interface definition files associated with source files for
+            the work, and the source code for shared libraries and dynamically
+            linked subprograms that the work is specifically designed to require,
+            such as by intimate data communication or control flow between those
+            subprograms and other parts of the work.</p>
+
+        <p>The Corresponding Source need not include anything that users
+            can regenerate automatically from other parts of the Corresponding
+            Source.</p>
+
+        <p>The Corresponding Source for a work in source code form is that
+            same work.</p>
+
+        <h4>2. Basic Permissions.</h4>
+
+        <p>All rights granted under this License are granted for the term of
+            copyright on the Program, and are irrevocable provided the stated
+            conditions are met.  This License explicitly affirms your unlimited
+            permission to run the unmodified Program.  The output from running a
+            covered work is covered by this License only if the output, given its
+            content, constitutes a covered work.  This License acknowledges your
+            rights of fair use or other equivalent, as provided by copyright law.</p>
+
+        <p>You may make, run and propagate covered works that you do not
+            convey, without conditions so long as your license otherwise remains
+            in force.  You may convey covered works to others for the sole purpose
+            of having them make modifications exclusively for you, or provide you
+            with facilities for running those works, provided that you comply with
+            the terms of this License in conveying all material for which you do
+            not control copyright.  Those thus making or running the covered works
+            for you must do so exclusively on your behalf, under your direction
+            and control, on terms that prohibit them from making any copies of
+            your copyrighted material outside their relationship with you.</p>
+
+        <p>Conveying under any other circumstances is permitted solely under
+            the conditions stated below.  Sublicensing is not allowed; section 10
+            makes it unnecessary.</p>
+
+        <h4>3. Protecting Users’ Legal Rights From Anti-Circumvention Law.</h4>
+
+        <p>No covered work shall be deemed part of an effective technological
+            measure under any applicable law fulfilling obligations under article
+            11 of the WIPO copyright treaty adopted on 20 December 1996, or
+            similar laws prohibiting or restricting circumvention of such
+            measures.</p>
+
+        <p>When you convey a covered work, you waive any legal power to forbid
+            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
+            technological measures.</p>
+
+        <h4>4. Conveying Verbatim Copies.</h4>
+
+        <p>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
+            non-permissive terms added in accord with section 7 apply to the code;
+            keep intact all notices of the absence of any warranty; and give all
+            recipients a copy of this License along with the Program.</p>
+
+        <p>You may charge any price or no price for each copy that you convey,
+            and you may offer support or warranty protection for a fee.</p>
+
+        <h4>5. Conveying Modified Source Versions.</h4>
+
+        <p>You may convey a work based on the Program, or the modifications to
+            produce it from the Program, in the form of source code under the
+            terms of section 4, provided that you also meet all of these conditions:</p>
 
         <ul>
-            <li>a) L'opera deve recare con sè delle informazioni adeguate che
-                affermino che tu l'hai modificata, indicando la data di modifica.</li>
-
-            <li>b) L'opera deve recare informazioni adeguate che affermino che essa è
-                rilasciata sotto questa Licenza e sotto le condizioni aggiuntive
-                secondo quanto indicato dalla Sezione 7. Questa condizione modifica la
-                condizione espressa alla sezione 4 di “lasciare intatti tutti gli
-                avvisi”.</li>
-
-            <li>c) Devi rilasciare l'intera opera, nel suo complesso, sotto questa
-                Licenza a chiunque venga in possesso di una copia di essa. Questa
-                Licenza sarà pertanto applicata, assieme ad eventuali clausole
-                aggiunte in osservanza della Sezione 7, all'opera nel suo complesso, a
-                tutte le sue parti, indipendentemente da come esse siano
-                pacchettizzate. Questa Licenza nega il permesso di licenziare l'opera
-                in qualunque altro modo, ma non rende nullo un tale permesso ammesso
-                che tu lo abbia ricevuto separatamente.</li>
-
-            <li>d) Se l'opera ha delle interfacce utente interattive, ciascuna deve
-                mostrare delle Adeguate Informazioni Legali; altrimenti, se il
-                Programma ha delle interfacce interattive che non visualizzano delle
-                Adeguate Informazioni Legali, il tuo programma non è obbligato a
-                visualizzarle.</li>
+            <li>a) The work must carry prominent notices stating that you modified
+                it, and giving a relevant date.</li>
+
+            <li>b) The work must carry prominent notices stating that it is
+                released under this License and any conditions added under section
+                7.  This requirement modifies the requirement in section 4 to
+                “keep intact all notices”.</li>
+
+            <li>c) You must license the entire work, as a whole, under this
+                License to anyone who comes into possession of a copy.  This
+                License will therefore apply, along with any applicable section 7
+                additional terms, to the whole of the work, and all its parts,
+                regardless of how they are packaged.  This License gives no
+                permission to license the work in any other way, but it does not
+                invalidate such permission if you have separately received it.</li>
+
+            <li>d) If the work has interactive user interfaces, each must display
+                Appropriate Legal Notices; however, if the Program has interactive
+                interfaces that do not display Appropriate Legal Notices, your
+                work need not make them do so.</li>
         </ul>
 
-        <p>La giustapposizione di un'opera coperta da questa Licenza assieme ad
-            altre opere separate e indipendenti, che non sono per loro natura
-            estensioni del Programma, e che non sono combinate con esso a formare un
-            altro programma più grande, dentro o in uno stesso supporto di
-            memorizzazione a lungo termine o di distribuzione, è semplicemente
-            detto “aggregato” se la raccolta e il suo copyright non sono
-            utilizzati per limitare l'accesso o i diritti legali degli utenti della
-            raccolta stessa oltre ciò che ciascun singolo programma
-            consente. L'inclusione di un programma coperto da questa Licenza in un
-            aggregato non comporta l'applicazione di questa Licenza alle altre parti
-            dell'aggregato.</p>
-
-        <h4>6. Distribuzione in formato non-sorgente.</h4>
-
-        <p>Puoi distribuire un programma coperto da questa Licenza in formato di
-            codice oggetto secondo i termini delle sezioni 4 e 5, ammesso che tu
-            fornisca anche il  Sorgente Corrispondente in formato comprensibile
-            da un computer sotto i termini di questa stessa Licenza, in uno dei
-            seguenti modi:</p>
+        <p>A compilation of a covered work with other separate and independent
+            works, which are not by their nature extensions of the covered work,
+            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
+            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.</p>
+
+        <h4>6. Conveying Non-Source Forms.</h4>
+
+        <p>You may convey a covered work in object code form under the terms
+            of sections 4 and 5, provided that you also convey the
+            machine-readable Corresponding Source under the terms of this License,
+            in one of these ways:</p>
 
         <ul>
-            <li>a) Distribuendo il codice oggetto in, o contenuto in, un prodotto
-                fisico (inclusi i mezzi fisici di distribuzione), accompagnato dal
-                Sorgente Corrispondente su un supporto fisico duraturo comunemente
-                utilizzato per lo scambio di software.</li>
-
-            <li>b) Distribuendo il codice oggetto in, o contenuto in, un prodotto fisico
-                (inclusi i mezzi fisici di distribuzione), accompagnato da un'offerta
-                scritta, valida per almeno tre anni e valida per tutto il tempo
-                durante il quale tu offri ricambi o supporto per quel modello di
-                prodotto, di fornire a chiunque possieda il codice oggetto (1) una
-                copia del Sorgente Corrispondente di tutto il software contenuto nel
-                prodotto che è coperto da questa Licenza, su un supporto fisico
-                duraturo comunemente utilizzato per lo scambio di software, ad un
-                prezzo non superiore al costo ragionevole per effettuare fisicamente
-                tale distribuzione del sorgente, oppure (2) accesso alla copia del
-                Sorgente Corrispondente attraverso un server di rete senza alcun costo
-                aggiuntivo.</li>
-
-            <li>c) Distribuendo copie singole del codice oggetto assieme ad una copia
-                dell'offerta scritta di fornire il Sorgente Corrispondente. Questa
-                possibilità è permessa soltanto occasionalmente e per fini non
-                commerciali, e solo se tu hai ricevuto il codice oggetto assieme ad
-                una tale offerta, in accordo alla sezione 6b.</li>
-
-            <li>d) Distribuendo il codice oggetto mediante accesso da un luogo designato
-                (gratis o dietro pagamento di un prezzo), e offrendo un accesso
-                equivalente al Sorgente Corrispondente alla stessa maniera a partire
-                dallo stesso luogo senza costi aggiuntivi. Non devi obbligare i
-                destinatari a copiare il Sorgente Corrispondente assieme al codice
-                oggetto. Se il luogo dal quale copiare il codice oggetto è un server
-                di rete, il Sorgente Corrispondente può trovarsi su un server
-                differente (gestito da te o da terze parti) che fornisca
-                funzionalità equivalenti per la copia, a patto che tu fornisca delle
-                indicazioni chiare accanto al codice oggetto che indichino dove
-                trovare il Sorgente Corrispondente. Indipendentemente da quale server
-                ospiti il Sorgente Corrispondente, tu rimani obbligato ad assicurare
-                che esso rimanga disponibile per tutto il tempo necessario a
-                soddisfare queste condizioni.</li>
-
-            <li>e) Distribuendo il codice oggetto mediante trasmissione peer-to-peer, a
-                patto che tu informi gli altri peer circa il luogo in cui il codice
-                oggetto e il Sorgente Corrispondente sono gratuitamente offerti al
-                pubblico secondo i termini della sezione 6d.</li>
+            <li>a) Convey the object code in, or embodied in, a physical product
+                (including a physical distribution medium), accompanied by the
+                Corresponding Source fixed on a durable physical medium
+                customarily used for software interchange.</li>
+
+            <li>b) Convey the object code in, or embodied in, a physical product
+                (including a physical distribution medium), accompanied by a
+                written offer, valid for at least three years and valid for as
+                long as you offer spare parts or customer support for that product
+                model, to give anyone who possesses the object code either (1) a
+                copy of the Corresponding Source for all the software in the
+                product that is covered by this License, on a durable physical
+                medium customarily used for software interchange, for a price no
+                more than your reasonable cost of physically performing this
+                conveying of source, or (2) access to copy the
+                Corresponding Source from a network server at no charge.</li>
+
+            <li>c) Convey individual copies of the object code with a copy of the
+                written offer to provide the Corresponding Source.  This
+                alternative is allowed only occasionally and noncommercially, and
+                only if you received the object code with such an offer, in accord
+                with subsection 6b.</li>
+
+            <li>d) Convey the object code by offering access from a designated
+                place (gratis or for a charge), and offer equivalent access to the
+                Corresponding Source in the same way through the same place at no
+                further charge.  You need not require recipients to copy the
+                Corresponding Source along with the object code.  If the place to
+                copy the object code is a network server, the Corresponding Source
+                may be on a different server (operated by you or a third party)
+                that supports equivalent copying facilities, provided you maintain
+                clear directions next to the object code saying where to find the
+                Corresponding Source.  Regardless of what server hosts the
+                Corresponding Source, you remain obligated to ensure that it is
+                available for as long as needed to satisfy these requirements.</li>
+
+            <li>e) Convey the object code using peer-to-peer transmission, provided
+                you inform other peers where the object code and Corresponding
+                Source of the work are being offered to the general public at no
+                charge under subsection 6d.</li>
         </ul>
 
-        <p>Una porzione separabile del codice oggetto, il cui sorgente è
-            escluso dal Sorgente Corrispondente e trattato come Libreria di
-            Sistema, non deve essere obbligatoriamente inclusa nella distribuzione
-            del codice oggetto del programma.</p>
-
-        <p>Un “Prodotto Utente” è un (1) “prodotto consumer”, cioè
-            qualunque proprietà personale tangibile che è normalmente utilizzata
-            per scopi personali, familiari o domestici, oppure (2) qualunque cosa
-            progettata o venduta per essere utilizzata in ambiente domestico. Nella
-            classificazione di un prodotto come “prodotto consumer”, i casi dubbi
-            andranno risolti in favore dell'ambito di applicazione. Per un dato
-            prodotto ricevuto da un dato utente, “normalmente utilizzato” si
-            riferisce ad un uso tipico o comune di quella classe di prodotti,
-            indipendentemente dallo stato dell'utente specifico o dal modo in cui
-            l'utente specifico utilizza, o si aspetta o ci si aspetta che utilizzi,
-            il prodotto. Un prodotto è un “prodotto consumer” indipendentemente
-            dal fatto che abbia usi commerciali, industriali o diversi da quelli
-            “consumer”, a meno che questi usi non rappresentino il solo modo utile
-            di utilizzare il prodotto in questione.</p>
-
-        <p>Le “Informazioni di Installazione” per un Prodotto Utente sono i
-            metodi, le procedure, le chiavi di autorizzazioni o altre informazioni
-            necessarie per installare ed eseguire versioni modificate di un
-            programma coperto da questa Licenza all'interno di un Prodotto Utente, a
-            partire da versioni modificate dei suoi Sorgenti Corrispondenti. Tali
-            informazioni devono essere sufficienti ad assicurare che il
-            funzionamento del codice oggetto modificato non sia in nessun caso
-            proibito o ostacolato per il solo fatto che sono state apportate delle
-            modifiche.</p>
-
-        <p>Se distribuisci un codice oggetto secondo le condizioni di questa
-            sezione in, o assieme, o specificatamente per l'uso in o con un Prodotto
-            Utente, e la distribuzione avviene come parte di una transazione nella
-            quale il diritto di possesso e di uso del Prodotto Utente viene
-            trasferito al destinatario per sempre o per un periodo prefissato
-            (indipendentemente da come la transazione sia caratterizzata), il
-            Sorgente Corrispondente distribuito secondo le condizioni di questa
-            sezione deve essere accompagnato dalle Informazioni di
-            Installazione. Questa condizione non è richiesta se nè tu nè una
-            terza parte ha la possibilità di installare versioni modificate del
-            codice oggetto sul Prodotto Utente (ad esempio, se il programma è
-            installato su una ROM).</p>
-
-        <p>La condizione che richiede di fornire delle Informazioni di Installazione
-            non implica che venga fornito supporto, garanzia o aggiornamenti per un
-            programma che è stato modificato o installato dal destinatario, o per
-            il Prodotto Utente in cui esso è stato modificato o installato.
-            L'accesso ad una rete può essere negato se le modifiche apportate
-            impattano materialmente sull'operatività della rete o se violano le
-            regole e i protocolli di comunicazione attraverso la rete.</p>
-
-        <p>Il Sorgente Corrispondente distribuito, e le Informazioni di
-            Installazione fornite, in accordo con questa sezione, devono essere in
-            un formato che sia pubblicamente documentato (e con una implementazione
-            pubblicamente disponibile in formato di codice sorgente), e non devono
-            richiedere speciali password o chiavi per essere spacchettate, lette o
-            copiate.</p>
-
-        <h4>7. Condizioni Aggiuntive.</h4>
-
-        <p>Le “Condizioni Aggiuntive” sono condizioni che completano le
-            condizioni di questa Licenza permettendo delle eccezioni a una o più
-            delle condizioni sopra elencate. Le condizioni aggiuntive che sono
-            applicabili all'intero Programma devono essere considerate come se
-            fossero incluse in questa Licenza, a patto che esse siano valide secondo
-            le normative vigenti. Se alcune condizioni aggiuntive fanno riferimento
-            soltanto ad alcune parti del Programma, quelle parti possono essere
-            utilizzate separatamente sotto le stesse condizioni, ma l'intero
-            Programma rimane sottoposto a questa Licenza senza riferimento ad alcuna
-            condizione aggiuntiva.</p>
-
-        <p>Quando distribuisci una copia di un programma coperto da questa Licenza,
-            puoi, a tua discrezione, eliminare qualunque condizione aggiuntiva dalla
-            copia, o da parte di essa. (Le Condizioni Aggiuntive possono essere
-            scritte in maniera tale da richiedere la loro rimozione in certi casi di
-            modifica del Programma). Puoi aggiungere Condizioni Aggiuntive su
-            materiale, aggiunto da te ad un'opera coperta da questa Licenza, per il
-            quale hai o puoi garantire un'adeguata licenza di copyright.</p>
-
-        <p>Indipendentemente da qualunque altra condizione di questa Licenza, e per
-            il materiale che aggiungi ad un'opera coperta da questa Licenza, puoi
-            (se autorizzato dai legittimi detentori del copyright per il suddetto
-            materiale) aggiungere alle condizioni di questa Licenza delle condizioni
-            che:</p>
+        <p>A separable portion of the object code, whose source code is excluded
+            from the Corresponding Source as a System Library, need not be
+            included in conveying the object code work.</p>
+
+        <p>A “User Product” is either (1) a “consumer product”, which means any
+            tangible personal property which is normally used for personal, family,
+            or household purposes, or (2) anything designed or sold for incorporation
+            into a dwelling.  In determining whether a product is a consumer product,
+            doubtful cases shall be resolved in favor of coverage.  For a particular
+            product received by a particular user, “normally used” refers to a
+            typical or common use of that class of product, regardless of the status
+            of the particular user or of the way in which the particular user
+            actually uses, or expects or is expected to use, the product.  A product
+            is a consumer product regardless of whether the product has substantial
+            commercial, industrial or non-consumer uses, unless such uses represent
+            the only significant mode of use of the product.</p>
+
+        <p>“Installation Information” for a User Product means any methods,
+            procedures, authorization keys, or other information required to install
+            and execute modified versions of a covered work in that User Product from
+            a modified version of its Corresponding Source.  The information must
+            suffice to ensure that the continued functioning of the modified object
+            code is in no case prevented or interfered with solely because
+            modification has been made.</p>
+
+        <p>If you convey an object code work under this section in, or with, or
+            specifically for use in, a User Product, and the conveying occurs as
+            part of a transaction in which the right of possession and use of the
+            User Product is transferred to the recipient in perpetuity or for a
+            fixed term (regardless of how the transaction is characterized), the
+            Corresponding Source conveyed under this section must be accompanied
+            by the Installation Information.  But this requirement does not apply
+            if neither you nor any third party retains the ability to install
+            modified object code on the User Product (for example, the work has
+            been installed in ROM).</p>
+
+        <p>The requirement to provide Installation Information does not include a
+            requirement to continue to provide support service, warranty, or updates
+            for a work that has been modified or installed by the recipient, or for
+            the User Product in which it has been modified or installed.  Access to a
+            network may be denied when the modification itself materially and
+            adversely affects the operation of the network or violates the rules and
+            protocols for communication across the network.</p>
+
+        <p>Corresponding Source conveyed, and Installation Information provided,
+            in accord with this section must be in a format that is publicly
+            documented (and with an implementation available to the public in
+            source code form), and must require no special password or key for
+            unpacking, reading or copying.</p>
+
+        <h4>7. Additional Terms.</h4>
+
+        <p>“Additional permissions” are terms that supplement the terms of this
+            License by making exceptions from one or more of its conditions.
+            Additional permissions that are applicable to the entire Program shall
+            be treated as though they were included in this License, to the extent
+            that they are valid under applicable law.  If additional permissions
+            apply only to part of the Program, that part may be used separately
+            under those permissions, but the entire Program remains governed by
+            this License without regard to the additional permissions.</p>
+
+        <p>When you convey a copy of a covered work, you may at your option
+            remove any additional permissions from that copy, or from any part of
+            it.  (Additional permissions may be written to require their own
+            removal in certain cases when you modify the work.)  You may place
+            additional permissions on material, added by you to a covered work,
+            for which you have or can give appropriate copyright permission.</p>
+
+        <p>Notwithstanding any other provision of this License, for material you
+            add to a covered work, you may (if authorized by the copyright holders of
+            that material) supplement the terms of this License with terms:</p>
 
         <ul>
-            <li>a) Negano la garanzia o limitano la responsabilità del Programma in
-                maniera differente da quanto riportato nelle sezioni 15 e 16 di questa
-                Licenza; oppure</li>
-
-            <li>b) Richiedono il mantenimento di specifiche e circostanziate informative
-                legali o di note di attribuzione ad autori nel materiale o assieme
-                alle Adeguate Informazioni Legali mostrate dal Programma che lo
-                contiene; oppure</li>
-
-            <li>c) Proibiscono di fornire informazioni errate o ingannevoli sull'origine
-                e la provenienza del materiale in oggetto, o richiedono che versioni
-                modificate di tale materiale siano appositamente marcate in maniera
-                differente rispetto alla versione originale; oppure</li>
-
-            <li>d) Limitano l'utilizzo per scopi pubblicitari del nome dei detentori del
-                copyright o degli autori del materiale; oppure</li>
-
-            <li>e) Rifiutano di garantire diritti secondo le leggi sulla proprietà
-                intellettuale circa l'uso di nomi, marchi di fabbrica o similari;
-                oppure</li>
-
-            <li>f) Richiedono l'indennizzo dei detentori del copyright o degli autori del
-                materiale in oggetto da parte di chi distribuisce il materiale (o
-                versioni modificate dello stesso) con impegni contrattuali circa la
-                responsabilità nei confronti del destinatario, per qualunque
-                responsabilità che questi impegni contrattuali dovessero imporre
-                direttamente ai suddetti detentori del copyright e autori.</li>
+            <li>a) Disclaiming warranty or limiting liability differently from the
+                terms of sections 15 and 16 of this License; or</li>
+
+            <li>b) Requiring preservation of specified reasonable legal notices or
+                author attributions in that material or in the Appropriate Legal
+                Notices displayed by works containing it; or</li>
+
+            <li>c) Prohibiting misrepresentation of the origin of that material, or
+                requiring that modified versions of such material be marked in
+                reasonable ways as different from the original version; or</li>
+
+            <li>d) Limiting the use for publicity purposes of names of licensors or
+                authors of the material; or</li>
+
+            <li>e) Declining to grant rights under trademark law for use of some
+                trade names, trademarks, or service marks; or</li>
+
+            <li>f) Requiring indemnification of licensors and authors of that
+                material by anyone who conveys the material (or modified versions of
+                it) with contractual assumptions of liability to the recipient, for
+                any liability that these contractual assumptions directly impose on
+                those licensors and authors.</li>
         </ul>
 
-        <p>Tutte le altre condizioni addizionali non-permissive sono considerate
-            “ulteriori restrizioni”, secondo il significato specificato alla
-            sezione 10. Se il Programma o parti di esso contengono, all'atto della
-            ricezione dello stesso, informative che specificano che esso è
-            soggetto a questa Licenza assieme ad una condizione che è una
-            “ulteriore restrizione”, puoi rimuovere quest'ultima condizione. Se un
-            documento di licenza contiene ulteriori restrizioni ma permette di
-            rilicenziare o distribuire il Programma con questa Licenza, puoi
-            aggiungere al Programma del materiale coperto dalle condizioni di quel
-            documento di licenza, a patto che le ulteriori restrizioni non compaiano
-            nelle versioni rilicenziate o ridistribuite.</p>
-
-        <p>Se aggiungi ad un Programma coperto da questa Licenza delle condizioni
-            aggiuntive in accordo con questa sezione, devi aggiungere anche, nei
-            file sorgenti corrispondenti, un avviso che riassuma le condizioni
-            aggiuntive applicate a quei file, ovvero un avviso che specifichi dove
-            è possibile trovare copia delle condizioni aggiunte.</p>
-
-        <p>Tutte le Condizioni aggiuntive, permissive o non-permissive, devono
-            essere espresse nella forma di una licenza scritta e separata, o
-            espresse esplicitamente come eccezioni; in entrambi i casi valgono le
-            condizioni succitate.</p>
-
-        <h4>8. Cessazione di Licenza.</h4>
-
-        <p>Non puoi propagare o modificare un programma coperto da questa Licenza
-            in maniera diversa da quanto espressamente consentito da questa
-            Licenza. Qualunque tentativo di propagare o modificare altrimenti il
-            Programma è nullo, e provoca l'immediata cessazione dei diritti
-            garantiti da questa Licenza (compresi tutte le eventuali licenze di
-            brevetto garantite ai sensi del terzo paragrafo della sezione 11).</p>
-
-        <p>In ogni caso, se cessano tutte le violazioni di questa Licenza, allora
-            la tua licenza da parte di un dato detentore del copyright viene
-            ripristinata (a) in via cautelativa, a meno che e fino a quando il
-            detentore del copyright non cessa esplicitamente e definitivamente la
-            tua licenza, e (b) in via permanente se il detentore del copyright non
-            ti notifica in alcun modo la violazione entro 60 giorni dalla cessazione
-            della licenza.</p>
-
-        <p>Inoltre, la tua licenza da parte di un dato detentore del copyright
-            viene ripristinata in maniera permanente se il detentore del copyright ti
-            notifica la violazione in maniera adeguata, se questa è la prima volta
-            che ricevi una notifica di violazione di questa Licenza (per qualunque
-            Programma) dallo stesso detentore di copyright, e se rimedi alla
-            violazione entro 30 giorni dalla data di ricezione della notifica di
-            violazione.</p>
-
-        <p>La cessazione dei tuoi diritti come specificato in questa sezione non
-            provoca la cessazione delle licenze di terze parti che abbiano ricevuto
-            copie o diritti da te secondo questa Licenza. Se i tuoi diritti cessano
-            e non sono ristabiliti in via permanente, non hai diritto di ricevere
-            nuove licenze per lo stesso materiale, secondo quanto stabilito nella
-            sezione 10.</p>
-
-        <h4>9. L'ottenimento di copie non richiede l'accettazione della Licenza.</h4>
-
-        <p>Non sei obbligato ad accettare i termini di questa Licenza al solo fine
-            di ottenere o eseguire una copia del Programma. Similmente, propagazioni
-            collaterali di un Programma coperto da questa Licenza che occorrono come
-            semplice conseguenza dell'utilizzo di trasmissioni peer-to-peer per la
-            ricezione di una copia non richiedono l'accettazione della Licenza. In
-            ogni caso, solo e soltanto questa Licenza ti garantiscono il permesso di
-            propagare e modificare qualunque programma coperto da questa
-            Licenza. Queste azioni violano le leggi sul copyright nel caso in cui tu
-            non accetti questa Licenza. Pertanto, modificando o propagando un
-            programma coperto da questa Licenza, indichi implicitamente la tua
-            accettazione della Licenza.</p>
-
-        <h4>10. Licenza Automatica per i successivi destinatari.</h4>
-
-        <p>Ogni qual volta distribuisci un programma coperto da questa Licenza, il
-            destinatario riceve automaticamente una licenza, dal detentore
-            originario del copyright, di eseguire, modificare e propagare il
-            programma, nel rispetto di questa Licenza. Non sei ritenuto responsabile
-            del rispetto di questa Licenza da parte di terze parti.</p>
-
-        <p>Una “transazione d' entità” è una transazione che trasferisce il
-            controllo di una organizzazione, o sostanzialmente di tutti i suoi beni,
-            che suddivide una organizzazione o che fonde più organizzazioni. Se la
-            propagazione di un programma coperto da questa Licenza è conseguente
-            ad una transazione di entità, ciascuna parte che ha ruolo nella
-            transazione e che riceve una copia del programma riceve allo stesso tempo
-            qualsiasi licenza sul programma che i predecessori della parte
-            possedevano o potevano rilasciare nel rispetto del paragrafo precedente,
-            e in più il diritto di possesso del Sorgente Corrispondente del
-            programma dal predecessore in interesse, se il predecessore lo possiede
-            o se può ottenerlo senza troppe difficoltà.</p>
-
-        <p>Non puoi imporre nessuna ulteriore restrizione sull'esercizio dei
-            diritti garantiti o affermati da questa Licenza. Per esempio, non puoi
-            imporre un prezzo di licenza, una royalty, o altri costi per
-            l'esercizio dei diritti garantiti da questa Licenza, a non puoi dar
-            corso ad una controversia (ivi incluse le controversie incrociate o la
-            difesa in cause legali) affermando che siano stati violati dei
-            brevetti a causa della produzione, dell'uso, della vendita, della
-            messa in vendita o dell'importazione del Programma o di sue parti.</p>
-
-        <h4>11. Brevetti.</h4>
-
-        <p>Un “contribuente” è un detentore di copyright che autorizza l'uso
-            secondo questa Licenza di un Programma o di un'opera basata sul
-            Programma. L'opera così licenziata viene chiamata “versione del
-            contribuente”.</p>
-
-        <p>I “diritti essenziali di brevetto” da parte di un contribuente sono
-            tutti i diritti di brevetto che appartengono o che sono controllati dal
-            contribuente, che siano già acquisiti o che saranno acquisiti in
-            futuro, che possano essere violati in qualche maniera, consentita da
-            questa Licenza, generando, modificando o vendendo la versione del
-            contribuente, ma non includono i diritti che possano essere violati
-            soltanto come conseguenza di ulteriori modifiche alla versione del
-            contribuente. In relazione a questa definizione, il termine
-            “controllo” include il diritto di garantire sottolicenze di brevetto
-            in maniera consistente con le condizioni di questa Licenza.</p>
-
-        <p>Ciascun contribuente ti garantisce la licenza di brevetto sui diritti
-            essenziali di brevetto del contribuente stesso non-esclusiva, valida in
-            tutto il mondo, esente da royalty, di creare, usare, vendere, offrire in
-            vendita, importare e altrimenti eseguire, modificare e propagare i
-            contenuti della versione del contribuente.</p>
-
-        <p>Nei tre paragrafi successivi, con “licenza di brevetto” si intende
-            qualunque accordo o contratto, comunque denominato, di non
-            rivendicazione di un brevetto (come ad esempio un permesso esplicito di
-            utilizzare un brevetto o un accordo di rinuncia alla persecuzione per
-            violazione di brevetto). “Garantire” una tale licenza di brevetto ad
-            una parte significa portare a termine un tale accordo o contratto di non
-            rivendicazione di brevetto contro la parte.</p>
-
-        <p>Se distribuisci un programma coperto da questa Licenza, confidando
-            consapevolmente su una licenza di brevetto, e il Sorgente Corrispondente
-            per il programma non è reso disponibile per la copia, senza alcun
-            onere aggiuntivo e comunque nel rispetto delle condizioni di questa
-            Licenza, attraverso un server di rete pubblicamente accessibile o
-            tramite altri mezzi facilmente accessibili, allora devi (1) fare in modo
-            che il Sorgente Corrispondente sia reso disponibile come sopra, oppure
-            (2) fare in modo di rinunciare ai benefici della licenza di brevetto per
-            quel particolare programma, oppure (3) adoperarti, in maniera
-            consistente con le condizioni di questa Licenza, per estendere la
-            licenza di brevetto a tutti i destinatari successivi. “Confidare
-            consapevolmente” significa che tu sei attualmente cosciente che,
-            eccettuata la licenza di brevetto, la distribuzione da parte tua di un
-            programma protetto da questa Licenza in un paese, o l'utilizzo in un
-            paese del programma coperto da questa Licenza da parte di un
-            destinatario, può violare uno o più brevetti in quel paese che tu
-            hai ragione di ritenere validi.</p>
-
-        <p>Se, come conseguenza o in connessione con una singola transazione o
-            con un dato accordo, distribuisci, o fai in modo di distribuire, un
-            programma coperto da questa Licenza, e garantisci una licenza di
-            brevetto per alcune delle parti che ricevono il Programma
-            autorizzandole ad utilizzare, propagare, modificare o distribuire una
-            specifica copia del Programma, allora la licenza di brevetto che
-            fornisci è automaticamente estesa a tutti i destinatari del
-            Programma coperto da questa Licenza e delle opere basate sul
-            Programma.</p>
-
-        <p>Una licenza di brevetto è “discriminatoria” se non include
-            nell'ambito della sua copertura, proibisce l'esercizio, o è vincolata
-            al non-esercizio di uno o più dei diritti che sono specificatamente
-            garantiti da questa Licenza. Non puoi distribuire un Programma coperto
-            da questa Licenza se sei parte di un accordo con una terza parte la cui
-            attività comprende la distribuzione di software, secondo il quale tu
-            sei costretto ad un pagamento alla parte terza in funzione della tua
-            attività di distribuzione del Programma, e in conseguenza del quale la
-            parte terza garantisce, a qualunque delle parti che riceveranno il
-            Programma da te, una licenza di brevetto discriminatoria (a) assieme a
-            copie del Programma coperto da questa Licenza distribuite da te (o ad
-            altre copie fatte da codeste copie), oppure (b) principalmente per e in
-            connessione con specifici prodotti o raccolte di prodotti che contengono
-            il Programma, a meno che l'accordo non sia stato stipulato, o le licenze
-            di brevetto non siano state rilasciate, prima del 28 Marzo 2007.</p>
-
-        <p>Nessuna parte di questa Licenza può essere interpretata come atta ad
-            escludere o limitare gli effetti di qualunque altra licenza o altri
-            meccanismi di difesa dalla violazione che possano altrimenti essere resi
-            disponibili dalla normativa vigente in materia di brevetti.</p>
-
-        <h4>12. Nessuna resa di libertà altrui.</h4>
-
-        <p>Se ti vengono imposte delle condizioni (da un ordine giudiziario, da
-            un accordo o da qualunque altra eventualità) che contraddicono le
-            condizioni di questa Licenza, non sei in nessun modo esonerato dal
-            rispetto delle condizioni di questa Licenza. Se non puoi distribuire
-            un Programma coperto da questa Licenza per sottostare simultaneamente
-            agli obblighi derivanti da questa Licenza e ad altri obblighi
-            pertinenti, allora non puoi distribuire il Programma per nessun
-            motivo. Per esempio, se accetti delle condizioni che ti obbligano a
-            richiedere il pagamento di una royalty per le distribuzioni
-            successivamente effettuate da coloro ai quali hai distribuito il
-            Programma, l'unico modo per soddisfare sia queste condizioni che
-            questa Licenza è evitare del tutto la distribuzione del Programma.</p>
-
-        <h4>13. Utilizzo con la GNU Affero General Public License.</h4>
-
-        <p>Indipendentemente da qualunque altra condizione espressa da questa
-            Licenza, hai il permesso di collegare o combinare qualunque Programma
-            coperto da questa Licenza con un'opera rilasciata sotto la versione 3
-            della licenza GNU Affero General Public License, ottenendo un singolo
-            Programma derivato, e di distribuire il Programma risultante. Le
-            condizioni di questa Licenza continuano a valere per le parti
-            riguardanti il Programma che sono coperte da questa Licenza, mentre le
-            condizioni speciali della GNU Affero General Public License, sezione 13,
-            riguardanti l'interazione mediante rete, saranno applicate al Programma
-            così risultante.</p>
-
-        <h4>14. Versioni rivedute di questa Licenza.</h4>
-
-        <p>La Free Software Foundation può pubblicare delle versioni rivedute
-            e/o delle nuove versioni della GNU General Public License di tanto in
-            tanto. Tali versioni saranno simili, nello spirito, alla presente
-            versione, ma potranno differire nei dettagli al fine di affrontare
-            nuovi problemi e nuove situazioni.</p>
-
-        <p>A ciascuna versione viene assegnato un numero identificativo di
-            versione. Se il Programma specifica che si applica a sè stesso una
-            certa versione della GNU General Public License, “o qualunque altra
-            versione successiva”, hai la possibilità di sottostare alle
-            condizioni di quella specifica versione o di qualunque altra versione
-            successiva pubblicata dalla Free Software Foundation. Se il Programma
-            non specifica un numero di versione della GNU General Public License,
-            puoi scegliere qualunque versione della GNU General Public License
-            pubblicata dalla Free Software Foundation.</p>
-
-        <p>Se il Programma specifica che un sostituto o un procuratore può
-            decidere quali versioni future della GNU General Public License posso
-            essere utilizzate, allora tale scelta di accettazione di una data
-            versione ti autorizza, in maniera permanente, ad utilizzare quella
-            versione della Licenza per il Programma.</p>
-
-        <p>Versioni successive della Licenza possono garantire diritti aggiuntivi o
-            leggermente differenti. Ad ogni modo, nessun obbligo aggiuntivo viene
-            imposto agli autori o ai detentori di copyright come conseguenza della
-            tua scelta di adottare una versione successiva della Licenza.</p>
-
-        <h4>15. Rinuncia alla Garanzia.</h4>
-
-        <p>NON C'E' NESSUNA GARANZIA PER IL PROGRAMMA, PER QUANTO CONSENTITO DALLE
-            VIGENTI NORMATIVE. ECCETTO QUANDO ALTRIMENTI STABILITO PER ISCRITTO, I
-            DETENTORI DEL COPYRIGHT E/O LE ALTRE PARTI FORNISCONO IL PROGRAMMA
-            “COSI' COME È” SENZA GARANZIA DI ALCUN TIPO, NE' ESPRESSA NE'
-            IMPLICITA, INCLUSE, MA NON LIMITATE A, LE GARANZIE DI COMMERCIABILITA' O
-            DI UTILIZZABILITA' PER UN PARTICOLARE SCOPO. L'INTERO RISCHIO
-            CONCERNENTE LA QUALITA' E LE PRESTAZIONI DEL PROGRAMMA E' DEL
-            LICENZIATARIO. SE IL PROGRAMMA DOVESSE RISULTARE DIFETTOSO, IL
-            LICENZIATARIO SI ASSUME I COSTI DI MANUTENZIONE, RIPARAZIONE O
-            CORREZIONE.</p>
-
-        <h4>16. Limitazione di Responsabilità.</h4>
-
-        <p>IN NESSUN CASO, A MENO CHE NON SIA RICHIESTO DALLA NORMATIVA VIGENTE
-            O CONCORDATO PER ISCRITTO, I DETENTORI DEL COPYRIGHT, O QUALUNQUE
-            ALTRA PARTE CHE MODIICA E/O DISTRIBUISCE IL PROGRAMMA SECONDO LE
-            CONDIZIONI PRECEDENTI, POSSONO ESSERE RITENUTI RESPONSABILI NEI
-            CONFRONTI DEL LICENZIATARIO PER DANNI, INCLUSO QUALUNQUE
-            DANNEGGIAMENTO GENERICO, SPECIALE, INCIDENTALE O CONSEQUENZIALE
-            DOVUTO ALL'USO O ALL'IMPOSSIBILITA' D'USO DEL PROGRAMMA (INCLUSI, MA
-            NON LIMITATI A, LE PERDITE DI DATI, LA CORRUZIONE DI DATI, LE
-            PERDITE SOSTENUTE DAL LICENZIATARIO O DA TERZE PARTI O
-            L'IMPOSSIBILITA' DEL PROGRAMMA A FUNZIONARE ASSIEME AD ALTRI
-            PROGRAMMI), ANCHE NEL CASO IN CUI IL DETENTORE O LE ALTRE PARTI
-            SIANO STATI AVVISATI CIRCA LA POSSIBILITA' DI TALI DANNEGGIAMENTI.</p>
-
-        <h4>17. Interpretazione delle Sezioni 15 e 16.</h4>
-
-        <p>Se la dichiarazione di garanzia e la limitazione di responsabilità
-            fornite precedentemente non hanno effetto legale in un paese a causa
-            delle loro condizioni, le corti di giustizia devono applicare la norma
-            locale che più si avvicini al rifiuto assoluto di qualsivoglia
-            responsabilità civile relativa al Programma, a meno che una garanzia o
-            una assunzione di responsabilità scritta non accompagni una copia del
-            programma ottenuta dietro pagamento.</p>
-
-        <p>FINE DEI TERMINI E DELLE CONDIZIONI</p>
-
-        <h3>Come applicare questre condizioni di Licenza ai vostri programmi</h3>
-
-        <p>Se sviluppi un nuovo programma, e vuoi che esso sia della massima utilità,
-            il modo migliore è quello di renderlo software libero, in modo che chiunque
-            possa ridistribuirlo e modificarlo secondo i termini di questa Licenza.</p>
-
-        <p>Per fare ciò, allega le seguenti note informative al programma.
-            Il modo migliore è quello di inserirle all’inizio di ciascun file sorgente,
-            al fine di rimarcare adeguatamente la mancanza di garanzia; ciascun file dovrebbe inoltre contenere
-            la dichiarazione di copyright e un riferimento al posto in cui è possibile ottenere
-            la versione completa delle note informative.</p>
-
-<pre>&lt;one line to give the program’s name
+        <p>All other non-permissive additional terms are considered “further
+            restrictions” within the meaning of section 10.  If the Program as you
+            received it, or any part of it, contains a notice stating that it is
+            governed by this License along with a term that is a further
+            restriction, you may remove that term.  If a license document contains
+            a further restriction but permits relicensing or conveying under this
+            License, you may add to a covered work material governed by the terms
+            of that license document, provided that the further restriction does
+            not survive such relicensing or conveying.</p>
+
+        <p>If you add terms to a covered work in accord with this section, you
+            must place, in the relevant source files, a statement of the
+            additional terms that apply to those files, or a notice indicating
+            where to find the applicable terms.</p>
+
+        <p>Additional terms, permissive or non-permissive, may be stated in the
+            form of a separately written license, or stated as exceptions;
+            the above requirements apply either way.</p>
+
+        <h4>8. Termination.</h4>
+
+        <p>You may not propagate or modify a covered work except as expressly
+            provided under this License.  Any attempt otherwise to propagate or
+            modify it is void, and will automatically terminate your rights under
+            this License (including any patent licenses granted under the third
+            paragraph of section 11).</p>
+
+        <p>However, if you cease all violation of this License, then your
+            license from a particular copyright holder is reinstated (a)
+            provisionally, unless and until the copyright holder explicitly and
+            finally terminates your license, and (b) permanently, if the copyright
+            holder fails to notify you of the violation by some reasonable means
+            prior to 60 days after the cessation.</p>
+
+        <p>Moreover, your license from a particular copyright holder is
+            reinstated permanently if the copyright holder notifies you of the
+            violation by some reasonable means, this is the first time you have
+            received notice of violation of this License (for any work) from that
+            copyright holder, and you cure the violation prior to 30 days after
+            your receipt of the notice.</p>
+
+        <p>Termination of your rights under this section does not terminate the
+            licenses of parties who have received copies or rights from you under
+            this License.  If your rights have been terminated and not permanently
+            reinstated, you do not qualify to receive new licenses for the same
+            material under section 10.</p>
+
+        <h4>9. Acceptance Not Required for Having Copies.</h4>
+
+        <p>You are not required to accept this License in order to receive or
+            run a copy of the Program.  Ancillary propagation of a covered work
+            occurring solely as a consequence of using peer-to-peer transmission
+            to receive a copy likewise does not require acceptance.  However,
+            nothing other than this License grants you permission to propagate or
+            modify any covered work.  These actions infringe copyright if you do
+            not accept this License.  Therefore, by modifying or propagating a
+            covered work, you indicate your acceptance of this License to do so.</p>
+
+        <h4>10. Automatic Licensing of Downstream Recipients.</h4>
+
+        <p>Each time you convey a covered work, the recipient automatically
+            receives a license from the original licensors, to run, modify and
+            propagate that work, subject to this License.  You are not responsible
+            for enforcing compliance by third parties with this License.</p>
+
+        <p>An “entity transaction” is a transaction transferring control of an
+            organization, or substantially all assets of one, or subdividing an
+            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
+            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.</p>
+
+        <p>You may not impose any further restrictions on the exercise of the
+            rights granted or affirmed under this License.  For example, you may
+            not impose a license fee, royalty, or other charge for exercise of
+            rights granted under this License, and you may not initiate litigation
+            (including a cross-claim or counterclaim in a lawsuit) alleging that
+            any patent claim is infringed by making, using, selling, offering for
+            sale, or importing the Program or any portion of it.</p>
+
+        <h4>11. Patents.</h4>
+
+        <p>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”.</p>
+
+        <p>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,
+            but do not include claims that would be infringed only as a
+            consequence of further modification of the contributor version.  For
+            purposes of this definition, “control” includes the right to grant
+            patent sublicenses in a manner consistent with the requirements of
+            this License.</p>
+
+        <p>Each contributor grants you a non-exclusive, worldwide, royalty-free
+            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.</p>
+
+        <p>In the following three paragraphs, a “patent license” is any express
+            agreement or commitment, however denominated, not to enforce a patent
+            (such as an express permission to practice a patent or covenant not to
+            sue for patent infringement).  To “grant” such a patent license to a
+            party means to make such an agreement or commitment not to enforce a
+            patent against the party.</p>
+
+        <p>If you convey a covered work, knowingly relying on a patent license,
+            and the Corresponding Source of the work is not available for anyone
+            to copy, free of charge and under the terms of this License, through a
+            publicly available network server or other readily accessible means,
+            then you must either (1) cause the Corresponding Source to be so
+            available, or (2) arrange to deprive yourself of the benefit of the
+            patent license for this particular work, or (3) arrange, in a manner
+            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
+            in a country, would infringe one or more identifiable patents in that
+            country that you have reason to believe are valid.</p>
+
+        <p>If, pursuant to or in connection with a single transaction or
+            arrangement, you convey, or propagate by procuring conveyance of, a
+            covered work, and grant a patent license to some of the parties
+            receiving the covered work authorizing them to use, propagate, modify
+            or convey a specific copy of the covered work, then the patent license
+            you grant is automatically extended to all recipients of the covered
+            work and works based on it.</p>
+
+        <p>A patent license is “discriminatory” if it does not include within
+            the scope of its coverage, prohibits the exercise of, or is
+            conditioned on the non-exercise of one or more of the rights that are
+            specifically granted under this License.  You may not convey a covered
+            work if you are a party to an arrangement with a third party that is
+            in the business of distributing software, under which you make payment
+            to the third party based on the extent of your activity of conveying
+            the work, and under which the third party grants, to any of the
+            parties who would receive the covered work from you, a discriminatory
+            patent license (a) in connection with copies of the covered work
+            conveyed by you (or copies made from those copies), or (b) primarily
+            for and in connection with specific products or compilations that
+            contain the covered work, unless you entered into that arrangement,
+            or that patent license was granted, prior to 28 March 2007.</p>
+
+        <p>Nothing in this License shall be construed as excluding or limiting
+            any implied license or other defenses to infringement that may
+            otherwise be available to you under applicable patent law.</p>
+
+        <h4>12. No Surrender of Others’ Freedom.</h4>
+
+        <p>If conditions are imposed on you (whether by court order, agreement or
+            otherwise) that contradict the conditions of this License, they do not
+            excuse you from the conditions of this License.  If you cannot convey a
+            covered work so as to satisfy simultaneously your obligations under this
+            License and any other pertinent obligations, then as a consequence you may
+            not convey it at all.  For example, if you agree to terms that obligate you
+            to collect a royalty for further conveying from those to whom you convey
+            the Program, the only way you could satisfy both those terms and this
+            License would be to refrain entirely from conveying the Program.</p>
+
+        <h4>13. Use with the GNU Affero General Public License.</h4>
+
+        <p>Notwithstanding any other provision of this License, you have
+            permission to link or combine any covered work with a work licensed
+            under version 3 of the GNU Affero General Public License into a single
+            combined work, and to convey the resulting work.  The terms of this
+            License will continue to apply to the part which is the covered work,
+            but the special requirements of the GNU Affero General Public License,
+            section 13, concerning interaction through a network will apply to the
+            combination as such.</p>
+
+        <h4>14. Revised Versions of this License.</h4>
+
+        <p>The Free Software Foundation may publish revised and/or new versions of
+            the GNU General Public License from time to time.  Such new versions will
+            be similar in spirit to the present version, but may differ in detail to
+            address new problems or concerns.</p>
+
+        <p>Each version is given a distinguishing version number.  If the
+            Program specifies that a certain numbered version of the GNU General
+            Public License “or any later version” applies to it, you have the
+            option of following the terms and conditions either of that numbered
+            version or of any later version published by the Free Software
+            Foundation.  If the Program does not specify a version number of the
+            GNU General Public License, you may choose any version ever published
+            by the Free Software Foundation.</p>
+
+        <p>If the Program specifies that a proxy can decide which future
+            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.</p>
+
+        <p>Later license versions may give you additional or different
+            permissions.  However, no additional obligations are imposed on any
+            author or copyright holder as a result of your choosing to follow a
+            later version.</p>
+
+        <h4>15. Disclaimer of Warranty.</h4>
+
+        <p>THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+            APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+            HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY
+            OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+            THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+            PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+            IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+            ALL NECESSARY SERVICING, REPAIR OR CORRECTION.</p>
+
+        <h4>16. Limitation of Liability.</h4>
+
+        <p>IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+            WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+            THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+            GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+            USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+            DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+            PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+            EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+            SUCH DAMAGES.</p>
+
+        <h4>17. Interpretation of Sections 15 and 16.</h4>
+
+        <p>If the disclaimer of warranty and limitation of liability provided
+            above cannot be given local legal effect according to their terms,
+            reviewing courts shall apply local law that most closely approximates
+            an absolute waiver of all civil liability in connection with the
+            Program, unless a warranty or assumption of liability accompanies a
+            copy of the Program in return for a fee.</p>
+
+        <p>END OF TERMS AND CONDITIONS</p>
+
+        <h3>How to Apply These Terms to Your New Programs</h3>
+
+        <p>If you develop a new program, and you want it to be of the greatest
+            possible use to the public, the best way to achieve this is to make it
+            free software which everyone can redistribute and change under these terms.</p>
+
+        <p>To do so, attach the following notices to the program.  It is safest
+            to attach them to the start of each source file to most effectively
+            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.</p>
+
+        <pre>&lt;one line to give the program’s name
 and a brief idea of what it does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
@@ -941,36 +850,38 @@ 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
 &lt;http://www.gnu.org/licenses/&gt;.</pre>
 
-        <p>Inoltre, aggiungi le informazioni necessarie a contattarti via posta ordinaria o via posta elettronica.</p>
+        <p>Also add information on how to contact you by electronic and paper mail.</p>
 
-        <p>Se il programma interagisce mediante terminale, fai in modo che visualizzi,
-            quando viene avviato in modalità interattiva, un breve messaggio come quello che segue:</p>
+        <p>If the program does terminal interaction, make it output a short
+            notice like this when it starts in an interactive mode:</p>
 
-<pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
+        <pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
 &lt;name of author&gt;
-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.</pre>
 
-        <p>Gli ipotetici comandi `show w' e `show c' devono visualizzare le parti corrispondenti
-            della GNU General Public License. Naturalmente i comandi del tuo programma potrebbero essere differenti;
-            per una interfaccia di tipo GUI, dovresti usare un bottone “About” o “Info”.</p>
+        <p>The hypothetical commands `show w' and `show c' should show the appropriate
+            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”.</p>
 
-        <p>Devi inoltre fare in modo che il tuo datore di lavoro (se lavori come programmatore presso terzi) o la tua scuola,
-            eventualmente, firmino una “rinuncia al copyright” sul programma, se necessario.
-            Per maggiori informazioni su questo punto, e su come applicare e rispettare la GNU GPL, consultare la pagina
+        <p>You should also get your employer (if you work as a programmer) or school,
+            if any, to sign a “copyright disclaimer” for the program, if necessary.
+            For more information on this, and how to apply and follow the GNU GPL, see
             <<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>>.</p>
 
-        <p>La GNU General Public License non consente di incorporare il programma all’interno di software proprietario.
-            Se il tuo programma è una libreria di funzioni, potresti ritenere più opportuno consentire il collegamento
-            tra software proprietario e la tua libreria. Se è questo ciò che vuoi, allora utilizza la GNU Lesser General Public License
-            anziché questa Licenza, ma prima leggi
+        <p>The GNU General Public License does not permit incorporating your program
+            into proprietary programs.  If your program is a subroutine library, you
+            may consider it more useful to permit linking proprietary applications with
+            the library.  If this is what you want to do, use the GNU Lesser General
+            Public License instead of this License.  But first, please read
             <<a href="http://www.gnu.org/philosophy/why-not-lgpl.html">http://www.gnu.org/philosophy/why-not-lgpl.html</a>>.</p>
 
         <hr/>
@@ -1621,19 +1532,20 @@ for details.</pre>
             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.</p>
 
-<pre>&lt;one line to give the program's name
-and a brief idea of what it does.&gt;
+        <pre>&lt;one line to give the program's
+name and a brief idea of what it
+does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
@@ -1860,15 +1772,15 @@ If not, see
             on the same “printed page” as the copyright notice for easier
             identification within third-party archives.</p>
 
-<pre>Copyright [yyyy] [name of copyright
+        <pre>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
index 94df5f90748a2830e4cd920c6c650f9b251aabda..cc93f346dad0cca5451590f9f167d9314b9eb68a 100644 (file)
         <p><svg class="icon"><use href="../shared_images/aod_tablet_rounded_grade200.svg#icon"/></svg> aod_tablet_rounded_grade200.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_back.svg#icon"/></svg> arrow_back.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_forward.svg#icon"/></svg> arrow_forward.</p>
+        <p><svg class="icon"><use href="../shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> bookmark_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/bookmarks.svg#icon"/></svg> bookmarks.</p>
         <p><svg class="icon"><use href="../shared_images/bug_report.svg#icon"/></svg> bug_report.</p>
         <p><svg class="icon"><use href="../shared_images/call_to_action.svg#icon"/></svg> call_to_action.</p>
         <p><svg class="icon"><use href="../shared_images/file_download.svg#icon"/></svg> file_download.</p>
         <p><svg class="icon"><use href="../shared_images/find_in_page.svg#icon"/></svg> find_in_page.</p>
         <p><svg class="icon"><use href="../shared_images/folder.svg#icon"/></svg> folder.</p>
+        <p><svg class="icon"><use href="../shared_images/folder_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> folder_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/home.svg#icon"/></svg> home.</p>
         <p><svg class="icon"><use href="../shared_images/image.svg#icon"/></svg> image.</p>
         <p><svg class="icon"><use href="../shared_images/import_contacts.svg#icon"/></svg> import_contacts.</p>
             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.</p>
 
-<pre>&lt;one line to give the program’s name
+        <pre>&lt;one line to give the program’s name
 and a brief idea of what it does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
@@ -843,7 +845,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
 &lt;http://www.gnu.org/licenses/&gt;.</pre>
 
         <p>Also add information on how to contact you by electronic and paper mail.</p>
@@ -851,12 +854,12 @@ along with this program.  If not, see
         <p>If the program does terminal interaction, make it output a short
             notice like this when it starts in an interactive mode:</p>
 
-<pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
+        <pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
 &lt;name of author&gt;
-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.</pre>
 
@@ -1524,19 +1527,20 @@ for details.</pre>
             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.</p>
 
-<pre>&lt;one line to give the program's name
-and a brief idea of what it does.&gt;
+        <pre>&lt;one line to give the program's
+name and a brief idea of what it
+does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
@@ -1763,15 +1767,15 @@ If not, see
             on the same “printed page” as the copyright notice for easier
             identification within third-party archives.</p>
 
-<pre>Copyright [yyyy] [name of copyright
+        <pre>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
index ea12fd22727948380e9aa2797c6207aac6939848..4c99e7ba0d4ce6c81a9b793a91de3260734beb94 100644 (file)
         <p><svg class="icon"><use href="../shared_images/aod_tablet_rounded_grade200.svg#icon"/></svg> aod_tablet_rounded_grade200.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_back.svg#icon"/></svg> arrow_back.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_forward.svg#icon"/></svg> arrow_forward.</p>
+        <p><svg class="icon"><use href="../shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> bookmark_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/bookmarks.svg#icon"/></svg> bookmarks.</p>
         <p><svg class="icon"><use href="../shared_images/bug_report.svg#icon"/></svg> bug_report.</p>
         <p><svg class="icon"><use href="../shared_images/call_to_action.svg#icon"/></svg> call_to_action.</p>
         <p><svg class="icon"><use href="../shared_images/file_download.svg#icon"/></svg> file_download.</p>
         <p><svg class="icon"><use href="../shared_images/find_in_page.svg#icon"/></svg> find_in_page.</p>
         <p><svg class="icon"><use href="../shared_images/folder.svg#icon"/></svg> folder.</p>
+        <p><svg class="icon"><use href="../shared_images/folder_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> folder_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/home.svg#icon"/></svg> home.</p>
         <p><svg class="icon"><use href="../shared_images/image.svg#icon"/></svg> image.</p>
         <p><svg class="icon"><use href="../shared_images/import_contacts.svg#icon"/></svg> import_contacts.</p>
             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.</p>
 
-<pre>&lt;one line to give the program’s name
+        <pre>&lt;one line to give the program’s name
 and a brief idea of what it does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
 &lt;http://www.gnu.org/licenses/&gt;.</pre>
 
         <p>Also add information on how to contact you by electronic and paper mail.</p>
@@ -848,12 +851,12 @@ along with this program.  If not, see
         <p>If the program does terminal interaction, make it output a short
             notice like this when it starts in an interactive mode:</p>
 
-<pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
+        <pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
 &lt;name of author&gt;
-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.</pre>
 
@@ -1521,19 +1524,20 @@ for details.</pre>
             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.</p>
 
-<pre>&lt;one line to give the program's name
-and a brief idea of what it does.&gt;
+        <pre>&lt;one line to give the program's
+name and a brief idea of what it
+does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
@@ -1760,15 +1764,15 @@ If not, see
             on the same “printed page” as the copyright notice for easier
             identification within third-party archives.</p>
 
-<pre>Copyright [yyyy] [name of copyright
+        <pre>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
index 8b6cb91208e5bd9cd9deaddb60b504b64d64d909..4e9b4dfc8cab908ab48ab8220c38c9d3e8404248 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 `arrow_forward`, which is part of the Android Material icon set and is released under the Apache License 2.0.
+  It is a modified version of `arrow_forward`, 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/bookmark_rounded_fill0_weight400_grade0_24px.svg b/app/src/main/assets/shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg
new file mode 100644 (file)
index 0000000..232a38f
--- /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 `bookmark_rounded_fill0_weight400_grade0_24px`, 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="m480-240-168 72q-40 17-76-6.5T200-241v-519q0-33 23.5-56.5T280-840h400q33 0 56.5 23.5T760-760v519q0 43-36 66.5t-76 6.5l-168-72Zm0-88 200 86v-518H280v518l200-86Zm0-432H280h400-200Z" />
+</svg>
index c1f2129807f9eb0769b391ab7f1eae4ce622e473..0b636ad774a4f652f004c061effa0b3bf7591dbb 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 `bookmarks`, which is part of the Android Material icon set and is released under the Apache License 2.0.
+  It is a modified version of `bookmarks`, 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/folder_rounded_fill0_weight400_grade0_24px.svg b/app/src/main/assets/shared_images/folder_rounded_fill0_weight400_grade0_24px.svg
new file mode 100644 (file)
index 0000000..447a78f
--- /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 `folder_rounded_fill0_weight400_grade0_24px`, 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="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h207q16 0 30.5 6t25.5 17l57 57h320q33 0 56.5 23.5T880-640v400q0 33-23.5 56.5T800-160H160Zm0-80h640v-400H447l-80-80H160v480Zm0 0v-480 480Z" />
+</svg>
index 43a4a06599fefcd57b506db3e9d4136fe070364c..01adbba5d7e310758fd329747cf0ba4a5c8e41af 100644 (file)
         <p><svg class="icon"><use href="../shared_images/aod_tablet_rounded_grade200.svg#icon"/></svg> aod_tablet_rounded_grade200.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_back.svg#icon"/></svg> arrow_back.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_forward.svg#icon"/></svg> arrow_forward.</p>
+        <p><svg class="icon"><use href="../shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> bookmark_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/bookmarks.svg#icon"/></svg> bookmarks.</p>
         <p><svg class="icon"><use href="../shared_images/bug_report.svg#icon"/></svg> bug_report.</p>
         <p><svg class="icon"><use href="../shared_images/call_to_action.svg#icon"/></svg> call_to_action.</p>
         <p><svg class="icon"><use href="../shared_images/file_download.svg#icon"/></svg> file_download.</p>
         <p><svg class="icon"><use href="../shared_images/find_in_page.svg#icon"/></svg> find_in_page.</p>
         <p><svg class="icon"><use href="../shared_images/folder.svg#icon"/></svg> folder.</p>
+        <p><svg class="icon"><use href="../shared_images/folder_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> folder_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/home.svg#icon"/></svg> home.</p>
         <p><svg class="icon"><use href="../shared_images/image.svg#icon"/></svg> image.</p>
         <p><svg class="icon"><use href="../shared_images/import_contacts.svg#icon"/></svg> import_contacts.</p>
             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.</p>
 
-<pre>&lt;one line to give the program’s name
+        <pre>&lt;one line to give the program’s name
 and a brief idea of what it does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
 &lt;http://www.gnu.org/licenses/&gt;.</pre>
 
         <p>Also add information on how to contact you by electronic and paper mail.</p>
@@ -849,12 +852,12 @@ along with this program.  If not, see
         <p>If the program does terminal interaction, make it output a short
             notice like this when it starts in an interactive mode:</p>
 
-<pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
+        <pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
 &lt;name of author&gt;
-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.</pre>
 
@@ -1522,19 +1525,20 @@ for details.</pre>
             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.</p>
 
-<pre>&lt;one line to give the program's name
-and a brief idea of what it does.&gt;
+        <pre>&lt;one line to give the program's
+name and a brief idea of what it
+does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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.</p>
 
-<pre>Copyright [yyyy] [name of copyright
+        <pre>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
index d5f4a6b2bb438a8ca55d62bb05c28f5a69f4d656..8855c8c2dcc32b8c18c77f5161a29ea380dffbc7 100644 (file)
         <p><svg class="icon"><use href="../shared_images/aod_tablet_rounded_grade200.svg#icon"/></svg> aod_tablet_rounded_grade200.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_back.svg#icon"/></svg> arrow_back.</p>
         <p><svg class="icon"><use href="../shared_images/arrow_forward.svg#icon"/></svg> arrow_forward.</p>
+        <p><svg class="icon"><use href="../shared_images/bookmark_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> bookmark_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/bookmarks.svg#icon"/></svg> bookmarks.</p>
         <p><svg class="icon"><use href="../shared_images/bug_report.svg#icon"/></svg> bug_report.</p>
         <p><svg class="icon"><use href="../shared_images/call_to_action.svg#icon"/></svg> call_to_action.</p>
         <p><svg class="icon"><use href="../shared_images/file_download.svg#icon"/></svg> file_download.</p>
         <p><svg class="icon"><use href="../shared_images/find_in_page.svg#icon"/></svg> find_in_page.</p>
         <p><svg class="icon"><use href="../shared_images/folder.svg#icon"/></svg> folder.</p>
+        <p><svg class="icon"><use href="../shared_images/folder_rounded_fill0_weight400_grade0_24px.svg#icon"/></svg> folder_<wbr>rounded_<wbr>fill0_<wbr>weight400_<wbr>grade0_<wbr>24px.</p>
         <p><svg class="icon"><use href="../shared_images/home.svg#icon"/></svg> home.</p>
         <p><svg class="icon"><use href="../shared_images/image.svg#icon"/></svg> image.</p>
         <p><svg class="icon"><use href="../shared_images/import_contacts.svg#icon"/></svg> import_contacts.</p>
             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.</p>
 
-<pre>&lt;one line to give the program’s name
-and a brief idea of what it does.&gt;
+<pre>&lt;one line to give the program’s
+name and a brief idea of what it
+does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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
 &lt;http://www.gnu.org/licenses/&gt;.</pre>
 
         <p>Also add information on how to contact you by electronic and paper mail.</p>
@@ -849,12 +853,12 @@ along with this program.  If not, see
         <p>If the program does terminal interaction, make it output a short
             notice like this when it starts in an interactive mode:</p>
 
-<pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
+        <pre>&lt;program&gt;  Copyright (C) &lt;year&gt;
 &lt;name of author&gt;
-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.</pre>
 
@@ -1522,19 +1526,20 @@ for details.</pre>
             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.</p>
 
-<pre>&lt;one line to give the program's name
-and a brief idea of what it does.&gt;
+        <pre>&lt;one line to give the program's
+name and a brief idea of what it
+does.&gt;
 Copyright (C) &lt;year&gt;  &lt;name of
 author&gt;
 
 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.</p>
 
-<pre>Copyright [yyyy] [name of copyright
+        <pre>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
index 737d6393be75dadd936d94968a211f58ab41f52a..fe7d080854cd60654136491e3b17d3343a53230c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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>.
  *
@@ -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<EditText>(R.id.create_bookmark_name_edittext)
-        val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
+        // Get handles for the views from the dialog fragment.
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
+        val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
+        val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
+
+        // Get the strings from the edit texts.
+        val bookmarkNameString = bookmarkNameEditText.text.toString()
+        val bookmarkUrlString = bookmarkUrlEditText.text.toString()
 
-        // 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<RadioButton>(R.id.default_folder_icon_radiobutton)
+        val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_folder_icon_imageview)
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
         val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
-        val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
-        val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
 
-        // Get new folder name string.
+        // Get the folder name string.
         val folderNameString = folderNameEditText.text.toString()
 
-        // Set the folder icon bitmap according to the dialog.
-        val folderIconBitmap = if (defaultIconRadioButton.isChecked) {  // Use the default folder icon.
-            // Get the default folder icon drawable.
-            val folderIconDrawable = defaultIconImageView.drawable
+        // Get the selected folder icon drawable.
+        val folderIconDrawable = if (defaultFolderIconRadioButton.isChecked)  // Use the default folder icon.
+            defaultFolderIconImageView.drawable
+        else if (webpageFavoriteIconRadioButton.isChecked)  // Use the webpage favorite icon.
+            webpageFavoriteIconImageView.drawable
+        else  // Use the custom icon.
+            customIconImageView.drawable
 
-            // Convert the folder icon drawable to a bitmap drawable.
-            val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
+        // Cast the folder icon bitmap to a bitmap drawable.
+        val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
 
-            // Convert the folder icon bitmap drawable to a bitmap.
-            folderIconBitmapDrawable.bitmap
-        } else {  // Use the WebView favorite icon.
-            // Copy the favorite icon bitmap to the folder icon bitmap.
-            favoriteIconBitmap
-        }
+        // Convert the folder icon bitmap drawable to a bitmap.
+        val folderIconBitmap = folderIconBitmapDrawable.bitmap
 
         // Create a folder icon byte array output stream.
         val folderIconByteArrayOutputStream = ByteArrayOutputStream()
@@ -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<RadioButton>(R.id.current_icon_radiobutton)
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
         val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
         val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
-        val currentIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
 
-        // Get the bookmark strings.
+        // Get the strings from the edit texts.
         val bookmarkNameString = bookmarkNameEditText.text.toString()
         val bookmarkUrlString = bookmarkUrlEditText.text.toString()
 
@@ -815,6 +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<RadioButton>(R.id.current_icon_radiobutton)
-        val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
-        val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
-        val editFolderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
+        val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_folder_icon_radiobutton)
+        val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_folder_icon_imageview)
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
+        val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
 
         // Get the new folder name.
-        val newFolderName = editFolderNameEditText.text.toString()
+        val newFolderName = folderNameEditText.text.toString()
 
-        // Check if the favorite icon has changed.
+        // Check if the folder icon has changed.
         if (currentFolderIconRadioButton.isChecked) {  // Only the name has changed.
             // Update the name in the database.
             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderName)
         } else {  // The icon has changed.
-            // Populate the new folder icon bitmap.
-            val folderIconBitmap: Bitmap = if (defaultFolderIconRadioButton.isChecked) {
-                // Get the default folder icon drawable.
-                val folderIconDrawable = defaultFolderIconImageView.drawable
-
-                // Convert the folder icon drawable to a bitmap drawable.
-                val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
-
-                // Convert the folder icon bitmap drawable to a bitmap.
-                folderIconBitmapDrawable.bitmap
-            } else {  // Use the WebView favorite icon.
-                // Copy the favorite icon bitmap to the folder icon bitmap.
-                favoriteIconBitmap
-            }
-
-            // Create a folder icon byte array output stream.
+            // Get the selected folder icon drawable.
+            val folderIconDrawable = if (defaultFolderIconRadioButton.isChecked)  // The default folder icon is checked.
+                defaultFolderIconImageView.drawable
+            else if (webpageFavoriteIconRadioButton.isChecked)  // The webpage favorite icon is checked.
+                webpageFavoriteIconImageView.drawable
+            else  // The custom icon is checked.
+                customIconImageView.drawable
+
+            // Convert the folder icon drawable to a bitmap.  Once the minimum API >= 33, this can use Bitmap.Config.RGBA_1010102.
+            val folderIconBitmap = folderIconDrawable.toBitmap(128, 128, Bitmap.Config.ARGB_8888)
+
+            // Create a new folder icon byte array output stream.
             val newFolderIconByteArrayOutputStream = ByteArrayOutputStream()
 
             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
index 227c4149846db099a471ad573ab39418dc98d277..b9f161e8c154fd8a07c4df292df35348f7d21efd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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>.
  *
@@ -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<RadioButton>(R.id.current_icon_radiobutton)
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
         val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
         val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
         val folderSpinner = dialog.findViewById<Spinner>(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<RadioButton>(R.id.current_icon_radiobutton)
-        val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
-        val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
+        val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_folder_icon_radiobutton)
+        val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_folder_icon_imageview)
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
         val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
         val parentFolderSpinner = dialog.findViewById<Spinner>(R.id.parent_folder_spinner)
         val displayOrderEditText = dialog.findViewById<EditText>(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.
index 5036e86a490a89cae3698e86e7942157e3ff29e4..b56a02f0d0adc08e0da393bcbc448303f5191768 100644 (file)
@@ -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<EditText>(R.id.create_bookmark_name_edittext)
-        val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
+        val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
+        val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
 
         // 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<RadioButton>(R.id.default_folder_icon_radiobutton)
+        val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_folder_icon_imageview)
+        val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+        val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+        val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
         val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
-        val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
-        val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
 
         // Get new folder name string.
         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<ImageView>(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)
index 145c895b88175fb1c51f97354038515572daff90..60d199cac820216e91c46c6f6efcc083bed4c52e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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>.
  *
@@ -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<EditText>(R.id.create_bookmark_name_edittext)!!
-        val createBookmarkUrlEditText = alertDialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)!!
+        val webpageFavoriteIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.webpage_favorite_icon_linearlayout)!!
+        val webpageFavoriteIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)!!
+        val webpageFavoriteIconImageView = alertDialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)!!
+        customIconLinearLayout = alertDialog.findViewById(R.id.custom_icon_linearlayout)!!
+        val customIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.custom_icon_radiobutton)!!
+        customIconImageView = alertDialog.findViewById(R.id.custom_icon_imageview)!!
+        val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
+        bookmarkNameEditText = alertDialog.findViewById(R.id.bookmark_name_edittext)!!
+        bookmarkUrlEditText = alertDialog.findViewById(R.id.bookmark_url_edittext)!!
+        createButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+
+        // Populate the views.
+        webpageFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
+        customIconImageView.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.world))
+        bookmarkNameEditText.setText(title)
+        bookmarkUrlEditText.setText(urlString)
+
+        // Set the radio button listeners.  These perform a click on the linear layout, which contains the necessary logic.
+        webpageFavoriteIconRadioButton.setOnClickListener { webpageFavoriteIconLinearLayout.performClick() }
+        customIconRadioButton.setOnClickListener { customIconLinearLayout.performClick() }
+
+        // Set the webpage favorite icon linear layout click listener.
+        webpageFavoriteIconLinearLayout.setOnClickListener {
+            // Check the webpage favorite icon radio button.
+            webpageFavoriteIconRadioButton.isChecked = true
+
+            // Uncheck the custom icon radio button.
+            customIconRadioButton.isChecked = false
+        }
+
+        // Set the custom icon linear layout click listener.
+        customIconLinearLayout.setOnClickListener {
+            // Check the custom icon radio button.
+            customIconRadioButton.isChecked = true
+
+            // Uncheck the webpage favorite icon radio button.
+            webpageFavoriteIconRadioButton.isChecked = false
+        }
 
-        // Set the initial texts for the edit texts.
-        createBookmarkNameEditText.setText(title)
-        createBookmarkUrlEditText.setText(urlString)
+        browseButton.setOnClickListener {
+            // Open the file picker.
+            browseActivityResultLauncher.launch("image/*")
+        }
+
+        // Update the UI when the bookmark name changes.
+        bookmarkNameEditText.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
+                // Do nothing.
+            }
+
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
+                // Do nothing.
+            }
+
+            override fun afterTextChanged(editable: Editable?) {
+                // Update the UI.
+                updateUi()
+            }
+        })
+
+        // Update the UI when the bookmark name changes.
+        bookmarkUrlEditText.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
+                // Do nothing.
+            }
+
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, befire: Int, count: Int) {
+                // Do nothing.
+            }
+
+            override fun afterTextChanged(editable: Editable?) {
+                // Update the UI.
+                updateUi()
+            }
+        })
 
         // Allow the enter key on the keyboard to create the bookmark from the create bookmark name edit text.
-        createBookmarkNameEditText.setOnKeyListener { _: View, keyCode: Int, keyEvent: KeyEvent ->
+        bookmarkNameEditText.setOnKeyListener { _: View, keyCode: Int, keyEvent: KeyEvent ->
             // Check the key code and event.
             if (keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.action == KeyEvent.ACTION_DOWN) {  // The event is a key-down on the enter key.
                 // Trigger the create bookmark listener and return the dialog fragment and the favorite icon bitmap to the parent activity.
-                createBookmarkListener.createBookmark(this, favoriteIconBitmap)
+                createBookmarkListener.createBookmark(this)
 
                 // Manually dismiss the alert dialog.
                 alertDialog.dismiss()
@@ -173,11 +289,11 @@ class CreateBookmarkDialog : DialogFragment() {
         }
 
         // Allow the enter key on the keyboard to create the bookmark from create bookmark URL edit text.
-        createBookmarkUrlEditText.setOnKeyListener { _: View, keyCode: Int, keyEvent: KeyEvent ->
+        bookmarkUrlEditText.setOnKeyListener { _: View, keyCode: Int, keyEvent: KeyEvent ->
             // Check the key code and event.
             if (keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.action == KeyEvent.ACTION_DOWN) {  // The event is a key-down on the enter key.
                 // Trigger the create bookmark listener and return the dialog fragment and the favorite icon bitmap to the parent activity.
-                createBookmarkListener.createBookmark(this, favoriteIconBitmap)
+                createBookmarkListener.createBookmark(this)
 
                 // Manually dismiss the alert dialog.
                 alertDialog.dismiss()
@@ -190,7 +306,19 @@ class CreateBookmarkDialog : DialogFragment() {
             }
         }
 
+        // Populate the UI.
+        updateUi()
+
         // Return the alert dialog.
         return alertDialog
     }
+
+    private fun updateUi() {
+        // Get the contents of the edit texts.
+        val bookmarkName = bookmarkNameEditText.text.toString()
+        val bookmarkUrl = bookmarkUrlEditText.text.toString()
+
+        // Enable the create button if the edit texts are populated.
+        createButton.isEnabled = bookmarkName.isNotBlank() && bookmarkUrl.isNotBlank()
+    }
 }
index 04d7d250c7553cdfcbf3418f51296ecc997bb9de..1bc8c0e9ae2278267dc85fdde5fccdbaab6241f1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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>.
  *
@@ -24,27 +24,33 @@ import android.content.Context
 import android.content.DialogInterface
 import android.graphics.Bitmap
 import android.graphics.BitmapFactory
+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.core.graphics.drawable.toBitmap
 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 FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
+private const val FAVORITE_ICON_BYTE_ARRAY = "A"
 
 class CreateBookmarkFolderDialog : DialogFragment() {
     companion object {
@@ -75,12 +81,50 @@ class CreateBookmarkFolderDialog : 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(customIconImageView, 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 customIconImageView: ImageView
+    private lateinit var customIconLinearLayout: LinearLayout
+
     // Declare the class variables.
     private lateinit var createBookmarkFolderListener: CreateBookmarkFolderListener
 
     // The public interface is used to send information back to the parent activity.
     interface CreateBookmarkFolderListener {
-        fun createBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap)
+        fun createBookmarkFolder(dialogFragment: DialogFragment)
     }
 
     override fun onAttach(context: Context) {
@@ -107,6 +151,9 @@ class CreateBookmarkFolderDialog : DialogFragment() {
         // Set the title.
         dialogBuilder.setTitle(R.string.create_folder)
 
+        // Set the icon.
+        dialogBuilder.setIcon(R.drawable.folder)
+
         // Set the view.
         dialogBuilder.setView(R.layout.create_bookmark_folder_dialog)
 
@@ -116,7 +163,7 @@ class CreateBookmarkFolderDialog : DialogFragment() {
         // Set the create button listener.
         dialogBuilder.setPositiveButton(R.string.create) { _: DialogInterface, _: Int ->
             // Return the dialog fragment to the parent activity on create.
-            createBookmarkFolderListener.createBookmarkFolder(this, favoriteIconBitmap)
+            createBookmarkFolderListener.createBookmarkFolder(this)
         }
 
         // Create an alert dialog from the builder.
@@ -140,31 +187,35 @@ class CreateBookmarkFolderDialog : DialogFragment() {
         alertDialog.show()
 
         // Get handles for the views in the dialog.
-        val defaultIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.default_icon_linearlayout)!!
-        val defaultIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)!!
+        val defaultFolderIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.default_folder_icon_linearlayout)!!
+        val defaultFolderIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.default_folder_icon_radiobutton)!!
         val webpageFavoriteIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.webpage_favorite_icon_linearlayout)!!
         val webpageFavoriteIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)!!
         val webpageFavoriteIconImageView = alertDialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)!!
+        customIconLinearLayout = alertDialog.findViewById(R.id.custom_icon_linearlayout)!!
+        val customIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.custom_icon_radiobutton)!!
+        customIconImageView = alertDialog.findViewById(R.id.custom_icon_imageview)!!
+        val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
         val folderNameEditText = alertDialog.findViewById<EditText>(R.id.folder_name_edittext)!!
         val createButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
 
-        // Display the current favorite icon.
+        // Populate the views.  The vectored drawable must be converted to a bitmap or the save function will fail later.  `Bitmap.Config.RGBA_1010102` can be used once the minimum API >= 33.
         webpageFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
-
-        // Initially disable the create button.
-        createButton.isEnabled = false
+        customIconImageView.setImageBitmap(AppCompatResources.getDrawable(requireContext(), R.drawable.folder)!!.toBitmap(128, 128, Bitmap.Config.ARGB_8888))
 
         // Set the radio button listeners.  These perform a click on the linear layout, which contains the necessary logic.
-        defaultIconRadioButton.setOnClickListener { defaultIconLinearLayout.performClick() }
+        defaultFolderIconRadioButton.setOnClickListener { defaultFolderIconLinearLayout.performClick() }
         webpageFavoriteIconRadioButton.setOnClickListener { webpageFavoriteIconLinearLayout.performClick() }
+        customIconRadioButton.setOnClickListener { customIconLinearLayout.performClick() }
 
         // Set the default icon linear layout click listener.
-        defaultIconLinearLayout.setOnClickListener {
+        defaultFolderIconLinearLayout.setOnClickListener {
             // Check the default icon radio button.
-            defaultIconRadioButton.isChecked = true
+            defaultFolderIconRadioButton.isChecked = true
 
-            // Uncheck the webpage favorite icon radio button.
+            // Uncheck the other radio buttons.
             webpageFavoriteIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
         }
 
         // Set the webpage favorite icon linear layout click listener.
@@ -172,21 +223,37 @@ class CreateBookmarkFolderDialog : DialogFragment() {
             // Check the webpage favorite icon radio button.
             webpageFavoriteIconRadioButton.isChecked = true
 
-            // Uncheck the default icon radio button.
-            defaultIconRadioButton.isChecked = false
+            // Uncheck the other radio buttons.
+            defaultFolderIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
+        }
+
+        // Set the custom icon linear layout click listener.
+        customIconLinearLayout.setOnClickListener {
+            // Check the custom icon radio button.
+            customIconRadioButton.isChecked = true
+
+            // Uncheck the other radio buttons.
+            defaultFolderIconRadioButton.isChecked = false
+            webpageFavoriteIconRadioButton.isChecked = false
+        }
+
+        browseButton.setOnClickListener {
+            // Open the file picker.
+            browseActivityResultLauncher.launch("image/*")
         }
 
         // Enable the create button if the folder name is populated.
         folderNameEditText.addTextChangedListener(object: TextWatcher {
-            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(editable: Editable) {
+            override fun afterTextChanged(editable: Editable?) {
                 // Convert the current text to a string.
                 val folderName = editable.toString()
 
@@ -200,7 +267,7 @@ class CreateBookmarkFolderDialog : DialogFragment() {
             // Check the key code, event, and button status.
             if (keyEvent.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && createButton.isEnabled) {  // The event is a key-down on the enter key and the create button is enabled.
                 // Trigger the create bookmark folder listener and return the dialog fragment to the parent activity.
-                createBookmarkFolderListener.createBookmarkFolder(this, favoriteIconBitmap)
+                createBookmarkFolderListener.createBookmarkFolder(this)
 
                 // Manually dismiss the alert dialog.
                 alertDialog.dismiss()
@@ -212,6 +279,9 @@ class CreateBookmarkFolderDialog : DialogFragment() {
             }
         }
 
+        // Initially disable the create button.
+        createButton.isEnabled = false
+
         // Return the alert dialog.
         return alertDialog
     }
index d5902f217b86a603afc4eaa2717c8884afc48115..60f7609063e1bb5768e23800320bf74d60bdc231 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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>.
  *
@@ -27,6 +27,7 @@ import android.database.MatrixCursor
 import android.database.MergeCursor
 import android.graphics.Bitmap
 import android.graphics.BitmapFactory
+import android.net.Uri
 import android.os.Bundle
 import android.text.Editable
 import android.text.TextWatcher
@@ -43,12 +44,15 @@ import android.widget.RadioButton
 import android.widget.Spinner
 import android.widget.TextView
 
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.appcompat.app.AlertDialog
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.cursoradapter.widget.ResourceCursorAdapter
 import androidx.fragment.app.DialogFragment
 import androidx.preference.PreferenceManager
 
+import com.google.android.material.snackbar.Snackbar
+
 import com.stoutner.privacybrowser.R
 import com.stoutner.privacybrowser.activities.HOME_FOLDER_DATABASE_ID
 import com.stoutner.privacybrowser.activities.HOME_FOLDER_ID
@@ -64,8 +68,8 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
 import java.io.ByteArrayOutputStream
 
 // Define the class constants.
-private const val DATABASE_ID = "database_id"
-private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
+private const val DATABASE_ID = "A"
+private const val FAVORITE_ICON_BYTE_ARRAY = "B"
 
 class EditBookmarkDatabaseViewDialog : DialogFragment() {
     companion object {
@@ -97,20 +101,56 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() {
         }
     }
 
-    // Declare the class variables.
-    private lateinit var editBookmarkDatabaseViewListener: EditBookmarkDatabaseViewListener
+    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 webpageFavoriteIconRadioButton: RadioButton
-    private lateinit var nameEditText: EditText
-    private lateinit var urlEditText: EditText
-    private lateinit var folderSpinner: Spinner
+    private lateinit var bookmarkNameEditText: EditText
+    private lateinit var bookmarkUrlEditText: EditText
+    private lateinit var currentIconRadioButton: RadioButton
+    private lateinit var customIconImageView: ImageView
+    private lateinit var customIconLinearLayout: LinearLayout
     private lateinit var displayOrderEditText: EditText
+    private lateinit var folderSpinner: Spinner
     private lateinit var saveButton: Button
 
+    // Declare the class variables.
+    private lateinit var editBookmarkDatabaseViewListener: EditBookmarkDatabaseViewListener
+
     // The public interface is used to send information back to the parent activity.
     interface EditBookmarkDatabaseViewListener {
-        fun saveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap)
+        fun saveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int)
     }
 
     override fun onAttach(context: Context) {
@@ -147,6 +187,9 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() {
         // Set the title.
         dialogBuilder.setTitle(R.string.edit_bookmark)
 
+        // Set the icon.
+        dialogBuilder.setIcon(R.drawable.bookmark)
+
         // Set the view.
         dialogBuilder.setView(R.layout.edit_bookmark_databaseview_dialog)
 
@@ -156,7 +199,7 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() {
         // Set the save button listener.
         dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface, _: Int ->
             // Return the dialog fragment to the parent activity on save.
-            editBookmarkDatabaseViewListener.saveBookmark(this, bookmarkDatabaseId, favoriteIconBitmap)
+            editBookmarkDatabaseViewListener.saveBookmark(this, bookmarkDatabaseId)
         }
 
         // Create an alert dialog from the alert dialog builder.
@@ -179,40 +222,39 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() {
         // Get handles for the layout items.
         val databaseIdTextView = alertDialog.findViewById<TextView>(R.id.bookmark_database_id_textview)!!
         val currentIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.current_icon_linearlayout)!!
-        val currentIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)!!
+        currentIconRadioButton = alertDialog.findViewById(R.id.current_icon_radiobutton)!!
         val currentIconImageView = alertDialog.findViewById<ImageView>(R.id.current_icon_imageview)!!
         val webpageFavoriteIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.webpage_favorite_icon_linearlayout)!!
-        webpageFavoriteIconRadioButton = alertDialog.findViewById(R.id.webpage_favorite_icon_radiobutton)!!
+        val webpageFavoriteIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)!!
         val webpageFavoriteIconImageView = alertDialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)!!
-        nameEditText = alertDialog.findViewById(R.id.bookmark_name_edittext)!!
-        urlEditText = alertDialog.findViewById(R.id.bookmark_url_edittext)!!
+        customIconLinearLayout = alertDialog.findViewById(R.id.custom_icon_linearlayout)!!
+        val customIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.custom_icon_radiobutton)!!
+        customIconImageView = alertDialog.findViewById(R.id.custom_icon_imageview)!!
+        val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
+        bookmarkNameEditText = alertDialog.findViewById(R.id.bookmark_name_edittext)!!
+        bookmarkUrlEditText = alertDialog.findViewById(R.id.bookmark_url_edittext)!!
         folderSpinner = alertDialog.findViewById(R.id.bookmark_folder_spinner)!!
         displayOrderEditText = alertDialog.findViewById(R.id.bookmark_display_order_edittext)!!
         saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
 
-        // Store the current bookmark values.
-        val currentBookmarkName = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_NAME))
-        val currentUrl = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_URL))
-        val currentDisplayOrder = bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(DISPLAY_ORDER))
-
-        // Set the database ID.
-        databaseIdTextView.text = bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(ID)).toString()
-
         // Get the current favorite icon byte array from the cursor.
         val currentIconByteArray = bookmarkCursor.getBlob(bookmarkCursor.getColumnIndexOrThrow(FAVORITE_ICON))
 
         // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
         val currentIconBitmap = BitmapFactory.decodeByteArray(currentIconByteArray, 0, currentIconByteArray.size)
 
-        // Display the current icon bitmap.
-        currentIconImageView.setImageBitmap(currentIconBitmap)
+        // Get the current bookmark values.
+        val currentBookmarkName = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_NAME))
+        val currentUrl = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_URL))
+        val currentDisplayOrder = bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(DISPLAY_ORDER))
 
-        // Set the webpage favorite icon bitmap.
+        // Populate the views.
+        databaseIdTextView.text = bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(ID)).toString()
+        currentIconImageView.setImageBitmap(currentIconBitmap)
         webpageFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
-
-        // Populate the bookmark name and URL edit texts.
-        nameEditText.setText(currentBookmarkName)
-        urlEditText.setText(currentUrl)
+        customIconImageView.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.world))
+        bookmarkNameEditText.setText(currentBookmarkName)
+        bookmarkUrlEditText.setText(currentUrl)
 
         // Create an an array of column names for the matrix cursor comprised of the ID and the name.
         val matrixCursorColumnNamesArray = arrayOf(ID, BOOKMARK_NAME, PARENT_FOLDER_ID)
@@ -315,14 +357,16 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() {
         // Set the radio button listeners.  These perform a click on the linear layout, which contains the necessary logic.
         currentIconRadioButton.setOnClickListener { currentIconLinearLayout.performClick() }
         webpageFavoriteIconRadioButton.setOnClickListener { webpageFavoriteIconLinearLayout.performClick() }
+        customIconRadioButton.setOnClickListener { customIconLinearLayout.performClick() }
 
         // Set the current icon linear layout click listener.
         currentIconLinearLayout.setOnClickListener {
             // Check the current icon radio button.
             currentIconRadioButton.isChecked = true
 
-            // Uncheck the webpage favorite icon radio button.
+            // Uncheck the other radio buttons.
             webpageFavoriteIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
 
             // Update the save button.
             updateSaveButton(currentBookmarkName, currentUrl, currentFolderDatabaseId, currentDisplayOrder)
@@ -333,40 +377,59 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() {
             // Check the webpage favorite icon radio button.
             webpageFavoriteIconRadioButton.isChecked = true
 
-            // Uncheck the current icon radio button.
+            // Uncheck the other radio buttons.
             currentIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
 
             // Update the save button.
             updateSaveButton(currentBookmarkName, currentUrl, currentFolderDatabaseId, currentDisplayOrder)
         }
 
+        // Set the custom icon linear layout click listener.
+        customIconLinearLayout.setOnClickListener {
+            // Check the custom icon radio button.
+            customIconRadioButton.isChecked = true
+
+            // Uncheck the other radio buttons.
+            currentIconRadioButton.isChecked = false
+            webpageFavoriteIconRadioButton.isChecked = false
+
+            // Update the save button.
+            updateSaveButton(currentBookmarkName, currentUrl, currentFolderDatabaseId, currentDisplayOrder)
+        }
+
+        browseButton.setOnClickListener {
+            // Open the file picker.
+            browseActivityResultLauncher.launch("image/*")
+        }
+
         // Update the save button if the bookmark name changes.
-        nameEditText.addTextChangedListener(object: TextWatcher {
-            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
+        bookmarkNameEditText.addTextChangedListener(object: TextWatcher {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(s: Editable) {
+            override fun afterTextChanged(editable: Editable?) {
                 // Update the Save button.
                 updateSaveButton(currentBookmarkName, currentUrl, currentFolderDatabaseId, currentDisplayOrder)
             }
         })
 
         // Update the save button if the URL changes.
-        urlEditText.addTextChangedListener(object: TextWatcher {
-            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
+        bookmarkUrlEditText.addTextChangedListener(object: TextWatcher {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(s: Editable) {
+            override fun afterTextChanged(editable: Editable?) {
                 // Update the save button.
                 updateSaveButton(currentBookmarkName, currentUrl, currentFolderDatabaseId, currentDisplayOrder)
             }
@@ -389,26 +452,26 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() {
 
         // Update the save button if the display order changes.
         displayOrderEditText.addTextChangedListener(object: TextWatcher {
-            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(s: Editable) {
+            override fun afterTextChanged(editable: Editable?) {
                 // Update the save button.
                 updateSaveButton(currentBookmarkName, currentUrl, currentFolderDatabaseId, currentDisplayOrder)
             }
         })
 
         // Allow the enter key on the keyboard to save the bookmark from the bookmark name edit text.
-        nameEditText.setOnKeyListener { _: View, keyCode: Int, keyEvent: KeyEvent ->
+        bookmarkNameEditText.setOnKeyListener { _: View, keyCode: Int, keyEvent: KeyEvent ->
             // Check the key code, event, and button status.
             if (keyEvent.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && saveButton.isEnabled) {  // The enter key was pressed and the save button is enabled.
                 // Trigger the listener and return the dialog fragment to the parent activity.
-                editBookmarkDatabaseViewListener.saveBookmark(this, bookmarkDatabaseId, favoriteIconBitmap)
+                editBookmarkDatabaseViewListener.saveBookmark(this, bookmarkDatabaseId)
 
                 // Manually dismiss the alert dialog.
                 alertDialog.dismiss()
@@ -421,11 +484,11 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() {
         }
 
         // Allow the enter key on the keyboard to save the bookmark from the URL edit text.
-        urlEditText.setOnKeyListener { _: View, keyCode: Int, keyEvent: KeyEvent ->
+        bookmarkUrlEditText.setOnKeyListener { _: View, keyCode: Int, keyEvent: KeyEvent ->
             // Check the key code, event, and button status.
             if (keyEvent.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && saveButton.isEnabled) {  // The enter key was pressed and the save button is enabled.
                 // Trigger the listener and return the dialog fragment to the parent activity.
-                editBookmarkDatabaseViewListener.saveBookmark(this, bookmarkDatabaseId, favoriteIconBitmap)
+                editBookmarkDatabaseViewListener.saveBookmark(this, bookmarkDatabaseId)
 
                 // Manually dismiss the alert dialog.
                 alertDialog.dismiss()
@@ -442,7 +505,7 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() {
             // Check the key code, event, and button status.
             if (keyEvent.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && saveButton.isEnabled) {  // The enter key was pressed and the save button is enabled.
                 // Trigger the listener and return the dialog fragment to the parent activity.
-                editBookmarkDatabaseViewListener.saveBookmark(this, bookmarkDatabaseId, favoriteIconBitmap)
+                editBookmarkDatabaseViewListener.saveBookmark(this, bookmarkDatabaseId)
 
                 // Manually dismiss the alert dialog.
                 alertDialog.dismiss()
@@ -460,13 +523,13 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() {
 
     private fun updateSaveButton(currentBookmarkName: String, currentUrl: String, currentFolderDatabaseId: Int, currentDisplayOrder: Int) {
         // Get the values from the dialog.
-        val newName = nameEditText.text.toString()
-        val newUrl = urlEditText.text.toString()
+        val newName = bookmarkNameEditText.text.toString()
+        val newUrl = bookmarkUrlEditText.text.toString()
         val newFolderDatabaseId = folderSpinner.selectedItemId.toInt()
         val newDisplayOrder = displayOrderEditText.text.toString()
 
         // Has the favorite icon changed?
-        val iconChanged = webpageFavoriteIconRadioButton.isChecked
+        val iconChanged = !currentIconRadioButton.isChecked
 
         // Has the name changed?
         val nameChanged = (newName != currentBookmarkName)
index 0a8be3ec4922b3cc3ef9990beb3ac0af52d28ed1..a81a41c3952064d52788ba4285e5849a3c20f635 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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>.
  *
@@ -24,6 +24,7 @@ import android.content.Context
 import android.content.DialogInterface
 import android.graphics.Bitmap
 import android.graphics.BitmapFactory
+import android.net.Uri
 import android.os.Bundle
 import android.text.Editable
 import android.text.TextWatcher
@@ -36,10 +37,14 @@ 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 com.stoutner.privacybrowser.helpers.BOOKMARK_NAME
 import com.stoutner.privacybrowser.helpers.BOOKMARK_URL
@@ -49,8 +54,8 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
 import java.io.ByteArrayOutputStream
 
 // Define the class constants.
-private const val DATABASE_ID = "database_id"
-private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
+private const val DATABASE_ID = "A"
+private const val FAVORITE_ICON_BYTE_ARRAY = "B"
 
 class EditBookmarkDialog : DialogFragment() {
     companion object {
@@ -82,18 +87,56 @@ class EditBookmarkDialog : DialogFragment() {
         }
     }
 
-    // Declare the class variables.
-    private lateinit var editBookmarkListener: EditBookmarkListener
+    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 64 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 webpageFavoriteIconRadioButton: RadioButton
-    private lateinit var nameEditText: EditText
-    private lateinit var urlEditText: EditText
+    private lateinit var currentIconRadioButton: RadioButton
+    private lateinit var customIconLinearLayout: LinearLayout
+    private lateinit var customIconImageView: ImageView
+    private lateinit var bookmarkNameEditText: EditText
+    private lateinit var bookmarkUrlEditText: EditText
     private lateinit var saveButton: Button
 
+    // Declare the class variables.
+    private lateinit var currentName: String
+    private lateinit var currentUrl: String
+    private lateinit var editBookmarkListener: EditBookmarkListener
+
     // The public interface is used to send information back to the parent activity.
     interface EditBookmarkListener {
-        fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap)
+        fun saveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int)
     }
 
     override fun onAttach(context: Context) {
@@ -130,6 +173,9 @@ class EditBookmarkDialog : DialogFragment() {
         // Set the title.
         dialogBuilder.setTitle(R.string.edit_bookmark)
 
+        // Set the icon.
+        dialogBuilder.setIcon(R.drawable.bookmark)
+
         // Set the view.
         dialogBuilder.setView(R.layout.edit_bookmark_dialog)
 
@@ -139,7 +185,7 @@ class EditBookmarkDialog : DialogFragment() {
         // Set the save button listener.
         dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface?, _: Int ->
             // Return the dialog fragment to the parent activity.
-            editBookmarkListener.onSaveBookmark(this, selectedBookmarkDatabaseId, favoriteIconBitmap)
+            editBookmarkListener.saveBookmark(this, selectedBookmarkDatabaseId)
         }
 
         // Create an alert dialog from the builder.
@@ -161,13 +207,17 @@ class EditBookmarkDialog : DialogFragment() {
 
         // Get handles for the layout items.
         val currentIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.current_icon_linearlayout)!!
-        val currentIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)!!
+        currentIconRadioButton = alertDialog.findViewById(R.id.current_icon_radiobutton)!!
         val currentIconImageView = alertDialog.findViewById<ImageView>(R.id.current_icon_imageview)!!
         val webpageFavoriteIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.webpage_favorite_icon_linearlayout)!!
-        webpageFavoriteIconRadioButton = alertDialog.findViewById(R.id.webpage_favorite_icon_radiobutton)!!
+        val webpageFavoriteIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)!!
         val webpageFavoriteIconImageView = alertDialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)!!
-        nameEditText = alertDialog.findViewById(R.id.bookmark_name_edittext)!!
-        urlEditText = alertDialog.findViewById(R.id.bookmark_url_edittext)!!
+        customIconLinearLayout = alertDialog.findViewById(R.id.custom_icon_linearlayout)!!
+        val customIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.custom_icon_radiobutton)!!
+        customIconImageView = alertDialog.findViewById(R.id.custom_icon_imageview)!!
+        val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
+        bookmarkNameEditText = alertDialog.findViewById(R.id.bookmark_name_edittext)!!
+        bookmarkUrlEditText = alertDialog.findViewById(R.id.bookmark_url_edittext)!!
         saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
 
         // Get the current favorite icon byte array from the cursor.
@@ -176,19 +226,16 @@ class EditBookmarkDialog : DialogFragment() {
         // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
         val currentIconBitmap = BitmapFactory.decodeByteArray(currentIconByteArray, 0, currentIconByteArray.size)
 
-        // Display the current icon bitmap.
-        currentIconImageView.setImageBitmap(currentIconBitmap)
+        // Get the current bookmark name and URL.
+        currentName = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_NAME))
+        currentUrl = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_URL))
 
-        // Set the webpage favorite icon bitmap.
+        // Populate the views.
+        currentIconImageView.setImageBitmap(currentIconBitmap)
         webpageFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
-
-        // Store the current bookmark name and URL.
-        val currentName = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_NAME))
-        val currentUrl = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_URL))
-
-        // Populate the edit texts.
-        nameEditText.setText(currentName)
-        urlEditText.setText(currentUrl)
+        customIconImageView.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.world))
+        bookmarkNameEditText.setText(currentName)
+        bookmarkUrlEditText.setText(currentUrl)
 
         // Initially disable the save button.
         saveButton.isEnabled = false
@@ -196,17 +243,19 @@ class EditBookmarkDialog : DialogFragment() {
         // Set the radio button listeners.  These perform a click on the linear layout, which contains the necessary logic.
         currentIconRadioButton.setOnClickListener { currentIconLinearLayout.performClick() }
         webpageFavoriteIconRadioButton.setOnClickListener { webpageFavoriteIconLinearLayout.performClick() }
+        customIconRadioButton.setOnClickListener { customIconLinearLayout.performClick() }
 
         // Set the current icon linear layout click listener.
         currentIconLinearLayout.setOnClickListener {
             // Check the current icon radio button.
             currentIconRadioButton.isChecked = true
 
-            // Uncheck the webpage favorite icon radio button.
+            // Uncheck the other radio buttons.
             webpageFavoriteIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
 
             // Update the save button.
-            updateSaveButton(currentName, currentUrl)
+            updateSaveButton()
         }
 
         // Set the webpage favorite icon linear layout click listener.
@@ -214,51 +263,70 @@ class EditBookmarkDialog : DialogFragment() {
             // Check the webpage favorite icon radio button.
             webpageFavoriteIconRadioButton.isChecked = true
 
-            // Uncheck the current icon radio button.
+            // Uncheck the other radio buttons.
+            currentIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
+
+            // Update the save button.
+            updateSaveButton()
+        }
+
+        // Set the custom icon linear layout click listener.
+        customIconLinearLayout.setOnClickListener {
+            // Check the custom icon radio button.
+            customIconRadioButton.isChecked = true
+
+            // Uncheck the other radio buttons.
             currentIconRadioButton.isChecked = false
+            webpageFavoriteIconRadioButton.isChecked = false
 
             // Update the save button.
-            updateSaveButton(currentName, currentUrl)
+            updateSaveButton()
+        }
+
+        browseButton.setOnClickListener {
+            // Open the file picker.
+            browseActivityResultLauncher.launch("image/*")
         }
 
         // Update the save button if the bookmark name changes.
-        nameEditText.addTextChangedListener(object: TextWatcher {
-            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
+        bookmarkNameEditText.addTextChangedListener(object: TextWatcher {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(s: Editable) {
+            override fun afterTextChanged(editable: Editable?) {
                 // Update the save button.
-                updateSaveButton(currentName, currentUrl)
+                updateSaveButton()
             }
         })
 
         // Update the save button if the URL changes.
-        urlEditText.addTextChangedListener(object: TextWatcher {
-            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
+        bookmarkUrlEditText.addTextChangedListener(object: TextWatcher {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(s: Editable) {
+            override fun afterTextChanged(editable: Editable?) {
                 // Update the edit button.
-                updateSaveButton(currentName, currentUrl)
+                updateSaveButton()
             }
         })
 
         // Allow the enter key on the keyboard to save the bookmark from the bookmark name edit text.
-        nameEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
+        bookmarkNameEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
             // Check the key code, event, and button status.
             if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && saveButton.isEnabled) {  // The enter key was pressed and the save button is enabled.
                 // Trigger the listener and return the dialog fragment to the parent activity.
-                editBookmarkListener.onSaveBookmark(this, selectedBookmarkDatabaseId, favoriteIconBitmap)
+                editBookmarkListener.saveBookmark(this, selectedBookmarkDatabaseId)
 
                 // Manually dismiss the alert dialog.
                 alertDialog.dismiss()
@@ -271,11 +339,11 @@ class EditBookmarkDialog : DialogFragment() {
         }
 
         // Allow the enter key on the keyboard to save the bookmark from the URL edit text.
-        urlEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
+        bookmarkUrlEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
             // Check the key code, event, and button status.
             if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && saveButton.isEnabled) {  // The enter key was pressed and the save button is enabled.
                 // Trigger the listener and return the dialog fragment to the parent activity.
-                editBookmarkListener.onSaveBookmark(this, selectedBookmarkDatabaseId, favoriteIconBitmap)
+                editBookmarkListener.saveBookmark(this, selectedBookmarkDatabaseId)
 
                 // Manually dismiss the alert dialog.
                 alertDialog.dismiss()
@@ -291,13 +359,13 @@ class EditBookmarkDialog : DialogFragment() {
         return alertDialog
     }
 
-    private fun updateSaveButton(currentName: String, currentUrl: String) {
+    private fun updateSaveButton() {
         // Get the text from the edit texts.
-        val newName = nameEditText.text.toString()
-        val newUrl = urlEditText.text.toString()
+        val newName = bookmarkNameEditText.text.toString()
+        val newUrl = bookmarkUrlEditText.text.toString()
 
         // Has the favorite icon changed?
-        val iconChanged = webpageFavoriteIconRadioButton.isChecked
+        val iconChanged = !currentIconRadioButton.isChecked
 
         // Has the name changed?
         val nameChanged = newName != currentName
index edcf6e05e1f6fbfb8bb6d0a6c954e343f113dedf..ba66c8d7af67a629d00fe0aaa80be19caf26ac5b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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>.
  *
@@ -27,6 +27,7 @@ import android.database.MatrixCursor
 import android.database.MergeCursor
 import android.graphics.Bitmap
 import android.graphics.BitmapFactory
+import android.net.Uri
 import android.os.Bundle
 import android.text.Editable
 import android.text.TextWatcher
@@ -42,12 +43,17 @@ import android.widget.RadioButton
 import android.widget.Spinner
 import android.widget.TextView
 
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.content.res.AppCompatResources
 import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.toBitmap
 import androidx.cursoradapter.widget.ResourceCursorAdapter
 import androidx.fragment.app.DialogFragment
 import androidx.preference.PreferenceManager
 
+import com.google.android.material.snackbar.Snackbar
+
 import com.stoutner.privacybrowser.R
 import com.stoutner.privacybrowser.activities.HOME_FOLDER_DATABASE_ID
 import com.stoutner.privacybrowser.activities.HOME_FOLDER_ID
@@ -62,8 +68,8 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
 import java.io.ByteArrayOutputStream
 
 // Define the class constants.
-private const val DATABASE_ID = "database_id"
-private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
+private const val DATABASE_ID = "A"
+private const val FAVORITE_ICON_BYTE_ARRAY = "B"
 
 class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
     companion object {
@@ -95,19 +101,60 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
         }
     }
 
-    // Declare the class variables.
-    private lateinit var editBookmarkFolderDatabaseViewListener: EditBookmarkFolderDatabaseViewListener
+    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(customIconImageView, 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 64 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 currentIconRadioButton: RadioButton
+    private lateinit var customIconLinearLayout: LinearLayout
+    private lateinit var customIconImageView: ImageView
+    private lateinit var displayOrderEditText: EditText
     private lateinit var nameEditText: EditText
     private lateinit var parentFolderSpinner: Spinner
-    private lateinit var displayOrderEditText: EditText
     private lateinit var saveButton: Button
 
+    // Declare the class variables.
+    private lateinit var currentFolderName: String
+    private lateinit var editBookmarkFolderDatabaseViewListener: EditBookmarkFolderDatabaseViewListener
+
+    // Declare the class variables.
+    private var currentDisplayOrder: Int = 0
+    private var currentParentFolderDatabaseIdInt: Int = 0
+
     // The public interface is used to send information back to the parent activity.
     interface EditBookmarkFolderDatabaseViewListener {
-        fun saveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap)
+        fun saveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int)
     }
 
     override fun onAttach(context: Context) {
@@ -144,6 +191,9 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
         // Set the title.
         dialogBuilder.setTitle(R.string.edit_folder)
 
+        // Set the icon.
+        dialogBuilder.setIcon(R.drawable.folder)
+
         // Set the view.
         dialogBuilder.setView(R.layout.edit_bookmark_folder_databaseview_dialog)
 
@@ -153,7 +203,7 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
         // Set the save button listener.
         dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface?, _: Int ->
             // Return the dialog fragment to the parent activity.
-            editBookmarkFolderDatabaseViewListener.saveBookmarkFolder(this, folderDatabaseId, favoriteIconBitmap)
+            editBookmarkFolderDatabaseViewListener.saveBookmarkFolder(this, folderDatabaseId)
         }
 
         // Create an alert dialog from the alert dialog builder.
@@ -179,41 +229,38 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
         val currentIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.current_icon_linearlayout)!!
         currentIconRadioButton = alertDialog.findViewById(R.id.current_icon_radiobutton)!!
         val currentIconImageView = alertDialog.findViewById<ImageView>(R.id.current_icon_imageview)!!
-        val defaultIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.default_icon_linearlayout)!!
-        val defaultIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)!!
+        val defaultFolderIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.default_folder_icon_linearlayout)!!
+        val defaultFolderIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.default_folder_icon_radiobutton)!!
         val webpageFavoriteIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.webpage_favorite_icon_linearlayout)!!
         val webpageFavoriteIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)!!
         val webpageFavoriteIconImageView = alertDialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)!!
+        customIconLinearLayout = alertDialog.findViewById(R.id.custom_icon_linearlayout)!!
+        val customIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.custom_icon_radiobutton)!!
+        customIconImageView = alertDialog.findViewById(R.id.custom_icon_imageview)!!
+        val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
         nameEditText = alertDialog.findViewById(R.id.folder_name_edittext)!!
         parentFolderSpinner = alertDialog.findViewById(R.id.parent_folder_spinner)!!
         displayOrderEditText = alertDialog.findViewById(R.id.display_order_edittext)!!
         saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
 
-        // Store the current folder values.
-        val currentFolderName = folderCursor.getString(folderCursor.getColumnIndexOrThrow(BOOKMARK_NAME))
-        val currentDisplayOrder = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(DISPLAY_ORDER))
-        val parentFolderId = folderCursor.getLong(folderCursor.getColumnIndexOrThrow(PARENT_FOLDER_ID))
-        val currentFolderId = folderCursor.getLong(folderCursor.getColumnIndexOrThrow(FOLDER_ID))
-
-        // Populate the database ID text view.
-        databaseIdTextView.text = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(ID)).toString()
-
-        // Populate the folder ID text view.
-        folderIdTextView.text = folderCursor.getLong(folderCursor.getColumnIndexOrThrow(FOLDER_ID)).toString()
-
         // Get the current favorite icon byte array from the cursor.
         val currentIconByteArray = folderCursor.getBlob(folderCursor.getColumnIndexOrThrow(FAVORITE_ICON))
 
         // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
         val currentIconBitmap = BitmapFactory.decodeByteArray(currentIconByteArray, 0, currentIconByteArray.size)
 
-        // Populate the current icon image view.
-        currentIconImageView.setImageBitmap(currentIconBitmap)
+        // Get the current folder values.
+        currentFolderName = folderCursor.getString(folderCursor.getColumnIndexOrThrow(BOOKMARK_NAME))
+        currentDisplayOrder = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(DISPLAY_ORDER))
+        val currentParentFolderIdLong = folderCursor.getLong(folderCursor.getColumnIndexOrThrow(PARENT_FOLDER_ID))
+        val currentFolderIdLong = folderCursor.getLong(folderCursor.getColumnIndexOrThrow(FOLDER_ID))
 
-        // Populate the webpage favorite icon image view.
+        // Populate the views.
+        databaseIdTextView.text = folderDatabaseId.toString()
+        folderIdTextView.text = currentFolderIdLong.toString()
+        currentIconImageView.setImageBitmap(currentIconBitmap)
         webpageFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
-
-        // Populate the folder name edit text.
+        customIconImageView.setImageBitmap(AppCompatResources.getDrawable(requireContext(), R.drawable.folder)!!.toBitmap(128, 128, Bitmap.Config.ARGB_8888))
         nameEditText.setText(currentFolderName)
 
         // Define an array of matrix cursor column names.
@@ -229,10 +276,10 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
         val currentAndSubfolderIds = mutableListOf<Long>()
 
         // Add the current folder ID to the list.
-        currentAndSubfolderIds.add(currentFolderId)
+        currentAndSubfolderIds.add(currentFolderIdLong)
 
         // Get a long array of all the subfolders IDs.
-        val subfolderIdLongList = getListOfSubfolderIds(currentFolderId, bookmarksDatabaseHelper)
+        val subfolderIdLongList = getListOfSubfolderIds(currentFolderIdLong, bookmarksDatabaseHelper)
 
         // Add the subfolder IDs to the list.
         for (subfolderId in subfolderIdLongList)
@@ -291,9 +338,9 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
         parentFolderSpinner.adapter = foldersCursorAdapter
 
         // Select the current folder in the spinner if the bookmark isn't in the home folder.
-        if (parentFolderId != HOME_FOLDER_ID) {
+        if (currentParentFolderIdLong != HOME_FOLDER_ID) {
             // Get the database ID of the parent folder as a long.
-            val parentFolderDatabaseId = bookmarksDatabaseHelper.getFolderDatabaseId(parentFolderId).toLong()
+            val parentFolderDatabaseIdLong = bookmarksDatabaseHelper.getFolderDatabaseId(currentParentFolderIdLong).toLong()
 
             // Initialize the parent folder position and the iteration variable.
             var parentFolderPosition = 0
@@ -301,7 +348,7 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
 
             // Find the parent folder position in the folders cursor adapter.
             do {
-                if (foldersCursorAdapter.getItemId(i) == parentFolderDatabaseId) {
+                if (foldersCursorAdapter.getItemId(i) == parentFolderDatabaseIdLong) {
                     // Store the current position for the parent folder.
                     parentFolderPosition = i
                 } else {
@@ -316,18 +363,16 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
         }
 
         // Store the current folder database ID.
-        val currentParentFolderDatabaseId = parentFolderSpinner.selectedItemId.toInt()
+        currentParentFolderDatabaseIdInt = parentFolderSpinner.selectedItemId.toInt()
 
         // Populate the display order edit text.
         displayOrderEditText.setText(folderCursor.getInt(folderCursor.getColumnIndexOrThrow(DISPLAY_ORDER)).toString())
 
-        // Initially disable the edit button.
-        saveButton.isEnabled = false
-
         // Set the radio button listeners.  These perform a click on the linear layout, which contains the necessary logic.
         currentIconRadioButton.setOnClickListener { currentIconLinearLayout.performClick() }
-        defaultIconRadioButton.setOnClickListener { defaultIconLinearLayout.performClick() }
+        defaultFolderIconRadioButton.setOnClickListener { defaultFolderIconLinearLayout.performClick() }
         webpageFavoriteIconRadioButton.setOnClickListener { webpageFavoriteIconLinearLayout.performClick() }
+        customIconRadioButton.setOnClickListener { customIconLinearLayout.performClick() }
 
         // Set the current icon linear layout click listener.
         currentIconLinearLayout.setOnClickListener {
@@ -335,24 +380,26 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
             currentIconRadioButton.isChecked = true
 
             // Uncheck the other radio buttons.
-            defaultIconRadioButton.isChecked = false
+            defaultFolderIconRadioButton.isChecked = false
             webpageFavoriteIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
 
             // Update the save button.
-            updateSaveButton(currentFolderName, currentParentFolderDatabaseId, currentDisplayOrder)
+            updateSaveButton()
         }
 
         // Set the default icon linear layout click listener.
-        defaultIconLinearLayout.setOnClickListener {
+        defaultFolderIconLinearLayout.setOnClickListener {
             // Check the default icon radio button.
-            defaultIconRadioButton.isChecked = true
+            defaultFolderIconRadioButton.isChecked = true
 
             // Uncheck the other radio buttons.
             currentIconRadioButton.isChecked = false
             webpageFavoriteIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
 
             // Update the save button.
-            updateSaveButton(currentFolderName, currentParentFolderDatabaseId, currentDisplayOrder)
+            updateSaveButton()
         }
 
         // Set the webpage favorite icon linear layout click listener.
@@ -362,25 +409,45 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
 
             // Uncheck the other radio buttons.
             currentIconRadioButton.isChecked = false
-            defaultIconRadioButton.isChecked = false
+            defaultFolderIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
 
             // Update the save button.
-            updateSaveButton(currentFolderName, currentParentFolderDatabaseId, currentDisplayOrder)
+            updateSaveButton()
+        }
+
+        // Set the custom icon linear layout click listener.
+        customIconLinearLayout.setOnClickListener {
+            // Check the current icon radio button.
+            customIconRadioButton.isChecked = true
+
+            // Uncheck the other radio buttons.
+            currentIconRadioButton.isChecked = false
+            defaultFolderIconRadioButton.isChecked = false
+            webpageFavoriteIconRadioButton.isChecked = false
+
+            // Update the save button.
+            updateSaveButton()
+        }
+
+        browseButton.setOnClickListener {
+            // Open the file picker.
+            browseActivityResultLauncher.launch("image/*")
         }
 
         // Update the save button if the bookmark name changes.
         nameEditText.addTextChangedListener(object: TextWatcher {
-            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(s: Editable) {
+            override fun afterTextChanged(editable: Editable?) {
                 // Update the save button.
-                updateSaveButton(currentFolderName, currentParentFolderDatabaseId, currentDisplayOrder)
+                updateSaveButton()
             }
         })
 
@@ -390,7 +457,7 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
             parentFolderSpinner.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
                 override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
                     // Update the save button.
-                    updateSaveButton(currentFolderName, currentParentFolderDatabaseId, currentDisplayOrder)
+                    updateSaveButton()
                 }
 
                 override fun onNothingSelected(parent: AdapterView<*>) {
@@ -401,17 +468,17 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
 
         // Update the save button if the display order changes.
         displayOrderEditText.addTextChangedListener(object: TextWatcher {
-            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(s: Editable) {
+            override fun afterTextChanged(editable: Editable?) {
                 // Update the save button.
-                updateSaveButton(currentFolderName, currentParentFolderDatabaseId, currentDisplayOrder)
+                updateSaveButton()
             }
         })
 
@@ -420,7 +487,7 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
             // Check the key code, event, and button status.
             if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && saveButton.isEnabled) {  // The enter key was pressed and the save button is enabled.
                 // Trigger the listener and return the dialog fragment to the parent activity.
-                editBookmarkFolderDatabaseViewListener.saveBookmarkFolder(this, folderDatabaseId, favoriteIconBitmap)
+                editBookmarkFolderDatabaseViewListener.saveBookmarkFolder(this, folderDatabaseId)
 
                 // Manually dismiss the alert dialog.
                 alertDialog.dismiss()
@@ -437,7 +504,7 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
             // Check the key code, event, and button status.
             if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && saveButton.isEnabled) {  // The enter key was pressed and the save button is enabled.
                 // Trigger the listener and return the dialog fragment to the parent activity.
-                editBookmarkFolderDatabaseViewListener.saveBookmarkFolder(this, folderDatabaseId, favoriteIconBitmap)
+                editBookmarkFolderDatabaseViewListener.saveBookmarkFolder(this, folderDatabaseId)
 
                 // Manually dismiss the alert dialog.
                 alertDialog.dismiss()
@@ -449,14 +516,17 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
             }
         }
 
+        // Initially disable the edit button.
+        saveButton.isEnabled = false
+
         // Return the alert dialog.
         return alertDialog
     }
 
-    private fun updateSaveButton(currentFolderName: String, currentParentFolderDatabaseId: Int, currentDisplayOrder: Int) {
+    private fun updateSaveButton() {
         // Get the values from the views.
         val newFolderName = nameEditText.text.toString()
-        val newParentFolderDatabaseId = parentFolderSpinner.selectedItemId.toInt()
+        val newParentFolderDatabaseIdInt = parentFolderSpinner.selectedItemId.toInt()
         val newDisplayOrder = displayOrderEditText.text.toString()
 
         // Has the favorite icon changed?
@@ -466,10 +536,10 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
         val folderRenamed = (newFolderName != currentFolderName)
 
         // Has the parent folder changed?
-        val parentFolderChanged = newParentFolderDatabaseId != currentParentFolderDatabaseId
+        val parentFolderChanged = (newParentFolderDatabaseIdInt != currentParentFolderDatabaseIdInt)
 
         // Has the display order changed?
-        val displayOrderChanged = newDisplayOrder != currentDisplayOrder.toString()
+        val displayOrderChanged = (newDisplayOrder != currentDisplayOrder.toString())
 
         // Update the enabled status of the edit button.
         saveButton.isEnabled = (iconChanged || folderRenamed || parentFolderChanged || displayOrderChanged) && newFolderName.isNotBlank() && newDisplayOrder.isNotBlank()
index 7427173d9c87d7767a33dd5801f21c47fa6aa04c..3c19eb132946606327014520a7f1a3ac5783408e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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>.
  *
@@ -24,6 +24,7 @@ import android.content.Context
 import android.content.DialogInterface
 import android.graphics.Bitmap
 import android.graphics.BitmapFactory
+import android.net.Uri
 import android.os.Bundle
 import android.text.Editable
 import android.text.TextWatcher
@@ -36,10 +37,15 @@ 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.core.graphics.drawable.toBitmap
 import androidx.fragment.app.DialogFragment
 import androidx.preference.PreferenceManager
 
+import com.google.android.material.snackbar.Snackbar
+
 import com.stoutner.privacybrowser.R
 import com.stoutner.privacybrowser.helpers.BOOKMARK_NAME
 import com.stoutner.privacybrowser.helpers.FAVORITE_ICON
@@ -48,8 +54,8 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
 import java.io.ByteArrayOutputStream
 
 // Define the class constants.
-private const val DATABASE_ID = "database_id"
-private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
+private const val DATABASE_ID = "A"
+private const val FAVORITE_ICON_BYTE_ARRAY = "B"
 
 class EditBookmarkFolderDialog : DialogFragment() {
     companion object {
@@ -81,19 +87,54 @@ class EditBookmarkFolderDialog : DialogFragment() {
         }
     }
 
-    // Declare the class variables.
-    private lateinit var editBookmarkFolderListener: EditBookmarkFolderListener
-    private lateinit var bookmarksDatabaseHelper: BookmarksDatabaseHelper
-    private lateinit var currentFolderName: String
+    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(customIconImageView, 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 64 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 currentIconRadioButton: RadioButton
+    private lateinit var customIconLinearLayout: LinearLayout
+    private lateinit var customIconImageView: ImageView
     private lateinit var folderNameEditText: EditText
     private lateinit var saveButton: Button
 
+    // Declare the class variables.
+    private lateinit var currentFolderName: String
+    private lateinit var editBookmarkFolderListener: EditBookmarkFolderListener
+
     // The public interface is used to send information back to the parent activity.
     interface EditBookmarkFolderListener {
-        fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap)
+        fun saveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int)
     }
 
     override fun onAttach(context: Context) {
@@ -116,7 +157,7 @@ class EditBookmarkFolderDialog : DialogFragment() {
         val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
 
         // Initialize the database helper.
-        bookmarksDatabaseHelper = BookmarksDatabaseHelper(requireContext())
+        val bookmarksDatabaseHelper = BookmarksDatabaseHelper(requireContext())
 
         // Get a cursor with the selected folder.
         val folderCursor = bookmarksDatabaseHelper.getBookmark(selectedFolderDatabaseId)
@@ -130,6 +171,9 @@ class EditBookmarkFolderDialog : DialogFragment() {
         // Set the title.
         dialogBuilder.setTitle(R.string.edit_folder)
 
+        // Set the icon.
+        dialogBuilder.setIcon(R.drawable.folder)
+
         // Set the view.
         dialogBuilder.setView(R.layout.edit_bookmark_folder_dialog)
 
@@ -139,7 +183,7 @@ class EditBookmarkFolderDialog : DialogFragment() {
         // Set the save button listener.
         dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface?, _: Int ->
             // Return the dialog fragment to the parent activity on save.
-            editBookmarkFolderListener.onSaveBookmarkFolder(this, selectedFolderDatabaseId, favoriteIconBitmap)
+            editBookmarkFolderListener.saveBookmarkFolder(this, selectedFolderDatabaseId)
         }
 
         // Create an alert dialog from the alert dialog builder.
@@ -163,11 +207,15 @@ class EditBookmarkFolderDialog : DialogFragment() {
         val currentIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.current_icon_linearlayout)!!
         currentIconRadioButton = alertDialog.findViewById(R.id.current_icon_radiobutton)!!
         val currentIconImageView = alertDialog.findViewById<ImageView>(R.id.current_icon_imageview)!!
-        val defaultIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.default_icon_linearlayout)!!
-        val defaultIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)!!
+        val defaultFolderIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.default_folder_icon_linearlayout)!!
+        val defaultFolderIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.default_folder_icon_radiobutton)!!
         val webpageFavoriteIconLinearLayout = alertDialog.findViewById<LinearLayout>(R.id.webpage_favorite_icon_linearlayout)!!
         val webpageFavoriteIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)!!
         val webpageFavoriteIconImageView = alertDialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)!!
+        customIconLinearLayout = alertDialog.findViewById(R.id.custom_icon_linearlayout)!!
+        val customIconRadioButton = alertDialog.findViewById<RadioButton>(R.id.custom_icon_radiobutton)!!
+        customIconImageView = alertDialog.findViewById(R.id.custom_icon_imageview)!!
+        val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
         folderNameEditText = alertDialog.findViewById(R.id.folder_name_edittext)!!
         saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
 
@@ -177,25 +225,20 @@ class EditBookmarkFolderDialog : DialogFragment() {
         // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
         val currentIconBitmap = BitmapFactory.decodeByteArray(currentIconByteArray, 0, currentIconByteArray.size)
 
-        // Display the current icon bitmap.
-        currentIconImageView.setImageBitmap(currentIconBitmap)
-
-        // Set the webpage favorite icon bitmap.
-        webpageFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
-
         // Get the current folder name.
         currentFolderName = folderCursor.getString(folderCursor.getColumnIndexOrThrow(BOOKMARK_NAME))
 
-        // Display the current folder name.
+        // Populate the views.
+        currentIconImageView.setImageBitmap(currentIconBitmap)
+        webpageFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
+        customIconImageView.setImageBitmap(AppCompatResources.getDrawable(requireContext(), R.drawable.folder)!!.toBitmap(128, 128, Bitmap.Config.ARGB_8888))
         folderNameEditText.setText(currentFolderName)
 
-        // Initially disable the save button.
-        saveButton.isEnabled = false
-
         // Set the radio button listeners.  These perform a click on the linear layout, which contains the necessary logic.
         currentIconRadioButton.setOnClickListener { currentIconLinearLayout.performClick() }
-        defaultIconRadioButton.setOnClickListener { defaultIconLinearLayout.performClick() }
+        defaultFolderIconRadioButton.setOnClickListener { defaultFolderIconLinearLayout.performClick() }
         webpageFavoriteIconRadioButton.setOnClickListener { webpageFavoriteIconLinearLayout.performClick() }
+        customIconRadioButton.setOnClickListener { customIconLinearLayout.performClick() }
 
         // Set the current icon linear layout click listener.
         currentIconLinearLayout.setOnClickListener {
@@ -203,21 +246,23 @@ class EditBookmarkFolderDialog : DialogFragment() {
             currentIconRadioButton.isChecked = true
 
             // Uncheck the other radio buttons.
-            defaultIconRadioButton.isChecked = false
+            defaultFolderIconRadioButton.isChecked = false
             webpageFavoriteIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
 
             // Update the save button.
             updateSaveButton()
         }
 
-        // Set the default icon linear layout click listener.
-        defaultIconLinearLayout.setOnClickListener {
+        // Set the default folder icon linear layout click listener.
+        defaultFolderIconLinearLayout.setOnClickListener {
             // Check the default icon radio button.
-            defaultIconRadioButton.isChecked = true
+            defaultFolderIconRadioButton.isChecked = true
 
             // Uncheck the other radio buttons.
             currentIconRadioButton.isChecked = false
             webpageFavoriteIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
 
             // Update the save button.
             updateSaveButton()
@@ -230,23 +275,43 @@ class EditBookmarkFolderDialog : DialogFragment() {
 
             // Uncheck the other radio buttons.
             currentIconRadioButton.isChecked = false
-            defaultIconRadioButton.isChecked = false
+            defaultFolderIconRadioButton.isChecked = false
+            customIconRadioButton.isChecked = false
 
             // Update the save button.
             updateSaveButton()
         }
 
+        // Set the custom icon linear layout click listener.
+        customIconLinearLayout.setOnClickListener {
+            // Check the current icon radio button.
+            customIconRadioButton.isChecked = true
+
+            // Uncheck the other radio buttons.
+            currentIconRadioButton.isChecked = false
+            defaultFolderIconRadioButton.isChecked = false
+            webpageFavoriteIconRadioButton.isChecked = false
+
+            // Update the save button.
+            updateSaveButton()
+        }
+
+        browseButton.setOnClickListener {
+            // Open the file picker.
+            browseActivityResultLauncher.launch("image/*")
+        }
+
         // Update the status of the save button when the folder name is changed.
         folderNameEditText.addTextChangedListener(object: TextWatcher {
-            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(s: Editable) {
+            override fun afterTextChanged(editable: Editable?) {
                 // Update the save button.
                 updateSaveButton()
             }
@@ -257,7 +322,7 @@ class EditBookmarkFolderDialog : DialogFragment() {
             // Check the key code, event, and button status.
             if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && saveButton.isEnabled) {  // The enter key was pressed and the save button is enabled.
                 // Trigger the listener and return the dialog fragment to the parent activity.
-                editBookmarkFolderListener.onSaveBookmarkFolder(this, selectedFolderDatabaseId, favoriteIconBitmap)
+                editBookmarkFolderListener.saveBookmarkFolder(this, selectedFolderDatabaseId)
 
                 // Manually dismiss the the alert dialog.
                 alertDialog.dismiss()
@@ -269,6 +334,9 @@ class EditBookmarkFolderDialog : DialogFragment() {
             }
         }
 
+        // Initially disable the save button.
+        saveButton.isEnabled = false
+
         // Return the alert dialog.
         return alertDialog
     }
index c6dd64470bf3a60660f9a6633414245e890df38b..dca20947c0ed69f644a917d75bca8cab0d0e9603 100644 (file)
@@ -207,11 +207,11 @@ class SaveDialog : DialogFragment() {
 
         // Update the UI when the URL changes.
         urlEditText.addTextChangedListener(object : TextWatcher {
-            override fun beforeTextChanged(charSequence: CharSequence?, i: Int, i1: Int, i2: Int) {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(charSequence: CharSequence?, i: Int, i1: Int, i2: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
@@ -230,7 +230,7 @@ class SaveDialog : DialogFragment() {
                     blobUrlWarningTextView.visibility = View.GONE
 
                 // Enable the save button if the edit texts are populated and this isn't a blob URL.
-                saveButton.isEnabled = urlToSave.isNotEmpty() && fileName.isNotEmpty() && !blobUrl
+                saveButton.isEnabled = urlToSave.isNotBlank() && fileName.isNotBlank() && !blobUrl
 
                 // Determine if this is a data URL.
                 val dataUrl = urlToSave.startsWith("data:")
@@ -259,11 +259,11 @@ class SaveDialog : DialogFragment() {
 
         // Update the UI when the file name changes.
         fileNameEditText.addTextChangedListener(object : TextWatcher {
-            override fun beforeTextChanged(charSequence: CharSequence?, p1: Int, p2: Int, p3: Int) {
+            override fun beforeTextChanged(charSequence: CharSequence?, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
 
-            override fun onTextChanged(charSequence: CharSequence?, p1: Int, p2: Int, p3: Int) {
+            override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
                 // Do nothing.
             }
 
@@ -276,7 +276,7 @@ class SaveDialog : DialogFragment() {
                 val blobUrl = urlToSave.startsWith("blob:")
 
                 // Enable the save button if the edit texts are populated and this isn't a blob URL (or a data URL using Android's download manager).
-                saveButton.isEnabled = urlToSave.isNotEmpty() && fileName.isNotEmpty() && !blobUrl && !dataUrlWarningTextView.isVisible
+                saveButton.isEnabled = urlToSave.isNotBlank() && fileName.isNotBlank() && !blobUrl && !dataUrlWarningTextView.isVisible
             }
         })
 
index 683c7aef14ea3df4ded5cf1376cfadeda4029ec4..6076950291c304e74fa75d0326913973da8c1984 100644 (file)
@@ -156,9 +156,9 @@ class NestedScrollWebView @JvmOverloads constructor(context: Context, attributeS
         // Store the current favorite icon height.
         favoriteIconHeight = icon.height
 
-        // Scale the favorite icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
-        favoriteIcon = if (icon.height > 256 || icon.width > 256)  // Scale the icon before storing it.
-            Bitmap.createScaledBitmap(icon, 256, 256, true)
+        // Scale the favorite icon bitmap down if it is larger than 128 in either direction.  Filtering uses bilinear interpolation.
+        favoriteIcon = if (icon.height > 128 || icon.width > 128)  // Scale the icon before storing it.
+            Bitmap.createScaledBitmap(icon, 128, 128, true)
         else  // Store the icon as presented.
             icon
     }
index f652bffbcac499ba2336c4a4b96af9d95c71b899..b88d74dfdfb345e79bb04e8ed4fffb94f74e34e1 100644 (file)
Binary files a/app/src/main/res/drawable-hdpi/folder_blue_bitmap.png and b/app/src/main/res/drawable-hdpi/folder_blue_bitmap.png differ
index 2e8e77df5ddc3539eb897d6d15859ee8be445c3c..cdac1bc0ca0dcd88abc3693310e7af47ab2fb3d6 100644 (file)
Binary files a/app/src/main/res/drawable-mdpi/folder_blue_bitmap.png and b/app/src/main/res/drawable-mdpi/folder_blue_bitmap.png differ
index ab7a3083fe349b8b3d70e4f75e7e87ced7006a7c..3e36293cb45c660c987b5cf2c39c6c333e45157d 100644 (file)
Binary files a/app/src/main/res/drawable-xhdpi/folder_blue_bitmap.png and b/app/src/main/res/drawable-xhdpi/folder_blue_bitmap.png differ
index 73264e0897dea33c754ec099f206214103acc51c..94d29c90e3b4fee36b38f65e2aff9cd633dcc7f8 100644 (file)
Binary files a/app/src/main/res/drawable-xxhdpi/folder_blue_bitmap.png and b/app/src/main/res/drawable-xxhdpi/folder_blue_bitmap.png differ
index 225a217970e7fc558a1363e929d3642e3285cd21..eeca45bb123c40cb4ab5179398fd605d77fc2223 100644 (file)
Binary files a/app/src/main/res/drawable-xxxhdpi/folder_blue_bitmap.png and b/app/src/main/res/drawable-xxxhdpi/folder_blue_bitmap.png differ
diff --git a/app/src/main/res/drawable/bookmark.xml b/app/src/main/res/drawable/bookmark.xml
new file mode 100644 (file)
index 0000000..12811c3
--- /dev/null
@@ -0,0 +1,13 @@
+<!-- This file comes from the Android Material icon set, where it is called `bookmark_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="M480,720L312,792Q272,809 236,785.5Q200,762 200,719L200,200Q200,167 223.5,143.5Q247,120 280,120L680,120Q713,120 736.5,143.5Q760,167 760,200L760,719Q760,762 724,785.5Q688,809 648,792L480,720ZM480,632L680,718Q680,718 680,718Q680,718 680,718L680,200Q680,200 680,200Q680,200 680,200L280,200Q280,200 280,200Q280,200 280,200L280,718Q280,718 280,718Q280,718 280,718L480,632ZM480,200L280,200Q280,200 280,200Q280,200 280,200L280,200Q280,200 280,200Q280,200 280,200L680,200Q680,200 680,200Q680,200 680,200L680,200Q680,200 680,200Q680,200 680,200L480,200Z" />
+</vector>
index ddf327d98d034b37d37d607ce17c8d841c1333ac..7b039a8475024983db354a7b11de87ce345f8613 100644 (file)
@@ -1,4 +1,4 @@
-<!-- This file comes from the Android Material icon set, where it is called `bookmarks`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `bookmarks`.  It is released under the Apache License 2.0 <https://fonts.google.com/icons>. -->
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
@@ -10,4 +10,4 @@
     <path
         android:fillColor="@color/blue_icon"
         android:pathData="M19,18l2,1V3c0,-1.1 -0.9,-2 -2,-2H8.99C7.89,1 7,1.9 7,3h10c1.1,0 2,0.9 2,2v13zM15,5H5c-1.1,0 -2,0.9 -2,2v16l7,-3 7,3V7c0,-1.1 -0.9,-2 -2,-2z"/>
-</vector>
\ No newline at end of file
+</vector>
index 6e82716ae84b9b3f88de1a8ab480aa9f580b836a..d719c3d5fb9952deb2ee48ed0ae24e5a775111f1 100644 (file)
@@ -1,7 +1,7 @@
 <!--
-  Copyright © 2017,2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2017, 2022, 2024 Soren Stoutner <soren@stoutner.com>.
 
-  This file is derived from elements of `bookmark` and `create_new_folder`, 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 `bookmark` and `create_new_folder`, 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>.
 
@@ -26,6 +26,6 @@
     android:viewportWidth="24" >
 
     <path
-        android:fillColor="@color/white"
+        android:fillColor="@color/blue_icon"
         android:pathData="M7,3C5.9,3 5.01,3.9 5.01,5L5,21L12,18L19,21L19,5C19,3.9 18.1,3 17,3L7,3zM11,7L13,7L13,10L16,10L16,12L13,12L13,15L11,15L11,12L8,12L8,10L11,10L11,7z" />
-</vector>
\ No newline at end of file
+</vector>
diff --git a/app/src/main/res/drawable/folder.xml b/app/src/main/res/drawable/folder.xml
new file mode 100644 (file)
index 0000000..1727e40
--- /dev/null
@@ -0,0 +1,13 @@
+<!-- This file comes from the Android Material icon set, where it is called `folder_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="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L367,160Q383,160 397.5,166Q412,172 423,183L480,240L800,240Q833,240 856.5,263.5Q880,287 880,320L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320Q800,320 800,320Q800,320 800,320L447,320L367,240Q367,240 367,240Q367,240 367,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720ZM160,720Q160,720 160,720Q160,720 160,720L160,240Q160,240 160,240Q160,240 160,240L160,240Q160,240 160,240Q160,240 160,240L160,320L160,320Q160,320 160,320Q160,320 160,320L160,720Q160,720 160,720Q160,720 160,720L160,720Z" />
+</vector>
index 23f9156f03f580c215960682f434b26e9a857151..1fd1a0492551ea7284dd940c83a684ea1f791c51 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2016-2019,2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2016-2019, 2022, 2024 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
 
   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/>. -->
 
-<ScrollView
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="wrap_content"
-    android:layout_width="match_parent" >
+    android:layout_width="match_parent"
+    xmlns:tools="http://schemas.android.com/tools" >
 
     <LinearLayout
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
         android:orientation="vertical" >
 
-        <!-- The `TextInputLayout` makes the `android:hint` float above the `EditText`. -->
-        <com.google.android.material.textfield.TextInputLayout
+        <!-- Webpage favorite icon. -->
+        <LinearLayout
+            android:id="@+id/webpage_favorite_icon_linearlayout"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
+            android:orientation="horizontal"
             android:layout_marginTop="12dp"
+            android:layout_marginBottom="6dp" >
+
+            <RadioButton
+                android:id="@+id/webpage_favorite_icon_radiobutton"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:checked="true" />
+
+            <ImageView
+                android:id="@+id/webpage_favorite_icon_imageview"
+                android:layout_height="30dp"
+                android:layout_width="30dp"
+                android:layout_gravity="center_vertical"
+                tools:ignore="ContentDescription" />
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginStart="7dp"
+                android:layout_gravity="center_vertical"
+                android:text="@string/webpage_favorite_icon"
+                android:textSize="18sp"
+                android:textColor="?android:textColorPrimary" />
+        </LinearLayout>
+
+        <!-- Custom icon. -->
+        <LinearLayout
+            android:id="@+id/custom_icon_linearlayout"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:orientation="horizontal"
+            android:layout_marginTop="6dp" >
+
+            <RadioButton
+                android:id="@+id/custom_icon_radiobutton"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical" />
+
+            <ImageView
+                android:id="@+id/custom_icon_imageview"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center_vertical"
+                tools:ignore="ContentDescription" />
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginStart="7dp"
+                android:layout_gravity="center_vertical"
+                android:text="@string/custom_bookmark_icon"
+                android:textSize="18sp"
+                android:textColor="?android:textColorPrimary" />
+        </LinearLayout>
+
+        <!-- Browse button. -->
+        <Button
+            android:id="@+id/browse_button"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/browse"
+            android:layout_marginBottom="12dp" />
+
+        <!-- The `TextInputLayout` makes the `android:hint` float above the edit text. -->
+        <com.google.android.material.textfield.TextInputLayout
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:layout_marginTop="6dp"
             android:layout_marginBottom="6dp"
             android:layout_marginStart="4dp"
             android:layout_marginEnd="4dp" >
 
             <!-- `android:imeOptions="actionGo"` sets the keyboard to have a `go` key instead of a `new line` key.  `android:inputType="textUri"` disables spell check in the `EditText`. -->
             <com.google.android.material.textfield.TextInputEditText
-                android:id="@+id/create_bookmark_name_edittext"
+                android:id="@+id/bookmark_name_edittext"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:hint="@string/bookmark_name"
                 android:selectAllOnFocus="true" />
             </com.google.android.material.textfield.TextInputLayout>
 
-        <!-- The `TextInputLayout` makes the `android:hint` float above the `EditText`. -->
+        <!-- The `TextInputLayout` makes the `android:hint` float above the edit text. -->
         <com.google.android.material.textfield.TextInputLayout
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
 
             <!-- `android:imeOptions="actionGo"` sets the keyboard to have a `go` key instead of a `new line` key.  `android:inputType="textUri"` disables spell check in the `EditText`. -->
             <com.google.android.material.textfield.TextInputEditText
-                android:id="@+id/create_bookmark_url_edittext"
+                android:id="@+id/bookmark_url_edittext"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:hint="@string/bookmark_url"
                 android:selectAllOnFocus="true" />
         </com.google.android.material.textfield.TextInputLayout>
     </LinearLayout>
-</ScrollView>
\ No newline at end of file
+</ScrollView>
index adacea94af78777aad2731404834b0d308070b55..51cb7e2ebcb3bdfda9b26f02ee286e5f8761d065 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright 2016-2019,2021-2023 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2016-2019, 2021-2024 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
 
 
 <ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
     android:layout_height="wrap_content"
-    android:layout_width="match_parent" >
+    android:layout_width="match_parent"
+    xmlns:tools="http://schemas.android.com/tools" >
 
     <LinearLayout
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
         android:orientation="vertical" >
 
-        <!-- Default icon. -->
+        <!-- Default folder icon. -->
         <LinearLayout
-            android:id="@+id/default_icon_linearlayout"
+            android:id="@+id/default_folder_icon_linearlayout"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
             android:orientation="horizontal"
             android:layout_marginBottom="6dp" >
 
             <RadioButton
-                android:id="@+id/default_icon_radiobutton"
+                android:id="@+id/default_folder_icon_radiobutton"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_gravity="center_vertical"
                 android:checked="true" />
 
             <ImageView
-                android:id="@+id/default_icon_imageview"
+                android:id="@+id/default_folder_icon_imageview"
                 android:layout_height="30dp"
                 android:layout_width="30dp"
                 android:layout_gravity="center_vertical"
@@ -70,7 +70,7 @@
             android:layout_width="match_parent"
             android:orientation="horizontal"
             android:layout_marginTop="6dp"
-            android:layout_marginBottom="12dp" >
+            android:layout_marginBottom="6dp" >
 
             <RadioButton
                 android:id="@+id/webpage_favorite_icon_radiobutton"
                 android:textColor="?android:textColorPrimary" />
         </LinearLayout>
 
+        <!-- Custom icon. -->
+        <LinearLayout
+            android:id="@+id/custom_icon_linearlayout"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:orientation="horizontal"
+            android:layout_marginTop="6dp" >
+
+            <RadioButton
+                android:id="@+id/custom_icon_radiobutton"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical" />
+
+            <ImageView
+                android:id="@+id/custom_icon_imageview"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center_vertical"
+                tools:ignore="ContentDescription" />
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginStart="7dp"
+                android:layout_gravity="center_vertical"
+                android:text="@string/custom_folder_icon"
+                android:textSize="18sp"
+                android:textColor="?android:textColorPrimary" />
+        </LinearLayout>
+
+        <!-- Browse button. -->
+        <Button
+            android:id="@+id/browse_button"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/browse"
+            android:layout_marginBottom="12dp" />
+
         <!-- The text input layout makes the `android:hint` float above the edit text. -->
         <com.google.android.material.textfield.TextInputLayout
             android:layout_height="wrap_content"
index c4d6fd7e296657eb1251abc3a319537ee4b44978..45385969a7c8593b60547403435630ec85f2f261 100644 (file)
                 android:textColor="?android:textColorPrimary" />
         </LinearLayout>
 
+        <!-- Custom icon. -->
+        <LinearLayout
+            android:id="@+id/custom_icon_linearlayout"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:orientation="horizontal"
+            android:layout_marginTop="6dp" >
+
+            <RadioButton
+                android:id="@+id/custom_icon_radiobutton"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical" />
+
+            <ImageView
+                android:id="@+id/custom_icon_imageview"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center_vertical"
+                tools:ignore="ContentDescription" />
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginStart="7dp"
+                android:layout_gravity="center_vertical"
+                android:text="@string/custom_bookmark_icon"
+                android:textSize="18sp"
+                android:textColor="?android:textColorPrimary" />
+        </LinearLayout>
+
+        <!-- Browse button. -->
+        <Button
+            android:id="@+id/browse_button"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/browse"
+            android:layout_marginBottom="12dp" />
+
         <!-- Bookmark name.  The text input layout makes the `android:hint` float above the edit text. -->
         <com.google.android.material.textfield.TextInputLayout
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
-            android:layout_marginTop="12dp"
+            android:layout_marginTop="6dp"
             android:layout_marginBottom="6dp"
             android:layout_marginStart="4dp"
             android:layout_marginEnd="4dp" >
index 2ce1495adcadd844b49758107c50b80046f8e95c..e14ebd796ad5b6b637bff6db1e26f8d383e9eeee 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2016-2017,2019-2022 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2016-2017, 2019-2022, 2024 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
 
                 android:textColor="?android:textColorPrimary" />
         </LinearLayout>
 
+        <!-- Custom icon. -->
+        <LinearLayout
+            android:id="@+id/custom_icon_linearlayout"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:orientation="horizontal"
+            android:layout_marginTop="6dp" >
+
+            <RadioButton
+                android:id="@+id/custom_icon_radiobutton"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical" />
+
+            <ImageView
+                android:id="@+id/custom_icon_imageview"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center_vertical"
+                tools:ignore="ContentDescription" />
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginStart="7dp"
+                android:layout_gravity="center_vertical"
+                android:text="@string/custom_bookmark_icon"
+                android:textSize="18sp"
+                android:textColor="?android:textColorPrimary" />
+        </LinearLayout>
+
+        <!-- Browse button. -->
+        <Button
+            android:id="@+id/browse_button"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/browse"
+            android:layout_marginBottom="12dp" />
+
         <!-- The text input layout makes the `android:hint` float above the edit text. -->
         <com.google.android.material.textfield.TextInputLayout
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
-            android:layout_marginTop="12dp"
+            android:layout_marginTop="6dp"
             android:layout_marginBottom="6dp"
             android:layout_marginStart="4dp"
             android:layout_marginEnd="4dp" >
                 android:selectAllOnFocus="true" />
         </com.google.android.material.textfield.TextInputLayout>
     </LinearLayout>
-</ScrollView>
\ No newline at end of file
+</ScrollView>
index e297a95652ac6cc1685b5f9a98389796f549d2a5..db029e392afda56cd7ef3f4985f4165ed72b0173 100644 (file)
                 android:textColor="?android:textColorPrimary" />
         </LinearLayout>
 
-        <!-- Default icon. -->
+        <!-- Default folder icon. -->
         <LinearLayout
-            android:id="@+id/default_icon_linearlayout"
+            android:id="@+id/default_folder_icon_linearlayout"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
             android:orientation="horizontal"
             android:layout_marginBottom="6dp" >
 
             <RadioButton
-                android:id="@+id/default_icon_radiobutton"
+                android:id="@+id/default_folder_icon_radiobutton"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_gravity="center_vertical" />
 
             <ImageView
-                android:id="@+id/default_icon_imageview"
+                android:id="@+id/default_folder_icon_imageview"
                 android:layout_height="30dp"
                 android:layout_width="30dp"
                 android:layout_gravity="center_vertical"
                 android:textColor="?android:textColorPrimary" />
         </LinearLayout>
 
+        <!-- Custom icon. -->
+        <LinearLayout
+            android:id="@+id/custom_icon_linearlayout"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:orientation="horizontal"
+            android:layout_marginTop="6dp" >
+
+            <RadioButton
+                android:id="@+id/custom_icon_radiobutton"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical" />
+
+            <ImageView
+                android:id="@+id/custom_icon_imageview"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center_vertical"
+                tools:ignore="ContentDescription" />
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginStart="7dp"
+                android:layout_gravity="center_vertical"
+                android:text="@string/custom_folder_icon"
+                android:textSize="18sp"
+                android:textColor="?android:textColorPrimary" />
+        </LinearLayout>
+
+        <!-- Browse button. -->
+        <Button
+            android:id="@+id/browse_button"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/browse"
+            android:layout_marginBottom="12dp" />
+
         <!-- Folder name.  The text input layout makes the `android:hint` float above the edit text. -->
         <com.google.android.material.textfield.TextInputLayout
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
-            android:layout_marginTop="12dp"
+            android:layout_marginTop="6dp"
             android:layout_marginBottom="6dp"
             android:layout_marginStart="4dp"
             android:layout_marginEnd="4dp" >
index 7bf352f5fb754a7bbf6046408105d66199e512bb..9eecad1c2acb61d6f38ac768e58b61e3b46e8454 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright 2016-2017,2019-2023 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2016-2017, 2019-2024 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
 
@@ -62,9 +62,9 @@
                 android:textColor="?android:textColorPrimary" />
         </LinearLayout>
 
-        <!-- Default icon. -->
+        <!-- Default folder icon. -->
         <LinearLayout
-            android:id="@+id/default_icon_linearlayout"
+            android:id="@+id/default_folder_icon_linearlayout"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
             android:orientation="horizontal"
             android:layout_marginBottom="6dp" >
 
             <RadioButton
-                android:id="@+id/default_icon_radiobutton"
+                android:id="@+id/default_folder_icon_radiobutton"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="center_vertical" />
 
             <ImageView
-                android:id="@+id/default_icon_imageview"
+                android:id="@+id/default_folder_icon_imageview"
                 android:layout_height="30dp"
                 android:layout_width="30dp"
                 android:layout_gravity="center_vertical"
                 android:textColor="?android:textColorPrimary" />
         </LinearLayout>
 
+        <!-- Custom icon. -->
+        <LinearLayout
+            android:id="@+id/custom_icon_linearlayout"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:orientation="horizontal"
+            android:layout_marginTop="6dp" >
+
+            <RadioButton
+                android:id="@+id/custom_icon_radiobutton"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical" />
+
+            <ImageView
+                android:id="@+id/custom_icon_imageview"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center_vertical"
+                tools:ignore="ContentDescription" />
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginStart="7dp"
+                android:layout_gravity="center_vertical"
+                android:text="@string/custom_folder_icon"
+                android:textSize="18sp"
+                android:textColor="?android:textColorPrimary" />
+        </LinearLayout>
+
+        <!-- Browse button. -->
+        <Button
+            android:id="@+id/browse_button"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/browse"
+            android:layout_marginBottom="12dp" />
+
         <!-- The text input layout makes the `android:hint` float above the edit text. -->
         <com.google.android.material.textfield.TextInputLayout
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
-            android:layout_marginTop="12dp"
+            android:layout_marginTop="6dp"
             android:layout_marginStart="4dp"
             android:layout_marginEnd="4dp" >
 
index c44f2ed28dba14f4e7bb0060aa73015c1ea932be..20a5e9a3b3a7f19f7baf78a3925867d2d50a33f8 100644 (file)
     <string name="create_folder">Create Folder</string>
     <string name="current_bookmark_icon">Current bookmark icon</string>
     <string name="current_folder_icon">Current folder icon</string>
+    <string name="custom_bookmark_icon">Custom bookmark icon</string>
+    <string name="custom_folder_icon">Custom folder icon</string>
     <string name="default_folder_icon">Default folder icon</string>
     <string name="webpage_favorite_icon">Webpage favorite icon</string>
     <string name="bookmark_name">Bookmark name</string>
     <string name="edit_folder">Edit Folder</string>
     <string name="move_to_folder">Move to Folder</string>
     <string name="move">Move</string>
+    <string name="cannot_use_svg">An SVG cannot currently be used as a bookmark favorite icon.</string>
 
     <!-- Bookmarks Contextual App Bar.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
         The `%1$d` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
index 0ba31c86d51a95cb7895a6d9b7330abd59fce0a5..2491a84056b932fa455ec3d81447edd9cf4f0a82 100644 (file)
@@ -26,7 +26,7 @@ buildscript {
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:8.3.0'
+        classpath 'com.android.tools.build:gradle:8.3.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20"
 
         // NOTE: Do not place your application dependencies here; they belong