/* Class that handles the Transaction Fragment seen in the Checkbook screen
* Does everything from setting up the view to Add/Delete/Edit Transactions to calculating the balance
*/
package com.databases.example.features.checkbook.transactions;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
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.support.v4.view.MenuItemCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.Toast;
import com.databases.example.R;
import com.databases.example.app.BaseActionMode;
import com.databases.example.app.BaseActionModeInterface;
import com.databases.example.app.RecyclerViewListener;
import com.databases.example.database.DatabaseHelper;
import com.databases.example.database.MyContentProvider;
import com.databases.example.features.checkbook.CheckbookActivity;
import com.databases.example.features.checkbook.accounts.Account;
import com.databases.example.features.checkbook.accounts.AccountsFragment;
import com.databases.example.features.plans.PlansActivity;
import com.databases.example.features.search.SearchActivity;
import com.databases.example.features.search.SearchWidget;
import com.databases.example.utils.Constants;
import com.databases.example.utils.Money;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Locale;
import timber.log.Timber;
public class TransactionsFragment extends Fragment implements OnSharedPreferenceChangeListener, LoaderManager.LoaderCallbacks<Cursor>, BaseActionModeInterface {
public static final String TRANSACTION_FRAG_TAG = "transaction_frag_tag";
public static final int TRANS_LOADER = 987654321;
public static final int TRANS_SEARCH_LOADER = 98765;
public static final int TRANS_SUBCATEGORY_LOADER = 987;
private View myFragmentView;
//Used to determine if fragment should show all transactions
private boolean showAllTransactions = false;
//ID of account transaction belongs to
//TODO make non-static
public static Account account;
private RecyclerView recyclerView = null;
//Constants for ContextMenu
public static final int CONTEXT_MENU_VIEW = 5;
public static final int CONTEXT_MENU_EDIT = 6;
public static final int CONTEXT_MENU_DELETE = 7;
//RecyclerView Adapter
private static TransactionsRecyclerViewAdapter adapterTransactions = null;
//For Autocomplete
public static ArrayList<String> dropdownResults = new ArrayList<String>();
//Adapter for category spinner
public static SimpleCursorAdapter adapterCategory;
//ActionMode
private Object mActionMode = null;
private final String SHOW_ALL_TRANSACTIONS = "boolShowAll";
private final String ADD_FRAGMENT_TAG = "transaction_add_fragment";
private final String EDIT_FRAGMENT_TAG = "transaction_edit_fragment";
private final String VIEW_FRAGMENT_TAG = "transaction_view_fragment";
private final String SORT_FRAGMENT_TAG = "transaction_sort_fragment";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
account = null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
myFragmentView = inflater.inflate(R.layout.transactions, container, false);
recyclerView = (RecyclerView) myFragmentView.findViewById(R.id.transaction_list);
//Turn clicks on
recyclerView.setClickable(true);
recyclerView.setLongClickable(true);
//Set up a listener for changes in settings menu
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.getActivity());
prefs.registerOnSharedPreferenceChangeListener(this);
adapterCategory = new SimpleCursorAdapter(this.getActivity(), android.R.layout.simple_spinner_item, null, new String[]{DatabaseHelper.SUBCATEGORY_NAME}, new int[]{android.R.id.text1}, 0);
adapterCategory.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
adapterTransactions = new TransactionsRecyclerViewAdapter(getActivity(), null, new RecyclerViewListener() {
@Override
public void onItemClick(Object model, int position) {
if (mActionMode != null) {
listItemChecked(position);
} else {
String item = adapterTransactions.getTransaction(position).name;
Toast.makeText(TransactionsFragment.this.getActivity(), "Click\nRow: " + position + "\nEntry: " + item, Toast.LENGTH_SHORT).show();
}
}
@Override
public boolean onItemLongClick(Object model, int position) {
if (mActionMode != null) {
return false;
}
listItemChecked(position);
return true;
}
});
recyclerView.setAdapter(adapterTransactions);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
//Call Loaders to get data
populate();
//Arguments
Bundle bundle = getArguments();
//bundle is empty if from search, so don't add extra menu options
if (bundle != null) {
setHasOptionsMenu(true);
}
setRetainInstance(false);
return myFragmentView;
}
//Used for ActionMode
private void listItemChecked(int position) {
adapterTransactions.toggleSelection(position);
boolean hasCheckedItems = adapterTransactions.getSelectedCount() > 0;
if (hasCheckedItems && mActionMode == null) {
BaseActionMode baseActionMode = new BaseActionMode();
baseActionMode.setBaseActionModeInterface(this);
mActionMode = getActivity().startActionMode(baseActionMode);
} else if (!hasCheckedItems && mActionMode != null) {
((ActionMode) mActionMode).finish();
}
if (mActionMode != null) {
((ActionMode) mActionMode).invalidate();
((ActionMode) mActionMode).setTitle(String.valueOf(adapterTransactions.getSelectedCount()));
}
}
//Populate view with all the transactions of selected account
private void populate() {
Bundle bundle = getArguments();
boolean searchFragment = true;
if (bundle != null) {
showAllTransactions = bundle.getBoolean(CheckbookActivity.SHOW_ALL_KEY);
searchFragment = bundle.getBoolean(SearchActivity.BOOLEAN_SEARCH_KEY);
if (!showAllTransactions && !searchFragment) {
account = bundle.getParcelable(AccountsFragment.ACCOUNT_KEY);
}
Timber.v("searchFragment=" + searchFragment + "\nshowAllTransactions=" + showAllTransactions + "\nAccount_id=" + account);
}
if (showAllTransactions) {
Bundle b = new Bundle();
b.putBoolean(SHOW_ALL_TRANSACTIONS, true);
Timber.v("start loader (all transactions)...");
getLoaderManager().initLoader(TRANS_LOADER, b, this);
} else if (searchFragment) {
String query = getActivity().getIntent().getStringExtra(SearchActivity.QUERY_KEY);
try {
Bundle b = new Bundle();
b.putBoolean(SearchActivity.BOOLEAN_SEARCH_KEY, true);
b.putString(SearchActivity.QUERY_KEY, query);
Timber.v("start search loader...");
getLoaderManager().initLoader(TRANS_SEARCH_LOADER, b, this);
} catch (Exception e) {
Timber.e("SearchActivity Failed. Error e=" + e);
Toast.makeText(getActivity(), "SearchActivity Failed\n" + e, Toast.LENGTH_SHORT).show();
//return;
}
} else {
Bundle b = new Bundle();
b.putParcelable("aID", account); //Not sure if we need this, "aID" doesn't seem to be used anywhere...
Timber.v("start loader (" + DatabaseHelper.TRANS_ACCT_ID + "=" + account.id + ")...");
getLoaderManager().initLoader(TRANS_LOADER, b, this);
}
//Load the categories
getLoaderManager().initLoader(TRANS_SUBCATEGORY_LOADER, null, this);
}
//For Adding a Transaction
private void transactionAdd() {
if (account == null) {
Timber.w("No account selected before attempting to add transaction...");
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
alertDialogBuilder.setTitle(R.string.no_account_selected);
alertDialogBuilder.setMessage(R.string.select_an_account);
alertDialogBuilder.setNeutralButton(R.string.okay, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
alertDialogBuilder.create().show();
} else {
TransactionWizard frag = TransactionWizard.newInstance(null);
frag.show(getChildFragmentManager(), ADD_FRAGMENT_TAG);
}
}
//For Sorting TransactionsFragment
private void transactionSort() {
DialogFragment newFragment = TransactionSortDialogFragment.newInstance();
newFragment.show(getChildFragmentManager(), SORT_FRAGMENT_TAG);
}
//For Menu
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
View account_frame = getActivity().findViewById(R.id.account_frag_frame);
if (account_frame != null) {
SubMenu subMMenuTransaction = menu.addSubMenu(R.string.transaction);
subMMenuTransaction.add(Menu.NONE, R.id.transaction_menu_add, Menu.NONE, R.string.add);
subMMenuTransaction.add(Menu.NONE, R.id.transaction_menu_schedule, Menu.NONE, R.string.schedule);
subMMenuTransaction.add(Menu.NONE, R.id.transaction_menu_sort, Menu.NONE, R.string.sort);
MenuItem subMenu1Item = subMMenuTransaction.getItem();
subMenu1Item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
} else {
menu.clear();
inflater.inflate(R.menu.transaction_menu, menu);
new SearchWidget(getActivity(), (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.transaction_menu_search)));
}
}
//For Menu Items
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.transaction_menu_add:
transactionAdd();
return true;
case R.id.transaction_menu_schedule:
Intent intentPlans = new Intent(getActivity(), PlansActivity.class);
getActivity().startActivity(intentPlans);
return true;
case R.id.transaction_menu_sort:
transactionSort();
return true;
}
return super.onOptionsItemSelected(item);
}
//Used after a change in settings occurs
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
Timber.v("OptionsActivity Changed");
if (!isDetached()) {
Timber.d("Transaction is attached");
//Toast.makeText(this.getActivity(), "Transaction is attached", Toast.LENGTH_SHORT).show();
//populate();
} else {
Timber.d("Transaction is detached");
//Toast.makeText(this.getActivity(), "Transaction is detached", Toast.LENGTH_SHORT).show();
}
}
@Override
public Loader<Cursor> onCreateLoader(int loaderID, Bundle bundle) {
getActivity().setProgressBarIndeterminateVisibility(true);
String sortOrder = PreferenceManager.getDefaultSharedPreferences(getActivity())
.getString(getString(R.string.pref_key_transaction_sort), null);
switch (loaderID) {
case TRANS_LOADER:
if (bundle != null && bundle.getBoolean(SHOW_ALL_TRANSACTIONS)) {
Timber.v("new loader (ShowAll) created");
return new CursorLoader(
getActivity(), // Parent activity context
MyContentProvider.TRANSACTIONS_URI,// Table to query
null, // Projection to return
null, // No selection clause
null, // No selection arguments
sortOrder // Default sort order
);
} else {
String selection = DatabaseHelper.TRANS_ACCT_ID + "=" + account.id;
Timber.v("new loader created");
return new CursorLoader(
getActivity(), // Parent activity context
MyContentProvider.TRANSACTIONS_URI,// Table to query
null, // Projection to return
selection, // No selection clause
null, // No selection arguments
sortOrder // Default sort order
);
}
case TRANS_SEARCH_LOADER:
String query = getActivity().getIntent().getStringExtra(SearchActivity.QUERY_KEY);
Timber.v("new loader (boolSearch " + query + ") created");
return new CursorLoader(
getActivity(), // Parent activity context
(Uri.parse(MyContentProvider.TRANSACTIONS_URI + "/SEARCH/" + query)),// Table to query
null, // Projection to return
null, // No selection clause
null, // No selection arguments
sortOrder // Default sort order
);
case TRANS_SUBCATEGORY_LOADER:
Timber.v("new category loader created");
return new CursorLoader(
getActivity(), // Parent activity context
MyContentProvider.SUBCATEGORIES_URI,// Table to query
null, // Projection to return
null, // No selection clause
null, // No selection arguments
sortOrder // Default sort order
);
default:
Timber.e("Not a valid CursorLoader ID");
return null;
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
TextView footerTV = (TextView) this.myFragmentView.findViewById(R.id.transaction_footer);
switch (loader.getId()) {
case TRANS_LOADER:
ArrayList<Transaction> transactions = Transaction.getTransactions(data);
adapterTransactions.setTransactions(transactions);
Timber.v("loader finished. loader=" + loader.getId() + " data=" + data + " data size=" + data.getCount());
BigDecimal totalBalance = BigDecimal.ZERO;
Locale locale = getResources().getConfiguration().locale;
for (Transaction transaction : transactions) {
if (transaction.type.equals(Constants.DEPOSIT)) {
totalBalance = totalBalance.add(new Money(transaction.value).getBigDecimal(locale));
} else {
totalBalance = totalBalance.subtract(new Money(transaction.value).getBigDecimal(locale));
}
}
try {
footerTV.setText("Total Balance: " + new Money(totalBalance).getNumberFormat(locale));
} catch (Exception e) {
Timber.e("Error setting balance TextView. e=" + e);
}
if (account != null) {
ContentValues values = new ContentValues();
values.put(DatabaseHelper.ACCOUNT_BALANCE, totalBalance + "");
getActivity().getContentResolver().update(Uri.parse(MyContentProvider.ACCOUNTS_URI + "/" + account.id), values, DatabaseHelper.ACCOUNT_ID + "=" + account.id, null);
}
break;
case TRANS_SEARCH_LOADER:
adapterTransactions.setTransactions(Transaction.getTransactions(data));
Timber.v("loader finished. loader=" + loader.getId() + " data=" + data + " data size=" + data.getCount());
try {
footerTV.setText("SearchActivity Results");
} catch (Exception e) {
Timber.e("Error setting search TextView. e=" + e);
}
break;
case TRANS_SUBCATEGORY_LOADER:
adapterCategory.swapCursor(data);
break;
default:
Timber.e("Error. Unknown loader (" + loader.getId());
break;
}
if (!getActivity().getSupportLoaderManager().hasRunningLoaders()) {
getActivity().setProgressBarIndeterminateVisibility(false);
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
switch (loader.getId()) {
case TRANS_LOADER:
adapterTransactions.setTransactions(null);
Timber.v("loader reset. loader=" + loader.getId());
break;
case TRANS_SEARCH_LOADER:
adapterTransactions.setTransactions(null);
Timber.v("loader reset. loader=" + loader.getId());
break;
case TRANS_SUBCATEGORY_LOADER:
adapterCategory.swapCursor(null);
Timber.v("loader reset. loader=" + loader.getId());
break;
default:
Timber.e("Error. Unknown loader (" + loader.getId());
break;
}
}
@Override
public void onDestroyView() {
if (mActionMode != null) {
((ActionMode) mActionMode).finish();
}
super.onDestroyView();
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
menu.add(0, CONTEXT_MENU_VIEW, 0, R.string.view).setIcon(android.R.drawable.ic_menu_view);
menu.add(0, CONTEXT_MENU_EDIT, 1, R.string.edit).setIcon(android.R.drawable.ic_menu_edit);
menu.add(0, CONTEXT_MENU_DELETE, 2, R.string.delete).setIcon(android.R.drawable.ic_menu_delete);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu.clear();
if (adapterTransactions.getSelectedCount() == 1 && mode != null) {
menu.add(0, CONTEXT_MENU_VIEW, 0, R.string.view).setIcon(android.R.drawable.ic_menu_view);
menu.add(0, CONTEXT_MENU_EDIT, 1, R.string.edit).setIcon(android.R.drawable.ic_menu_edit);
menu.add(0, CONTEXT_MENU_DELETE, 2, R.string.delete).setIcon(android.R.drawable.ic_menu_delete);
return true;
} else if (adapterTransactions.getSelectedCount() > 1) {
menu.add(0, CONTEXT_MENU_DELETE, 2, R.string.delete).setIcon(android.R.drawable.ic_menu_delete);
return true;
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
adapterTransactions.removeSelection();
}
@Override
public boolean viewClicked(ActionMode mode, MenuItem item, SparseBooleanArray selectedIds) {
for (int i = 0; i < selectedIds.size(); i++) {
if (selectedIds.valueAt(i)) {
DialogFragment newFragment = TransactionViewFragment.newInstance(adapterTransactions.getTransaction(selectedIds.keyAt(i)).id);
newFragment.show(getChildFragmentManager(), VIEW_FRAGMENT_TAG);
}
}
mode.finish();
return true;
}
@Override
public boolean editClicked(ActionMode mode, MenuItem item, SparseBooleanArray selectedIds) {
for (int i = 0; i < selectedIds.size(); i++) {
if (selectedIds.valueAt(i)) {
final Transaction record = adapterTransactions.getTransaction(selectedIds.keyAt(i));
final TransactionWizard frag = TransactionWizard.newInstance(record);
frag.show(getChildFragmentManager(), EDIT_FRAGMENT_TAG);
}
}
mode.finish();
return true;
}
@Override
public boolean deleteClicked(ActionMode mode, MenuItem item, SparseBooleanArray selectedIds) {
Transaction record;
for (int i = 0; i < selectedIds.size(); i++) {
if (selectedIds.valueAt(i)) {
record = adapterTransactions.getTransaction(selectedIds.keyAt(i));
Uri uri = Uri.parse(MyContentProvider.TRANSACTIONS_URI + "/" + record.id);
getActivity().getContentResolver().delete(uri, DatabaseHelper.TRANS_ID + "=" + record.id, null);
Toast.makeText(getActivity(), "Deleted Transaction:\n" + record.name, Toast.LENGTH_SHORT).show();
}
}
mode.finish();
return true;
}
@Override
public SparseBooleanArray getSelectedIds() {
return adapterTransactions.getSelectedIds();
}
}