/* * Copyright (C) 2012-2016 The Android Money Manager Ex Project Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.money.manager.ex.reports; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.graphics.Typeface; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.ContextCompat; import android.support.v4.content.Loader; import android.text.TextUtils; import android.util.SparseBooleanArray; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.TableRow; import android.widget.TextView; import com.afollestad.materialdialogs.MaterialDialog; import com.money.manager.ex.R; import com.money.manager.ex.common.MmxCursorLoader; import com.money.manager.ex.core.IntentFactory; import com.money.manager.ex.core.UIHelper; import com.money.manager.ex.currency.CurrencyService; import com.money.manager.ex.database.QueryReportIncomeVsExpenses; import com.money.manager.ex.database.SQLDataSet; import com.money.manager.ex.database.ViewMobileData; import com.money.manager.ex.datalayer.Select; import com.money.manager.ex.search.SearchParameters; import com.money.manager.ex.utils.MmxDate; import com.money.manager.ex.viewmodels.IncomeVsExpenseReportEntity; import org.apache.commons.lang3.ArrayUtils; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import info.javaperformance.money.MoneyFactory; import timber.log.Timber; /** * Income/Expense Report, list. */ public class IncomeVsExpensesListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> { private static final int ID_LOADER_REPORT = 1; private static final int ID_LOADER_YEARS = 2; private static final String SORT_ASCENDING = "ASC"; private static final String SORT_DESCENDING = "DESC"; private static final String KEY_BUNDLE_YEAR = "IncomeVsExpensesListFragment:Years"; private View mFooterListView; private SparseBooleanArray mYearsSelected = new SparseBooleanArray(); private String mSort = SORT_ASCENDING; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setHasOptionsMenu(true); if (savedInstanceState != null && savedInstanceState.containsKey(KEY_BUNDLE_YEAR) && savedInstanceState.getIntArray(KEY_BUNDLE_YEAR) != null) { for (int year : savedInstanceState.getIntArray(KEY_BUNDLE_YEAR)) { mYearsSelected.put(year, true); } } else { mYearsSelected.put(Calendar.getInstance().get(Calendar.YEAR), true); } initializeListView(); // set home button // ActionBarActivity activity = (ActionBarActivity) getActivity(); // AppCompatActivity activity = (AppCompatActivity) getActivity(); // if (activity != null) { //activity.getSupportActionBar().setDisplayHomeAsUpEnabled(false); // } // create adapter IncomeVsExpensesAdapter adapter = new IncomeVsExpensesAdapter(getActivity(), null); setListAdapter(adapter); setListShown(false); // start loader //getLoaderManager().restartLoader(ID_LOADER_YEARS, null, this); getLoaderManager().initLoader(ID_LOADER_YEARS, null, this); } // Loader @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { String selection = null; Select query; switch (id) { case ID_LOADER_REPORT: if (args != null && args.containsKey(KEY_BUNDLE_YEAR) && args.getString(KEY_BUNDLE_YEAR) != null) { selection = IncomeVsExpenseReportEntity.YEAR + " IN (" + args.getString(KEY_BUNDLE_YEAR) + ")"; if (!TextUtils.isEmpty(selection)) { selection = "(" + selection + ")"; } } // if don't have selection abort query if (TextUtils.isEmpty(selection)) { selection = "1=2"; } QueryReportIncomeVsExpenses report = new QueryReportIncomeVsExpenses(getActivity()); query = new Select(report.getAllColumns()) .where(selection) .orderBy(IncomeVsExpenseReportEntity.YEAR + " " + mSort + ", " + IncomeVsExpenseReportEntity.Month + " " + mSort); return new MmxCursorLoader(getActivity(), report.getUri(), query); case ID_LOADER_YEARS: ViewMobileData mobileData = new ViewMobileData(getContext()); selection = "SELECT DISTINCT Year FROM " + mobileData.getSource() + " ORDER BY Year DESC"; query = new Select().where(selection); return new MmxCursorLoader(getActivity(), new SQLDataSet().getUri(), query); } return null; } @Override public void onLoaderReset(Loader<Cursor> loader) { // ((IncomeVsExpensesAdapter) getListAdapter()).swapCursor(null); ((IncomeVsExpensesAdapter) getListAdapter()).changeCursor(null); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { switch (loader.getId()) { case ID_LOADER_REPORT: // ((IncomeVsExpensesAdapter) getListAdapter()).swapCursor(data); ((IncomeVsExpensesAdapter) getListAdapter()).changeCursor(data); if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); } // calculate income, expenses double income = 0, expenses = 0; if (data == null) return; while (data.moveToNext()) { if (data.getInt(data.getColumnIndex(IncomeVsExpenseReportEntity.Month)) != IncomeVsExpensesActivity.SUBTOTAL_MONTH) { income += data.getDouble(data.getColumnIndex(IncomeVsExpenseReportEntity.Income)); expenses += data.getDouble(data.getColumnIndex(IncomeVsExpenseReportEntity.Expenses)); } } updateListViewFooter(mFooterListView, income, expenses); if (data.getCount() > 0) { getListView().removeFooterView(mFooterListView); getListView().addFooterView(mFooterListView); } if (((IncomeVsExpensesActivity) getActivity()).mIsDualPanel) { Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { showChart(); } }, 1 * 1000); } break; case ID_LOADER_YEARS: if (data != null && data.moveToFirst()) { while (!data.isAfterLast()) { int year = data.getInt(data.getColumnIndex("Year")); if (mYearsSelected.get(year, false) == false) { mYearsSelected.put(year, false); } data.moveToNext(); } startLoader(); } } } // Menu @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_report_income_vs_expenses, menu); // fix menu char MenuItem itemChart = menu.findItem(R.id.menu_chart); if (itemChart != null) { Activity activity = getActivity(); if (activity instanceof IncomeVsExpensesActivity) { itemChart.setVisible(!((IncomeVsExpensesActivity) activity).mIsDualPanel); } } } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { getActivity().finish(); } else if (item.getItemId() == R.id.menu_sort_asceding || item.getItemId() == R.id.menu_sort_desceding) { mSort = item.getItemId() == R.id.menu_sort_asceding ? SORT_ASCENDING : SORT_DESCENDING; startLoader(); item.setChecked(true); } else if (item.getItemId() == R.id.menu_chart) { showChart(); } else if (item.getItemId() == R.id.menu_period) { showDialogYears(); } return super.onOptionsItemSelected(item); } // @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); ArrayList<Integer> years = new ArrayList<Integer>(); for (int i = 0; i < mYearsSelected.size(); i++) { if (mYearsSelected.get(mYearsSelected.keyAt(i))) { years.add(mYearsSelected.keyAt(i)); } } outState.putIntArray(KEY_BUNDLE_YEAR, ArrayUtils.toPrimitive(years.toArray(new Integer[0]))); } // Other public void showDialogYears() { ArrayList<String> years = new ArrayList<String>(); Integer[] selected = new Integer[0]; for (int i = 0; i < mYearsSelected.size(); i++) { years.add(String.valueOf(mYearsSelected.keyAt(i))); if (mYearsSelected.valueAt(i)) { selected = ArrayUtils.add(selected, i); } } new MaterialDialog.Builder(getActivity()) .items(years.toArray(new String[years.size()])) .itemsCallbackMultiChoice(selected, new MaterialDialog.ListCallbackMultiChoice() { @Override public boolean onSelection(MaterialDialog materialDialog, Integer[] integers, CharSequence[] charSequences) { // reset to false all years for (int i = 0; i < mYearsSelected.size(); i++) { mYearsSelected.put(mYearsSelected.keyAt(i), false); } // set year select for (int index : integers) { mYearsSelected.put(mYearsSelected.keyAt(index), true); } startLoader(); return true; } }) //.alwaysCallMultiChoiceCallback() .positiveText(android.R.string.ok) .show(); } // Private /** * Add footer to ListView * * @return View of footer */ private View addListViewFooter() { TableRow row = (TableRow) View.inflate(getActivity(), R.layout.tablerow_income_vs_expenses, null); TextView txtYear = (TextView) row.findViewById(R.id.textViewYear); txtYear.setText(getString(R.string.total)); txtYear.setTypeface(null, Typeface.BOLD); TextView txtMonth = (TextView) row.findViewById(R.id.textViewMonth); txtMonth.setText(null); return row; } /** * Add header to ListView */ private View addListViewHeader() { TableRow row = (TableRow) View.inflate(getActivity(), R.layout.tablerow_income_vs_expenses, null); int[] ids = new int[]{ R.id.textViewYear, R.id.textViewMonth, R.id.textViewIncome, R.id.textViewExpenses, R.id.textViewDifference }; for (int id : ids) { TextView textView = (TextView) row.findViewById(id); textView.setTypeface(null, Typeface.BOLD); textView.setSingleLine(true); } getListView().addHeaderView(row); return row; } private void initializeListView() { setEmptyText(getString(R.string.no_data)); // add header and footer try { setListAdapter(null); addListViewHeader(); mFooterListView = addListViewFooter(); } catch (Exception e) { Timber.e(e, "adding header and footer in income vs expense report"); } getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Object positionObj = parent.getItemAtPosition(position); Cursor cursor = (Cursor) positionObj; if (cursor == null) return; // i.e. footer row. IncomeVsExpenseReportEntity entity = IncomeVsExpenseReportEntity.from(cursor); SearchParameters params = new SearchParameters(); // show the details for the selected month/year. MmxDate dateTime = new MmxDate(); dateTime.setYear(entity.getYear()); int month = entity.getMonth(); if (month != IncomeVsExpensesActivity.SUBTOTAL_MONTH) { dateTime.setMonth(entity.getMonth() - 1); } else { // full year dateTime.setMonth(Calendar.JANUARY); } dateTime.firstDayOfMonth(); params.dateFrom = dateTime.toDate(); if (month == IncomeVsExpensesActivity.SUBTOTAL_MONTH) { dateTime.setMonth(Calendar.DECEMBER); } dateTime.lastDayOfMonth(); params.dateTo = dateTime.toDate(); Intent intent = IntentFactory.getSearchIntent(getActivity(), params); startActivity(intent); } }); } /** * Start loader with arrays year * */ private void startLoader() { Bundle bundle = new Bundle(); String years = ""; for (int i = 0; i < mYearsSelected.size(); i++) { if (mYearsSelected.get(mYearsSelected.keyAt(i))) { years += (!TextUtils.isEmpty(years) ? ", " : "") + Integer.toString(mYearsSelected.keyAt(i)); } } bundle.putString(KEY_BUNDLE_YEAR, years); getLoaderManager().restartLoader(ID_LOADER_REPORT, bundle, this); } /** * update View of footer with income, expenses and difference * * @param footer * @param income * @param expenses */ private void updateListViewFooter(View footer, double income, double expenses) { if (footer == null) { return; } TextView txtIncome = (TextView) footer.findViewById(R.id.textViewIncome); TextView txtExpenses = (TextView) footer.findViewById(R.id.textViewExpenses); TextView txtDifference = (TextView) footer.findViewById(R.id.textViewDifference); CurrencyService currencyService = new CurrencyService(getActivity().getApplicationContext()); //set income txtIncome.setText(currencyService.getCurrencyFormatted(currencyService.getBaseCurrencyId(), MoneyFactory.fromDouble(income))); txtIncome.setTypeface(null, Typeface.BOLD); //set expenses txtExpenses.setText(currencyService.getCurrencyFormatted(currencyService.getBaseCurrencyId(), MoneyFactory.fromDouble(Math.abs(expenses)))); txtExpenses.setTypeface(null, Typeface.BOLD); //set difference txtDifference.setText(currencyService.getCurrencyFormatted(currencyService.getBaseCurrencyId(), MoneyFactory.fromDouble(income - Math.abs(expenses)))); txtDifference.setTypeface(null, Typeface.BOLD); //change colors UIHelper uiHelper = new UIHelper(getActivity()); if (income - Math.abs(expenses) < 0) { txtDifference.setTextColor(ContextCompat.getColor(getActivity(), uiHelper.resolveAttribute(R.attr.holo_red_color_theme))); } else { txtDifference.setTextColor(ContextCompat.getColor(getActivity(), uiHelper.resolveAttribute(R.attr.holo_green_color_theme))); } } private void showChart() { try { showChartInternal(); } catch (IllegalStateException ise) { Timber.e(ise, "showing chart"); } } private void showChartInternal() { // take a adapter and cursor IncomeVsExpensesAdapter adapter = ((IncomeVsExpensesAdapter) getListAdapter()); if (adapter == null) return; Cursor cursor = adapter.getCursor(); if (cursor == null) return; // Move to the first record. if (cursor.getCount() <= 0) return; // arrays ArrayList<Double> incomes = new ArrayList<>(); ArrayList<Double> expenses = new ArrayList<>(); ArrayList<String> titles = new ArrayList<>(); // Reset cursor to initial position. cursor.moveToPosition(-1); // cycle cursor while (cursor.moveToNext()) { int month = cursor.getInt(cursor.getColumnIndex(IncomeVsExpenseReportEntity.Month)); // check if not subtotal if (month != IncomeVsExpensesActivity.SUBTOTAL_MONTH) { // incomes and expenses incomes.add(cursor.getDouble(cursor.getColumnIndex(IncomeVsExpenseReportEntity.Income))); expenses.add(Math.abs(cursor.getDouble(cursor.getColumnIndex(IncomeVsExpenseReportEntity.Expenses)))); // titles int year = cursor.getInt(cursor.getColumnIndex(IncomeVsExpenseReportEntity.YEAR)); // format month Calendar calendar = Calendar.getInstance(); calendar.set(year, month - 1, 1); // titles titles.add(Integer.toString(year) + "-" + new SimpleDateFormat("MMM").format(calendar.getTime())); } } //compose bundle for arguments Bundle args = new Bundle(); args.putDoubleArray(IncomeVsExpensesChartFragment.KEY_EXPENSES_VALUES, ArrayUtils.toPrimitive(expenses.toArray(new Double[0]))); args.putDoubleArray(IncomeVsExpensesChartFragment.KEY_INCOME_VALUES, ArrayUtils.toPrimitive(incomes.toArray(new Double[0]))); args.putStringArray(IncomeVsExpensesChartFragment.KEY_XTITLES, titles.toArray(new String[titles.size()])); //get fragment manager FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); if (fragmentManager != null) { IncomeVsExpensesChartFragment fragment; fragment = (IncomeVsExpensesChartFragment) fragmentManager.findFragmentByTag(IncomeVsExpensesChartFragment.class.getSimpleName()); if (fragment == null) { fragment = new IncomeVsExpensesChartFragment(); } fragment.setChartArguments(args); fragment.setDisplayHomeAsUpEnabled(true); if (fragment.isVisible()) fragment.onResume(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); if (((IncomeVsExpensesActivity) getActivity()).mIsDualPanel) { fragmentTransaction.replace(R.id.fragmentChart, fragment, IncomeVsExpensesChartFragment.class.getSimpleName()); } else { fragmentTransaction.replace(R.id.fragmentMain, fragment, IncomeVsExpensesChartFragment.class.getSimpleName()); fragmentTransaction.addToBackStack(null); } fragmentTransaction.commit(); } } }