Back to project page journal.
The source code is released under:
MIT License
If you think the Android project journal listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package cochrane343.journal; /*from w w w. jav a 2 s . c om*/ import android.content.ContentUris; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; import android.widget.TextView; import cochrane343.journal.Constants.DisplayMode; import cochrane343.journal.contentprovider.JournalContract; import cochrane343.journal.contentprovider.JournalContract.Category; import cochrane343.journal.contentprovider.JournalContract.Expense; import cochrane343.journal.dialogs.ExpenseDialogListener; import cochrane343.journal.exceptions.IllegalDisplayModeException; import cochrane343.journal.exceptions.IllegalLoaderIdException; import cochrane343.journal.exceptions.MissingFragmentArgumentException; /** * The {@link Fragment} used to display the expenses of a specific month. * * TODO Two display modes, back press, loaders, fragment arguments, ...S * * @author cochrane343 * @since 1.0 */ public class MonthlyExpensesFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener { private static final int LOADER_ID_EXPENSES = 0; private static final int LOADER_ID_CATEGORY_TOTALS = 1; private static final int LOADER_ID_MONTH_TOTAL = 2; private static final int LOADER_ID_CATEGORY_NAME = 3; private static final int CONTEXT_MENU_ITEM_EDIT = 0; private static final int CONTEXT_MENU_ITEM_DELETE = 1; private final static String BUNDLE_KEY_DISPLAYED_MONTH = "displayedMonth"; private final static String BUNDLE_KEY_DISPLAY_MODE = "displayMode"; private final static String BUNDLE_KEY_DISPLAYED_CATEGORY = "displayedCategory"; private DisplayMode displayMode; private long displayedCategory; private TextView monthNameTextView; private TextView monthTotalTextView; private TextView categoryNameTextView; private ListView expensesListView; private ExpensesListAdapter expensesListAdapter; /* - - - - - Instance Creation - - - - - - - - - - */ /** * Required empty constructor */ public MonthlyExpensesFragment() { // No-op } /** * @param displayedMonth the number of months elapsed since the Java epoch, determining the month * displayed by the newly created fragment instance */ public static MonthlyExpensesFragment newInstance(final int displayedMonth) { final MonthlyExpensesFragment fragment = new MonthlyExpensesFragment(); final Bundle arguments = new Bundle(); arguments.putInt(BUNDLE_KEY_DISPLAYED_MONTH, displayedMonth); fragment.setArguments(arguments); return fragment; } /* - - - - - Displayed Month - - - - - - - - - - */ /** * @return the number of months elapsed since the Java epoch, determining the month * displayed by this fragment * @throws MissingFragmentArgumentException if this fragment has no arguments or the * <code>BUNDLE_KEY_DISPLAYED_MONTH</code> key can not be found in the arguments */ private int getDisplayedMonth() { final Bundle arguments = getArguments(); if (arguments == null || !arguments.containsKey(BUNDLE_KEY_DISPLAYED_MONTH)) { throw new MissingFragmentArgumentException(BUNDLE_KEY_DISPLAYED_MONTH); } return arguments.getInt(BUNDLE_KEY_DISPLAYED_MONTH); } /** * @return the number of milliseconds elapsed between the Java epoch and the beginning of the month * displayed by this fragment * @see DateTimeHelper#getStartOfMonth(int) */ private long getStartOfDisplayedMonthMillis() { final int displayedMonth = getDisplayedMonth(); return DateTimeHelper.getStartOfMonth(displayedMonth).getMillis(); } /** * @return the number of milliseconds elapsed between the Java epoch and the end of the month * displayed by this fragment * @see DateTimeHelper#getEndOfMonth(int) */ private long getEndOfDisplayedMonthMillis() { final int displayedMonth = getDisplayedMonth(); return DateTimeHelper.getEndOfMonth(displayedMonth).getMillis(); } /* - - - - - Fragment Lifecycle - - - - - - - - - - */ /** * Inflates the main view and sets up the GUI elements. */ @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.expenses_list_fragment, container, false); final int displayedMonth = getDisplayedMonth(); final String displayedMonthPrintstring = DateTimeHelper.printMonth(displayedMonth); monthNameTextView = (TextView) view.findViewById(R.id.expenses_list_month_name); monthNameTextView.setText(displayedMonthPrintstring); monthTotalTextView = (TextView) view.findViewById(R.id.expenses_list_month_total); categoryNameTextView = (TextView) view.findViewById(R.id.expenses_list_category_name); expensesListView = (ListView) view.findViewById(R.id.expenses_list); expensesListView.setOnItemClickListener(this); registerForContextMenu(expensesListView); return view; } /** * Saves the current display mode and the currently displayed category. */ @Override public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(BUNDLE_KEY_DISPLAY_MODE, displayMode.ordinal()); outState.putLong(BUNDLE_KEY_DISPLAYED_CATEGORY, displayedCategory); } /** * Sets the adapter for the expenses list view, retrieved the saved instance state * and switches to the correct display mode accordingly. */ @Override public void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); expensesListAdapter = new ExpensesListAdapter(getActivity(), null); expensesListView.setAdapter(expensesListAdapter); if (savedInstanceState != null) { final int displayModeOrdinal = savedInstanceState.getInt(BUNDLE_KEY_DISPLAY_MODE); displayMode = DisplayMode.values()[displayModeOrdinal]; displayedCategory = savedInstanceState.getLong(BUNDLE_KEY_DISPLAYED_CATEGORY); } else { displayMode = DisplayMode.CATEGORY_OVERVIEW; displayedCategory = Constants.NO_CATEGORY; } switch (displayMode) { case CATEGORY_OVERVIEW: switchToCategoryOverview(); break; case SINGLE_CATEGORY: switchToSingleCategory(displayedCategory); break; default: throw new IllegalDisplayModeException(displayMode.name()); } } /* - - - - - Display Modes - - - - - - - - - - */ private void switchToCategoryOverview() { displayMode = DisplayMode.CATEGORY_OVERVIEW; displayedCategory = Constants.NO_CATEGORY; categoryNameTextView.setText(""); expensesListView.setLongClickable(false); getLoaderManager().restartLoader(LOADER_ID_CATEGORY_TOTALS, null, this); getLoaderManager().restartLoader(LOADER_ID_MONTH_TOTAL, null, this); getLoaderManager().destroyLoader(LOADER_ID_EXPENSES); getLoaderManager().destroyLoader(LOADER_ID_CATEGORY_NAME); } private void switchToSingleCategory(final long category) { displayMode = DisplayMode.SINGLE_CATEGORY; displayedCategory = category; expensesListView.setLongClickable(true); getLoaderManager().restartLoader(LOADER_ID_EXPENSES, null, this); getLoaderManager().restartLoader(LOADER_ID_MONTH_TOTAL, null, this); getLoaderManager().restartLoader(LOADER_ID_CATEGORY_NAME, null, this); getLoaderManager().destroyLoader(LOADER_ID_CATEGORY_TOTALS); } /* - - - - - LoaderCallbacks - - - - - - - - - - */ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) { switch (id) { case LOADER_ID_EXPENSES: return createExpensesLoader(); case LOADER_ID_CATEGORY_TOTALS: return createCategoryTotalsLoader(); case LOADER_ID_CATEGORY_NAME: return createCategoryNameLoader(); case LOADER_ID_MONTH_TOTAL: return createMonthTotalLoader(); default: throw new IllegalLoaderIdException(id); } } /** * @return the cursor loader for loading the list of expenses, when displaying a single category */ private Loader<Cursor> createExpensesLoader() { final Uri uri = Expense.EXPENSES_URI; final String[] projection = new String[]{ Expense._ID, Expense._DESCRIPTION, Expense._COST}; final String selection = JournalContract.TIMESTAMP_RANGE_CLAUSE + " AND " + JournalContract.EXPENSE_CATEGORY_CLAUSE; final String[] selectionArgs = new String[]{ String.valueOf(getStartOfDisplayedMonthMillis()), String.valueOf(getEndOfDisplayedMonthMillis()), String.valueOf(displayedCategory)}; final String sortOrder = Expense._TIMESTAMP + " ASC"; return new CursorLoader(getActivity(), uri, projection, selection, selectionArgs, sortOrder); } /** * @return the cursor loader for loading the totals of all categories, when displaying the category overview */ private Loader<Cursor> createCategoryTotalsLoader() { final Uri uri = Category.CATEGORY_TOTALS_URI; final String[] projection = new String[]{ Category._ID, Category._NAME, "COALESCE(SUM(" + Expense._COST + "),0) AS " + Category._TOTAL}; final String selection = JournalContract.TIMESTAMP_RANGE_CLAUSE; final String[] selectionArgs = new String[]{ String.valueOf(getStartOfDisplayedMonthMillis()), String.valueOf(getEndOfDisplayedMonthMillis())}; final String sortOrder = Category._SORT_ORDER + " ASC"; return new CursorLoader(getActivity(), uri, projection, selection, selectionArgs, sortOrder); } /** * the cursor loader for loading the category name, when displaying a single category */ private Loader<Cursor> createCategoryNameLoader() { final Uri uri = ContentUris.withAppendedId(Category.CATEGORIES_URI, displayedCategory); final String[] projection = new String[]{ Category._NAME }; return new CursorLoader(getActivity(), uri, projection, null, null, null); } /** * @return the cursor loader for loading the total cost of the displayed month */ private Loader<Cursor> createMonthTotalLoader() { final Uri uri = Expense.EXPENSES_URI; final String[] projection = new String[]{ "COALESCE(SUM(" + Expense._COST + "),0)" }; String selection; String[] selectionArgs; switch (displayMode) { case CATEGORY_OVERVIEW: selection = JournalContract.TIMESTAMP_RANGE_CLAUSE; selectionArgs = new String[]{ String.valueOf(getStartOfDisplayedMonthMillis()), String.valueOf(getEndOfDisplayedMonthMillis())}; break; case SINGLE_CATEGORY: selection = JournalContract.TIMESTAMP_RANGE_CLAUSE + " AND " + JournalContract.EXPENSE_CATEGORY_CLAUSE; selectionArgs = new String[]{ String.valueOf(getStartOfDisplayedMonthMillis()), String.valueOf(getEndOfDisplayedMonthMillis()), String.valueOf(displayedCategory)}; break; default: throw new IllegalDisplayModeException(displayMode.name()); } return new CursorLoader(getActivity(), uri, projection, selection, selectionArgs, null); } public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) { final int id = loader.getId(); switch (id) { case LOADER_ID_EXPENSES: expensesListAdapter.setDisplayMode(DisplayMode.SINGLE_CATEGORY); expensesListAdapter.swapCursor(data); break; case LOADER_ID_CATEGORY_TOTALS: expensesListAdapter.setDisplayMode(DisplayMode.CATEGORY_OVERVIEW); expensesListAdapter.swapCursor(data); break; case LOADER_ID_CATEGORY_NAME: if (data.moveToFirst()) { final String categoryName = data.getString(0); final String translatedCategoryName = TranslationHelper.translate(categoryName, loader.getContext()); categoryNameTextView.setText(" - " + translatedCategoryName); } break; case LOADER_ID_MONTH_TOTAL: if (data.moveToFirst()) { final long monthTotalInCents = data.getLong(0); final String monthTotalString = CurrencyHelper.toPrintString(monthTotalInCents); monthTotalTextView.setText(monthTotalString); } break; default: throw new IllegalLoaderIdException(id); } } public void onLoaderReset(final Loader<Cursor> loader) { final int id = loader.getId(); switch (id) { case LOADER_ID_EXPENSES: case LOADER_ID_CATEGORY_TOTALS: expensesListAdapter.swapCursor(null); break; case LOADER_ID_CATEGORY_NAME: case LOADER_ID_MONTH_TOTAL: // No-op break; default: throw new IllegalLoaderIdException(id); } } /* - - - - - Event Handling - - - - - - - - - - */ @Override public void onCreateContextMenu(final ContextMenu contextMenu, final View view, final ContextMenuInfo menuInfo) { super.onCreateContextMenu(contextMenu, view, menuInfo); contextMenu.add(0, CONTEXT_MENU_ITEM_EDIT, 0, getString(R.string.label_edit)); contextMenu.add(0, CONTEXT_MENU_ITEM_DELETE, 0, getString(R.string.label_delete)); } @Override public boolean onContextItemSelected(final MenuItem menuItem) { final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); switch (menuItem.getItemId()) { case CONTEXT_MENU_ITEM_EDIT: ((ExpenseDialogListener) getActivity()).showExpenseDialogForEdit(menuInfo.id); return true; case CONTEXT_MENU_ITEM_DELETE: ((ExpenseEditListener) getActivity()).onDeleteExpense(menuInfo.id); return true; default: return super.onContextItemSelected(menuItem); } } /** * Callback method to be invoked when the user clicks on an item in this fragment's list view. * If the fragment is in category overview display mode, it switches to single category display mode * for the category selected by the user and restarts cursor loaders accordingly. */ @Override public void onItemClick (final AdapterView<?> parent, final View view, final int position, final long id) { if (displayMode == DisplayMode.CATEGORY_OVERVIEW) { switchToSingleCategory(id); } } /** * Callback method to be invoked, when the activity, which this fragment is attached to, detects * that the user pressed the back button. If this fragment is in single category display mode, it * consumes the event and switches to category overview display mode. Otherwise the event is propagated. * @return true if the onBackPressed event was consumed - false if it should be propagated further */ public boolean onBackPressed() { if (displayMode == DisplayMode.SINGLE_CATEGORY) { switchToCategoryOverview(); return true; } else { return false; } } }