Add the ability to move bookmarks to folders.
authorSoren Stoutner <soren@stoutner.com>
Sat, 16 Jul 2016 11:43:51 +0000 (04:43 -0700)
committerSoren Stoutner <soren@stoutner.com>
Sat, 16 Jul 2016 11:43:51 +0000 (04:43 -0700)
19 files changed:
.idea/dictionaries/soren.xml
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/BookmarksDatabaseViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/MoveToFolder.java
app/src/main/res/drawable-hdpi/folder_grey_bitmap.png [new file with mode: 0644]
app/src/main/res/drawable-mdpi/folder_dark_blue.xml [new file with mode: 0644]
app/src/main/res/drawable-mdpi/folder_grey_bitmap.png [new file with mode: 0644]
app/src/main/res/drawable-xhdpi/folder_grey_bitmap.png [new file with mode: 0644]
app/src/main/res/drawable-xxhdpi/folder_grey_bitmap.png [new file with mode: 0644]
app/src/main/res/drawable/folder.xml [deleted file]
app/src/main/res/drawable/folder_grey.xml [new file with mode: 0644]
app/src/main/res/layout/bookmarks_database_view_item_linearlayout.xml [new file with mode: 0644]
app/src/main/res/layout/bookmarks_database_view_linearlayout.xml [deleted file]
app/src/main/res/layout/main_coordinatorlayout.xml
app/src/main/res/layout/move_to_folder_dialog.xml [new file with mode: 0644]
app/src/main/res/layout/move_to_folder_item_linearlayout.xml [new file with mode: 0644]
app/src/main/res/values/colors.xml
app/src/main/res/values/strings.xml

index f6e91ca9d0c663dfb9ddd0319ac3465cdf17f06b..8a435c03a9cd3a2fa46c96b8cca4d2e3690b3914 100644 (file)
@@ -35,6 +35,7 @@
       <w>orbot</w>
       <w>panopticlick</w>
       <w>parentfolder</w>
+      <w>programatically</w>
       <w>radiobutton</w>
       <w>radiogroup</w>
       <w>redmine</w>
@@ -46,6 +47,7 @@
       <w>securitypatch</w>
       <w>snackbar</w>
       <w>snackbars</w>
+      <w>subfolders</w>
       <w>tablayout</w>
       <w>techrepublic</w>
       <w>textview</w>
index ca004072a54ac11c68d78991849687a2e591b4c5..f228c5268a620abe125690ddc56afd564902d660 100644 (file)
@@ -24,7 +24,6 @@ import android.app.DialogFragment;
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
-import android.database.DatabaseUtils;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Typeface;
@@ -35,7 +34,6 @@ import android.support.design.widget.FloatingActionButton;
 import android.support.design.widget.Snackbar;
 import android.support.v4.app.NavUtils;
 import android.support.v4.content.ContextCompat;
-import android.support.v4.widget.CursorAdapter;
 import android.support.v7.app.ActionBar;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
@@ -47,7 +45,7 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
-import android.widget.CheckBox;
+import android.widget.CursorAdapter;
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.ListView;
@@ -58,17 +56,18 @@ import java.io.ByteArrayOutputStream;
 
 public class BookmarksActivity extends AppCompatActivity implements CreateBookmark.CreateBookmarkListener,
         CreateBookmarkFolder.CreateBookmarkFolderListener, EditBookmark.EditBookmarkListener,
-        EditBookmarkFolder.EditBookmarkFolderListener {
-    // `bookmarksDatabaseHandler` is public static so it can be accessed from EditBookmark.  It is also used in `onCreate()`,
+        EditBookmarkFolder.EditBookmarkFolderListener, MoveToFolder.MoveToFolderListener {
+    // `bookmarksDatabaseHandler` is public static so it can be accessed from `EditBookmark` and `MoveToFolder`.  It is also used in `onCreate()`,
     // `onCreateBookmarkCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`.
     public static BookmarksDatabaseHandler bookmarksDatabaseHandler;
 
-    // `bookmarksListView` is public static so it can be accessed from EditBookmark.
+    // `bookmarksListView` is public static so it can be accessed from `EditBookmark`.
     // It is also used in `onCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`.
     public static ListView bookmarksListView;
 
-    // `currentFolder` is used in `onCreate`, `onOptionsItemSelected()`, `onCreateBookmarkCreate`, `onCreateBookmarkFolderCreate`, and `onEditBookmarkSave`.
-    private String currentFolder;
+    // `currentFolder` is public static so it can be accessed from `MoveToFolder`.
+    // It is used in `onCreate`, `onOptionsItemSelected()`, `onCreateBookmarkCreate`, `onCreateBookmarkFolderCreate`, and `onEditBookmarkSave`.
+    public static String currentFolder;
 
     // `contextualActionMode` is used in `onCreate()` and `onEditBookmarkSave()`.
     private ActionMode contextualActionMode;
@@ -179,6 +178,9 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
                 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark);
                 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks);
 
+                // Get a handle for `contextualActionMode` so we can close it programatically.
+                contextualActionMode = mode;
+
                 return true;
             }
 
@@ -195,6 +197,11 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
                 // Calculate the number of selected bookmarks.
                 int numberOfSelectedBookmarks = selectedBookmarksLongArray.length;
 
+                // Sometimes Android forgets to close the contextual app bar when all the items are deselected.
+                if (numberOfSelectedBookmarks == 0) {
+                    mode.finish();
+                }
+
                 // List the number of selected bookmarks in the subtitle.
                 mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected));
 
@@ -331,13 +338,20 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
                         bookmarksListView.setSelection(selectedBookmarkNewPosition - 5);
                         break;
 
-                    case R.id.edit_bookmark:
-                        // Get a handle for `contextualActionMode` so we can close it when `editBookmarkDialog` is finished.
-                        contextualActionMode = mode;
+                    case R.id.move_to_folder:
+                        // Show the `MoveToFolder` `AlertDialog` and name the instance `@string/move_to_folder
+                        DialogFragment moveToFolderDialog = new MoveToFolder();
+                        moveToFolderDialog.show(getFragmentManager(), getResources().getString(R.string.move_to_folder));
+                        break;
 
+                    case R.id.edit_bookmark:
                         // Get a handle for `selectedBookmarkPosition` so we can scroll to it after refreshing the ListView.
                         bookmarkPositionSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
-                        selectedBookmarkPosition = bookmarkPositionSparseBooleanArray.keyAt(0);
+                        for (int i = 0; i < bookmarkPositionSparseBooleanArray.size(); i++) {
+                            // Find the bookmark that is selected and save the position to `selectedBookmarkPosition`.
+                            if (bookmarkPositionSparseBooleanArray.valueAt(i))
+                                selectedBookmarkPosition = bookmarkPositionSparseBooleanArray.keyAt(i);
+                        }
 
                         // Move to the selected database ID and find out if it is a folder.
                         bookmarksCursor.moveToPosition(selectedBookmarkPosition);
@@ -363,12 +377,16 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
 
                         // Get a handle for `selectedBookmarkPosition` so we can scroll to it after refreshing the ListView.
                         bookmarkPositionSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
-                        selectedBookmarkPosition = bookmarkPositionSparseBooleanArray.keyAt(0);
+                        for (int i = 0; i < bookmarkPositionSparseBooleanArray.size(); i++) {
+                            // Find the bookmark that is selected and save the position to `selectedBookmarkPosition`.
+                            if (bookmarkPositionSparseBooleanArray.valueAt(i))
+                                selectedBookmarkPosition = bookmarkPositionSparseBooleanArray.keyAt(i);
+                        }
 
                         updateBookmarksListViewExcept(selectedBookmarksLongArray, currentFolder);
 
                         // Scroll to where the deleted bookmark was located.
-                        bookmarksListView.setSelection(selectedBookmarkPosition);
+                        bookmarksListView.setSelection(selectedBookmarkPosition - 5);
 
                         String snackbarMessage;
 
@@ -396,6 +414,9 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
                                                 // Refresh the ListView to show the rows again.
                                                 updateBookmarksListView(currentFolder);
 
+                                                // Scroll to where the deleted bookmark was located.
+                                                bookmarksListView.setSelection(selectedBookmarkPosition - 5);
+
                                                 break;
 
                                             // The Snackbar was dismissed without the "Undo" button being pushed.
@@ -405,7 +426,11 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
                                                     // Convert `databaseIdLong` to an int.
                                                     int databaseIdInt = (int) databaseIdLong;
 
-                                                    // Delete the database row.
+                                                    if (bookmarksDatabaseHandler.isFolder(databaseIdInt)) {
+                                                        deleteBookmarkFolderContents(databaseIdInt);
+                                                    }
+
+                                                    // Delete `databaseIdInt`.
                                                     bookmarksDatabaseHandler.deleteBookmark(databaseIdInt);
                                                 }
                                                 break;
@@ -644,7 +669,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         int existingFoldersWithNewName = bookmarkFolderCursor.getCount();
         bookmarkFolderCursor.close();
         if ( ((existingFoldersWithNewName == 0) || newFolderNameString.equals(oldFolderNameString)) && !newFolderNameString.isEmpty()) {
-            // Get a long array with the the databaseId of the selected folder and convert it to an `int`.
+            // Get a long array with the the database ID of the selected folder and convert it to an `int`.
             long[] selectedFolderLongArray = bookmarksListView.getCheckedItemIds();
             int selectedFolderDatabaseId = (int) selectedFolderLongArray[0];
 
@@ -690,11 +715,57 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         }
     }
 
+    @Override
+    public void onCancelMoveToFolder(DialogFragment dialogFragment) {
+        // Do nothing because the user selected `Cancel`.
+    }
+
+    @Override
+    public void onMoveToFolder(DialogFragment dialogFragment) {
+        // Get the new folder database id.
+        ListView folderListView = (ListView) dialogFragment.getDialog().findViewById(R.id.move_to_folder_listview);
+        long[] newFolderLongArray = folderListView.getCheckedItemIds();
+
+        if (newFolderLongArray.length == 0) {  // No new folder was selected.
+            Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.cannot_move_bookmarks), Snackbar.LENGTH_LONG).show();
+        } else {  // Move the selected bookmarks.
+            // Get the new folder database ID.
+            int newFolderDatabaseId = (int) newFolderLongArray[0];
+
+            // Instantiate `newFolderName`.
+            String newFolderName;
+
+            if (newFolderDatabaseId == 0) {
+                // The new folder is the home folder, represented as `""` in the database.
+                newFolderName = "";
+            } else {
+                // Get the new folder name from the database.
+                newFolderName = bookmarksDatabaseHandler.getFolderName(newFolderDatabaseId);
+            }
+
+            // Get a long array with the the database ID of the selected bookmarks.
+            long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
+            for (long databaseIdLong : selectedBookmarksLongArray) {
+                // Get `databaseIdInt` for each selected bookmark.
+                int databaseIdInt = (int) databaseIdLong;
+
+                // Move the selected bookmark to the new folder.
+                bookmarksDatabaseHandler.moveToFolder(databaseIdInt, newFolderName);
+            }
+
+            // Refresh the `ListView`.
+            updateBookmarksListView(currentFolder);
+
+            // Close the contextual app bar.
+            contextualActionMode.finish();
+        }
+    }
+
     private void updateBookmarksListView(String folderName) {
         // Get a `Cursor` with the current contents of the bookmarks database.
         bookmarksCursor = bookmarksDatabaseHandler.getAllBookmarksCursorByDisplayOrder(folderName);
 
-        // Setup `bookmarksCursorAdapter` with `this` context.  The `false` disables autoRequery.
+        // Setup `bookmarksCursorAdapter` with `this` context.  `false` disables autoRequery.
         CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
             @Override
             public View newView(Context context, Cursor cursor, ViewGroup parent) {
@@ -745,7 +816,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         // Get a `Cursor` with the current contents of the bookmarks database except for the specified database IDs.
         bookmarksCursor = bookmarksDatabaseHandler.getBookmarksCursorExcept(exceptIdLongArray, folderName);
 
-        // Setup `bookmarksCursorAdapter` with `this` context.  The `false` disables autoRequery.
+        // Setup `bookmarksCursorAdapter` with `this` context.  `false` disables autoRequery.
         CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
             @Override
             public View newView(Context context, Cursor cursor, ViewGroup parent) {
@@ -784,4 +855,27 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         // Update the ListView.
         bookmarksListView.setAdapter(bookmarksCursorAdapter);
     }
+
+    private void deleteBookmarkFolderContents(int databaseId) {
+        // Get the name of the folder.
+        String folderName = bookmarksDatabaseHandler.getFolderName(databaseId);
+
+        // Get the contents of the folder.
+        Cursor folderCursor = bookmarksDatabaseHandler.getAllBookmarksCursorByDisplayOrder(folderName);
+
+        for (int i = 0; i < folderCursor.getCount(); i++) {
+            // Move `folderCursor` to the current row.
+            folderCursor.moveToPosition(i);
+
+            // Get the database ID of the item.
+            int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHandler._ID));
+
+            // If this is a folder, delete the contents first.
+            if (bookmarksDatabaseHandler.isFolder(itemDatabaseId)) {
+                deleteBookmarkFolderContents(itemDatabaseId);
+            }
+
+            bookmarksDatabaseHandler.deleteBookmark(itemDatabaseId);
+        }
+    }
 }
\ No newline at end of file
index bc17bc7dba7c4d4c068a36d08dca8f8f41efbb4d..2a81097efa507fbc22228e04ff44b3100eaeb64e 100644 (file)
@@ -26,6 +26,8 @@ import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.provider.ContactsContract;
+import android.support.design.widget.Snackbar;
+import android.support.v4.content.ContextCompat;
 
 public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
     private static final int SCHEMA_VERSION = 1;
@@ -118,6 +120,29 @@ public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
         return bookmarksDatabase.rawQuery(GET_ONE_BOOKMARK, null);
     }
 
+    public String getFolderName (int databaseId) {
+        // Get a readable database handle.
+        SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
+
+        // Prepare the SQL statement to get the `Cursor` for the folder.
+        final String GET_FOLDER = "Select * FROM " + BOOKMARKS_TABLE +
+                " WHERE " + _ID + " = " + databaseId;
+
+        // Get `folderCursor`.  The second argument is `null` because there are no `selectionArgs`.
+        Cursor folderCursor = bookmarksDatabase.rawQuery(GET_FOLDER, null);
+
+        // Get `folderName`.
+        folderCursor.moveToFirst();
+        String folderName = folderCursor.getString(folderCursor.getColumnIndex(BOOKMARK_NAME));
+
+        // Close the cursor and the database handle.
+        folderCursor.close();
+        bookmarksDatabase.close();
+
+        // Return the folder name.
+        return folderName;
+    }
+
     public Cursor getFolderCursor(String folderName) {
         // Get a readable database handle.
         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
@@ -135,6 +160,38 @@ public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
         return bookmarksDatabase.rawQuery(GET_FOLDER, null);
     }
 
+    public Cursor getFoldersCursorExcept(String exceptFolders) {
+        // Get a readable database handle.
+        SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
+
+        // Prepare the SQL statement to get the `Cursor` for the folders.
+        final String GET_FOLDERS_EXCEPT = "Select * FROM " + BOOKMARKS_TABLE +
+                " WHERE " + IS_FOLDER + " = " + 1 +
+                " AND " + BOOKMARK_NAME + " NOT IN (" + exceptFolders +
+                ") ORDER BY " + BOOKMARK_NAME + " ASC";
+
+        // 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_FOLDERS_EXCEPT, null);
+    }
+
+    public Cursor getSubfoldersCursor(String currentFolder) {
+        // Get a readable database handle.
+        SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
+
+        // SQL escape `currentFolder.
+        currentFolder = DatabaseUtils.sqlEscapeString(currentFolder);
+
+        // Prepare the SQL statement to get the `Cursor` for the subfolders.
+        final String GET_SUBFOLDERS = "Select * FROM " + BOOKMARKS_TABLE +
+                " WHERE " + PARENT_FOLDER + " = " + currentFolder +
+                " AND " + IS_FOLDER + " = " + 1;
+
+        // 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_SUBFOLDERS, null);
+    }
+
     public String getParentFolder(String currentFolder) {
         // Get a readable database handle.
         SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
@@ -144,7 +201,8 @@ public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
 
         // Prepare the SQL statement to get the parent folder.
         final String GET_PARENT_FOLDER = "Select * FROM " + BOOKMARKS_TABLE +
-                " WHERE " + IS_FOLDER + " = " + 1 + " AND " + BOOKMARK_NAME + " = " + currentFolder;
+                " WHERE " + IS_FOLDER + " = " + 1 +
+                " AND " + BOOKMARK_NAME + " = " + currentFolder;
 
         // The second argument is `null` because there are no `selectionArgs`.
         Cursor bookmarkCursor = bookmarksDatabase.rawQuery(GET_PARENT_FOLDER, null);
@@ -180,7 +238,8 @@ public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
 
         // Get everything in the BOOKMARKS_TABLE.
         final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE +
-                " WHERE " + PARENT_FOLDER + " = " + folderName + " ORDER BY " + DISPLAY_ORDER + " ASC";
+                " WHERE " + PARENT_FOLDER + " = " + folderName +
+                " ORDER BY " + DISPLAY_ORDER + " ASC";
 
         // 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.
@@ -208,13 +267,37 @@ public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
 
         // 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 " + PARENT_FOLDER + " = " + folderName + " AND " + _ID + " NOT IN (" + doNotGetIdsString + ") ORDER BY " + DISPLAY_ORDER + " ASC";
+                " WHERE " + PARENT_FOLDER + " = " + folderName +
+                " AND " + _ID + " NOT IN (" + doNotGetIdsString +
+                ") ORDER BY " + DISPLAY_ORDER + " ASC";
 
-        // 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_EXCEPT_SPECIFIED, null);
     }
 
+    public boolean isFolder(int databaseId) {
+        // Get a readable database handle.
+        SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
+
+        // Prepare the SQL statement to determine if `databaseId` is a folder.
+        final String CHECK_IF_FOLDER = "Select * FROM " + BOOKMARKS_TABLE +
+                " WHERE " + _ID + " = " + databaseId;
+
+        // Populate folderCursor.  The second argument is `null` because there are no `selectionArgs`.
+        Cursor folderCursor = bookmarksDatabase.rawQuery(CHECK_IF_FOLDER, null);
+
+        // Ascertain if this database ID is a folder.
+        folderCursor.moveToFirst();
+        boolean isFolder = (folderCursor.getInt(folderCursor.getColumnIndex(IS_FOLDER)) == 1);
+
+        // Close the `Cursor` and the database handle.
+        folderCursor.close();
+        bookmarksDatabase.close();
+
+        return isFolder;
+    }
+
     public void updateBookmark(int databaseId, String bookmarkName, String bookmarkUrl) {
         // Store the updated values in `bookmarkContentValues`.
         ContentValues bookmarkContentValues = new ContentValues();
@@ -308,15 +391,46 @@ public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
     }
 
     public void updateBookmarkDisplayOrder(int databaseId, int displayOrder) {
-        // Store the updated values in `bookmarkContentValues`.
-        ContentValues bookmarkContentValues = new ContentValues();
+        // Get a writable database handle.
+        SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
 
+        // Store the new display order in `bookmarkContentValues`.
+        ContentValues bookmarkContentValues = new ContentValues();
         bookmarkContentValues.put(DISPLAY_ORDER, displayOrder);
 
+        // Update the database.  The last argument is `null` because there are no `whereArgs`.
+        bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null);
+
+        // Close the database handle.
+        bookmarksDatabase.close();
+    }
+
+    public void moveToFolder(int databaseId, String newFolder) {
         // Get a writable database handle.
         SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
 
-        // Update the database.  The last argument is `null` because there are no `whereArgs`.
+        // Get the highest `DISPLAY_ORDER` in the new folder
+        String newFolderSqlEscaped = DatabaseUtils.sqlEscapeString(newFolder);
+        final String NEW_FOLDER = "Select * FROM " + BOOKMARKS_TABLE +
+                " WHERE " + PARENT_FOLDER + " = " + newFolderSqlEscaped +
+                " ORDER BY " + DISPLAY_ORDER + " ASC";
+        // The second argument is `null` because there are no `selectionArgs`.
+        Cursor newFolderCursor = bookmarksDatabase.rawQuery(NEW_FOLDER, null);
+        int displayOrder;
+        if (newFolderCursor.getCount() > 0) {
+            newFolderCursor.moveToLast();
+            displayOrder = newFolderCursor.getInt(newFolderCursor.getColumnIndex(DISPLAY_ORDER)) + 1;
+        } else {
+            displayOrder = 0;
+        }
+        newFolderCursor.close();
+
+        // Store the new values in `bookmarkContentValues`.
+        ContentValues bookmarkContentValues = new ContentValues();
+        bookmarkContentValues.put(DISPLAY_ORDER, displayOrder);
+        bookmarkContentValues.put(PARENT_FOLDER, newFolder);
+
+        // Update the database.  The last argument is 'null' because there are no 'whereArgs'.
         bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null);
 
         // Close the database handle.
index 00bad900770f8f2feb1d43fc8b490ba49eab2fe3..db5b198e8cf8d213495f00378284badc49e33a8d 100644 (file)
@@ -77,7 +77,7 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity {
             @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_database_view_linearlayout, parent, false);
+                return getLayoutInflater().inflate(R.layout.bookmarks_database_view_item_linearlayout, parent, false);
             }
 
             @Override
@@ -116,12 +116,15 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity {
 
                 // Get the parent folder from the `Cursor` and display it in `bookmarkParentFolder`.
                 String bookmarkParentFolder = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHandler.PARENT_FOLDER));
+                ImageView parentFolderImageView = (ImageView) view.findViewById(R.id.bookmarks_database_view_parent_folder_icon);
                 TextView bookmarkParentFolderTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_parent_folder);
                 // Make the folder name gray if it is the home folder.
                 if (bookmarkParentFolder.isEmpty()) {
+                    parentFolderImageView.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.folder_grey));
                     bookmarkParentFolderTextView.setText(R.string.home_folder);
                     bookmarkParentFolderTextView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.grey));
                 } else {
+                    parentFolderImageView.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.folder_dark_blue));
                     bookmarkParentFolderTextView.setText(bookmarkParentFolder);
                     bookmarkParentFolderTextView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.black));
                 }
index 2ae2cc6597d1ea9d53f194ea79427277e62adae7..7ecde8f50883a48ba1ef6ad6639fa36acecd8772 100644 (file)
 
 package com.stoutner.privacybrowser;
 
+import android.app.Activity;
+import android.app.Dialog;
 import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.MatrixCursor;
+import android.database.MergeCursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard.
+import android.support.design.widget.Snackbar;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AlertDialog;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.io.ByteArrayOutputStream;
 
 public class MoveToFolder extends DialogFragment {
+    // The public interface is used to send information back to the parent activity.
+    public interface MoveToFolderListener {
+        void onCancelMoveToFolder(DialogFragment dialogFragment);
+
+        void onMoveToFolder(DialogFragment dialogFragment);
+    }
+
+    // `moveToFolderListener` is used in `onAttach()` and `onCreateDialog`.
+    private MoveToFolderListener moveToFolderListener;
+
+    public void onAttach(Activity parentActivity) {
+        super.onAttach(parentActivity);
+
+        // Get a handle for `MoveToFolderListener` from `parentActivity`.
+        try {
+            moveToFolderListener = (MoveToFolderListener) parentActivity;
+        } catch(ClassCastException exception) {
+            throw new ClassCastException(parentActivity.toString() + " must implement EditBookmarkFolderListener.");
+        }
+    }
+
+    // `exceptFolders` is used in `onCreateDialog()` and `addSubfoldersToExceptFolders()`.
+    private String exceptFolders;
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use `AlertDialog.Builder` to create the `AlertDialog`.  The style formats the color of the button text.
+        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog);
+        dialogBuilder.setTitle(R.string.move_to_folder);
+        // The parent view is `null` because it will be assigned by `AlertDialog`.
+        dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_dialog, null));
+
+        // Set an `onClick()` listener for the negative button.
+        dialogBuilder.setNegativeButton(R.string.cancel, new Dialog.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Return the `DialogFragment` to the parent activity on cancel.
+                moveToFolderListener.onCancelMoveToFolder(MoveToFolder.this);
+            }
+        });
+
+        // Set the `onClick()` listener fo the positive button.
+        dialogBuilder.setPositiveButton(R.string.move, new Dialog.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Return the `DialogFragment` to the parent activity on save.
+                moveToFolderListener.onMoveToFolder(MoveToFolder.this);
+            }
+        });
+
+        // Create an `AlertDialog` from the `AlertDialog.Builder`.
+        final AlertDialog alertDialog = dialogBuilder.create();
+
+        // We need to show the `AlertDialog` before we can modify items in the layout.
+        alertDialog.show();
+
+        // Initialize the `Cursor` and `CursorAdapter` variables.
+        Cursor foldersCursor;
+        CursorAdapter foldersCursorAdapter;
+
+        // Check to see if we are in the `Home Folder`.
+        if (BookmarksActivity.currentFolder.isEmpty()) {  // Don't display `Home Folder` at the top of the `ListView`.
+            // Initialize `exceptFolders`.
+            exceptFolders = "";
+
+            // If a folder is selected, add it and all children to the list of folders not to display.
+            long[] selectedBookmarksLongArray = BookmarksActivity.bookmarksListView.getCheckedItemIds();
+            for (long databaseIdLong : selectedBookmarksLongArray) {
+                // Get `databaseIdInt` for each selected bookmark.
+                int databaseIdInt = (int) databaseIdLong;
+
+                // If `databaseIdInt` is a folder.
+                if (BookmarksActivity.bookmarksDatabaseHandler.isFolder(databaseIdInt)) {
+                    // Get the name of the selected folder.
+                    String folderName = BookmarksActivity.bookmarksDatabaseHandler.getFolderName(databaseIdInt);
+
+                    if (exceptFolders.isEmpty()){
+                        // Add the selected folder to the list of folders not to display.
+                        exceptFolders = DatabaseUtils.sqlEscapeString(folderName);
+                    } else {
+                        // Add the selected folder to the end of the list of folders not to display.
+                        exceptFolders = exceptFolders + "," + DatabaseUtils.sqlEscapeString(folderName);
+                    }
+
+                    // Add the selected folder's subfolders to the list of folders not to display.
+                    addSubfoldersToExceptFolders(folderName);
+                }
+            }
+
+            // Get a `Cursor` containing the folders to display.
+            foldersCursor = BookmarksActivity.bookmarksDatabaseHandler.getFoldersCursorExcept(exceptFolders);
+
+            // Setup `foldersCursorAdaptor` with `this` context.  `false` disables autoRequery.
+            foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersCursor, 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 getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false);
+                }
+
+                @Override
+                public void bindView(View view, Context context, Cursor cursor) {
+                    // Get the folder icon from `cursor`.
+                    byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHandler.FAVORITE_ICON));
+                    // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
+                    Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length);
+                    // Display `folderIconBitmap` in `move_to_folder_icon`.
+                    ImageView folderIconImageView = (ImageView) view.findViewById(R.id.move_to_folder_icon);
+                    assert folderIconImageView != null;  // Remove the warning below that `currentIconImageView` might be null;
+                    folderIconImageView.setImageBitmap(folderIconBitmap);
+
+                    // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`.
+                    String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
+                    TextView folderNameTextView = (TextView) view.findViewById(R.id.move_to_folder_name_textview);
+                    folderNameTextView.setText(folderName);
+                }
+            };
+        } else {  // Display `Home Folder` at the top of the `ListView`.
+            // Get the home folder icon drawable and convert it to a `Bitmap`.  `this` specifies the current context.
+            Drawable homeFolderIconDrawable = ContextCompat.getDrawable(getActivity().getApplicationContext(), R.drawable.folder_grey_bitmap);
+            BitmapDrawable homeFolderIconBitmapDrawable = (BitmapDrawable) homeFolderIconDrawable;
+            Bitmap homeFolderIconBitmap = homeFolderIconBitmapDrawable.getBitmap();
+            // Convert the folder `Bitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            ByteArrayOutputStream homeFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
+            homeFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, homeFolderIconByteArrayOutputStream);
+            byte[] homeFolderIconByteArray = homeFolderIconByteArrayOutputStream.toByteArray();
+
+            // Setup a `MatrixCursor` for the `Home Folder`.
+            String[] homeFolderMatrixCursorColumnNames = {BookmarksDatabaseHandler._ID, BookmarksDatabaseHandler.BOOKMARK_NAME, BookmarksDatabaseHandler.FAVORITE_ICON};
+            MatrixCursor homeFolderMatrixCursor = new MatrixCursor(homeFolderMatrixCursorColumnNames);
+            homeFolderMatrixCursor.addRow(new Object[]{0, getString(R.string.home_folder), homeFolderIconByteArray});
+
+            // Add the parent folder to the list of folders not to display.
+            exceptFolders = DatabaseUtils.sqlEscapeString(BookmarksActivity.currentFolder);
+
+            // If a folder is selected, add it and all children to the list of folders not to display.
+            long[] selectedBookmarksLongArray = BookmarksActivity.bookmarksListView.getCheckedItemIds();
+            for (long databaseIdLong : selectedBookmarksLongArray) {
+                // Get `databaseIdInt` for each selected bookmark.
+                int databaseIdInt = (int) databaseIdLong;
+
+                // If `databaseIdInt` is a folder.
+                if (BookmarksActivity.bookmarksDatabaseHandler.isFolder(databaseIdInt)) {
+                    // Get the name of the selected folder.
+                    String folderName = BookmarksActivity.bookmarksDatabaseHandler.getFolderName(databaseIdInt);
+
+                    // Add the selected folder to the end of the list of folders not to display.
+                    exceptFolders = exceptFolders + "," + DatabaseUtils.sqlEscapeString(folderName);
+
+                    // Add the selected folder's subfolders to the list of folders not to display.
+                    addSubfoldersToExceptFolders(folderName);
+                }
+            }
+
+            // Get a `foldersCursor`.
+            foldersCursor = BookmarksActivity.bookmarksDatabaseHandler.getFoldersCursorExcept(exceptFolders);
+
+            // Combine `homeFolderMatrixCursor` and `foldersCursor`.
+            MergeCursor foldersMergeCursor = new MergeCursor(new Cursor[]{homeFolderMatrixCursor, foldersCursor});
+
+            // Setup `foldersCursorAdaptor` with `this` context.  `false` disables autoRequery.
+            foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersMergeCursor, 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 getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false);
+                }
+
+                @Override
+                public void bindView(View view, Context context, Cursor cursor) {
+                    // Get the folder icon from `cursor`.
+                    byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHandler.FAVORITE_ICON));
+                    // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
+                    Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length);
+                    // Display `folderIconBitmap` in `move_to_folder_icon`.
+                    ImageView folderIconImageView = (ImageView) view.findViewById(R.id.move_to_folder_icon);
+                    assert folderIconImageView != null;  // Remove the warning below that `currentIconImageView` might be null;
+                    folderIconImageView.setImageBitmap(folderIconBitmap);
+
+                    // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`.
+                    String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
+                    TextView folderNameTextView = (TextView) view.findViewById(R.id.move_to_folder_name_textview);
+                    folderNameTextView.setText(folderName);
+                }
+            };
+        }
+
+        // Display the ListView
+        ListView foldersListView = (ListView) alertDialog.findViewById(R.id.move_to_folder_listview);
+        assert foldersListView != null;  // Remove the warning below that `foldersListView` might be null.
+        foldersListView.setAdapter(foldersCursorAdapter);
+
+        // `onCreateDialog` requires the return of an `AlertDialog`.
+        return alertDialog;
+    }
+
+    public void addSubfoldersToExceptFolders(String folderName) {
+        // Get a `Cursor` will all the immediate subfolders.
+        Cursor subfoldersCursor = BookmarksActivity.bookmarksDatabaseHandler.getSubfoldersCursor(folderName);
+
+        for (int i = 0; i < subfoldersCursor.getCount(); i++) {
+            // Move `subfolderCursor` to the current item.
+            subfoldersCursor.moveToPosition(i);
+
+            // Get the name of the subfolder.
+            String subfolderName = subfoldersCursor.getString(subfoldersCursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
+
+            // Run the same tasks for any subfolders of the subfolder.
+            addSubfoldersToExceptFolders(subfolderName);
+
+            // Add the subfolder to `exceptFolders`.
+            subfolderName = DatabaseUtils.sqlEscapeString(subfolderName);
+            exceptFolders = exceptFolders + "," + subfolderName;
+        }
+
+    }
 }
diff --git a/app/src/main/res/drawable-hdpi/folder_grey_bitmap.png b/app/src/main/res/drawable-hdpi/folder_grey_bitmap.png
new file mode 100644 (file)
index 0000000..cf76501
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/folder_grey_bitmap.png differ
diff --git a/app/src/main/res/drawable-mdpi/folder_dark_blue.xml b/app/src/main/res/drawable-mdpi/folder_dark_blue.xml
new file mode 100644 (file)
index 0000000..ffa9477
--- /dev/null
@@ -0,0 +1,14 @@
+<!-- folder_dark_blue.xml.xml comes from the Android Material icon set, where it is called ic_folder.
+  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="#FF0D47A1"
+        android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
+</vector>
diff --git a/app/src/main/res/drawable-mdpi/folder_grey_bitmap.png b/app/src/main/res/drawable-mdpi/folder_grey_bitmap.png
new file mode 100644 (file)
index 0000000..c37add7
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/folder_grey_bitmap.png differ
diff --git a/app/src/main/res/drawable-xhdpi/folder_grey_bitmap.png b/app/src/main/res/drawable-xhdpi/folder_grey_bitmap.png
new file mode 100644 (file)
index 0000000..7d08575
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/folder_grey_bitmap.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/folder_grey_bitmap.png b/app/src/main/res/drawable-xxhdpi/folder_grey_bitmap.png
new file mode 100644 (file)
index 0000000..4ad866e
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/folder_grey_bitmap.png differ
diff --git a/app/src/main/res/drawable/folder.xml b/app/src/main/res/drawable/folder.xml
deleted file mode 100644 (file)
index a6d3d31..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<!-- folder.xml comes from the Android Material icon set, where it is called ic_folder.
-  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="#FF9E9E9E"
-        android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
-</vector>
diff --git a/app/src/main/res/drawable/folder_grey.xml b/app/src/main/res/drawable/folder_grey.xml
new file mode 100644 (file)
index 0000000..5710e29
--- /dev/null
@@ -0,0 +1,14 @@
+<!-- folder_grey.xml comes from the Android Material icon set, where it is called ic_folder.
+  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="#FF9E9E9E"
+        android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
+</vector>
diff --git a/app/src/main/res/layout/bookmarks_database_view_item_linearlayout.xml b/app/src/main/res/layout/bookmarks_database_view_item_linearlayout.xml
new file mode 100644 (file)
index 0000000..393ac28
--- /dev/null
@@ -0,0 +1,111 @@
+<?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/>. -->
+
+<LinearLayout
+    android:id="@+id/bookmarks_database_view_item_linearlayout"
+    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:orientation="vertical" >
+
+    <!-- First row. -->
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_marginTop="10dp"
+        android:layout_marginStart="10dp"
+        android:layout_marginEnd="10dp"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/bookmarks_database_view_database_id"
+            android:layout_height="wrap_content"
+            android:layout_width="50dp"
+            android:layout_marginEnd="10dp"
+            android:gravity="end"
+            android:textColor="@color/grey"
+            android:textSize="22sp" />
+
+        <ImageView
+            android:id="@+id/bookmarks_database_view_favorite_icon"
+            android:layout_width="30dp"
+            android:layout_height="30dp"
+            android:layout_gravity="center_vertical"
+            android:layout_marginEnd="10dp"
+            tools:ignore="ContentDescription" />
+
+        <TextView
+            android:id='@+id/bookmarks_database_view_bookmark_name'
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:textColor="@color/black"
+            android:textSize="22sp"
+            android:singleLine="true" />
+    </LinearLayout>
+
+    <!-- Second row. -->
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginStart="10dp"
+        android:layout_marginEnd="10dp"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/bookmarks_database_view_display_order"
+            android:layout_height="wrap_content"
+            android:layout_width="50dp"
+            android:layout_marginEnd="10dp"
+            android:gravity="end"
+            android:textColor="@color/black"
+            android:textSize="22sp" />
+
+        <ImageView
+            android:id="@+id/bookmarks_database_view_parent_folder_icon"
+            android:layout_height="30dp"
+            android:layout_width="30dp"
+            android:layout_gravity="center_vertical"
+            android:layout_marginEnd="10dp"
+            android:src="@drawable/folder_grey"
+            tools:ignore="ContentDescription" />
+
+        <TextView
+            android:id="@+id/bookmarks_database_view_parent_folder"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:textColor="@color/black"
+            android:textSize="22sp"
+            android:textStyle="italic"
+            android:singleLine="true" />
+    </LinearLayout>
+
+    <!-- Third row. -->
+    <TextView
+        android:id="@+id/bookmarks_database_view_bookmark_url"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginBottom="10dp"
+        android:layout_marginStart="13dp"
+        android:layout_marginEnd="10dp"
+        android:textColor="@color/black"
+        android:textSize="22sp"
+        android:singleLine="true" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/bookmarks_database_view_linearlayout.xml b/app/src/main/res/layout/bookmarks_database_view_linearlayout.xml
deleted file mode 100644 (file)
index f1949e8..0000000
+++ /dev/null
@@ -1,110 +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/>. -->
-
-<LinearLayout
-    android:id="@+id/bookmarks_database_view_item_linearlayout"
-    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:orientation="vertical" >
-
-    <!-- First row. -->
-    <LinearLayout
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"
-        android:layout_marginTop="10dp"
-        android:layout_marginStart="10dp"
-        android:layout_marginEnd="10dp"
-        android:orientation="horizontal" >
-
-        <TextView
-            android:id="@+id/bookmarks_database_view_database_id"
-            android:layout_height="wrap_content"
-            android:layout_width="50dp"
-            android:layout_marginEnd="10dp"
-            android:gravity="end"
-            android:textColor="@color/grey"
-            android:textSize="22sp" />
-
-        <ImageView
-            android:id="@+id/bookmarks_database_view_favorite_icon"
-            android:layout_width="30dp"
-            android:layout_height="30dp"
-            android:layout_gravity="center_vertical"
-            android:layout_marginEnd="10dp"
-            tools:ignore="ContentDescription" />
-
-        <TextView
-            android:id='@+id/bookmarks_database_view_bookmark_name'
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:textColor="@color/black"
-            android:textSize="22sp"
-            android:singleLine="true" />
-    </LinearLayout>
-
-    <!-- Second row. -->
-    <LinearLayout
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:layout_marginStart="10dp"
-        android:layout_marginEnd="10dp"
-        android:orientation="horizontal" >
-
-        <TextView
-            android:id="@+id/bookmarks_database_view_display_order"
-            android:layout_height="wrap_content"
-            android:layout_width="50dp"
-            android:layout_marginEnd="10dp"
-            android:gravity="end"
-            android:textColor="@color/black"
-            android:textSize="22sp" />
-
-        <ImageView
-            android:layout_height="30dp"
-            android:layout_width="30dp"
-            android:layout_gravity="center_vertical"
-            android:layout_marginEnd="10dp"
-            android:src="@drawable/folder"
-            tools:ignore="ContentDescription" />
-
-        <TextView
-            android:id="@+id/bookmarks_database_view_parent_folder"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:textColor="@color/black"
-            android:textSize="22sp"
-            android:textStyle="italic"
-            android:singleLine="true" />
-    </LinearLayout>
-
-    <!-- Third row. -->
-    <TextView
-        android:id="@+id/bookmarks_database_view_bookmark_url"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:layout_marginBottom="10dp"
-        android:layout_marginStart="13dp"
-        android:layout_marginEnd="10dp"
-        android:textColor="@color/black"
-        android:textSize="22sp"
-        android:singleLine="true" />
-</LinearLayout>
\ No newline at end of file
index 3394098da8732f8b2a9ed71d27bd25e4a07e1f64..aaa4bd66beae0250e380ea0b1c97d6cfc16cec29 100644 (file)
@@ -72,7 +72,7 @@
         android:layout_gravity="start"
         app:headerLayout="@layout/navigation_header"
         app:menu="@menu/webview_navigation_menu"
-        app:itemIconTint="@color/blue_gray" />
+        app:itemIconTint="@color/blue_grey" />
 
     <!-- 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/layout/move_to_folder_dialog.xml b/app/src/main/res/layout/move_to_folder_dialog.xml
new file mode 100644 (file)
index 0000000..a0cfd98
--- /dev/null
@@ -0,0 +1,30 @@
+<?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/>. -->
+
+<!-- `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/move_to_folder_listview"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:choiceMode="singleChoice"
+    android:divider="@color/white"
+    android:dividerHeight="0dp" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/move_to_folder_item_linearlayout.xml b/app/src/main/res/layout/move_to_folder_item_linearlayout.xml
new file mode 100644 (file)
index 0000000..be277d9
--- /dev/null
@@ -0,0 +1,46 @@
+<?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/>. -->
+
+<LinearLayout
+    android:id="@+id/move_to_folder_item_linearlayout"
+    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:orientation="horizontal"
+    android:background="@drawable/bookmarks_list_selector" >
+
+    <ImageView
+        android:id="@+id/move_to_folder_icon"
+        android:layout_height="30dp"
+        android:layout_width="30dp"
+        android:layout_gravity="center_vertical"
+        android:layout_marginStart="10dp"
+        tools:ignore="ContentDescription" />
+
+    <TextView
+        android:id="@+id/move_to_folder_name_textview"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_margin="10dp"
+        android:singleLine="true"
+        android:textColor="@color/black"
+        android:textSize="22sp" />
+</LinearLayout>
\ No newline at end of file
index 598c51b790b6ec7f44c6267c48170dcec179dc25..423664c57d25c687e6075bc7403d0cdb518fc12c 100644 (file)
@@ -23,7 +23,7 @@
     <color name="black">#FF000000</color>
     <color name="blue">#FF1976D2</color>
     <color name="dark_blue">#FF0D47A1</color>
-    <color name="blue_gray">#FF607D8B</color>
+    <color name="blue_grey">#FF607D8B</color>
     <color name="grey">#FF9E9E9E</color>
     <color name="green">#FF64DD17</color>
     <color name="light_blue">#FFBBDEFB</color>
index 42396d5919dd7a826f4cbe56943c64124a8e9e63..24b5fbaff15b8b9d6b4ad46d02ffbd25f59a4e2f 100644 (file)
     <string name="folder_names_must_be_unique">Folder names must be unique</string>
     <string name="cannot_create_folder">Cannot create the folder because the name is not unique:</string>
     <string name="cannot_rename_folder">Cannot rename the folder because the new name is not unique:</string>
+    <string name="cannot_move_bookmarks">Cannot move the selected bookmarks because no new folder was selected.</string>
     <string name="edit_bookmark">Edit bookmark</string>
     <string name="edit_folder">Edit folder</string>
     <string name="move_to_folder">Move to Folder</string>
+    <string name="move">Move</string>
     <string name="save">Save</string>
     <string name="bookmark_name">Bookmark name</string>
     <string name="bookmark_url">Bookmark URL</string>
 
     <!-- Bookmarks Database View. -->
     <string name="bookmarks_database_view">Bookmarks Database View</string>
-    <string name="home_folder">Home folder</string>
+    <string name="home_folder">Home Folder</string>
 
     <!-- Guide. -->
     <string name="privacy_browser_guide">Privacy Browser Guide</string>