Add the ability to delete bookmarks.
authorSoren Stoutner <soren@stoutner.com>
Wed, 6 Jul 2016 02:48:28 +0000 (19:48 -0700)
committerSoren Stoutner <soren@stoutner.com>
Wed, 6 Jul 2016 02:48:28 +0000 (19:48 -0700)
21 files changed:
app/src/main/assets/about_license.html
app/src/main/assets/guide_clear_and_exit.html
app/src/main/assets/images/ic_add.png [new file with mode: 0644]
app/src/main/assets/images/ic_delete.png [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/BookmarksActivity.java
app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java
app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java
app/src/main/res/drawable/bookmarks_list_selector.xml [new file with mode: 0644]
app/src/main/res/drawable/delete.xml [new file with mode: 0644]
app/src/main/res/layout/bookmarks_coordinatorlayout.xml
app/src/main/res/layout/bookmarks_item_linearlayout.xml
app/src/main/res/layout/main_coordinatorlayout.xml
app/src/main/res/menu/bookmarks_context_menu.xml [new file with mode: 0644]
app/src/main/res/menu/menu_bookmarks_options.xml [new file with mode: 0644]
app/src/main/res/menu/menu_navigation.xml [deleted file]
app/src/main/res/menu/menu_options.xml [deleted file]
app/src/main/res/menu/webview_navigation_menu.xml [new file with mode: 0644]
app/src/main/res/menu/webview_options_menu.xml [new file with mode: 0644]
app/src/main/res/values-v19/styles.xml [deleted file]
app/src/main/res/values/strings.xml
app/src/main/res/values/styles.xml

index 6e4dc0a811db85f8c3662c6534108623a0771801..b495595d1cbdd1d2191cc842fb497b234310474b 100644 (file)
@@ -77,6 +77,7 @@
 
 <p><img class="center" src="images/ic_add.png" height="32" width="32"> ic_add.</p>
 
+<p><img class="center" src="images/ic_delete.png" height="32" width="32"> ic_download.</p>
 <hr/>
 
 <h3>GNU General Public License</h3>
index 9d5c1f34e9d5524f73198022fbbc09c80b960bae..c00ac623ae7438f93741685fb044af383b84c47d 100644 (file)
@@ -41,6 +41,7 @@
     <li><item>Removes all form data</item>.</li>
     <li><item>Clears the cache, including disk files</item>.</li>
     <li><item>Clears the back/forward history</item>.</li>
+    <li><item>Deletes the current URL</item>.</li>
     <li><item>Destroys the internal state of the WebView</item>.</li>
     <li><item>Closes Privacy Browser</item>. For Android Lollipop and newer (version >= 5.0 or API >= 21), Privacy Browser is also removed from the recent app list.</li>
 </ul>
diff --git a/app/src/main/assets/images/ic_add.png b/app/src/main/assets/images/ic_add.png
new file mode 100644 (file)
index 0000000..5c486bb
Binary files /dev/null and b/app/src/main/assets/images/ic_add.png differ
diff --git a/app/src/main/assets/images/ic_delete.png b/app/src/main/assets/images/ic_delete.png
new file mode 100644 (file)
index 0000000..46c58c3
Binary files /dev/null and b/app/src/main/assets/images/ic_delete.png differ
index 6d0819913cbb7bf009b597d7e7f6ed028b3130d3..556bd60cafb215618bef6f40fc6ee6a04d4c1d54 100644 (file)
@@ -21,21 +21,29 @@ package com.stoutner.privacybrowser;
 
 import android.app.Activity;
 import android.app.DialogFragment;
+import android.content.Context;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Bundle;
 import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
 import android.support.v4.app.NavUtils;
+import android.support.v4.widget.CursorAdapter;
 import android.support.v7.app.ActionBar;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.ListView;
-import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
 
 import java.io.ByteArrayOutputStream;
 
@@ -43,13 +51,16 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
     private BookmarksDatabaseHandler bookmarksDatabaseHandler;
     private ListView bookmarksListView;
 
+    // deleteBookmarkMenuItem is used in onCreate() and onPrepareOptionsMenu().
+    private MenuItem deleteBookmarkMenuItem;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.bookmarks_coordinatorlayout);
 
         // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
-        Toolbar bookmarksAppBar = (Toolbar) findViewById(R.id.bookmarks_toolbar);
+        final Toolbar bookmarksAppBar = (Toolbar) findViewById(R.id.bookmarks_toolbar);
         setSupportActionBar(bookmarksAppBar);
 
         // Display the home arrow on supportAppBar.
@@ -81,6 +92,101 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
             }
         });
 
+        // registerForContextMenu(bookmarksListView);
+
+        bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
+            @Override
+            public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+                String numberSelectedString;
+                long[] selectedItemsLongArray = bookmarksListView.getCheckedItemIds();
+
+                numberSelectedString = selectedItemsLongArray.length + " " + getString(R.string.selected);
+
+                mode.setSubtitle(numberSelectedString);
+            }
+
+            @Override
+            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                // Inflate the menu for the contextual app bar.
+                getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu);
+
+                mode.setTitle(R.string.bookmarks);
+
+                return true;
+            }
+
+            @Override
+            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+                return false;
+            }
+
+            @Override
+            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                int menuItemId = item.getItemId();
+
+                switch (menuItemId) {
+                    case R.id.delete_bookmark:
+                        // Get an array of the selected rows.
+                        final long[] selectedItemsLongArray = bookmarksListView.getCheckedItemIds();
+
+                        String snackbarMessage;
+
+                        // Determine how many items are in the array and prepare an appropriate Snackbar message.
+                        if (selectedItemsLongArray.length == 1) {
+                            snackbarMessage = getString(R.string.one_bookmark_deleted);
+                        } else {
+                            snackbarMessage = selectedItemsLongArray.length + " " + getString(R.string.bookmarks_deleted);
+                        }
+
+                        updateBookmarksListViewExcept(selectedItemsLongArray);
+
+                        // Show a SnackBar.
+                        Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), snackbarMessage, Snackbar.LENGTH_LONG)
+                                .setAction(R.string.undo, new View.OnClickListener() {
+                                    @Override
+                                    public void onClick(View view) {
+                                        // Do nothing because everything will be handled by `onDismissed()` below.
+                                    }
+                                })
+                                .setCallback(new Snackbar.Callback() {
+                                    @Override
+                                    public void onDismissed(Snackbar snackbar, int event) {
+                                        switch (event) {
+                                            // The user pushed the "Undo" button.
+                                            case Snackbar.Callback.DISMISS_EVENT_ACTION:
+                                                // Refresh the ListView to show the rows again.
+                                                updateBookmarksListView();
+
+                                                break;
+
+                                            // The Snackbar was dismissed without the "Undo" button being pushed.
+                                            default:
+                                                // Delete each selected row.
+                                                for (long databaseIdLong : selectedItemsLongArray) {
+                                                    // Convert `databaseIdLong` to an int.
+                                                    int databaseIdInt = (int) databaseIdLong;
+
+                                                    // Delete the database row.
+                                                    bookmarksDatabaseHandler.deleteBookmark(databaseIdInt);
+                                                }
+                                                break;
+                                        }
+                                    }
+                                })
+                                .show();
+
+                        // Close the contextual app bar.
+                        mode.finish();
+                }
+                return true;
+            }
+
+            @Override
+            public void onDestroyActionMode(ActionMode mode) {
+
+            }
+        });
+
         // Set a FloatingActionButton for creating new bookmarks.
         FloatingActionButton createBookmarkFAB = (FloatingActionButton) findViewById(R.id.create_bookmark_fab);
         assert createBookmarkFAB != null;
@@ -94,6 +200,33 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         });
     }
 
+    /*
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, v, menuInfo);
+        getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu);
+    } */
+
+    /*@Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.menu_bookmarks_options, menu);
+
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        // Disable the delete icon.
+        deleteBookmarkMenuItem = menu.findItem(R.id.delete_bookmark);
+        deleteBookmarkMenuItem.setVisible(false);
+
+        // Run all the other default commands.
+        super.onPrepareOptionsMenu(menu);
+
+        // `return true` displays the menu;
+        return true;
+    }*/
+
     @Override
     public void onCreateBookmarkCancel(DialogFragment createBookmarkDialogFragment) {
         // Do nothing because the user selected "Cancel".
@@ -123,35 +256,73 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         // Get a Cursor with the current contents of the bookmarks database.
         final Cursor bookmarksCursor = bookmarksDatabaseHandler.getBookmarksCursor();
 
-        // The last argument is 0 because no special behavior is required.
-        SimpleCursorAdapter bookmarksAdapter = new SimpleCursorAdapter(this,
-                R.layout.bookmarks_item_linearlayout,
-                bookmarksCursor,
-                new String[] { BookmarksDatabaseHandler.FAVORITEICON, BookmarksDatabaseHandler.BOOKMARK_NAME },
-                new int[] { R.id.bookmark_favorite_icon, R.id.bookmark_name },
-                0);
-
-        // Override the handling of R.id.bookmark_favorite_icon to load an image instead of a string.
-        bookmarksAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
-            public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
-                if (view.getId() == R.id.bookmark_favorite_icon) {
-                    // Get the byte array from the database.
-                    byte[] favoriteIconByteArray = cursor.getBlob(columnIndex);
-
-                    // Convert the byte array to a Bitmap beginning at the first byte and ending at the last.
-                    Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
-
-                    // Set the favoriteIconBitmap.
-                    ImageView bookmarkFavoriteIcon = (ImageView) view;
-                    bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
-                    return true;
-                } else {  // Process the rest of the bookmarksAdapter with default settings.
-                    return false;
-                }
+        // Setup bookmarksCursorAdapter with `this` context.  The `false` disables autoRequery.
+        CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
+            @Override
+            public View newView(Context context, Cursor cursor, ViewGroup parent) {
+                // Inflate the individual item layout.  `false` does not attach it to the root.
+                return getLayoutInflater().inflate(R.layout.bookmarks_item_linearlayout, parent, false);
             }
-        });
+
+            @Override
+            public void bindView(View view, Context context, Cursor cursor) {
+                // Get the favorite icon byte array from the cursor.
+                byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHandler.FAVORITEICON));
+
+                // Convert the byte array to a Bitmap beginning at the first byte and ending at the last.
+                Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
+
+                // Display the bitmap in `bookmarkFavoriteIcon`.
+                ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmark_favorite_icon);
+                bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
+
+
+                // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
+                String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
+                TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmark_name);
+                assert bookmarkNameTextView != null;  // This assert removes the warning that bookmarkNameTextView might be null.
+                bookmarkNameTextView.setText(bookmarkNameString);
+            }
+        };
+
+        // Update the ListView.
+        bookmarksListView.setAdapter(bookmarksCursorAdapter);
+    }
+
+    private void updateBookmarksListViewExcept(long[] exceptIdLongArray) {
+        // Get a Cursor with the current contents of the bookmarks database except for the specified database IDs.
+        final Cursor bookmarksCursor = bookmarksDatabaseHandler.getBookmarksCursorExcept(exceptIdLongArray);
+
+        // Setup bookmarksCursorAdapter with `this` context.  The `false` disables autoRequery.
+        CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
+            @Override
+            public View newView(Context context, Cursor cursor, ViewGroup parent) {
+                // Inflate the individual item layout.  `false` does not attach it to the root.
+                return getLayoutInflater().inflate(R.layout.bookmarks_item_linearlayout, parent, false);
+            }
+
+            @Override
+            public void bindView(View view, Context context, Cursor cursor) {
+                // Get the favorite icon byte array from the cursor.
+                byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHandler.FAVORITEICON));
+
+                // Convert the byte array to a Bitmap beginning at the first byte and ending at the last.
+                Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
+
+                // Display the bitmap in `bookmarkFavoriteIcon`.
+                ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmark_favorite_icon);
+                bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
+
+
+                // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
+                String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
+                TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmark_name);
+                assert bookmarkNameTextView != null;  // This assert removes the warning that bookmarkNameTextView might be null.
+                bookmarkNameTextView.setText(bookmarkNameString);
+            }
+        };
 
         // Update the ListView.
-        bookmarksListView.setAdapter(bookmarksAdapter);
+        bookmarksListView.setAdapter(bookmarksCursorAdapter);
     }
 }
\ No newline at end of file
index 45344d3bd93c70edb7027dd90b97af1481f5919b..cacc85307d64cbfd184cb61664d31944bcdbddd4 100644 (file)
@@ -30,7 +30,7 @@ public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
     private static final String BOOKMARKS_DATABASE = "bookmarks.db";
     private static final String BOOKMARKS_TABLE = "bookmarks";
 
-    public static final String ID = "_id";
+    public static final String _ID = "_id";
     public static final String DISPLAYORDER = "displayorder";
     public static final String BOOKMARK_NAME = "bookmarkname";
     public static final String BOOKMARK_URL = "bookmarkurl";
@@ -46,7 +46,7 @@ public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
     public void onCreate(SQLiteDatabase bookmarksDatabase) {
         // Create the database if it doesn't exist.
         String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" +
-                ID + " integer primary key, " +
+                _ID + " integer primary key, " +
                 DISPLAYORDER + " integer, " +
                 BOOKMARK_NAME + " text, " +
                 BOOKMARK_URL + " text, " +
@@ -87,20 +87,45 @@ public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
 
         // Get everything in the BOOKMARKS_TABLE.
-        String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE;
+        final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE;
 
-        // Return the results as a Cursor.  The second argument is "null" because there are no selectionArgs.
+        // Return the results as a Cursor.  The second argument is `null` because there are no selectionArgs.
         // We can't close the Cursor because we need to use it in the parent activity.
         return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null);
     }
 
-    public String getBookmarkURL(int databaseID) {
+    public Cursor getBookmarksCursorExcept(long[] exceptIdLongArray) {
         // Get a readable database handle.
         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
 
-        // Get the row for the selected databaseID.
-        String GET_BOOKMARK_URL = "Select * FROM " + BOOKMARKS_TABLE +
-                " WHERE " + ID + " = " + databaseID;
+        // Prepare a string that contains the comma-separated list of IDs not to get.
+        String doNotGetIdsString = "";
+        // Extract the array to `doNotGetIdsString`.
+        for (long databaseIdLong : exceptIdLongArray) {
+            // If this is the first number, only add the number.
+            if (doNotGetIdsString.isEmpty()) {
+                doNotGetIdsString = String.valueOf(databaseIdLong);
+            } else {  // If there already is a number in the string, place a `,` before the number.
+                doNotGetIdsString = doNotGetIdsString + "," + databaseIdLong;
+            }
+        }
+
+        // Prepare the SQL statement to select all items except those with the specified IDs.
+        final String GET_All_BOOKMARKS_EXCEPT_SPECIFIED = "Select * FROM " + BOOKMARKS_TABLE +
+                " WHERE " + _ID + " NOT IN (" + doNotGetIdsString + ")";
+
+        // Return the results as a Cursor.  The second argument is `null` because there are no selectionArgs.
+        // We can't close the Cursor because we need to use it in the parent activity.
+        return bookmarksDatabase.rawQuery(GET_All_BOOKMARKS_EXCEPT_SPECIFIED, null);
+    }
+
+    public String getBookmarkURL(int databaseId) {
+        // Get a readable database handle.
+        SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
+
+        // Prepare the SQL statement to get the row for the selected databaseId.
+        final String GET_BOOKMARK_URL = "Select * FROM " + BOOKMARKS_TABLE +
+                " WHERE " + _ID + " = " + databaseId;
 
         // Save the results as Cursor and move it to the first (only) row.  The second argument is "null" because there are no selectionArgs.
         Cursor bookmarksCursor = bookmarksDatabase.rawQuery(GET_BOOKMARK_URL, null);
@@ -117,4 +142,15 @@ public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
         // Return the bookmarkURLString.
         return bookmarkURLString;
     }
-}
+
+    public void deleteBookmark(int databaseId) {
+        // Get a writable database handle.
+        SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
+
+        // Deletes the row with the given databaseId.  The last argument is null because we don't need additional parameters.
+        bookmarksDatabase.delete(BOOKMARKS_TABLE, _ID + " = " + databaseId, null);
+
+        // Close the database handle.
+        bookmarksDatabase.close();
+    }
+}
\ No newline at end of file
index c83b8d6b870e3c1f8e543da4d6dbff50a4518915..5f549b2509d8c557723607a9fc9a4099db0bdc96 100644 (file)
@@ -445,7 +445,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         // Inflate the menu; this adds items to the action bar if it is present.
-        getMenuInflater().inflate(R.menu.menu_options, menu);
+        getMenuInflater().inflate(R.menu.webview_options_menu, menu);
 
         // Set mainMenu so it can be used by onOptionsItemSelected.
         mainMenu = menu;
@@ -497,7 +497,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         // Run all the other default commands.
         super.onPrepareOptionsMenu(menu);
 
-        // return true displays the menu.
+        // `return true` displays the menu.
         return true;
     }
 
@@ -723,6 +723,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 // Clear the back/forward history.
                 mainWebView.clearHistory();
 
+                formattedUrlString = null;
+
                 // Destroy the internal state of the webview.
                 mainWebView.destroy();
 
diff --git a/app/src/main/res/drawable/bookmarks_list_selector.xml b/app/src/main/res/drawable/bookmarks_list_selector.xml
new file mode 100644 (file)
index 0000000..834b9ec
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<!-- This selector changes the background of activated items in the bookmarks list view. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_activated="true"
+        android:drawable="@color/light_blue" />
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/delete.xml b/app/src/main/res/drawable/delete.xml
new file mode 100644 (file)
index 0000000..d314874
--- /dev/null
@@ -0,0 +1,14 @@
+<!-- delete.xml comes from the Android Material icon set, where it is called ic_delete.
+  It is released under the CC-BY license <https://creativecommons.org/licenses/by/4.0/>. -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0" >
+
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
+</vector>
index 152e3b3ab1bd35085572b0d8593428336c51804e..15bde09949fed7436b7c600a56143cda70b37182 100644 (file)
                 app:popupTheme="@style/PrivacyBrowser.PopupOverlay" />
         </android.support.design.widget.AppBarLayout>
 
+        <!-- android:choiceMode="multipleChoiceModal" allows the contextual action menu to select more than one item at a time.
+            android:dividerHeight must be specified with android:divider or the height will be 0dp.
+            In our case we want the height to be 0dp. -->
         <ListView
             android:id="@+id/bookmarks_listview"
             android:layout_height="match_parent"
-            android:layout_width="match_parent" />
+            android:layout_width="match_parent"
+            android:choiceMode="multipleChoiceModal"
+            android:divider="@color/white"
+            android:dividerHeight="0dp" />
     </LinearLayout>
 
     <android.support.design.widget.FloatingActionButton
index c1a8f21fd402af5cf227908954003881918fa101..408844fe7e82ba4444c9dbc9e535722dc928fc23 100644 (file)
@@ -24,6 +24,7 @@
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
     android:orientation="horizontal"
+    android:background="@drawable/bookmarks_list_selector"
     xmlns:tools="http://schemas.android.com/tools">
 
     <ImageView
index 07a5874f9b9c62391afbe615fdd78e4bdf4eadcd..b59aeb4591209aa1e861b22f0821140fe83bf017 100644 (file)
@@ -71,7 +71,7 @@
         android:layout_height="match_parent"
         android:layout_gravity="start"
         app:headerLayout="@layout/navigation_header"
-        app:menu="@menu/menu_navigation"/>
+        app:menu="@menu/webview_navigation_menu"/>
 
     <!-- fullScreenVideoFrameLayout is used to display full screen videos.  It is initially android:visibility="gone" to hide it from view. -->
     <FrameLayout
diff --git a/app/src/main/res/menu/bookmarks_context_menu.xml b/app/src/main/res/menu/bookmarks_context_menu.xml
new file mode 100644 (file)
index 0000000..0d64adc
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/delete_bookmark"
+        android:title="@string/delete"
+        android:orderInCategory="10"
+        android:icon="@drawable/delete"
+        app:showAsAction="ifRoom" />
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_bookmarks_options.xml b/app/src/main/res/menu/menu_bookmarks_options.xml
new file mode 100644 (file)
index 0000000..97d6487
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/delete_bookmark1"
+        android:title="@string/delete"
+        android:orderInCategory="10"
+        android:icon="@drawable/delete"
+        app:showAsAction="ifRoom" />
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_navigation.xml b/app/src/main/res/menu/menu_navigation.xml
deleted file mode 100644 (file)
index 756013c..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright 2016 Soren Stoutner <soren@stoutner.com>.
-
-  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
-
-  Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>. -->
-
-<menu
-    xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:id="@+id/home"
-        android:title="@string/home"
-        android:icon="@drawable/home"
-        android:orderInCategory="10" />
-
-    <item
-        android:id="@+id/back"
-        android:title="@string/back"
-        android:icon="@drawable/back"
-        android:orderInCategory="20" />
-
-    <item
-        android:id="@+id/forward"
-        android:title="@string/forward"
-        android:icon="@drawable/forward"
-        android:orderInCategory="30" />
-
-    <!-- If a group has an id, a line is drawn above it in the navigation view. -->
-    <group
-        android:id="@+id/navigationGroup1" >
-        <item
-            android:id="@+id/bookmarks"
-            android:title="@string/bookmarks"
-            android:icon="@drawable/bookmarks"
-            android:orderInCategory="40" />
-
-        <item
-            android:id="@+id/downloads"
-            android:title="@string/downloads"
-            android:icon="@drawable/downloads"
-            android:orderInCategory="50" />
-    </group>
-
-    <group
-        android:id="@+id/navigationGroup2" >
-        <item
-            android:id="@+id/settings"
-            android:title="@string/settings"
-            android:icon="@drawable/settings"
-            android:orderInCategory="60" />
-
-        <item
-            android:id="@+id/guide"
-            android:title="@string/guide"
-            android:icon="@drawable/guide"
-            android:orderInCategory="70" />
-
-        <item
-            android:id="@+id/about"
-            android:title="@string/about"
-            android:icon="@drawable/about"
-            android:orderInCategory="80" />
-    </group>
-
-    <!-- If a group has an id, a line is drawn above it in the navigation view. -->
-    <group
-        android:id="@+id/navigationGroup3" >
-        <item
-            android:id="@+id/clearAndExit"
-            android:title="@string/clear_and_exit"
-            android:icon="@drawable/exit"
-            android:orderInCategory="90" />
-    </group>
-</menu>
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_options.xml b/app/src/main/res/menu/menu_options.xml
deleted file mode 100644 (file)
index d380a06..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
-
-  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
-
-  Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>. -->
-
-<menu
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:context=".MainWebViewActivity">
-
-    <item
-        android:id="@+id/toggleJavaScript"
-        android:title="@string/javascript"
-        android:orderInCategory="10"
-        app:showAsAction="always" />
-
-    <item
-        android:id="@+id/toggleFirstPartyCookies"
-        android:title="@string/first_party_cookies"
-        android:orderInCategory="20"
-        android:checkable="true"
-        app:showAsAction="never" />
-
-    <item
-        android:id="@+id/toggleThirdPartyCookies"
-        android:title="@string/third_party_cookies"
-        android:orderInCategory="30"
-        android:checkable="true"
-        app:showAsAction="never" />
-
-    <item
-        android:id="@+id/toggleDomStorage"
-        android:title="@string/dom_storage"
-        android:orderInCategory="40"
-        android:checkable="true"
-        app:showAsAction="never" />
-
-    <item
-        android:id="@+id/toggleSaveFormData"
-        android:title="@string/form_data"
-        android:orderInCategory="50"
-        android:checkable="true"
-        app:showAsAction="never" />
-
-    <item
-        android:id="@+id/clearCookies"
-        android:title="@string/clear_cookies"
-        android:orderInCategory="60"
-        app:showAsAction="never" />
-
-    <item
-        android:id="@+id/clearDomStorage"
-        android:title="@string/clear_dom_storage"
-        android:orderInCategory="70"
-        app:showAsAction="never" />
-
-    <item
-        android:id="@+id/clearFormData"
-        android:title="@string/clear_form_data"
-        android:orderInCategory="80"
-        app:showAsAction="never" />
-
-    <item
-        android:id="@+id/share"
-        android:title="@string/share"
-        android:orderInCategory="90"
-        app:showAsAction="never" />
-
-    <item
-        android:id="@+id/addToHomescreen"
-        android:title="@string/add_to_home_screen"
-        android:orderInCategory="100"
-        app:showAsAction="never" />
-
-    <item
-        android:id="@+id/refresh"
-        android:title="@string/refresh"
-        android:orderInCategory="110"
-        app:showAsAction="never" />
-</menu>
diff --git a/app/src/main/res/menu/webview_navigation_menu.xml b/app/src/main/res/menu/webview_navigation_menu.xml
new file mode 100644 (file)
index 0000000..756013c
--- /dev/null
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/home"
+        android:title="@string/home"
+        android:icon="@drawable/home"
+        android:orderInCategory="10" />
+
+    <item
+        android:id="@+id/back"
+        android:title="@string/back"
+        android:icon="@drawable/back"
+        android:orderInCategory="20" />
+
+    <item
+        android:id="@+id/forward"
+        android:title="@string/forward"
+        android:icon="@drawable/forward"
+        android:orderInCategory="30" />
+
+    <!-- If a group has an id, a line is drawn above it in the navigation view. -->
+    <group
+        android:id="@+id/navigationGroup1" >
+        <item
+            android:id="@+id/bookmarks"
+            android:title="@string/bookmarks"
+            android:icon="@drawable/bookmarks"
+            android:orderInCategory="40" />
+
+        <item
+            android:id="@+id/downloads"
+            android:title="@string/downloads"
+            android:icon="@drawable/downloads"
+            android:orderInCategory="50" />
+    </group>
+
+    <group
+        android:id="@+id/navigationGroup2" >
+        <item
+            android:id="@+id/settings"
+            android:title="@string/settings"
+            android:icon="@drawable/settings"
+            android:orderInCategory="60" />
+
+        <item
+            android:id="@+id/guide"
+            android:title="@string/guide"
+            android:icon="@drawable/guide"
+            android:orderInCategory="70" />
+
+        <item
+            android:id="@+id/about"
+            android:title="@string/about"
+            android:icon="@drawable/about"
+            android:orderInCategory="80" />
+    </group>
+
+    <!-- If a group has an id, a line is drawn above it in the navigation view. -->
+    <group
+        android:id="@+id/navigationGroup3" >
+        <item
+            android:id="@+id/clearAndExit"
+            android:title="@string/clear_and_exit"
+            android:icon="@drawable/exit"
+            android:orderInCategory="90" />
+    </group>
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/menu/webview_options_menu.xml b/app/src/main/res/menu/webview_options_menu.xml
new file mode 100644 (file)
index 0000000..d380a06
--- /dev/null
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".MainWebViewActivity">
+
+    <item
+        android:id="@+id/toggleJavaScript"
+        android:title="@string/javascript"
+        android:orderInCategory="10"
+        app:showAsAction="always" />
+
+    <item
+        android:id="@+id/toggleFirstPartyCookies"
+        android:title="@string/first_party_cookies"
+        android:orderInCategory="20"
+        android:checkable="true"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/toggleThirdPartyCookies"
+        android:title="@string/third_party_cookies"
+        android:orderInCategory="30"
+        android:checkable="true"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/toggleDomStorage"
+        android:title="@string/dom_storage"
+        android:orderInCategory="40"
+        android:checkable="true"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/toggleSaveFormData"
+        android:title="@string/form_data"
+        android:orderInCategory="50"
+        android:checkable="true"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/clearCookies"
+        android:title="@string/clear_cookies"
+        android:orderInCategory="60"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/clearDomStorage"
+        android:title="@string/clear_dom_storage"
+        android:orderInCategory="70"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/clearFormData"
+        android:title="@string/clear_form_data"
+        android:orderInCategory="80"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/share"
+        android:title="@string/share"
+        android:orderInCategory="90"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/addToHomescreen"
+        android:title="@string/add_to_home_screen"
+        android:orderInCategory="100"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/refresh"
+        android:title="@string/refresh"
+        android:orderInCategory="110"
+        app:showAsAction="never" />
+</menu>
diff --git a/app/src/main/res/values-v19/styles.xml b/app/src/main/res/values-v19/styles.xml
deleted file mode 100644 (file)
index ea2d867..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright 2016 Soren Stoutner <soren@stoutner.com>.
-
-  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
-
-  Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>. -->
-
-<resources>
-    <!-- android:windowTranslucentStatus requires API >= 19.  It makes the system status bar transparent.
-      When it is specified the root layout should include android:fitsSystemWindows="true". -->
-    <style name="PrivacyBrowser.MainWebView">
-        <item name="android:windowTranslucentStatus">true</item>
-    </style>
-
-    <!-- android:windowTranslucentStatus requires API >= 19.  It makes the system status bar transparent.
-      When it is specified the root layout should include android:fitsSystemWindows="true".
-      colorPrimaryDark goes behind the status bar, which is then darkened by the overlay. -->
-    <style name="PrivacyBrowser.SecondaryActivity">
-        <item name="android:windowTranslucentStatus">true</item>
-        <item name="colorPrimaryDark">@color/blue</item>
-    </style>
-</resources>
\ No newline at end of file
index 77428aeeb866c53adca87a51b1700d30bde25c40..6c87a126c9b3afd09096fc24884b87dcb6fa8309 100644 (file)
@@ -36,7 +36,7 @@
     <!-- Custom App Bar. -->
     <string name="favorite_icon">Favorite Icon</string>
 
-    <!-- Navigation Drawer. -->
+    <!-- Main WebView Navigation Drawer. -->
     <string name="navigation_drawer">Navigation Drawer</string>
     <string name="navigation">Navigation</string>
     <string name="home">Home</string>
@@ -49,7 +49,7 @@
     <string name="about">About</string>
     <string name="clear_and_exit">Clear and Exit</string>
 
-    <!-- Options Menu. -->
+    <!-- Main WebView Options Menu. -->
     <string name="javascript">JavaScript</string>
     <string name="first_party_cookies">First-Party Cookies</string>
     <string name="third_party_cookies">Third-Party Cookies</string>
     <string name="bookmark_name">Bookmark name</string>
     <string name="bookmark_url">Bookmark URL</string>
 
+    <!-- Bookmarks Contextual App Bar. -->
+    <string name="selected">Selected</string>
+    <string name="delete">Delete</string>
+    <string name="one_bookmark_deleted">1 Bookmark Deleted</string>
+    <string name="bookmarks_deleted">Bookmarks Deleted</string>
+    <string name="undo">Undo</string>
+
     <!-- Guide. -->
     <string name="privacy_browser_guide">Privacy Browser Guide</string>
     <string name="overview">Overview</string>
index 36b667850ef9582e7ea0d048fcd1c6ee9de41b59..0b1697e722d2e7e076a45b271c6eb8ccb264b87f 100644 (file)
   along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
 <resources>
-
+    <!-- `android:windowTranslucentStatus` makes the system status bar transparent.
+        When it is specified the root layout should include android:fitsSystemWindows="true". -->
     <style name="PrivacyBrowser" parent="Theme.AppCompat.Light.NoActionBar">
+        <item name="android:windowTranslucentStatus">true</item>
         <item name="colorAccent">@color/blue</item>
     </style>
 
     <style name="PrivacyBrowser.MainWebView" />
 
-    <style name="PrivacyBrowser.SecondaryActivity" />
+    <!-- `android:windowTranslucentStatus` makes the system status bar transparent.
+        When it is specified the root layout should include android:fitsSystemWindows="true".
+        `colorPrimaryDark` goes behind the status bar, which is then darkened by the overlay.
+        `windowActionModeOverlay` makes the contextual app bar cover the support app bar.
+        `actionModeStyle` sets the style of the contextual app bar. -->
+    <style name="PrivacyBrowser.SecondaryActivity">
+        <item name="android:windowTranslucentStatus">true</item>
+        <item name="colorPrimaryDark">@color/blue</item>
+        <item name="windowActionModeOverlay">true</item>
+        <item name="android:actionModeBackground">@color/blue</item>
+    </style>
 
-    <!-- colorPrimaryDark is the color of the status bar. -->
+    <!-- `colorPrimaryDark` is the color of the status bar. -->
     <style name="PrivacyBrowser.Settings" parent="Theme.AppCompat.Light.DarkActionBar">
         <item name="colorPrimary">@color/blue</item>
         <item name="colorPrimaryDark">@color/dark_blue</item>
         <item name="colorAccent">@color/blue</item>
     </style>
 
-    <!-- ThemeOverlay.AppCompat.Dark.ActionBar" makes the text and the icons in the AppBar white. -->
+    <!-- `ThemeOverlay.AppCompat.Dark.ActionBar` makes the text and the icons in the AppBar white.-->
     <style name="PrivacyBrowser.DarkAppBar" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
 
     <style name="PrivacyBrowser.AppBarOverlay" parent="ThemeOverlay.AppCompat.ActionBar" />
@@ -45,5 +57,4 @@
     <style name="PrivacyBrowser.AlertDialog" parent="Theme.AppCompat.Light.Dialog.Alert">
         <item name="colorAccent">@color/blue</item>
     </style>
-
 </resources>
\ No newline at end of file