X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FBookmarksActivity.java;h=cea440a4239b0eb78f9bbf60c2ea856240e49a5c;hp=e55b6711f0b757d4ee78a497ae313f29991160d0;hb=1b27ac6f2b7c046945fc97e2aff9adbde8a152ce;hpb=70e33ff1c78812721732cd004efb9f8751215be7 diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java index e55b6711..cea440a4 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java @@ -1,27 +1,29 @@ /* - * Copyright © 2016-2018 Soren Stoutner . + * Copyright © 2016-2022 Soren Stoutner . * - * This file is part of Privacy Browser . + * This file is part of Privacy Browser Android . * - * Privacy Browser is free software: you can redistribute it and/or modify + * Privacy Browser Android is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privacy Browser is distributed in the hope that it will be useful, + * Privacy Browser Android is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Privacy Browser. If not, see . + * along with Privacy Browser Android. If not, see . */ package com.stoutner.privacybrowser.activities; -import android.app.Activity; +import android.annotation.SuppressLint; +import android.app.Dialog; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -29,20 +31,14 @@ import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; 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.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -// `AppCompatDialogFragment` is required instead of `DialogFragment` or an error is produced on API <=22. -import android.support.v7.app.AppCompatDialogFragment; -import android.support.v7.widget.Toolbar; +import android.preference.PreferenceManager; import android.util.SparseBooleanArray; import android.view.ActionMode; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.Window; import android.view.WindowManager; import android.widget.AbsListView; import android.widget.CursorAdapter; @@ -52,6 +48,15 @@ import android.widget.ListView; import android.widget.RadioButton; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.DialogFragment; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog; @@ -61,19 +66,24 @@ import com.stoutner.privacybrowser.R; import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; public class BookmarksActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener { - // `currentFolder` is public static so it can be accessed from `MoveToFolderDialog`. - // It is used in `onCreate`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, - // and `loadFolder()`. + // `currentFolder` is public static so it can be accessed from `BookmarksDatabaseViewActivity`. public static String currentFolder; - // `checkedItemIds` is public static so it can be accessed from `MoveToFolderDialog`. It is also used in `onCreate()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, - // and `updateMoveIcons()`. - public static long[] checkedItemIds; + // `restartFromBookmarksDatabaseViewActivity` is public static so it can be accessed from `BookmarksDatabaseViewActivity`. It is also used in `onRestart()`. + public static boolean restartFromBookmarksDatabaseViewActivity; + + // Define the saved instance state constants. + private final String CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list"; + + // Define the class menu items. + private MenuItem moveBookmarkUpMenuItem; + private MenuItem moveBookmarkDownMenuItem; // `bookmarksDatabaseHelper` is used in `onCreate()`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, // `onMoveToFolder()`, `deleteBookmarkFolderContents()`, `loadFolder()`, and `onDestroy()`. @@ -99,31 +109,31 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma // `oldFolderName` is used in `onCreate()` and `onSaveBookmarkFolder()`. private String oldFolderNameString; - // `moveBookmarkUpMenuItem` is used in `onCreate()` and `updateMoveIcons()`. - private MenuItem moveBookmarkUpMenuItem; - - // `moveBookmarkDownMenuItem` is used in `onCreate()` and `updateMoveIcons()`. - private MenuItem moveBookmarkDownMenuItem; - // `bookmarksDeletedSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`. private Snackbar bookmarksDeletedSnackbar; // `closeActivityAfterDismissingSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`. private boolean closeActivityAfterDismissingSnackbar; + // The favorite icon byte array is populated in `onCreate()` and used in `onOptionsItemSelected()`. + private byte[] favoriteIconByteArray; + @Override protected void onCreate(Bundle savedInstanceState) { + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + // Get the preferences. + boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false); + boolean bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false); + // Disable screenshots if not allowed. - if (!MainWebViewActivity.allowScreenshots) { + if (!allowScreenshots) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); } - // Set the activity theme. - if (MainWebViewActivity.darkTheme) { - setTheme(R.style.PrivacyBrowserDark_SecondaryActivity); - } else { - setTheme(R.style.PrivacyBrowserLight_SecondaryActivity); - } + // Set the theme. + setTheme(R.style.PrivacyBrowser); // Run the default commands. super.onCreate(savedInstanceState); @@ -131,22 +141,45 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma // Get the intent that launched the activity. Intent launchingIntent = getIntent(); + // Store the current URL and title. + String currentUrl = launchingIntent.getStringExtra("current_url"); + String currentTitle = launchingIntent.getStringExtra("current_title"); + // Set the current folder variable. - if (launchingIntent.getStringExtra("Current Folder") != null) { // Set the current folder from the intent. - currentFolder = launchingIntent.getStringExtra("Current Folder"); + if (launchingIntent.getStringExtra("current_folder") != null) { // Set the current folder from the intent. + currentFolder = launchingIntent.getStringExtra("current_folder"); } else { // Set the current folder to be `""`, which is the home folder. currentFolder = ""; } - // Set the content view. - setContentView(R.layout.bookmarks_coordinatorlayout); + // Get the favorite icon byte array. + favoriteIconByteArray = launchingIntent.getByteArrayExtra("favorite_icon_byte_array"); + + // Remove the incorrect lint warning that the favorite icon byte array might be null. + assert favoriteIconByteArray != null; + + // Convert the favorite icon byte array to a bitmap. + Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length); + + // Set the content according to the app bar position. + if (bottomAppBar) { + // Set the content view. + setContentView(R.layout.bookmarks_bottom_appbar); + } else { + // `Window.FEATURE_ACTION_MODE_OVERLAY` makes the contextual action mode cover the support action bar. It must be requested before the content is set. + supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY); + + // Set the content view. + setContentView(R.layout.bookmarks_top_appbar); + } + + // Get a handle for the toolbar. + final Toolbar toolbar = findViewById(R.id.bookmarks_toolbar); - // Use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. - final Toolbar bookmarksAppBar = findViewById(R.id.bookmarks_toolbar); - setSupportActionBar(bookmarksAppBar); + // Set the support action bar. + setSupportActionBar(toolbar); - // Get a handle for the activity, the app bar, and the ListView. - final Activity bookmarksActivity = this; + // Get handles for the views. appBar = getSupportActionBar(); bookmarksListView = findViewById(R.id.bookmarks_listview); @@ -166,44 +199,34 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma // Set a listener so that tapping a list item loads the URL or folder. bookmarksListView.setOnItemClickListener((parent, view, position, id) -> { // Convert the id from long to int to match the format of the bookmarks database. - int databaseID = (int) id; + int databaseId = (int) id; + + // Get the bookmark cursor for this ID. + Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId); - // Get the bookmark cursor for this ID and move it to the first row. - Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID); + // Move the cursor to the first entry. bookmarkCursor.moveToFirst(); // Act upon the bookmark according to the type. - if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder. + if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder. // Update the current folder. - currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)); // Load the new folder. loadFolder(); } else { // The selected bookmark is not a folder. - // Get the bookmark URL and assign it to `formattedUrlString`. - MainWebViewActivity.formattedUrlString = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)); - - // Set `MainWebViewActivity` to load the new URL on restart. - MainWebViewActivity.loadUrlOnRestart = true; + // Instantiate the edit bookmark dialog. + DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap); - // Update the bookmarks folder for the bookmarks drawer in `MainWebViewActivity`. - MainWebViewActivity.currentBookmarksFolder = currentFolder; - - // Close the bookmarks drawer and reload the bookmarks `ListView` when returning to `MainWebViewActivity`. - MainWebViewActivity.restartFromBookmarksActivity = true; - - // Return to `MainWebViewActivity`. - NavUtils.navigateUpFromSameTask(bookmarksActivity); + // Make it so. + editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark)); } - // Close the `Cursor`. + // Close the cursor. bookmarkCursor.close(); }); - // Get a handle for the activity. - Activity activity = this; - - // `MultiChoiceModeListener` handles long clicks. + // Handle long presses on the list view. bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { // Instantiate the common variables. MenuItem editBookmarkMenuItem; @@ -213,18 +236,17 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - // Inflate the menu for the contextual app bar and set the title. + // Inflate the menu for the contextual app bar. getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu); // Set the title. - if (currentFolder.isEmpty()) { - // Use `R.string.bookmarks` if we are in the home folder. + if (currentFolder.isEmpty()) { // Use `R.string.bookmarks` if in the home folder. mode.setTitle(R.string.bookmarks); } else { // Use the current folder name as the title. mode.setTitle(currentFolder); } - // Get a handle for `MenuItems` that need to be selectively disabled. + // Get handles for menu items that need to be selectively disabled. moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up); moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down); editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark); @@ -232,11 +254,9 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks); // Disable the delete bookmarks menu item if a delete is pending. - if (deletingBookmarks) { - deleteBookmarksMenuItem.setEnabled(false); - } + deleteBookmarksMenuItem.setEnabled(!deletingBookmarks); - // Store `contextualActionMode` so it can be closed programatically. + // Store a handle for the contextual action mode so it can be closed programatically. contextualActionMode = mode; // Make it so. @@ -248,8 +268,8 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma // Get a handle for the move to folder menu item. MenuItem moveToFolderMenuItem = menu.findItem(R.id.move_to_folder); - // Get a `Cursor` with all of the folders. - Cursor folderCursor = bookmarksDatabaseHelper.getAllFoldersCursor(); + // Get a Cursor with all of the folders. + Cursor folderCursor = bookmarksDatabaseHelper.getAllFolders(); // Enable the move to folder menu item if at least one folder exists. moveToFolderMenuItem.setVisible(folderCursor.getCount() > 0); @@ -260,335 +280,304 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { - // Get a long array of the selected bookmarks. - long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds(); - - // Calculate the number of selected bookmarks. - int numberOfSelectedBookmarks = selectedBookmarksLongArray.length; + // Get the number of selected bookmarks. + int numberOfSelectedBookmarks = bookmarksListView.getCheckedItemCount(); - // Adjust the `ActionMode` and the `Menu` according to the number of selected bookmarks. - if (numberOfSelectedBookmarks == 0) { // No bookmarks are selected. - // Close the `ActionMode`. - mode.finish(); - } else if (numberOfSelectedBookmarks == 1) { // One bookmark is selected. - // List the number of selected bookmarks in the subtitle. - mode.setSubtitle(getString(R.string.one_selected)); + // Only process commands if at least one bookmark is selected. Otherwise, a context menu with 0 selected bookmarks is briefly displayed. + if (numberOfSelectedBookmarks > 0) { + // Adjust the ActionMode and the menu according to the number of selected bookmarks. + if (numberOfSelectedBookmarks == 1) { // One bookmark is selected. + // List the number of selected bookmarks in the subtitle. + mode.setSubtitle(getString(R.string.selected) + " 1"); - // Show the `Move Up`, `Move Down`, and `Edit` options. - moveBookmarkUpMenuItem.setVisible(true); - moveBookmarkDownMenuItem.setVisible(true); - editBookmarkMenuItem.setVisible(true); + // Show the `Move Up`, `Move Down`, and `Edit` options. + moveBookmarkUpMenuItem.setVisible(true); + moveBookmarkDownMenuItem.setVisible(true); + editBookmarkMenuItem.setVisible(true); - // Update the enabled status of the move icons. - updateMoveIcons(); - } else { // More than one bookmark is selected. - // List the number of selected bookmarks according to the language. - if (getString(R.string.android_asset_path).equals("ru")) { // The Russian translation is used. - // Convert the number of selected bookmarks to a string. - String numberOfSelectedBookmarksString = String.valueOf(numberOfSelectedBookmarks); - - // Russian follows rule #7 at . - if (numberOfSelectedBookmarksString.endsWith("1") && !numberOfSelectedBookmarksString.equals("11")) { // Ends in 1. - mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected_russian_ends_in_1)); - } else if ((numberOfSelectedBookmarksString.endsWith("2") || numberOfSelectedBookmarksString.endsWith("3") || numberOfSelectedBookmarksString.endsWith("4")) && - !numberOfSelectedBookmarksString.equals("12") && !numberOfSelectedBookmarksString.equals("13") && !numberOfSelectedBookmarksString.equals("14")) { // Ends in 2-4. - mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected_russian_ends_in_2)); - } else { // Everything else. - mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected_russian_everything_else)); - } - } else { // Another language is used. + // Update the enabled status of the move icons. + updateMoveIcons(); + } else { // More than one bookmark is selected. // List the number of selected bookmarks in the subtitle. - mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected)); - } + mode.setSubtitle(getString(R.string.selected) + " " + numberOfSelectedBookmarks); - // Hide non-applicable `MenuItems`. - moveBookmarkUpMenuItem.setVisible(false); - moveBookmarkDownMenuItem.setVisible(false); - editBookmarkMenuItem.setVisible(false); - } + // Hide non-applicable `MenuItems`. + moveBookmarkUpMenuItem.setVisible(false); + moveBookmarkDownMenuItem.setVisible(false); + editBookmarkMenuItem.setVisible(false); + } - // Do not show `Select All` if all the bookmarks are already checked. - if (bookmarksListView.getCheckedItemIds().length == bookmarksListView.getCount()) { - selectAllBookmarksMenuItem.setVisible(false); - } else { - selectAllBookmarksMenuItem.setVisible(true); + // Show the select all menu item if all the bookmarks are not selected. + selectAllBookmarksMenuItem.setVisible(bookmarksListView.getCheckedItemCount() != bookmarksListView.getCount()); } } @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // Get the menu item ID. - int menuItemId = item.getItemId(); - - // Instantiate the common variables. - int selectedBookmarkPosition; + public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { + // Declare the common variables. int selectedBookmarkNewPosition; final SparseBooleanArray selectedBookmarksPositionsSparseBooleanArray; - switch (menuItemId) { - case R.id.move_bookmark_up: - // Get the array of checked bookmark positions. - selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions(); - - // Store the position of the selected bookmark. Only one bookmark is selected when `move_bookmark_up` is enabled. - selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(0); - - // Calculate the new position of the selected bookmark. - selectedBookmarkNewPosition = selectedBookmarkPosition - 1; - - // Iterate through the bookmarks. - for (int i = 0; i < bookmarksListView.getCount(); i++) { - // Get the database ID for the current bookmark. - int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i); - - // Update the display order for the current bookmark. - if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark. - // Move the current bookmark up one. - bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1); - } else if ((i + 1) == selectedBookmarkPosition){ // The current bookmark is immediately above the selected bookmark. - // Move the current bookmark down one. - bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1); - } else { // The current bookmark is not changing positions. - // Move `bookmarksCursor` to the current bookmark position. - bookmarksCursor.moveToPosition(i); - - // Update the display order only if it is not correct in the database. - if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) { - bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i); - } + // Initialize the selected bookmark position. + int selectedBookmarkPosition = 0; + + // Get the menu item ID. + int menuItemId = menuItem.getItemId(); + + // Run the commands according to the selected action item. + if (menuItemId == R.id.move_bookmark_up) { // Move the bookmark up. + // Get the array of checked bookmark positions. + selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions(); + + // Get the position of the bookmark that is selected. If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`. + for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) { + // Check to see if the value for the bookmark is true, meaning it is currently selected. + if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) { + // Only one bookmark should have a value of `true` when move bookmark up is enabled. + selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i); + } + } + + // Calculate the new position of the selected bookmark. + selectedBookmarkNewPosition = selectedBookmarkPosition - 1; + + // Iterate through the bookmarks. + for (int i = 0; i < bookmarksListView.getCount(); i++) { + // Get the database ID for the current bookmark. + int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i); + + // Update the display order for the current bookmark. + if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark. + // Move the current bookmark up one. + bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1); + } else if ((i + 1) == selectedBookmarkPosition) { // The current bookmark is immediately above the selected bookmark. + // Move the current bookmark down one. + bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1); + } else { // The current bookmark is not changing positions. + // Move `bookmarksCursor` to the current bookmark position. + bookmarksCursor.moveToPosition(i); + + // Update the display order only if it is not correct in the database. + if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) { + bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i); } } + } - // Update `bookmarksCursor` with the current contents of the bookmarks database. - bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder); + // Update the bookmarks cursor with the current contents of the bookmarks database. + bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder); - // Update the `ListView`. - bookmarksCursorAdapter.changeCursor(bookmarksCursor); + // Update the list view. + bookmarksCursorAdapter.changeCursor(bookmarksCursor); - // Scroll with the bookmark. - scrollBookmarks(selectedBookmarkNewPosition); + // Scroll with the bookmark. + scrollBookmarks(selectedBookmarkNewPosition); - // Update the enabled status of the move icons. - updateMoveIcons(); - break; - - case R.id.move_bookmark_down: - // Get the array of checked bookmark positions. - selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions(); - - // Store the position of the selected bookmark. Only one bookmark is selected when `move_bookmark_down` is enabled. - selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(0); - - // Calculate the new position of the selected bookmark. - selectedBookmarkNewPosition = selectedBookmarkPosition + 1; - - // Iterate through the bookmarks. - for (int i = 0; i . - if (numberOfBookmarksString.endsWith("1") && !numberOfBookmarksString.equals("11")) { // Ends in 1. - snackbarMessage = numberOfBookmarksString + " " + getString(R.string.bookmarks_deleted_russian_ends_in_1); - } else if ((numberOfBookmarksString.endsWith("2") || numberOfBookmarksString.endsWith("3") || numberOfBookmarksString.endsWith("4")) && - !numberOfBookmarksString.equals("12") && !numberOfBookmarksString.equals("13") && !numberOfBookmarksString.equals("14")) { // Ends in 2-4. - snackbarMessage = numberOfBookmarksString + " " + getString(R.string.bookmarks_deleted_russian_ends_in_2); - } else { // Everything else. - snackbarMessage = numberOfBookmarksString + " " + getString(R.string.bookmarks_deleted_russian_everything_else); - } - } else { // Another language is used. - snackbarMessage = selectedBookmarksIdsLongArray.length + " " + getString(R.string.bookmarks_deleted); - } + // Update the enabled status of the move icons. + updateMoveIcons(); + } else if (menuItemId == R.id.move_to_folder) { // Move to folder. + // Instantiate the move to folder alert dialog. + DialogFragment moveToFolderDialog = MoveToFolderDialog.moveBookmarks(currentFolder, bookmarksListView.getCheckedItemIds()); + + // Show the move to folder alert dialog. + moveToFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.move_to_folder)); + } else if (menuItemId == R.id.edit_bookmark) { + // Get the array of checked bookmark positions. + selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions(); + + // Get the position of the bookmark that is selected. If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`. + for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) { + // Check to see if the value for the bookmark is true, meaning it is currently selected. + if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) { + // Only one bookmark should have a value of `true` when move edit bookmark is enabled. + selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i); } + } - // Show a SnackBar. - bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), snackbarMessage, Snackbar.LENGTH_LONG) - .setAction(R.string.undo, view -> { - // Do nothing because everything will be handled by `onDismissed()` below. - }) - .addCallback(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: - // Update `bookmarksCursor` with the current contents of the bookmarks database, including the "deleted" bookmarks. - bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder); - - // Update the `ListView`. - bookmarksCursorAdapter.changeCursor(bookmarksCursor); - - // Re-select the previously selected bookmarks. - for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) { - bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true); - } - - break; - - // The `Snackbar` was dismissed without the `Undo` button being pushed. - default: - // Delete each selected bookmark. - for (long databaseIdLong : selectedBookmarksIdsLongArray) { - // Convert `databaseIdLong` to an int. - int databaseIdInt = (int) databaseIdLong; - - // Delete the contents of the folder if the selected bookmark is a folder. - if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) { - deleteBookmarkFolderContents(databaseIdInt); - } - - // Delete the selected bookmark. - bookmarksDatabaseHelper.deleteBookmark(databaseIdInt); - } - - // Update the display order. - for (int i = 0; i < bookmarksListView.getCount(); i++) { - // Get the database ID for the current bookmark. - int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i); - - // Move `bookmarksCursor` to the current bookmark position. - bookmarksCursor.moveToPosition(i); - - // Update the display order only if it is not correct in the database. - if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) { - bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i); - } - } - } + // Move the cursor to the selected position. + bookmarksCursor.moveToPosition(selectedBookmarkPosition); + + // Find out if this bookmark is a folder. + boolean isFolder = (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1); + + // Get the selected bookmark database ID. + int databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper._ID)); - // Enable the delete bookmarks menu item. - deleteBookmarksMenuItem.setEnabled(true); + // Show the edit bookmark or edit bookmark folder dialog. + if (isFolder) { + // Save the current folder name, which is used in `onSaveBookmarkFolder()`. + oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)); - // Reset the deleting bookmarks flag. - deletingBookmarks = false; + // Instantiate the edit bookmark folder dialog. + DialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, favoriteIconBitmap); + + // Make it so. + editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder)); + } else { + // Instantiate the edit bookmark dialog. + DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap); + + // Make it so. + editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark)); + } + } else if (menuItemId == R.id.delete_bookmark) { // Delete bookmark. + // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes. + deletingBookmarks = true; - // Close the activity if back has been pressed. - if (closeActivityAfterDismissingSnackbar) { - // Update the bookmarks folder for the bookmarks drawer in the main WebView activity. - MainWebViewActivity.currentBookmarksFolder = currentFolder; + // Get an array of the selected row IDs. + final long[] selectedBookmarksIdsLongArray = bookmarksListView.getCheckedItemIds(); - // Close the bookmarks drawer and reload the bookmarks ListView when returning to the main WebView activity. - MainWebViewActivity.restartFromBookmarksActivity = true; + // Initialize a variable to count the number of bookmarks to delete. + int numberOfBookmarksToDelete = 0; + + // Count the number of bookmarks. + for (long databaseIdLong : selectedBookmarksIdsLongArray) { + // Convert the database ID long to an int. + int databaseIdInt = (int) databaseIdLong; + + // Count the contents of the folder if the selected bookmark is a folder. + if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) { + // Add the bookmarks from the folder to the running total. + numberOfBookmarksToDelete = numberOfBookmarksToDelete + countBookmarkFolderContents(databaseIdInt); + } + + // Increment the count of the number of bookmarks to delete. + numberOfBookmarksToDelete++; + } - // Return to `MainWebViewActivity`. - NavUtils.navigateUpFromSameTask(activity); + // Get an array of checked bookmarks. `.clone()` makes a copy that won't change if the list view is reloaded, which is needed for re-selecting the bookmarks on undelete. + selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions().clone(); + + // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs. + bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder); + + // Update the list view. + bookmarksCursorAdapter.changeCursor(bookmarksCursor); + + // Create a Snackbar with the number of deleted bookmarks. + bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted) + " " + numberOfBookmarksToDelete, + Snackbar.LENGTH_LONG) + .setAction(R.string.undo, view -> { + // Do nothing because everything will be handled by `onDismissed()` below. + }) + .addCallback(new Snackbar.Callback() { + @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`. + @Override + public void onDismissed(Snackbar snackbar, int event) { + if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { // The user pushed the undo button. + // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks. + bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder); + + // Update the list view. + bookmarksCursorAdapter.changeCursor(bookmarksCursor); + + // Re-select the previously selected bookmarks. + for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) { + bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true); + } + } else { // The snackbar was dismissed without the undo button being pushed. + // Delete each selected bookmark. + for (long databaseIdLong : selectedBookmarksIdsLongArray) { + // Convert `databaseIdLong` to an int. + int databaseIdInt = (int) databaseIdLong; + + // Delete the contents of the folder if the selected bookmark is a folder. + if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) { + deleteBookmarkFolderContents(databaseIdInt); + } + + // Delete the selected bookmark. + bookmarksDatabaseHelper.deleteBookmark(databaseIdInt); + } + + // Update the display order. + for (int i = 0; i < bookmarksListView.getCount(); i++) { + // Get the database ID for the current bookmark. + int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i); + + // Move `bookmarksCursor` to the current bookmark position. + bookmarksCursor.moveToPosition(i); + + // Update the display order only if it is not correct in the database. + if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) { + bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i); + } } } - }); - //Show the `SnackBar`. - bookmarksDeletedSnackbar.show(); + // Reset the deleting bookmarks flag. + deletingBookmarks = false; - // Close the `ActionBar`. - mode.finish(); - break; + // Enable the delete bookmarks menu item. + deleteBookmarksMenuItem.setEnabled(true); - case R.id.context_menu_select_all_bookmarks: - // Get the total number of bookmarks. - int numberOfBookmarks = bookmarksListView.getCount(); + // Close the activity if back has been pressed. + if (closeActivityAfterDismissingSnackbar) { + onBackPressed(); + } + } + }); - // Select them all. - for (int i = 0; i < numberOfBookmarks; i++) { - bookmarksListView.setItemChecked(i, true); - } - break; + //Show the Snackbar. + bookmarksDeletedSnackbar.show(); + } else if (menuItemId == R.id.context_menu_select_all_bookmarks) { // Select all. + // Get the total number of bookmarks. + int numberOfBookmarks = bookmarksListView.getCount(); + + // Select them all. + for (int i = 0; i < numberOfBookmarks; i++) { + bookmarksListView.setItemChecked(i, true); + } } // Consume the click. @@ -601,23 +590,86 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma } }); - // Get handles for the `FloatingActionButtons`. + // Get handles for the floating action buttons. FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab); FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab); // Set the create new bookmark folder FAB to display the `AlertDialog`. createBookmarkFolderFab.setOnClickListener(v -> { - // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`. - AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog(); - createBookmarkFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_folder)); + // Create a create bookmark folder dialog. + DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(favoriteIconBitmap); + + // Show the create bookmark folder dialog. + createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder)); }); - // Set the create new bookmark FAB to display the `AlertDialog`. + // Set the create new bookmark FAB to display the alert dialog. createBookmarkFab.setOnClickListener(view -> { - // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`. - AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog(); + // Remove the incorrect lint warning below. + assert currentUrl != null; + assert currentTitle != null; + + // Instantiate the create bookmark dialog. + DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentUrl, currentTitle, favoriteIconBitmap); + + // Display the create bookmark dialog. createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark)); }); + + // Restore the state if the app has been restarted. + if (savedInstanceState != null) { + // Update the bookmarks list view after it has loaded. + bookmarksListView.post(() -> { + // Get the checked bookmarks array list. + ArrayList checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST); + + // Check each previously checked bookmark in the list view. When the minimum API >= 24 a `forEach()` command can be used instead. + if (checkedBookmarksArrayList != null) { + for (int i = 0; i < checkedBookmarksArrayList.size(); i++) { + bookmarksListView.setItemChecked(checkedBookmarksArrayList.get(i), true); + } + } + }); + } + } + + @Override + public void onRestart() { + // Run the default commands. + super.onRestart(); + + // Update the list view if returning from the bookmarks database view activity. + if (restartFromBookmarksDatabaseViewActivity) { + // Load the current folder in the list view. + loadFolder(); + + // Reset `restartFromBookmarksDatabaseViewActivity`. + restartFromBookmarksDatabaseViewActivity = false; + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { + // Run the default commands. + super.onSaveInstanceState(savedInstanceState); + + // Get the array of the checked items. + SparseBooleanArray checkedBookmarksSparseBooleanArray = bookmarksListView.getCheckedItemPositions(); + + // Create a checked items array list. + ArrayList checkedBookmarksArrayList = new ArrayList<>(); + + // Add each checked bookmark position to the array list. + for (int i = 0; i < checkedBookmarksSparseBooleanArray.size(); i++) { + // Check to see if the bookmark is currently checked. Bookmarks that have previously been checked but currently aren't will be populated in the sparse boolean array, but will return false. + if (checkedBookmarksSparseBooleanArray.valueAt(i)) { + // Add the bookmark position to the checked bookmarks array list. + checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i)); + } + } + + // Store the checked items array list in the saved instance state. + savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList); } @Override @@ -631,61 +683,51 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma @Override public boolean onOptionsItemSelected(MenuItem menuItem) { - // Get the ID of the `MenuItem` that was selected. + // Get a handle for the menu item ID. int menuItemId = menuItem.getItemId(); - switch (menuItemId) { - case android.R.id.home: // The home arrow is identified as `android.R.id.home`, not just `R.id.home`. - if (currentFolder.isEmpty()) { // Currently in the home folder. - // Go home. - if ((bookmarksDeletedSnackbar != null) && bookmarksDeletedSnackbar.isShown()) { // Close the bookmarks delete snackbar before going home. - // Set the close flag. - closeActivityAfterDismissingSnackbar = true; - - // Dismiss the snackbar. - bookmarksDeletedSnackbar.dismiss(); - } else { // Go home immediately. - // Update the bookmarks folder for the bookmarks drawer in `MainWebViewActivity`. - MainWebViewActivity.currentBookmarksFolder = ""; - - // Close the bookmarks drawer and reload the bookmarks `ListView` when returning to `MainWebViewActivity`. - MainWebViewActivity.restartFromBookmarksActivity = true; - - // Return to `MainWebViewActivity`. - NavUtils.navigateUpFromSameTask(this); - } - } else { // Currently in a subfolder. - // Place the former parent folder in `currentFolder`. - currentFolder = bookmarksDatabaseHelper.getParentFolder(currentFolder); + // Run the command according to the selected option. + if (menuItemId == android.R.id.home) { // Home. The home arrow is identified as `android.R.id.home`, not just `R.id.home`. + if (currentFolder.isEmpty()) { // Currently in the home folder. + // Run the back commands. + onBackPressed(); + } else { // Currently in a subfolder. + // Place the former parent folder in `currentFolder`. + currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder); - // Load the new folder. - loadFolder(); - } - break; + // Load the new folder. + loadFolder(); + } + } else if (menuItemId == R.id.options_menu_select_all_bookmarks) { // Select all. + // Get the total number of bookmarks. + int numberOfBookmarks = bookmarksListView.getCount(); - case R.id.options_menu_select_all_bookmarks: - // Get the total number of bookmarks. - int numberOfBookmarks = bookmarksListView.getCount(); + // Select them all. + for (int i = 0; i < numberOfBookmarks; i++) { + bookmarksListView.setItemChecked(i, true); + } + } else if (menuItemId == R.id.bookmarks_database_view) { + // Close the contextual action bar if it is displayed. This can happen if the bottom app bar is enabled. + if (contextualActionMode != null) { + contextualActionMode.finish(); + } - // Select them all. - for (int i = 0; i < numberOfBookmarks; i++) { - bookmarksListView.setItemChecked(i, true); - } - break; + // Create an intent to launch the bookmarks database view activity. + Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class); + + // Include the favorite icon byte array to the intent. + bookmarksDatabaseViewIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray); - case R.id.bookmarks_database_view: - // Launch `BookmarksDatabaseViewActivity`. - Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class); - startActivity(bookmarksDatabaseViewIntent); - break; + // Make it so. + startActivity(bookmarksDatabaseViewIntent); } return true; } @Override public void onBackPressed() { - // Check to see if a snackbar is currently displayed. If so, it must be closed before exiting so that a pending delete is completed before reloading the ListView in the bookmarks drawer. - if ((bookmarksDeletedSnackbar != null) && bookmarksDeletedSnackbar.isShown()) { // Close the bookmarks delete snackbar before going home. + // Check to see if a snackbar is currently displayed. If so, it must be closed before exiting so that a pending delete is completed before reloading the list view in the bookmarks drawer. + if ((bookmarksDeletedSnackbar != null) && bookmarksDeletedSnackbar.isShown()) { // Close the bookmarks deleted snackbar before going home. // Set the close flag. closeActivityAfterDismissingSnackbar = true; @@ -698,24 +740,34 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma // Close the bookmarks drawer and reload the bookmarks ListView when returning to the main WebView activity. MainWebViewActivity.restartFromBookmarksActivity = true; - // Exit `BookmarksActivity`. + // Exit the bookmarks activity. super.onBackPressed(); } } @Override - public void onCreateBookmark(AppCompatDialogFragment dialogFragment) { - // Get the `EditTexts` from the `dialogFragment`. - EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext); - EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext); + public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) { + // Get the alert dialog from the fragment. + Dialog dialog = dialogFragment.getDialog(); - // Extract the strings from the `EditTexts`. + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + + // Get the views from the dialog fragment. + EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext); + EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext); + + // Extract the strings from the edit texts. String bookmarkNameString = createBookmarkNameEditText.getText().toString(); String bookmarkUrlString = createBookmarkUrlEditText.getText().toString(); - // Convert the favoriteIcon Bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). + // Create a favorite icon byte array output stream. ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream(); - MainWebViewActivity.favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream); + + // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). + favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream); + + // Convert the favorite icon byte array stream to a byte array. byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray(); // Display the new bookmark below the current items in the (0 indexed) list. @@ -724,8 +776,8 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma // Create the bookmark. bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray); - // Update `bookmarksCursor` with the current contents of this folder. - bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder); + // Update the bookmarks cursor with the current contents of this folder. + bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder); // Update the `ListView`. bookmarksCursorAdapter.changeCursor(bookmarksCursor); @@ -735,29 +787,46 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma } @Override - public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) { - // Get handles for the views in `dialogFragment`. - EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext); - RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton); - ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon); + public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) { + // Get the dialog from the dialog fragment. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + + // Get handles for the views in the dialog fragment. + EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext); + RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton); + ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview); // Get new folder name string. - String folderNameString = createFolderNameEditText.getText().toString(); + String folderNameString = folderNameEditText.getText().toString(); - // Get the new folder icon `Bitmap`. + // Create a folder icon bitmap. Bitmap folderIconBitmap; - if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon. - // Get the default folder icon and convert it to a `Bitmap`. - Drawable folderIconDrawable = folderIconImageView.getDrawable(); + + // Set the folder icon bitmap according to the dialog. + if (defaultIconRadioButton.isChecked()) { // Use the default folder icon. + // Get the default folder icon drawable. + Drawable folderIconDrawable = defaultIconImageView.getDrawable(); + + // Convert the folder icon drawable to a bitmap drawable. BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; + + // Convert the folder icon bitmap drawable to a bitmap. folderIconBitmap = folderIconBitmapDrawable.getBitmap(); - } else { // Use the `WebView` favorite icon. - folderIconBitmap = MainWebViewActivity.favoriteIconBitmap; + } else { // Use the WebView favorite icon. + // Copy the favorite icon bitmap to the folder icon bitmap. + folderIconBitmap = favoriteIconBitmap; } - // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). + // Create a folder icon byte array output stream. ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream(); + + // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream); + + // Convert the folder icon byte array stream to a byte array. byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); // Move all the bookmarks down one in the display order. @@ -769,10 +838,10 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma // Create the folder, which will be placed at the top of the `ListView`. bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray); - // Update `bookmarksCursor` with the current contents of this folder. - bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder); + // Update the bookmarks cursor with the current contents of this folder. + bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder); - // Update the `ListView`. + // Update the list view. bookmarksCursorAdapter.changeCursor(bookmarksCursor); // Scroll to the new folder. @@ -780,46 +849,65 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma } @Override - public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) { - // Get handles for the views from `dialogFragment`. - EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext); - EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext); - RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton); + public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, @NonNull Bitmap favoriteIconBitmap) { + // Get the dialog from the dialog fragment. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + + // Get handles for the views from the dialog fragment. + EditText bookmarkNameEditText = dialog.findViewById(R.id.bookmark_name_edittext); + EditText bookmarkUrlEditText = dialog.findViewById(R.id.bookmark_url_edittext); + RadioButton currentIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton); // Store the bookmark strings. - String bookmarkNameString = editBookmarkNameEditText.getText().toString(); - String bookmarkUrlString = editBookmarkUrlEditText.getText().toString(); + String bookmarkNameString = bookmarkNameEditText.getText().toString(); + String bookmarkUrlString = bookmarkUrlEditText.getText().toString(); // Update the bookmark. - if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon. + if (currentIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon. bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString); - } else { // Update the bookmark using the `WebView` favorite icon. - // Convert the favorite icon to a byte array. `0` is for lossless compression (the only option for a PNG). + } else { // Update the bookmark using the WebView favorite icon. + // Create a favorite icon byte array output stream. ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream(); - MainWebViewActivity.favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream); + + // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). + favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream); + + // Convert the favorite icon byte array stream to a byte array. byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray(); // Update the bookmark and the favorite icon. bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray); } - // Close the contextual action mode. - contextualActionMode.finish(); + // Check to see if the contextual action mode has been created. + if (contextualActionMode != null) { + // Close the contextual action mode if it is open. + contextualActionMode.finish(); + } - // Update `bookmarksCursor` with the contents of the current folder. - bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder); + // Update the bookmarks cursor with the contents of the current folder. + bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder); // Update the `ListView`. bookmarksCursorAdapter.changeCursor(bookmarksCursor); } @Override - public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) { - // Get handles for the views from `dialogFragment`. - RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton); - RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton); - ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview); - EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext); + public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) { + // Get the dialog from the dialog fragment. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + + // Get handles for the views from the dialog fragment. + RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton); + RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton); + ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview); + EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext); // Get the new folder name. String newFolderNameString = editFolderNameEditText.getText().toString(); @@ -829,49 +917,69 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma // Update the name in the database. bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString); } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed. - // Get the new folder icon `Bitmap`. + // Create the new folder icon Bitmap. Bitmap folderIconBitmap; + + // Populate the new folder icon bitmap. if (defaultFolderIconRadioButton.isChecked()) { - // Get the default folder icon and convert it to a `Bitmap`. + // Get the default folder icon drawable. Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable(); + + // Convert the folder icon drawable to a bitmap drawable. BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; + + // Convert the folder icon bitmap drawable to a bitmap. folderIconBitmap = folderIconBitmapDrawable.getBitmap(); - } else { // Use the `WebView` favorite icon. - folderIconBitmap = MainWebViewActivity.favoriteIconBitmap; + } else { // Use the WebView favorite icon. + // Copy the favorite icon bitmap to the folder icon bitmap. + folderIconBitmap = favoriteIconBitmap; } - // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). - ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream(); - folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream); - byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); + // Create a folder icon byte array output stream. + ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream(); + + // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). + folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream); + + // Convert the folder icon byte array stream to a byte array. + byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray(); // Update the folder icon in the database. - bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray); + bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray); } else { // The folder icon and the name have changed. // Instantiate the new folder icon `Bitmap`. Bitmap folderIconBitmap; // Populate the new folder icon bitmap. if (defaultFolderIconRadioButton.isChecked()) { - // Get the default folder icon and convert it to a `Bitmap`. + // Get the default folder icon drawable. Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable(); + + // Convert the folder icon drawable to a bitmap drawable. BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; + + // Convert the folder icon bitmap drawable to a bitmap. folderIconBitmap = folderIconBitmapDrawable.getBitmap(); - } else { // Use the `WebView` favorite icon. - folderIconBitmap = MainWebViewActivity.favoriteIconBitmap; + } else { // Use the WebView favorite icon. + // Copy the favorite icon bitmap to the folder icon bitmap. + folderIconBitmap = favoriteIconBitmap; } - // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). - ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream(); - folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream); - byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); + // Create a folder icon byte array output stream. + ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream(); + + // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). + folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream); + + // Convert the folder icon byte array stream to a byte array. + byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray(); // Update the folder name and icon in the database. - bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray); + bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray); } - // Update `bookmarksCursor` with the current contents of this folder. - bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder); + // Update the bookmarks cursor with the current contents of this folder. + bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder); // Update the `ListView`. bookmarksCursorAdapter.changeCursor(bookmarksCursor); @@ -881,9 +989,15 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma } @Override - public void onMoveToFolder(AppCompatDialogFragment dialogFragment) { - // Get a handle for the `ListView` from `dialogFragment`. - ListView folderListView = dialogFragment.getDialog().findViewById(R.id.move_to_folder_listview); + public void onMoveToFolder(DialogFragment dialogFragment) { + // Get the dialog from the dialog fragment. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + + // Get a handle for the list view from the dialog. + ListView folderListView = dialog.findViewById(R.id.move_to_folder_listview); // Store a long array of the selected folders. long[] newFolderLongArray = folderListView.getCheckedItemIds(); @@ -915,8 +1029,8 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName); } - // Update `bookmarksCursor` with the current contents of this folder. - bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder); + // Update the bookmarks cursor with the current contents of this folder. + bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder); // Update the `ListView`. bookmarksCursorAdapter.changeCursor(bookmarksCursor); @@ -925,20 +1039,52 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma contextualActionMode.finish(); } + private int countBookmarkFolderContents(int databaseId) { + // Initialize the bookmark counter. + int bookmarkCounter = 0; + + // Get the name of the folder. + String folderName = bookmarksDatabaseHelper.getFolderName(databaseId); + + // Get the contents of the folder. + Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName); + + // Count each of the bookmarks in the folder. + for (int i = 0; i < folderCursor.getCount(); i++) { + // Move the folder cursor to the current row. + folderCursor.moveToPosition(i); + + // Get the database ID of the item. + int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper._ID)); + + // If this is a folder, recursively count the contents first. + if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) { + // Add the bookmarks from the folder to the running total. + bookmarkCounter = bookmarkCounter + countBookmarkFolderContents(itemDatabaseId); + } + + // Add the bookmark to the running total. + bookmarkCounter++; + } + + // Return the bookmark counter. + return bookmarkCounter; + } + private void deleteBookmarkFolderContents(int databaseId) { // Get the name of the folder. String folderName = bookmarksDatabaseHelper.getFolderName(databaseId); // Get the contents of the folder. - Cursor folderCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(folderName); + Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName); // Delete each of the bookmarks in the folder. for (int i = 0; i < folderCursor.getCount(); i++) { - // Move `folderCursor` to the current row. + // Move the folder cursor to the current row. folderCursor.moveToPosition(i); // Get the database ID of the item. - int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHelper._ID)); + int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper._ID)); // If this is a folder, recursively delete the contents first. if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) { @@ -960,42 +1106,34 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma // bookmarksListView is 0 indexed. int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1); - // Update the move bookmark up `MenuItem`. + // Update the move bookmark up menu item. if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position. - // Disable the move bookmark up `MenuItem`. + // Disable the move bookmark up menu item. moveBookmarkUpMenuItem.setEnabled(false); - // Set the move bookmark up icon to be ghosted. + // Set the icon. moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled); } else { // The selected bookmark is not in the first position. - // Enable the move bookmark up `MenuItem`. + // Enable the move bookmark up menu item. moveBookmarkUpMenuItem.setEnabled(true); // Set the icon according to the theme. - if (MainWebViewActivity.darkTheme) { - moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_dark); - } else { - moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_light); - } + moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled); } - // Update the move bookmark down `MenuItem`. + // Update the move bookmark down menu item. if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position. - // Disable the move bookmark down `MenuItem`. + // Disable the move bookmark down menu item. moveBookmarkDownMenuItem.setEnabled(false); - // Set the move bookmark down icon to be ghosted. + // Set the icon. moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled); } else { // The selected bookmark is not in the last position. - // Enable the move bookmark down `MenuItem`. + // Enable the move bookmark down menu item. moveBookmarkDownMenuItem.setEnabled(true); - // Set the icon according to the theme. - if (MainWebViewActivity.darkTheme) { - moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_dark); - } else { - moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_light); - } + // Set the icon. + moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled); } } @@ -1011,15 +1149,16 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen. // Scroll to the selected bookmark position. bookmarksListView.setSelection(selectedBookmarkPosition); - } else if (selectedBookmarkPosition >= (lastVisibleBookmarkPosition - 1)) { // The selected bookmark is at or below the bottom of the screen. The `-1` handles partial bookmarks displayed at the bottom of the `ListView`. - // Scroll to display the selected bookmark at the bottom of the screen. `+1` assured that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the `ListView`. + } else if (selectedBookmarkPosition >= (lastVisibleBookmarkPosition - 1)) { // The selected bookmark is at or below the bottom of the screen. + // The `-1` handles partial bookmarks displayed at the bottom of the list view. This command scrolls to display the selected bookmark at the bottom of the screen. + // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view. bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1); } } private void loadFolder() { - // Update `bookmarksCursor` with the contents of the bookmarks database for the current folder. - bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder); + // Update bookmarks cursor with the contents of the bookmarks database for the current folder. + bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder); // Setup a `CursorAdapter`. `this` specifies the `Context`. `false` disables `autoRequery`. bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) { @@ -1036,7 +1175,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name); // Get the favorite icon byte array from the `Cursor`. - byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); + byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON)); // 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); @@ -1045,11 +1184,11 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap); // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`. - String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + String bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)); bookmarkNameTextView.setText(bookmarkNameString); // Make the font bold for folders. - if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { + if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD); } else { // Reset the font to default for normal bookmarks. bookmarkNameTextView.setTypeface(Typeface.DEFAULT); @@ -1057,7 +1196,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma } }; - // Populate the `ListView` with the adapter. + // Populate the list view with the adapter. bookmarksListView.setAdapter(bookmarksCursorAdapter); // Set the `AppBar` title.