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

Java tutorial

Introduction

Here is the source code for com.akhbulatov.wordkeeper.ui.fragment.WordListFragment.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.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.text.Html;
import android.text.TextUtils;
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.Spinner;
import android.widget.TextView;

import com.akhbulatov.wordkeeper.R;
import com.akhbulatov.wordkeeper.adapter.WordAdapter;
import com.akhbulatov.wordkeeper.database.CategoryDatabaseAdapter;
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.MainActivity;
import com.akhbulatov.wordkeeper.ui.dialog.CategoryListDialog;
import com.akhbulatov.wordkeeper.ui.dialog.WordSortDialog;
import com.akhbulatov.wordkeeper.ui.listener.FabAddWordListener;
import com.akhbulatov.wordkeeper.util.CommonUtils;
import com.akhbulatov.wordkeeper.util.FilterCursorWrapper;
import com.akhbulatov.wordkeeper.util.SharedPreferencesManager;

import java.util.List;

/**
 * Shows a list of words from the database.
 * Loader uses a custom class for working with the database,
 * NOT the ContentProvider (temporary solution)
 */
public class WordListFragment extends BaseFragment
        implements LoaderManager.LoaderCallbacks<Cursor>, WordAdapter.WordItemClickListener,
        WordSortDialog.WordSortDialogListener, CategoryListDialog.CategoryListDialogListener {

    private static final int LOADER_ID = 1;

    private static final int WORD_SORT_DIALOG_REQUEST = 1;
    private static final int CATEGORY_LIST_DIALOG_REQUEST = 2;

    private static int sSortMode;

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

    private RecyclerView mWordList;
    private TextView mTextEmptyWordList;
    private TextView mTextNoResultsWord;

    private WordAdapter mWordAdapter;
    private WordDatabaseAdapter mWordDbAdapter;

    private ActionModeCallback mActionModeCallback;
    private ActionMode mActionMode;

    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);

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

        sSortMode = SharedPreferencesManager.getSortMode(getActivity());

        mActionModeCallback = new ActionModeCallback();
    }

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

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

        mTextEmptyWordList = view.findViewById(R.id.text_empty_word_list);
        mTextEmptyWordList.setVisibility(View.GONE);

        mTextNoResultsWord = view.findViewById(R.id.text_no_results_word);
        mTextNoResultsWord.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();
        mWordDbAdapter.close();
    }

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

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

        MenuItem searchItem = menu.findItem(R.id.menu_search_word);
        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 onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                final Cursor cursor = mWordDbAdapter.getAll(sSortMode);
                final int column = cursor.getColumnIndex(WordEntry.COLUMN_NAME);
                if (newText.length() > 0) {
                    mWordAdapter.swapCursor(new FilterCursorWrapper(cursor, newText, column));

                    mTextEmptyWordList.setVisibility(View.GONE);
                    if (mWordAdapter.getItemCount() == 0) {
                        String escapedNewText = TextUtils.htmlEncode(newText);
                        String formattedNoResults = String.format(getString(R.string.no_results_word),
                                escapedNewText);
                        CharSequence styledNoResults = Html.fromHtml(formattedNoResults);

                        mTextNoResultsWord.setText(styledNoResults);
                        mTextNoResultsWord.setVisibility(View.VISIBLE);
                    } else {
                        mTextNoResultsWord.setVisibility(View.GONE);
                    }
                } else {
                    mWordAdapter.swapCursor(cursor);

                    mTextNoResultsWord.setVisibility(View.GONE);
                    if (mWordAdapter.getItemCount() == 0) {
                        mTextEmptyWordList.setVisibility(View.VISIBLE);
                    }
                }
                return true;
            }
        });
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_sort_word:
            showWordSortDialog();
            return true;
        default:
            return super.onOptionsItemSelected(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(), mWordDbAdapter);
    }

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

        if (mWordAdapter.getItemCount() == 0) {
            mTextEmptyWordList.setVisibility(View.VISIBLE);
        } else {
            mTextEmptyWordList.setVisibility(View.GONE);
        }
    }

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

    @Override
    public void onWordItemClick(int position) {
        if (mActionMode != null) {
            toggleSelection(position);
        }
    }

    @Override
    public boolean onWordItemLongClick(int position) {
        if (mActionMode == null) {
            mActionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(mActionModeCallback);
        }
        toggleSelection(position);
        return true;
    }

    @Override
    public void onFinishWordSortDialog(int sortMode) {
        // Saves to pass to the inner class SimpleCursorLoader
        sSortMode = sortMode;
        getLoaderManager().restartLoader(LOADER_ID, null, this);
    }

    // Updates the word list with the new sort mode
    @Override
    public void onFinishCategoryListDialog(String category) {
        Word word = null;
        for (Integer i : mWordAdapter.getSelectedWords()) {
            word = mWordDbAdapter.get(mWordAdapter.getItemId(i));
            mWordDbAdapter.update(new Word(word.getId(), word.getName(), word.getTranslation(), category));
        }

        mActionMode.finish();

        if (word != null) {
            CommonUtils.showToast(getActivity(), R.string.success_move_word);
        } else {
            CommonUtils.showToast(getActivity(), R.string.error_move_word);
        }
    }

    /**
     * Receives the entered data (word) and saves in the database
     *
     * @param dialog The dialog from where take the data (word) to save
     */
    public void addWord(DialogFragment dialog) {
        Dialog dialogView = dialog.getDialog();

        EditText editName = dialogView.findViewById(R.id.edit_word_name);
        EditText editTranslation = dialogView.findViewById(R.id.edit_word_translation);
        Spinner spinnerCategory = dialogView.findViewById(R.id.spinner_categories);

        String name = editName.getText().toString();
        String translation = editTranslation.getText().toString();
        String category = spinnerCategory.getSelectedItem().toString();

        if ((TextUtils.isEmpty(name) & TextUtils.isEmpty(translation))
                | (TextUtils.isEmpty(name) | TextUtils.isEmpty(translation))) {
            CommonUtils.showToast(getActivity(), R.string.error_word_editor_empty_fields);
        } else {
            mWordDbAdapter.insert(new Word(name, translation, category));
            // Checked for null in case this method is called from the screen "Categories"
            if (mWordList != null) {
                mWordList.scrollToPosition(0);
            }
            getLoaderManager().restartLoader(LOADER_ID, null, this);
        }
    }

    public void editWord(String name, String translation, String category) {
        if ((TextUtils.isEmpty(name) & TextUtils.isEmpty(translation))
                | (TextUtils.isEmpty(name) | TextUtils.isEmpty(translation))) {
            CommonUtils.showToast(getActivity(), R.string.error_word_editor_empty_fields);
        } else {
            mWordDbAdapter.update(new Word(mSelectedItemId, name, translation, category));
            getLoaderManager().restartLoader(LOADER_ID, null, this);
        }
    }

    public String getName() {
        return mWordDbAdapter.get(mSelectedItemId).getName();
    }

    public String getTranslation() {
        return mWordDbAdapter.get(mSelectedItemId).getTranslation();
    }

    public String getCategory() {
        return mWordDbAdapter.get(mSelectedItemId).getCategory();
    }

    public String[] getCategories() {
        CategoryDatabaseAdapter categoryDbAdapter = new CategoryDatabaseAdapter(getActivity());
        categoryDbAdapter.open();

        Cursor cursor = categoryDbAdapter.getAll();
        List<Category> categoryList = Category.getCategories(cursor);
        String[] categories = new String[categoryList.size()];
        for (int i = 0; i < categoryList.size(); i++) {
            categories[i] = categoryList.get(i).getName();
        }

        cursor.close();
        categoryDbAdapter.close();
        return categories;
    }

    private void toggleSelection(int position) {
        mWordAdapter.toggleSelection(position);
        int count = mWordAdapter.getSelectedWordCount();

        if (count == 0) {
            mActionMode.finish();
        } else {
            mActionMode.setTitle(String.valueOf(count));
            mActionMode.invalidate();
        }
    }

    private void deleteWords(List<Integer> words) {
        for (Integer i : words) {
            mWordDbAdapter.delete(new Word(mWordAdapter.getItemId(i)));
        }
        getLoaderManager().restartLoader(LOADER_ID, null, this);
    }

    /**
     * Gets the single item (word) ID from the list of words,
     * despite the collection that is passed in the parameter
     *
     * @param words The list of selected words
     * @return Returns the item (word) ID
     */
    private long getWordItemId(List<Integer> words) {
        long id = 0;
        for (Integer i : words) {
            id = mWordAdapter.getItemId(i);
        }
        return id;
    }

    private void showWordSortDialog() {
        DialogFragment dialog = new WordSortDialog();
        dialog.setTargetFragment(WordListFragment.this, WORD_SORT_DIALOG_REQUEST);
        dialog.show(getActivity().getSupportFragmentManager(), null);
    }

    private void showCategoryListDialog() {
        DialogFragment dialog = new CategoryListDialog();
        dialog.setTargetFragment(WordListFragment.this, CATEGORY_LIST_DIALOG_REQUEST);
        dialog.show(getActivity().getSupportFragmentManager(), null);
    }

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

        private WordDatabaseAdapter mWordDbAdapter;

        SimpleCursorLoader(Context context, WordDatabaseAdapter wordDbAdapter) {
            super(context);
            mWordDbAdapter = wordDbAdapter;
        }

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

    /**
     * Provides support for CAB
     */
    private class ActionModeCallback implements ActionMode.Callback {

        private MenuItem mItemEditWord;

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            mode.getMenuInflater().inflate(R.menu.selected_word, menu);
            mItemEditWord = menu.findItem(R.id.menu_edit_word);
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            // Editing is available only for one selected word
            if (mWordAdapter.getSelectedWordCount() == 1) {
                mItemEditWord.setVisible(true);
            } else {
                mItemEditWord.setVisible(false);
            }
            return true;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            switch (item.getItemId()) {
            case R.id.menu_move_word:
                showCategoryListDialog();
                return true;
            case R.id.menu_edit_word:
                // Saves the id to use to retrieve the selected row
                // and paste the edited string into the database.
                // Called for only one selected word
                mSelectedItemId = getWordItemId(mWordAdapter.getSelectedWords());

                mListener.onFabAddWordClick(R.string.title_edit_word, R.string.word_editor_action_edit,
                        android.R.string.cancel);
                mode.finish();
                return true;
            case R.id.menu_delete_word:
                deleteWords(mWordAdapter.getSelectedWords());
                mode.finish();
                return true;
            default:
                return false;
            }
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            mWordAdapter.clearSelection();
            mActionMode = null;
        }
    }
}