/*
* 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.adapter;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Color;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.CursorAdapter;
import android.text.Html;
import android.text.TextUtils;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.money.manager.ex.Constants;
import com.money.manager.ex.MoneyManagerApplication;
import com.money.manager.ex.R;
import com.money.manager.ex.core.TransactionTypes;
import com.money.manager.ex.currency.CurrencyService;
import com.money.manager.ex.database.QueryAllData;
import com.money.manager.ex.database.QueryBillDeposits;
import com.money.manager.ex.database.TransactionStatus;
import com.money.manager.ex.utils.MmxDate;
import com.money.manager.ex.utils.MmxDateTimeUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import info.javaperformance.money.Money;
import info.javaperformance.money.MoneyFactory;
/**
* Adapter for all_data query. The list of transactions (account/recurring).
*/
public class AllDataAdapter
extends CursorAdapter {
public AllDataAdapter(Context context, Cursor c, TypeCursor typeCursor) {
super(context, c, -1);
this.mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mHeadersAccountIndex = new HashMap<>();
mCheckedPosition = new SparseBooleanArray();
mTypeCursor = typeCursor;
mContext = context;
this.requestingBalanceUpdate = new ArrayList<>();
setFieldFromTypeCursor();
}
// source type: AllData or RecurringTransaction
public enum TypeCursor {
ALLDATA,
RECURRINGTRANSACTION
}
// type cursor
private TypeCursor mTypeCursor = TypeCursor.ALLDATA;
// define cursor field
public String ID, DATE, ACCOUNTID, STATUS, AMOUNT, TRANSACTIONTYPE,
CURRENCYID, PAYEE, ACCOUNTNAME, CATEGORY, SUBCATEGORY, NOTES,
TOCURRENCYID, TOACCOUNTID, TOAMOUNT, TOACCOUNTNAME;
private LayoutInflater mInflater;
// hash map for group
private HashMap<Integer, Integer> mHeadersAccountIndex;
private SparseBooleanArray mCheckedPosition;
// account and currency
private int mAccountId = Constants.NOT_SET;
private int mCurrencyId = Constants.NOT_SET;
// show account name and show balance
private boolean mShowAccountName = false;
private boolean mShowBalanceAmount = false;
private Context mContext;
private HashMap<Integer, Money> balances;
private ArrayList<TextView> requestingBalanceUpdate;
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.item_alldata_account, parent, false);
// view holder pattern
AllDataViewHolder holder = new AllDataViewHolder();
// take a pointer of object UI
holder.linDate = (LinearLayout) view.findViewById(R.id.linearLayoutDate);
holder.txtDay = (TextView) view.findViewById(R.id.textViewDay);
holder.txtMonth = (TextView) view.findViewById(R.id.textViewMonth);
holder.txtYear = (TextView) view.findViewById(R.id.textViewYear);
holder.txtStatus = (TextView) view.findViewById(R.id.textViewStatus);
holder.txtAmount = (TextView) view.findViewById(R.id.textViewAmount);
holder.txtPayee = (TextView) view.findViewById(R.id.textViewPayee);
holder.txtAccountName = (TextView) view.findViewById(R.id.textViewAccountName);
holder.txtCategorySub = (TextView) view.findViewById(R.id.textViewCategorySub);
holder.txtNotes = (TextView) view.findViewById(R.id.textViewNotes);
holder.txtBalance = (TextView) view.findViewById(R.id.textViewBalance);
// set holder to view
view.setTag(holder);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
// take a holder
AllDataViewHolder holder = (AllDataViewHolder) view.getTag();
String transactionType = cursor.getString(cursor.getColumnIndex(TRANSACTIONTYPE));
boolean isTransfer = TransactionTypes.valueOf(transactionType).equals(TransactionTypes.Transfer);
// header index
int accountId = cursor.getInt(cursor.getColumnIndex(TOACCOUNTID));
if (!mHeadersAccountIndex.containsKey(accountId)) {
mHeadersAccountIndex.put(accountId, cursor.getPosition());
}
// Status
String status = cursor.getString(cursor.getColumnIndex(STATUS));
holder.txtStatus.setText(TransactionStatus.getStatusAsString(mContext, status));
// color status
int colorBackground = TransactionStatus.getBackgroundColorFromStatus(mContext, status);
holder.linDate.setBackgroundColor(colorBackground);
holder.txtStatus.setTextColor(Color.GRAY);
// Date
String dateString = cursor.getString(cursor.getColumnIndex(DATE));
if (!TextUtils.isEmpty(dateString)) {
Locale locale = MoneyManagerApplication.getApp().getAppLocale();
MmxDateTimeUtils dateUtils = new MmxDateTimeUtils(locale);
Date dateTime = new MmxDate(dateString).toDate();
String month = dateUtils.format(dateTime, "MMM");
holder.txtMonth.setText(month);
String year = dateUtils.format(dateTime, "yyyy");
holder.txtYear.setText(year);
String day = dateUtils.format(dateTime, "dd");
holder.txtDay.setText(day);
}
// Amount
double amount;
if (useDestinationValues(isTransfer, cursor)) {
amount = cursor.getDouble(cursor.getColumnIndex(TOAMOUNT));
setCurrencyId(cursor.getInt(cursor.getColumnIndex(TOCURRENCYID)));
} else {
amount = cursor.getDouble(cursor.getColumnIndex(AMOUNT));
setCurrencyId(cursor.getInt(cursor.getColumnIndex(CURRENCYID)));
}
CurrencyService currencyService = new CurrencyService(mContext);
holder.txtAmount.setText(currencyService.getCurrencyFormatted(getCurrencyId(), MoneyFactory.fromDouble(amount)));
// text color amount
int amountTextColor;
if (isTransfer) {
amountTextColor = ContextCompat.getColor(mContext, R.color.material_grey_700);
} else if (TransactionTypes.valueOf(transactionType).equals(TransactionTypes.Deposit)) {
amountTextColor = ContextCompat.getColor(mContext, R.color.material_green_700);
} else {
amountTextColor = ContextCompat.getColor(mContext, R.color.material_red_700);
}
holder.txtAmount.setTextColor(amountTextColor);
// Group header - account name.
if (isShowAccountName()) {
if (mHeadersAccountIndex.containsValue(cursor.getPosition())) {
holder.txtAccountName.setText(cursor.getString(cursor.getColumnIndex(TOACCOUNTNAME)));
holder.txtAccountName.setVisibility(View.VISIBLE);
} else {
holder.txtAccountName.setVisibility(View.GONE);
}
} else {
holder.txtAccountName.setVisibility(View.GONE);
}
// Payee
String payee = getPayeeName(cursor, isTransfer);
holder.txtPayee.setText(payee);
// compose category description
String categorySub;
if (!isTransfer) {
categorySub = cursor.getString(cursor.getColumnIndex(CATEGORY));
// check sub category
if (!(TextUtils.isEmpty(cursor.getString(cursor.getColumnIndex(SUBCATEGORY))))) {
categorySub += " : <i>" + cursor.getString(cursor.getColumnIndex(SUBCATEGORY)) + "</i>";
}
// write category/subcategory format html
if (!TextUtils.isEmpty(categorySub)) {
// Display category/sub-category.
categorySub = Html.fromHtml(categorySub).toString();
} else {
// It is either a Transfer or a split category.
// then it is a split? todo: improve this check to make it explicit.
categorySub = mContext.getString(R.string.split_category);
}
} else {
categorySub = mContext.getString(R.string.transfer);
}
holder.txtCategorySub.setText(categorySub);
// notes
if (!TextUtils.isEmpty(cursor.getString(cursor.getColumnIndex(NOTES)))) {
holder.txtNotes.setText(Html.fromHtml("<small>" + cursor.getString(cursor.getColumnIndex(NOTES)) + "</small>"));
holder.txtNotes.setVisibility(View.VISIBLE);
} else {
holder.txtNotes.setVisibility(View.GONE);
}
// check if item is checked
if (mCheckedPosition.get(cursor.getPosition(), false)) {
view.setBackgroundResource(R.color.material_green_100);
} else {
view.setBackgroundResource(android.R.color.transparent);
}
// Display balance account or days left.
displayBalanceAmountOrDaysLeft(holder, cursor, context);
}
public void clearPositionChecked() {
mCheckedPosition.clear();
}
public SparseBooleanArray getPositionsChecked() {
return mCheckedPosition;
}
/**
* Set checked in position
*/
public void setPositionChecked(int position, boolean checked) {
mCheckedPosition.put(position, checked);
}
/**
* @return the accountId
*/
public int getAccountId() {
return mAccountId;
}
/**
* @param mAccountId the accountId to set
*/
public void setAccountId(int mAccountId) {
this.mAccountId = mAccountId;
}
/**
* @return the mCurrencyId
*/
public int getCurrencyId() {
return mCurrencyId;
}
/**
* @param mCurrencyId the mCurrencyId to set
*/
public void setCurrencyId(int mCurrencyId) {
this.mCurrencyId = mCurrencyId;
}
/**
* @return the mShowAccountName
*/
public boolean isShowAccountName() {
return mShowAccountName;
}
public void resetAccountHeaderIndexes() {
mHeadersAccountIndex.clear();
}
/**
* @param showAccountName the mShowAccountName to set
*/
public void setShowAccountName(boolean showAccountName) {
this.mShowAccountName = showAccountName;
}
/**
* @return the mShowBalanceAmount
*/
public boolean isShowBalanceAmount() {
return mShowBalanceAmount;
}
/**
* @param mShowBalanceAmount the mShowBalanceAmount to set
*/
public void setShowBalanceAmount(boolean mShowBalanceAmount) {
this.mShowBalanceAmount = mShowBalanceAmount;
}
public void setFieldFromTypeCursor() {
ID = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.ID : QueryBillDeposits.BDID;
DATE = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.Date : QueryBillDeposits.NEXTOCCURRENCEDATE;
ACCOUNTID = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.ACCOUNTID : QueryBillDeposits.TOACCOUNTID;
STATUS = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.Status : QueryBillDeposits.STATUS;
AMOUNT = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.Amount : QueryBillDeposits.AMOUNT;
PAYEE = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.Payee : QueryBillDeposits.PAYEENAME;
TRANSACTIONTYPE = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.TransactionType : QueryBillDeposits.TRANSCODE;
CURRENCYID = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.CURRENCYID : QueryBillDeposits.CURRENCYID;
TOACCOUNTID = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.TOACCOUNTID : QueryBillDeposits.TOACCOUNTID;
TOAMOUNT = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.ToAmount : QueryBillDeposits.TOTRANSAMOUNT;
TOCURRENCYID = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.ToCurrencyId : QueryBillDeposits.CURRENCYID;
TOACCOUNTNAME = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.ToAccountName : QueryBillDeposits.TOACCOUNTNAME;
ACCOUNTNAME = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.AccountName : QueryBillDeposits.TOACCOUNTNAME;
CATEGORY = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.Category : QueryBillDeposits.CATEGNAME;
SUBCATEGORY = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.Subcategory : QueryBillDeposits.SUBCATEGNAME;
NOTES = mTypeCursor == TypeCursor.ALLDATA ? QueryAllData.Notes : QueryBillDeposits.NOTES;
}
public void setBalances(HashMap<Integer, Money> balances) {
this.balances = balances;
// update the balances on visible elements.
for (TextView textView : this.requestingBalanceUpdate) {
showBalanceAmount(textView);
}
this.requestingBalanceUpdate.clear();
}
/**
* Display the running balance on account transactions list, or days left on
* recurring transactions list.
*/
private void displayBalanceAmountOrDaysLeft(AllDataViewHolder holder, Cursor cursor,
Context context) {
if (mTypeCursor == TypeCursor.ALLDATA) {
if (isShowBalanceAmount()) {
// create thread for calculate balance amount
// calculateBalanceAmount(cursor, holder);
// Save transaction Id.
int txId = cursor.getInt(cursor.getColumnIndex(QueryAllData.ID));
holder.txtBalance.setTag(txId);
requestBalanceDisplay(holder.txtBalance);
} else {
holder.txtBalance.setVisibility(View.GONE);
}
} else {
int daysLeft = cursor.getInt(cursor.getColumnIndex(QueryBillDeposits.DAYSLEFT));
if (daysLeft == 0) {
holder.txtBalance.setText(R.string.due_today);
} else {
holder.txtBalance.setText(Integer.toString(Math.abs(daysLeft)) + " " +
context.getString(daysLeft > 0 ? R.string.days_remaining : R.string.days_overdue));
}
holder.txtBalance.setVisibility(View.VISIBLE);
}
}
/**
* The most important indicator. Detects whether the values should be from FROM or TO
* record.
* @return boolean indicating whether to use *TO values (amountTo)
*/
private boolean useDestinationValues(boolean isTransfer, Cursor cursor) {
boolean result;
if (mTypeCursor.equals(TypeCursor.RECURRINGTRANSACTION)) {
// Recurring transactions list.
return false;
}
if (isTransfer) {
// Account transactions lists.
if (getAccountId() == Constants.NOT_SET) {
// Search Results
result = true;
} else {
// Account transactions
// See which value to use.
if (getAccountId() == cursor.getInt(cursor.getColumnIndex(TOACCOUNTID))) {
result = true;
} else {
result = false;
}
}
} else {
result = false;
}
return result;
}
private String getPayeeName(Cursor cursor, boolean isTransfer) {
String result;
if (isTransfer) {
// write ToAccountName instead of payee on transfers.
String accountName;
if (mTypeCursor.equals(TypeCursor.RECURRINGTRANSACTION)) {
// Recurring transactions list.
// Show the destination for the transfer.
accountName = cursor.getString(cursor.getColumnIndex(ACCOUNTNAME));
} else {
// Account transactions list.
if (mAccountId == Constants.NOT_SET) {
// Search results or recurring transactions. Account id is always reset (-1).
accountName = cursor.getString(cursor.getColumnIndex(ACCOUNTNAME));
} else {
// Standard checking account. See whether the other account is the source
// or the destination of the transfer.
int cursorAccountId = cursor.getInt(cursor.getColumnIndex(ACCOUNTID));
if (mAccountId != cursorAccountId) {
// This is in account transactions list where we display transfers to and from.
accountName = cursor.getString(cursor.getColumnIndex(ACCOUNTNAME));
} else {
// Search results, where we display only incoming transactions.
accountName = cursor.getString(cursor.getColumnIndex(TOACCOUNTNAME));
}
}
}
if (TextUtils.isEmpty(accountName)) accountName = "-";
// append square brackets around the account name to distinguish transfers visually.
accountName = "[%]".replace("%", accountName);
result = accountName;
} else {
// compose payee description
result = cursor.getString(cursor.getColumnIndex(PAYEE));
}
return result;
}
private void showBalanceAmount(TextView textView) {
if (this.balances == null) {
return;
}
// get id
Object tag = textView.getTag();
if (tag == null) return;
int txId = (int) tag;
if (!this.balances.containsKey(txId)) return;
CurrencyService currencyService = new CurrencyService(mContext);
Money currentBalance = this.balances.get(txId);
String balanceFormatted = currencyService.getCurrencyFormatted(getCurrencyId(), currentBalance);
textView.setText(balanceFormatted);
textView.setVisibility(View.VISIBLE);
}
private void requestBalanceDisplay(TextView textView) {
// if we have balances, display it immediately.
if (this.balances != null) {
showBalanceAmount(textView);
} else {
// hide balance amount.
textView.setVisibility(View.GONE);
// store for later.
this.requestingBalanceUpdate.add(textView);
}
}
}