Android Open Source - journal Monthly Expenses Fragment






From Project

Back to project page journal.

License

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.

Java Source Code

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




Java Source Code List

cochrane343.journal.Constants.java
cochrane343.journal.CurrencyHelper.java
cochrane343.journal.DateTimeHelper.java
cochrane343.journal.ExpenseEditListener.java
cochrane343.journal.ExpensesListAdapter.java
cochrane343.journal.MainActivity.java
cochrane343.journal.MonthlyExpensesFragment.java
cochrane343.journal.MonthlyExpensesPagerAdapter.java
cochrane343.journal.SettingsActivity.java
cochrane343.journal.TranslationHelper.java
cochrane343.journal.contentprovider.JournalContentProvider.java
cochrane343.journal.contentprovider.JournalContract.java
cochrane343.journal.contentprovider.JournalDatabaseHelper.java
cochrane343.journal.dialogs.CategorySpinnerAdapter.java
cochrane343.journal.dialogs.ExpenseDialogFragment.java
cochrane343.journal.dialogs.ExpenseDialogListener.java
cochrane343.journal.exceptions.IllegalDisplayModeException.java
cochrane343.journal.exceptions.IllegalLoaderIdException.java
cochrane343.journal.exceptions.IllegalUriException.java
cochrane343.journal.exceptions.MissingFragmentArgumentException.java