com.akhbulatov.wordkeeper.ui.fragment.CategoryListFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.akhbulatov.wordkeeper.ui.fragment.CategoryListFragment.java

Source

/*
 * Copyright 2018 Alidibir Akhbulatov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.akhbulatov.wordkeeper.ui.fragment;

import android.app.Activity;
import android.app.Dialog;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.SearchView;
import android.text.Html;
import android.text.TextUtils;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;

import com.akhbulatov.wordkeeper.R;
import com.akhbulatov.wordkeeper.adapter.CategoryAdapter;
import com.akhbulatov.wordkeeper.adapter.WordAdapter;
import com.akhbulatov.wordkeeper.database.CategoryDatabaseAdapter;
import com.akhbulatov.wordkeeper.database.DatabaseContract.CategoryEntry;
import com.akhbulatov.wordkeeper.database.DatabaseContract.WordEntry;
import com.akhbulatov.wordkeeper.database.WordDatabaseAdapter;
import com.akhbulatov.wordkeeper.model.Category;
import com.akhbulatov.wordkeeper.model.Word;
import com.akhbulatov.wordkeeper.ui.activity.CategoryContentActivity;
import com.akhbulatov.wordkeeper.ui.activity.MainActivity;
import com.akhbulatov.wordkeeper.ui.dialog.CategoryDeleteDialog;
import com.akhbulatov.wordkeeper.ui.dialog.CategoryEditorDialog;
import com.akhbulatov.wordkeeper.ui.listener.FabAddWordListener;
import com.akhbulatov.wordkeeper.ui.widget.ContextMenuRecyclerView;
import com.akhbulatov.wordkeeper.util.CommonUtils;
import com.akhbulatov.wordkeeper.util.FilterCursorWrapper;

/**
 * Shows a list of categories from the database.
 * Loader uses a custom class for working with the database,
 * NOT the ContentProvider (temporary solution)
 */
public class CategoryListFragment extends BaseFragment
        implements LoaderManager.LoaderCallbacks<Cursor>, CategoryAdapter.CategoryItemClickListener,
        CategoryEditorDialog.CategoryEditorDialogListener, CategoryDeleteDialog.CategoryDeleteListener {

    private static final int LOADER_ID = 1;

    private static final int CATEGORY_EDITOR_DIALOG_REQUEST = 1;
    private static final int CATEGORY_DELETE_DIALOG_REQUEST = 2;

    // Contains the ID of the current selected item (category)
    private long mSelectedItemId;

    private ContextMenuRecyclerView mCategoryList;
    private TextView mTextNoResultsCategory;

    private CategoryAdapter mCategoryAdapter;
    private CategoryDatabaseAdapter mCategoryDbAdapter;
    private WordDatabaseAdapter mWordDbAdapter;

    private FabAddWordListener mListener;

    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (FabAddWordListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(
                    activity.toString() + " must implement " + FabAddWordListener.class.getName());
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);

        mCategoryDbAdapter = new CategoryDatabaseAdapter(getActivity());
        mCategoryDbAdapter.open();

        mWordDbAdapter = new WordDatabaseAdapter(getActivity());
        mWordDbAdapter.open();
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_category_list, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mCategoryList = view.findViewById(R.id.recycler_category_list);
        mCategoryList.setHasFixedSize(true);
        mCategoryList.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
        mCategoryList.setLayoutManager(new LinearLayoutManager(getActivity()));
        registerForContextMenu(mCategoryList);

        mTextNoResultsCategory = view.findViewById(R.id.text_no_results_category);
        mTextNoResultsCategory.setVisibility(View.GONE);

        FloatingActionButton fabAddWord = view.findViewById(R.id.fab_add_word);
        fabAddWord.setOnClickListener(view1 -> mListener.onFabAddWordClick(R.string.title_new_word,
                R.string.word_editor_action_add, android.R.string.cancel));
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getLoaderManager().initLoader(LOADER_ID, null, this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mCategoryDbAdapter.close();
        mWordDbAdapter.close();
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.fragment_category, menu);

        MenuItem searchItem = menu.findItem(R.id.menu_search_category);
        SearchView searchView = (SearchView) searchItem.getActionView();

        SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
        searchView.setSearchableInfo(
                searchManager.getSearchableInfo(new ComponentName(getActivity(), MainActivity.class)));
        searchView.setImeOptions(EditorInfo.IME_ACTION_SEARCH);
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextChange(String newText) {
                final Cursor cursor = mCategoryDbAdapter.getAll();
                final int column = cursor.getColumnIndex(CategoryEntry.COLUMN_NAME);
                if (newText.length() > 0) {
                    mCategoryAdapter.swapCursor(new FilterCursorWrapper(cursor, newText, column));

                    if (mCategoryAdapter.getItemCount() == 0) {
                        String escapedNewText = TextUtils.htmlEncode(newText);
                        String formattedNoResults = String.format(getString(R.string.no_results_category),
                                escapedNewText);
                        CharSequence styledNoResults = Html.fromHtml(formattedNoResults);

                        mTextNoResultsCategory.setText(styledNoResults);
                        mTextNoResultsCategory.setVisibility(View.VISIBLE);
                    } else {
                        mTextNoResultsCategory.setVisibility(View.GONE);
                    }
                } else {
                    mCategoryAdapter.swapCursor(cursor);
                    mTextNoResultsCategory.setVisibility(View.GONE);
                }
                return true;
            }

            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }
        });
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_add_category:
            showCategoryEditorDialog(R.string.title_new_category, R.string.category_editor_action_add,
                    android.R.string.cancel);
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        getActivity().getMenuInflater().inflate(R.menu.selected_category, menu);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        ContextMenuRecyclerView.RecyclerContextMenuInfo info = (ContextMenuRecyclerView.RecyclerContextMenuInfo) item
                .getMenuInfo();
        switch (item.getItemId()) {
        case R.id.menu_rename_category:
            mSelectedItemId = info.id;
            showCategoryEditorDialog(R.string.title_rename_category, R.string.category_editor_action_rename,
                    android.R.string.cancel);
            return true;
        case R.id.menu_delete_category:
            mSelectedItemId = info.id;
            showCategoryDeleteDialog();
            return true;
        default:
            return super.onContextItemSelected(item);
        }
    }

    @NonNull
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // Returns the cursor with all records from the database.
        // Uses own class instead of a ContentProvider
        return new SimpleCursorLoader(getActivity(), mCategoryDbAdapter);
    }

    @Override
    public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
        if (mCategoryAdapter == null) {
            // The adapter is created only the first time retrieving data from the database
            mCategoryAdapter = new CategoryAdapter(data, mWordDbAdapter);
            mCategoryAdapter.setHasStableIds(true);
            mCategoryAdapter.setOnItemClickListener(this);
            mCategoryList.setAdapter(mCategoryAdapter);
        } else {
            mCategoryAdapter.swapCursor(data);
        }
    }

    @Override
    public void onLoaderReset(@NonNull Loader<Cursor> loader) {
        if (mCategoryAdapter != null) {
            mCategoryAdapter.swapCursor(null);
        }
    }

    @Override
    public void onCategoryItemClick(String categoryName) {
        startActivity(CategoryContentActivity.newIntent(getActivity(), categoryName));
    }

    // Passes the ID of the text on the positive button
    // to determine which the dialog (category) button was pressed: add or edit
    @Override
    public void onFinishCategoryEditorDialog(DialogFragment dialog, int positiveTextId) {
        // Add the category
        if (positiveTextId == R.string.category_editor_action_add) {
            addCategory(dialog);
        } else {
            // Edit the category
            Dialog dialogView = dialog.getDialog();
            EditText editName = dialogView.findViewById(R.id.edit_category_name);
            String name = editName.getText().toString();

            renameCategory(name);
        }
    }

    // Confirms delete the category.
    // Also removed all words that are in the category
    @Override
    public void onFinishCategoryDeleteDialog(DialogFragment dialog) {
        deleteCategory();
    }

    public void updateCategoryList() {
        getLoaderManager().restartLoader(LOADER_ID, null, this);
    }

    private void addCategory(DialogFragment dialog) {
        Dialog dialogView = dialog.getDialog();

        EditText editName = dialogView.findViewById(R.id.edit_category_name);
        String name = editName.getText().toString();

        if (TextUtils.isEmpty(name)) {
            CommonUtils.showToast(getActivity(), R.string.error_category_editor_empty_field);
        } else {
            mCategoryDbAdapter.insert(new Category(name));
            mCategoryList.scrollToPosition(0);
            getLoaderManager().restartLoader(LOADER_ID, null, this);
        }
    }

    private void renameCategory(String name) {
        if (TextUtils.isEmpty(name)) {
            CommonUtils.showToast(getActivity(), R.string.error_category_editor_empty_field);
        } else {
            // First updates all words from the category with the new category name
            Cursor cursor = mWordDbAdapter.getRecordsByCategory(getName());
            while (!cursor.isAfterLast()) {
                long id = cursor.getLong(cursor.getColumnIndex(WordEntry._ID));
                String wordName = cursor.getString(cursor.getColumnIndex(WordEntry.COLUMN_NAME));
                String wordTranslation = cursor.getString(cursor.getColumnIndex(WordEntry.COLUMN_TRANSLATION));

                mWordDbAdapter.update(new Word(id, wordName, wordTranslation, name));
                cursor.moveToNext();
            }

            mCategoryDbAdapter.update(new Category(mSelectedItemId, name));
            getLoaderManager().restartLoader(LOADER_ID, null, this);
        }
    }

    private void deleteCategory() {
        // First, deletes all words that are in the deleted category
        Cursor cursor = mWordDbAdapter.getRecordsByCategory(getName());
        WordAdapter wordAdapter = new WordAdapter(cursor);
        while (!cursor.isAfterLast()) {
            long id = wordAdapter.getItemId(cursor.getPosition());
            mWordDbAdapter.delete(new Word(id));
            cursor.moveToNext();
        }

        mCategoryDbAdapter.delete(new Category(mSelectedItemId));
        getLoaderManager().restartLoader(LOADER_ID, null, this);
    }

    @Nullable
    private String getName() {
        return mCategoryDbAdapter.get(mSelectedItemId).getName();
    }

    private void showCategoryEditorDialog(int titleId, int positiveTextId, int negativeTextId) {
        DialogFragment dialog = CategoryEditorDialog.newInstance(titleId, positiveTextId, negativeTextId);
        dialog.setTargetFragment(CategoryListFragment.this, CATEGORY_EDITOR_DIALOG_REQUEST);
        dialog.show(getActivity().getSupportFragmentManager(), null);

        // Receives and shows data of the selected category to edit in the dialog
        // Data is the name of the category
        if (positiveTextId == R.string.category_editor_action_rename) {
            // NOTE! If the method is not called, the app crashes
            getActivity().getSupportFragmentManager().executePendingTransactions();

            Dialog dialogView = dialog.getDialog();
            EditText editName = dialogView.findViewById(R.id.edit_category_name);
            editName.setText(getName());
        }
    }

    private void showCategoryDeleteDialog() {
        DialogFragment dialog = new CategoryDeleteDialog();
        dialog.setTargetFragment(CategoryListFragment.this, CATEGORY_DELETE_DIALOG_REQUEST);
        dialog.show(getActivity().getSupportFragmentManager(), null);
    }

    /**
     * Used to work with a Loader instead of a ContentProvider
     */
    private static class SimpleCursorLoader extends CursorLoader {

        private CategoryDatabaseAdapter mCategoryDbAdapter;

        SimpleCursorLoader(Context context, CategoryDatabaseAdapter categoryDbAdapter) {
            super(context);
            mCategoryDbAdapter = categoryDbAdapter;
        }

        @Override
        public Cursor loadInBackground() {
            return mCategoryDbAdapter.getAll();
        }
    }
}