org.akop.crosswords.fragment.CrosswordFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.akop.crosswords.fragment.CrosswordFragment.java

Source

// Copyright (c) 2014-2015 Akop Karapetyan
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package org.akop.crosswords.fragment;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.widget.DrawerLayout;
import android.util.SparseIntArray;
import android.view.Gravity;
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.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;

import org.akop.crosswords.R;
import org.akop.crosswords.Storage;
import org.akop.crosswords.activity.CrosswordActivity;
import org.akop.crosswords.view.CrosswordView;
import org.akop.crosswords.widget.StickyHeaderHelper;
import org.akop.xross.object.Crossword;
import org.joda.time.DateTime;

import java.util.ArrayList;
import java.util.List;

public class CrosswordFragment extends BaseFragment implements CrosswordView.OnSelectionChangeListener,
        CrosswordView.OnLongPressListener, AdapterView.OnItemClickListener {
    public interface OnUpdateSelectedWordListener {
        void updateSelectedWord(Crossword.Word word, Crossword crossword);
    }

    public static Bundle createArgs(long crosswordId, Crossword crossword) {
        Bundle bundle = new Bundle();
        bundle.putLong("crosswordId", crosswordId);
        bundle.putParcelable("crossword", crossword);
        return bundle;
    }

    private long mCrosswordId;
    private Crossword mCrossword;
    private Crossword.Word mSelectedWord;
    private Crossword.State mLastState;
    private HintAdapter mHintAdapter;
    private StickyHeaderHelper mStickyHelper;
    private long mPlayStartTime;

    private Crossword.Word mPopupWord;
    private int mPopupCell;

    private View mMenuPlaceholder;
    private ListView mHintList;
    private CrosswordView mCrosswordView;
    private View mStickyHeader;
    private DrawerLayout mDrawerLayout;

    private DrawerLayout.DrawerListener mDrawerListener = new DrawerLayout.DrawerListener() {
        @Override
        public void onDrawerSlide(View drawerView, float slideOffset) {
        }

        @Override
        public void onDrawerOpened(View drawerView) {
            Activity activity = getActivity();
            if (activity != null) {
                activity.invalidateOptionsMenu();
            }
        }

        @Override
        public void onDrawerClosed(View drawerView) {
            Activity activity = getActivity();
            if (activity != null) {
                activity.invalidateOptionsMenu();
            }
        }

        @Override
        public void onDrawerStateChanged(int newState) {
        }
    };

    private PopupMenu.OnMenuItemClickListener mCellPopupListener = new PopupMenu.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            switch (item.getItemId()) {
            case R.id.menu_complete_cell:
                solveCell();
                return true;
            case R.id.menu_complete_word:
                solveWord();
                return true;
            }
            return false;
        }
    };

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

        Bundle bundle = getArguments();
        if (bundle != null) {
            mCrossword = bundle.getParcelable("crossword");
            mCrosswordId = bundle.getLong("crosswordId", CrosswordActivity.CROSSWORD_ID_NONE);
        }

        if (savedInstanceState != null) {
            mSelectedWord = savedInstanceState.getParcelable("selection");
            mLastState = savedInstanceState.getParcelable("lastState");
        } else if (mCrosswordId != CrosswordActivity.CROSSWORD_ID_NONE) {
            Storage storage = Storage.getInstance();
            mLastState = storage.getPuzzleState(mCrosswordId);
        }

        mHintAdapter = new HintAdapter(getActivity());
        if (mCrossword != null) {
            mHintAdapter.loadHints(mCrossword);
        }

        mStickyHelper = new StickyHeaderHelper();

        setHasOptionsMenu(true);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putParcelable("selection", mSelectedWord);
        outState.putParcelable("lastState", mLastState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View layout = inflater.inflate(R.layout.fragment_crossword, container, false);

        mCrosswordView = (CrosswordView) layout.findViewById(R.id.puzzle);
        mHintList = (ListView) layout.findViewById(R.id.hints);
        mStickyHeader = layout.findViewById(R.id.sticky_header);
        mMenuPlaceholder = layout.findViewById(R.id.menu_placeholder);
        mDrawerLayout = (DrawerLayout) layout.findViewById(R.id.drawer_layout);

        if (mDrawerLayout != null) {
            mDrawerLayout.setDrawerListener(mDrawerListener);
        }

        mHintList.setOnItemClickListener(this);
        mHintList.setAdapter(mHintAdapter);

        mStickyHelper.setListView(mHintList);
        mStickyHelper.setStickyHeader(mStickyHeader);

        mCrosswordView.setOnSelectionChangeListener(this);
        mCrosswordView.setOnLongPressListener(this);

        mCrosswordView.setCrossword(mCrossword);
        if (mLastState != null) {
            mCrosswordView.restoreState(mLastState);
        }

        updateSelection();

        return layout;
    }

    @Override
    public void onPause() {
        super.onPause();

        updateState();

        // Save state to long-term storage
        if (mLastState != null && mCrosswordId != CrosswordActivity.CROSSWORD_ID_NONE) {
            Storage.getInstance().write(mCrosswordId, mLastState);
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        mPlayStartTime = SystemClock.uptimeMillis();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();

        mStickyHelper.release();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);

        inflater.inflate(R.menu.fragment_crossword, menu);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        boolean showRevealErrors = false;
        boolean showConcealErrors = false;

        if (mCrosswordView != null) {
            showConcealErrors = mCrosswordView.getErrorHighlightingEnabled();
            showRevealErrors = !showConcealErrors;
        }

        menu.setGroupVisible(R.id.menu_group_reveal_errors, showRevealErrors);
        menu.setGroupVisible(R.id.menu_group_conceal_errors, showConcealErrors);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_previous_word:
            mCrosswordView.selectPreviousWord();
            return true;
        case R.id.menu_next_word:
            mCrosswordView.selectNextWord();
            return true;
        case R.id.menu_reveal_errors:
            mCrosswordView.setErrorHighlightingEnabled(true);
            getActivity().invalidateOptionsMenu();
            return true;
        case R.id.menu_conceal_errors:
            mCrosswordView.setErrorHighlightingEnabled(false);
            getActivity().invalidateOptionsMenu();
            return true;
        case R.id.menu_complete_puzzle:
            solvePuzzle();
            return true;
        case R.id.menu_reset_puzzle:
            resetPuzzle();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onWordDeselected(CrosswordView view) {
        mSelectedWord = null;
        if (mHintList != null) {
            // Clear selection
            mHintList.setItemChecked(-1, true);
        }

        updateSelection();
    }

    @Override
    public void onWordSelectionChanged(CrosswordView view, Crossword.Word word) {
        mSelectedWord = word;
        if (mHintList != null && mHintAdapter != null) {
            // Figure out the position of the word in the list
            int id = -1;
            if (word.getDirection() == Crossword.Word.DIR_ACROSS) {
                id = WordItem.MASK_ACROSS | word.getNumber();
            } else if (word.getDirection() == Crossword.Word.DIR_DOWN) {
                id = WordItem.MASK_DOWN | word.getNumber();
            }

            // Set selected item
            int newlySelected = mHintAdapter.findPosition(id);
            int currentlySelected = mHintList.getCheckedItemPosition();

            if (newlySelected != currentlySelected) {
                mHintList.setItemChecked(newlySelected, true);
                if (newlySelected != -1) {
                    int offset = 0;
                    if (mStickyHeader != null) {
                        offset = mStickyHeader.getHeight();
                    }
                    mHintList.smoothScrollToPositionFromTop(newlySelected, offset);
                }
            }
        }

        updateSelection();
    }

    @Override
    public void onCellLongPressed(CrosswordView view, Crossword.Word word, int cell) {
        // Get the highlighted cell's rectangle
        Rect rect = view.getCellRect(word, cell);

        // Offset the placeholder to the same position as the cell
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(rect.width(), rect.height());
        lp.setMargins(rect.left, rect.top, 0, 0);
        mMenuPlaceholder.setLayoutParams(lp);

        // Save the word/cell associated with the popup
        mPopupWord = word;
        mPopupCell = cell;

        // Initialize and show the popup menu
        PopupMenu popup = new PopupMenu(getActivity(), mMenuPlaceholder);
        popup.setOnMenuItemClickListener(mCellPopupListener);

        MenuInflater inflater = popup.getMenuInflater();
        inflater.inflate(R.menu.fragment_crossword_popup_cell, popup.getMenu());

        popup.show();
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Crossword.Word item = mHintAdapter.findWord((int) id);
        if (item != null) {
            mCrosswordView.selectWord(item);
            if (mDrawerLayout != null) {
                mDrawerLayout.closeDrawer(Gravity.END);
            }
        }
    }

    private void updateState() {
        if (mCrosswordView == null) {
            return;
        }

        Crossword.State state = mCrosswordView.getState();
        state.setLastPlayed(DateTime.now());

        if (mPlayStartTime != 0) {
            long totalPlayTimeMillis = SystemClock.uptimeMillis() - mPlayStartTime;
            if (mLastState != null) {
                totalPlayTimeMillis += mLastState.getPlayTimeMillis();
            }

            state.setPlayTimeMillis(totalPlayTimeMillis);
            mPlayStartTime = 0;
        }

        mLastState = state;
    }

    private void updateSelection() {
        if (getActivity() instanceof OnUpdateSelectedWordListener) {
            OnUpdateSelectedWordListener listener = (OnUpdateSelectedWordListener) getActivity();

            listener.updateSelectedWord(mSelectedWord, mCrossword);
        }
    }

    private void solveCell() {
        if (mPopupWord != null) {
            mCrosswordView.solveChar(mPopupWord, mPopupCell);
        }
    }

    private void solveWord() {
        if (mPopupWord != null) {
            mCrosswordView.solveWord(mPopupWord);
        }
    }

    private void resetPuzzle() {
        AlertDialog dialog = new AlertDialog.Builder(getActivity()).setMessage(R.string.clear_puzzle_contents)
                .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        mPlayStartTime = SystemClock.uptimeMillis();
                        mLastState = null;
                        mCrosswordView.reset();
                    }
                }).setNegativeButton(R.string.no, null).create();

        dialog.show();
    }

    private void solvePuzzle() {
        AlertDialog dialog = new AlertDialog.Builder(getActivity()).setMessage(R.string.solve_the_puzzle)
                .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        mCrosswordView.solveCrossword();
                    }
                }).setNegativeButton(R.string.no, null).create();

        dialog.show();
    }

    private static class WordItem implements HintAdapter.AdapterItem {
        public static final int MASK_ACROSS = 0x10000;
        public static final int MASK_DOWN = 0x20000;

        private static class ViewHolder {
            TextView mWordLocation;
            TextView mWordHint;
        }

        private Crossword.Word mWord;
        private int mId;
        private String mHintText;
        private String mLocationText;

        public WordItem(Crossword.Word word) {
            mWord = word;

            if (word != null) {
                if (word.getDirection() == Crossword.Word.DIR_ACROSS) {
                    mId = MASK_ACROSS | word.getNumber();
                } else if (word.getDirection() == Crossword.Word.DIR_DOWN) {
                    mId = MASK_DOWN | word.getNumber();
                }

                mLocationText = word.getNumber() + "";
                mHintText = word.getHint();
            }
        }

        @Override
        public View getView(LayoutInflater inflater, int position, View convertView, ViewGroup parent) {
            ViewHolder vh;
            if (convertView == null) {
                convertView = inflater.inflate(R.layout.template_crossword_hint_item, parent, false);

                vh = new ViewHolder();
                vh.mWordLocation = (TextView) convertView.findViewById(R.id.word_location);
                vh.mWordHint = (TextView) convertView.findViewById(R.id.word_hint);
                convertView.setTag(vh);
            } else {
                vh = (ViewHolder) convertView.getTag();
            }

            vh.mWordLocation.setText(mLocationText);
            vh.mWordHint.setText(mHintText);

            return convertView;
        }

        @Override
        public int getItemViewType() {
            return 0;
        }

        @Override
        public long getId() {
            return mId;
        }

        @Override
        public boolean isEnabled() {
            return true;
        }
    }

    private static class HeaderItem implements HintAdapter.AdapterItem, StickyHeaderHelper.StickableHeader {
        private String mTitle;
        private int mId;

        public HeaderItem(String title, int id) {
            mTitle = title;
            mId = id;
        }

        @Override
        public View getView(LayoutInflater inflater, int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = inflater.inflate(R.layout.template_crossword_hint_header, parent, false);
            }

            updateStickyHeaderView(convertView);

            return convertView;
        }

        @Override
        public int getItemViewType() {
            return 1;
        }

        @Override
        public long getId() {
            return mId;
        }

        @Override
        public boolean isEnabled() {
            return false;
        }

        @Override
        public void updateStickyHeaderView(View view) {
            TextView title = (TextView) view;
            title.setText(mTitle);
        }
    }

    private static class HintAdapter extends BaseAdapter {
        private interface AdapterItem {
            long getId();

            int getItemViewType();

            View getView(LayoutInflater inflater, int position, View convertView, ViewGroup parent);

            boolean isEnabled();
        }

        private Context mContext;
        private List<AdapterItem> mItems;
        private SparseIntArray mItemMap;

        public HintAdapter(Context context) {
            mContext = context;
            mItems = new ArrayList<>();
            mItemMap = new SparseIntArray();
        }

        public int findPosition(int id) {
            return mItemMap.get(id, -1);
        }

        public Crossword.Word findWord(int id) {
            Crossword.Word word = null;

            int position = mItemMap.get(id, -1);
            if (position > -1) {
                word = ((WordItem) mItems.get(position)).mWord;
            }

            return word;
        }

        public void loadHints(Crossword crossword) {
            mItems.clear();
            mItemMap.clear();

            List<Crossword.Word> wordsAcross = crossword.getWordsAcross();
            if (wordsAcross.size() > 0) {
                mItems.add(new HeaderItem(mContext.getString(R.string.across), -1));
                for (Crossword.Word word : wordsAcross) {
                    WordItem item = new WordItem(word);
                    mItemMap.put((int) item.getId(), mItems.size());
                    mItems.add(item);
                }
            }

            List<Crossword.Word> wordsDown = crossword.getWordsDown();
            if (wordsDown.size() > 0) {
                mItems.add(new HeaderItem(mContext.getString(R.string.down), -2));
                for (Crossword.Word word : wordsDown) {
                    WordItem item = new WordItem(word);
                    mItemMap.put((int) item.getId(), mItems.size());
                    mItems.add(item);
                }
            }

            notifyDataSetChanged();
        }

        @Override
        public int getViewTypeCount() {
            return 2;
        }

        @Override
        public int getItemViewType(int position) {
            return mItems.get(position).getItemViewType();
        }

        @Override
        public int getCount() {
            return mItems.size();
        }

        @Override
        public AdapterItem getItem(int position) {
            return mItems.get(position);
        }

        @Override
        public long getItemId(int position) {
            return mItems.get(position).getId();
        }

        @Override
        public boolean isEnabled(int position) {
            return mItems.get(position).isEnabled();
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            LayoutInflater inflater = LayoutInflater.from(mContext);
            return mItems.get(position).getView(inflater, position, convertView, parent);
        }
    }
}