Java tutorial
/* * Copyright 2014 Benjamin Linskey * * 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.benlinskey.greekreference; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentTransaction; import android.app.SearchManager; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v4.view.MenuItemCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.SearchView; import android.text.Html; import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import com.benlinskey.greekreference.data.appdata.AppDataContract; import com.benlinskey.greekreference.data.appdata.LexiconHistoryProvider; import com.benlinskey.greekreference.data.lexicon.LexiconContract; import com.benlinskey.greekreference.data.lexicon.LexiconProvider; import com.benlinskey.greekreference.data.syntax.SyntaxContract; import com.benlinskey.greekreference.lexicon.LexiconBrowseListFragment; import com.benlinskey.greekreference.lexicon.LexiconDetailActivity; import com.benlinskey.greekreference.lexicon.LexiconDetailFragment; import com.benlinskey.greekreference.lexicon.LexiconFavoritesListFragment; import com.benlinskey.greekreference.lexicon.LexiconHistoryListFragment; import com.benlinskey.greekreference.lexicon.LexiconListFragment; import com.benlinskey.greekreference.navigationdrawer.NavigationDrawerFragment; import com.benlinskey.greekreference.syntax.SyntaxBookmarksListFragment; import com.benlinskey.greekreference.syntax.SyntaxBrowseListFragment; import com.benlinskey.greekreference.syntax.SyntaxDetailActivity; import com.benlinskey.greekreference.syntax.SyntaxDetailFragment; import com.benlinskey.greekreference.syntax.SyntaxListFragment; /** * The app's primary activity. On tablets, this activity displays a two-pane * layout containing a {@link BaseListFragment} and a {@link DetailFragment}. * On phones, it displays only a <code>BaseListFragment</code>. */ public class MainActivity extends ActionBarActivity implements NavigationDrawerFragment.NavigationDrawerCallbacks, BaseListFragment.Callbacks { private static final String TAG = "MainActivity"; // Application state bundle keys private static final String KEY_TITLE = "action_bar_title"; private static final String KEY_SUBTITLE = "action_bar_subtitle"; // Intent bundle key public static final String KEY_MODE = "mode"; // Custom intent action public static final String ACTION_SET_MODE = "com.benlinskey.greekreference.SET_MODE"; private NavigationDrawerFragment mNavigationDrawerFragment; private CharSequence mTitle; private CharSequence mSubtitle; private Mode mMode; private boolean mTwoPane; // Indicates whether we're actually using the tablet layout. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_item_list); PreferenceManager.setDefaultValues(this, R.xml.preferences, false); // Restore any saved state. if (null == savedInstanceState) { mTitle = getString(R.string.title_lexicon); mSubtitle = getString(R.string.title_lexicon_browse); } else { mTitle = savedInstanceState.getString(KEY_TITLE); mSubtitle = savedInstanceState.getString(KEY_SUBTITLE); mMode = Mode.getModeFromName(savedInstanceState.getString(KEY_MODE)); } mNavigationDrawerFragment = (NavigationDrawerFragment) getFragmentManager() .findFragmentById(R.id.navigation_drawer); // Set up the drawer. mNavigationDrawerFragment.setUp(R.id.navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout)); if (findViewById(R.id.item_detail_container) != null) { // The detail container view will be present only in the // large-screen layouts (res/values-large and // res/values-sw600dp).. mTwoPane = true; } checkTabletDisplayMode(); handleIntent(getIntent()); } @Override protected void onResume() { super.onResume(); checkTabletDisplayMode(); } /** * If the app is running on a large screen, sets the display mode to either one-pane or * two-pane depending on the setting stored in the app preferences and the currently selected * navigation drawer mode. This method does no work on phones, since one-pane mode is always * used on small screens. */ private void checkTabletDisplayMode() { if (!mTwoPane) { return; } View leftPane = findViewById(R.id.item_list_container); if (onePaneModeSelected() && mMode.equals(Mode.LEXICON_BROWSE)) { leftPane.setVisibility(View.GONE); } else { leftPane.setVisibility(View.VISIBLE); } } /** * Checks whether the user has selected the one-pane mode preference. This method always * returns false on phones. * @return true if the user has selected the one-pane mode preference or false otherwise */ private boolean onePaneModeSelected() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); String key = getString(R.string.pref_onePane_key); return prefs.getBoolean(key, false); } @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putString(KEY_TITLE, (String) mTitle); outState.putString(KEY_SUBTITLE, (String) mSubtitle); outState.putString(KEY_MODE, mMode.getName()); } @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mTitle = savedInstanceState.getString(KEY_TITLE); mSubtitle = savedInstanceState.getString(KEY_SUBTITLE); mMode = Mode.getModeFromName(savedInstanceState.getString(KEY_MODE)); restoreActionBar(); } @Override protected void onNewIntent(Intent intent) { handleIntent(intent); } /** * Processes an <code>Intent</code> if it can be handled by this {@code Activity} or * throws an exception if this {@code Activity} cannot handle the specified {@code Intent}. * @param intent the {@code Intent} to handle */ void handleIntent(Intent intent) { if (Intent.ACTION_SEARCH.equals(intent.getAction())) { String query = intent.getStringExtra((SearchManager.QUERY)); search(query); } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { Uri data = intent.getData(); getLexiconEntry(data); } else if (ACTION_SET_MODE.equals(intent.getAction())) { String modeName = intent.getStringExtra(KEY_MODE); Mode mode = Mode.getModeFromName(modeName); switchToMode(mode); } } /** * Callback method from {@link BaseListFragment.Callbacks} * indicating that the item with the given ID was selected. */ @Override public void onItemSelected(String fragmentName) { switch (fragmentName) { case LexiconBrowseListFragment.NAME: case LexiconFavoritesListFragment.NAME: case LexiconHistoryListFragment.NAME: lexiconItemSelected(); break; case SyntaxBrowseListFragment.NAME: case SyntaxBookmarksListFragment.NAME: syntaxItemSelected(); break; default: throw new IllegalArgumentException("Invalid fragment name"); } invalidateOptionsMenu(); } /** * Retrieves and displays the currently selected lexicon item's entry. */ private void lexiconItemSelected() { // TODO: Verify that we're in the correct mode here and in similar // situations through this class and throw an exception if we're not. LexiconListFragment fragment = (LexiconListFragment) getFragmentManager() .findFragmentById(R.id.item_list_container); String id = Integer.toString(fragment.getSelectedLexiconId()); String[] columns = new String[] { LexiconContract.COLUMN_ENTRY, LexiconContract.COLUMN_GREEK_FULL_WORD }; String selection = LexiconContract._ID + " = ?"; String[] selectionArgs = new String[] { id }; Cursor cursor = getContentResolver().query(LexiconContract.CONTENT_URI, columns, selection, selectionArgs, null); String entry; String word; if (cursor.moveToFirst()) { entry = cursor.getString(0); word = cursor.getString(1); } else { throw new IllegalStateException("Failed to retrieve lexicon entry"); } displayLexiconEntry(id, word, entry); } /** * Retrieves and displays the currently selected Overview of Greek Syntax * item's entry. */ private void syntaxItemSelected() { SyntaxListFragment fragment = (SyntaxListFragment) getFragmentManager() .findFragmentById(R.id.item_list_container); String id = Integer.toString(fragment.getSelectedSyntaxId()); String[] columns = new String[] { SyntaxContract.COLUMN_NAME_XML, SyntaxContract.COLUMN_NAME_SECTION }; String selection = SyntaxContract._ID + " = ?"; String[] selectionArgs = new String[] { id }; Cursor cursor = getContentResolver().query(SyntaxContract.CONTENT_URI, columns, selection, selectionArgs, null); String xml; String section; if (cursor.moveToFirst()) { xml = cursor.getString(0); section = cursor.getString(1); Log.w("Syntax item selected", section + ": " + xml); } else { throw new IllegalStateException("Failed to retrieve syntax section"); } displaySyntaxSection(section, xml); } /** * Displays the specified lexicon entry in a {@link LexiconDetailFragment}. * @param id the lexicon database ID of the selected entry * @param word the word whose entry is selected * @param entry the selected entry's XML */ void displayLexiconEntry(final String id, String word, String entry) { // If user searches from Quick Search Box, we may need to change mode. if (!mMode.equals(Mode.LEXICON_BROWSE) && !mMode.equals(Mode.LEXICON_FAVORITES) && !mMode.equals(Mode.LEXICON_HISTORY)) { switchToLexiconBrowse(); } // Add entry to history, unless word was selected from history list. if (!mMode.equals(Mode.LEXICON_HISTORY)) { addHistory(id, word); } // Display entry. if (mTwoPane) { Bundle arguments = new Bundle(); arguments.putString(LexiconDetailFragment.ARG_ENTRY, entry); LexiconDetailFragment fragment = new LexiconDetailFragment(); fragment.setArguments(arguments); FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(R.id.item_detail_container, fragment); transaction.commitAllowingStateLoss(); } else { LexiconListFragment fragment = (LexiconListFragment) getFragmentManager() .findFragmentById(R.id.item_list_container); int lexiconId = fragment.getSelectedLexiconId(); Intent intent = new Intent(this, LexiconDetailActivity.class); intent.putExtra(LexiconDetailFragment.ARG_ENTRY, entry); intent.putExtra(LexiconDetailActivity.ARG_LEXICON_ID, lexiconId); intent.putExtra(LexiconDetailActivity.ARG_WORD, word); startActivity(intent); } } /** * Displays the specified Overview of Greek Syntax section in a {@link SyntaxDetailFragment}. * @param section the selected section's title * @param xml the selected section's XML */ void displaySyntaxSection(String section, String xml) { if (mTwoPane) { Bundle arguments = new Bundle(); arguments.putString(SyntaxDetailFragment.ARG_XML, xml); SyntaxDetailFragment fragment = new SyntaxDetailFragment(); fragment.setArguments(arguments); FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(R.id.item_detail_container, fragment); transaction.commitAllowingStateLoss(); } else { SyntaxListFragment fragment = (SyntaxListFragment) getFragmentManager() .findFragmentById(R.id.item_list_container); int syntaxId = fragment.getSelectedSyntaxId(); Intent intent = new Intent(this, SyntaxDetailActivity.class); intent.putExtra(SyntaxDetailFragment.ARG_XML, xml); intent.putExtra(SyntaxDetailActivity.ARG_SYNTAX_ID, syntaxId); intent.putExtra(SyntaxDetailActivity.ARG_SECTION, section); startActivity(intent); } } /** * Adds the specified word to the lexicon history list. If the word is already contained in the * list, it will be moved to the top of the list. * @param id the lexicon database ID of the selected word * @param word the selected word */ void addHistory(String id, String word) { // If the word is already in the list, delete it. String selection = AppDataContract.LexiconHistory.COLUMN_NAME_LEXICON_ID + " = ?"; String[] selectionArgs = { id }; getContentResolver().delete(LexiconHistoryProvider.CONTENT_URI, selection, selectionArgs); // Add word to top of list. ContentValues values = new ContentValues(); values.put(AppDataContract.LexiconHistory.COLUMN_NAME_LEXICON_ID, id); values.put(AppDataContract.LexiconHistory.COLUMN_NAME_WORD, word); getContentResolver().insert(LexiconHistoryProvider.CONTENT_URI, values); } @Override public void onNavigationDrawerItemSelected(int position) { /* * We consider the user to have learned the drawer once he or she selects an item. This * prevents the drawer from appearing repeatedly in the one-pane mode. This is just a quick * workaround; we might want to implement a more sophisticated solution at some point in * the future. */ if (mNavigationDrawerFragment != null) { mNavigationDrawerFragment.userLearnedDrawer(); } switchToMode(Mode.getModeFromPosition(position)); } /** * Sets the navigation bar navigation mode and title to the appropriate values. */ public void restoreActionBar() { ActionBar actionBar = getSupportActionBar(); assert actionBar != null; actionBar.setDisplayShowTitleEnabled(true); actionBar.setTitle(mTitle); actionBar.setSubtitle(mSubtitle); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Only show items in the action bar relevant to this screen if the drawer is not showing. // Otherwise, let the drawer decide what to show in the action bar. if (mNavigationDrawerFragment.isDrawerOpen()) { return super.onCreateOptionsMenu(menu); } // Inflate the options menu from XML. We have to handle the menu here rather than in the // fragment so that we can hide them when the navigation drawer is open. if (mMode.isLexiconMode()) { getMenuInflater().inflate(R.menu.lexicon_menu, menu); setLexiconFavoriteIcon(menu); // Get the SearchView and set the searchable configuration. SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.menu_search)); //SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); restoreActionBar(); return super.onCreateOptionsMenu(menu); } else if (mMode.isSyntaxMode()) { getMenuInflater().inflate(R.menu.syntax_menu, menu); setSyntaxBookmarkIcon(menu); restoreActionBar(); return super.onCreateOptionsMenu(menu); } else { throw new IllegalStateException("Invalid mode"); } } @Override public boolean onPrepareOptionsMenu(Menu menu) { // Only show items in the action bar relevant to this screen if the drawer is not showing. // Otherwise, let the drawer decide what to show in the action bar. if (mNavigationDrawerFragment.isDrawerOpen()) { return super.onCreateOptionsMenu(menu); } if (mMode.isLexiconMode()) { setLexiconFavoriteIcon(menu); } else if (mMode.isSyntaxMode()) { setSyntaxBookmarkIcon(menu); } else { throw new IllegalStateException("Invalid mode"); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // TODO: Move favorite and bookmark code to fragments? // TODO: Move favorite and bookmark options to fragments? switch (item.getItemId()) { case R.id.action_add_favorite: LexiconDetailFragment addFavoriteFragment = (LexiconDetailFragment) getFragmentManager() .findFragmentById(R.id.item_detail_container); addFavoriteFragment.addLexiconFavorite(); return true; case R.id.action_remove_favorite: LexiconDetailFragment removeFavoriteFragment = (LexiconDetailFragment) getFragmentManager() .findFragmentById(R.id.item_detail_container); removeFavoriteFragment.removeLexiconFavorite(); return true; case R.id.action_add_bookmark: SyntaxDetailFragment addBookmarkFragment = (SyntaxDetailFragment) getFragmentManager() .findFragmentById(R.id.item_detail_container); addBookmarkFragment.addSyntaxBookmark(); return true; case R.id.action_remove_bookmark: SyntaxDetailFragment removeBookmarkFragment = (SyntaxDetailFragment) getFragmentManager() .findFragmentById(R.id.item_detail_container); removeBookmarkFragment.removeSyntaxBookmark(); return true; case R.id.action_clear_history: clearHistory(); return true; case R.id.action_clear_favorites: clearLexiconFavorites(); return true; case R.id.action_clear_bookmarks: clearSyntaxBookmarks(); return true; case R.id.action_settings: startActivity(new Intent(this, SettingsActivity.class)); return true; case R.id.action_feedback: sendFeedback(); return true; case R.id.action_help: displayHelp(); return true; } return super.onOptionsItemSelected(item); } /** * Deletes all words from the lexicon history list. */ private void clearHistory() { getContentResolver().delete(AppDataContract.LexiconHistory.CONTENT_URI, null, null); swapInFragments(new LexiconHistoryListFragment(), new LexiconDetailFragment()); Toast toast = Toast.makeText(getApplicationContext(), getString(R.string.toast_clear_history), Toast.LENGTH_SHORT); toast.show(); } /** * Deletes all words from the lexicon favorites list. */ private void clearLexiconFavorites() { DialogFragment dialog = new ClearLexiconFavoritesDialogFragment(); dialog.show(getFragmentManager(), "clearFavorites"); } /** * A {@link DialogFragment} that asks the user to confirm that he or she wishes to clear the * lexicon favorites list. If the user answers in the affirmative, the list is cleared. * Otherwise, the dialog is dismissed and no further action is taken. */ public static class ClearLexiconFavoritesDialogFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage(R.string.clear_lexicon_favorites_dialog_message); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { getActivity().getContentResolver().delete(AppDataContract.LexiconFavorites.CONTENT_URI, null, null); MainActivity activity = (MainActivity) getActivity(); activity.swapInFragments(new LexiconFavoritesListFragment(), new LexiconDetailFragment()); Toast toast = Toast.makeText(getActivity(), getString(R.string.toast_clear_lexicon_favorites), Toast.LENGTH_SHORT); toast.show(); } }); builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }); return builder.create(); } } /** * Deletes all items from the syntax bookmarks list. */ private void clearSyntaxBookmarks() { DialogFragment dialog = new ClearSyntaxBookmarksDialogFragment(); dialog.show(getFragmentManager(), "clearBookmarks"); } /** * A {@link DialogFragment} that asks the user to confirm that he or she wishes to clear the * syntax bookmarks list. If the user answers in the affirmative, the list is cleared. * Otherwise, the dialog is dismissed and no further action is taken. */ public static class ClearSyntaxBookmarksDialogFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage(R.string.clear_syntax_bookmarks_dialog_message); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { getActivity().getContentResolver().delete(AppDataContract.SyntaxBookmarks.CONTENT_URI, null, null); MainActivity activity = (MainActivity) getActivity(); activity.swapInFragments(new SyntaxBookmarksListFragment(), new SyntaxDetailFragment()); // onPrepareOptionsMenu() isn't being called after the fragment transaction for // some reason, so we need to manually invalidate the menu here. activity.getFragmentManager().executePendingTransactions(); activity.invalidateOptionsMenu(); Toast toast = Toast.makeText(activity.getApplicationContext(), getString(R.string.toast_clear_syntax_bookmarks), Toast.LENGTH_SHORT); toast.show(); } }); builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }); return builder.create(); } } /** * Finds and selects the lexicon entry corresponding to the specified URI. * @param data the URI of the lexicon entry to select */ private void getLexiconEntry(Uri data) { ensureModeIsLexiconBrowse(); // Get data. Cursor cursor = getContentResolver().query(data, null, null, null, null); cursor.moveToFirst(); String id; try { int idIndex = cursor.getColumnIndexOrThrow(LexiconContract._ID); id = cursor.getString(idIndex); } catch (IllegalArgumentException e) { Log.e(TAG, "Failed to retrieve result from database."); throw e; } // Set this item's state to activated on tablets and scroll the list to the item. LexiconBrowseListFragment fragment = (LexiconBrowseListFragment) getFragmentManager() .findFragmentById(R.id.item_list_container); fragment.selectItem(Integer.parseInt(id)); } /** * Searches the lexicon for a word and displays the result. * @param query a string containing the word for which to search. This string is case * insensitive and may be written in either Greek characters or Beta code. */ // TODO: Handle words with multiple entries. void search(String query) { String[] columns = new String[] { LexiconContract._ID }; String selection = LexiconContract.COLUMN_BETA_SYMBOLS + " = ? OR " + LexiconContract.COLUMN_BETA_NO_SYMBOLS + " = ? OR " + LexiconContract.COLUMN_GREEK_LOWERCASE + " = ?"; String[] selectionArgs = new String[] { query.toLowerCase(), query.toLowerCase(), query.toLowerCase() }; String sortOrder = LexiconContract._ID + " ASC"; Cursor cursor = getContentResolver().query(LexiconProvider.CONTENT_URI, columns, selection, selectionArgs, sortOrder); if (cursor.moveToFirst()) { String id = cursor.getString(0); ensureModeIsLexiconBrowse(); LexiconBrowseListFragment fragment = (LexiconBrowseListFragment) getFragmentManager() .findFragmentById(R.id.item_list_container); fragment.selectItem(Integer.parseInt(id)); } else { Toast toast = Toast.makeText(getApplicationContext(), getString(R.string.toast_search_no_results), Toast.LENGTH_LONG); toast.show(); } cursor.close(); } /** * Switches the mode to Lexicon Browse if that is not the current mode. */ private void ensureModeIsLexiconBrowse() { if (!mMode.equals(Mode.LEXICON_BROWSE)) { switchToLexiconBrowse(); // Make sure the fragments are swapped before we try to get the // LexiconBrowseListFragment. getFragmentManager().executePendingTransactions(); } } /** * Switches the mode to the specified {@link Mode}. * @param mode the {@code Mode} to which to switch */ private void switchToMode(Mode mode) { // TODO: Condense this code by storing title and fragments as fields of each Mode? The only // problem with this idea is that we'd need static access to string resources. (I.e., we // can't get a context from the enum class itself, so we'd need to use some sort of kludgy // workaround.) switch (mode) { case LEXICON_BROWSE: switchToLexiconBrowse(); break; case LEXICON_FAVORITES: switchToLexiconFavorites(); break; case LEXICON_HISTORY: switchToLexiconHistory(); break; case SYNTAX_BROWSE: switchToSyntaxBrowse(); break; case SYNTAX_BOOKMARKS: switchToSyntaxBookmarks(); break; default: throw new IllegalArgumentException("Invalid mode"); } checkTabletDisplayMode(); // Make sure we're showing or hiding the left pane as is appropriate. } private void switchToLexiconBrowse() { mMode = Mode.LEXICON_BROWSE; mTitle = getString(R.string.title_lexicon); mSubtitle = getString(R.string.title_lexicon_browse); restoreActionBar(); swapInFragments(new LexiconBrowseListFragment(), new LexiconDetailFragment()); ensureNavDrawerSelection(Mode.LEXICON_BROWSE); } private void switchToLexiconFavorites() { mMode = Mode.LEXICON_FAVORITES; mTitle = getString(R.string.title_lexicon); mSubtitle = getString(R.string.title_lexicon_favorites); restoreActionBar(); swapInFragments(new LexiconFavoritesListFragment(), new LexiconDetailFragment()); ensureNavDrawerSelection(Mode.LEXICON_FAVORITES); } private void switchToLexiconHistory() { mMode = Mode.LEXICON_HISTORY; mTitle = getString(R.string.title_lexicon); mSubtitle = getString(R.string.title_lexicon_history); restoreActionBar(); swapInFragments(new LexiconHistoryListFragment(), new LexiconDetailFragment()); ensureNavDrawerSelection(Mode.LEXICON_HISTORY); } private void switchToSyntaxBrowse() { mMode = Mode.SYNTAX_BROWSE; mTitle = getString(R.string.title_syntax); mSubtitle = getString(R.string.title_syntax_browse); restoreActionBar(); swapInFragments(new SyntaxBrowseListFragment(), new SyntaxDetailFragment()); ensureNavDrawerSelection(Mode.SYNTAX_BROWSE); } private void switchToSyntaxBookmarks() { mMode = Mode.SYNTAX_BOOKMARKS; mTitle = getString(R.string.title_syntax); mSubtitle = getString(R.string.title_syntax_bookmarks); restoreActionBar(); swapInFragments(new SyntaxBookmarksListFragment(), new SyntaxDetailFragment()); ensureNavDrawerSelection(Mode.SYNTAX_BOOKMARKS); } /** * Sets the selected navigation drawer position to the position corresponding to the * current mode. * @param mode the {@link Mode} to which to set the navigation drawer selection */ private void ensureNavDrawerSelection(Mode mode) { // If the nav drawer hasn't been created yet, we don't need to worry about this. if (null == mNavigationDrawerFragment) { return; } int position = mNavigationDrawerFragment.getCurrentSelectedPosition(); Mode navDrawerMode = Mode.getModeFromPosition(position); if (!navDrawerMode.equals(mode)) { mNavigationDrawerFragment.setCurrentSelectedPosition(mode.getPosition()); } } /** * Replaces the currently displayed fragment(s) with the specified fragment(s). * @param listFragment the {@link BaseListFragment} to swap in * @param detailFragment the {@link DetailFragment} to swap in, or null if the app is in * one-pane mode */ private void swapInFragments(Fragment listFragment, Fragment detailFragment) { // TODO: Check for invalid null arguments. if (mTwoPane) { FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(R.id.item_list_container, listFragment); transaction.replace(R.id.item_detail_container, detailFragment); transaction.commit(); } else { FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(R.id.item_list_container, listFragment); transaction.commit(); } } public boolean isTwoPane() { return mTwoPane; } /** * Sets the Lexicon Favorite icon to the appropriate state based on the currently selected * lexicon entry. * @param menu the {@code Menu} containing the Favorite icon */ private void setLexiconFavoriteIcon(Menu menu) { LexiconListFragment fragment = (LexiconListFragment) getFragmentManager() .findFragmentById(R.id.item_list_container); MenuItem addFavorite = menu.findItem(R.id.action_add_favorite); MenuItem removeFavorite = menu.findItem(R.id.action_remove_favorite); if (fragment.nothingIsSelected() || !mTwoPane) { addFavorite.setVisible(false); removeFavorite.setVisible(false); } else if (fragment.selectedWordIsFavorite()) { addFavorite.setVisible(false); removeFavorite.setVisible(true); } else { addFavorite.setVisible(true); removeFavorite.setVisible(false); } } /** * Sets the Syntax Bookmark icon to the appropriate state based on the currently selected * syntax section. * @param menu the {@code Menu} containing the Bookmark icon */ private void setSyntaxBookmarkIcon(Menu menu) { SyntaxListFragment fragment = (SyntaxListFragment) getFragmentManager() .findFragmentById(R.id.item_list_container); MenuItem addBookmark = menu.findItem(R.id.action_add_bookmark); MenuItem removeBookmark = menu.findItem(R.id.action_remove_bookmark); if (fragment.nothingIsSelected() || !mTwoPane) { addBookmark.setVisible(false); removeBookmark.setVisible(false); } else if (fragment.selectedSectionIsBookmarked()) { addBookmark.setVisible(false); removeBookmark.setVisible(true); } else { addBookmark.setVisible(true); removeBookmark.setVisible(false); } } /** * Launches an email app that the user can use to send feedback about this app. */ private void sendFeedback() { Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", getString(R.string.feedback_email), null)); intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.feedback_subject)); startActivity(Intent.createChooser(intent, getString(R.string.feedback_intent_chooser))); } /** * A {@link DialogFragment} containing help text. */ public static class HelpDialogFragment extends DialogFragment { // TODO: Either make these dialog fragment classes private or reuse a single one everywhere. @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(R.string.title_help); TextView textView = new TextView(getActivity()); textView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Medium); textView.setTextColor(getResources().getColor(android.R.color.black)); textView.setPadding(25, 25, 25, 25); textView.setText(Html.fromHtml(getString(R.string.message_help))); textView.setMovementMethod(LinkMovementMethod.getInstance()); ScrollView scrollView = new ScrollView(getActivity()); scrollView.addView(textView); builder.setView(scrollView); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }); return builder.create(); } } /** * Displays a dialog fragment containing help text. */ private void displayHelp() { HelpDialogFragment dialogFragment = new HelpDialogFragment(); dialogFragment.show(getFragmentManager(), "help"); } public Mode getMode() { return mMode; } // The following two methods are a workaround for a bug related to the appcompat-v7 library // on some LG devices. Thanks to Alex Lockwood for the fix: http://stackoverflow.com/questions/26833242/nullpointerexception-phonewindowonkeyuppanel1002-main @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (KeyEvent.KEYCODE_MENU == keyCode && Build.BRAND.equalsIgnoreCase("LGE")) { return true; } return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (KeyEvent.KEYCODE_MENU == keyCode && Build.BRAND.equalsIgnoreCase("LGE")) { openOptionsMenu(); return true; } return super.onKeyUp(keyCode, event); } }