Java tutorial
// 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); } } }