package com.code44.finance.ui.transactions; import android.content.Context; import android.database.Cursor; import android.support.v4.util.LongSparseArray; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.code44.finance.R; import com.code44.finance.common.model.TransactionState; import com.code44.finance.common.model.TransactionType; import com.code44.finance.common.utils.StringUtils; import com.code44.finance.data.db.Tables; import com.code44.finance.data.model.Currency; import com.code44.finance.data.model.Tag; import com.code44.finance.data.model.Transaction; import com.code44.finance.ui.common.BaseModelsAdapter; import com.code44.finance.utils.BaseInterval; import com.code44.finance.utils.MoneyFormatter; import com.code44.finance.utils.TextBackgroundSpan; import org.joda.time.DateTime; import org.joda.time.Interval; import org.joda.time.Period; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; public class TransactionsAdapter extends BaseModelsAdapter implements StickyListHeadersAdapter { private static final String UNKNOWN_VALUE = "?"; private static final String TRANSFER_SYMBOL = " → "; private final Currency mainCurrency; private final BaseInterval interval; private final LongSparseArray<Long> totalExpenses; private final String unknownExpenseTitle; private final String unknownIncomeTitle; private final String unknownTransferTitle; private final int unknownExpenseColor; private final int unknownIncomeColor; private final int unknownTransferColor; private final int expenseAmountColor; private final int incomeAmountColor; private final int transferAmountColor; private final int primaryColor; private final int weakColor; private final int tagBackgroundColor; private final float tagBackgroundRadius; public TransactionsAdapter(Context context, Currency mainCurrency, BaseInterval interval) { super(context); this.mainCurrency = mainCurrency; this.interval = interval; totalExpenses = new LongSparseArray<>(); unknownExpenseTitle = context.getString(R.string.expense); unknownIncomeTitle = context.getString(R.string.income); unknownTransferTitle = context.getString(R.string.transfer); expenseAmountColor = context.getResources().getColor(R.color.text_primary); incomeAmountColor = context.getResources().getColor(R.color.text_positive); transferAmountColor = context.getResources().getColor(R.color.text_neutral); unknownExpenseColor = context.getResources().getColor(R.color.text_negative); unknownIncomeColor = context.getResources().getColor(R.color.text_positive); unknownTransferColor = context.getResources().getColor(R.color.text_neutral); primaryColor = context.getResources().getColor(R.color.text_primary); weakColor = context.getResources().getColor(R.color.text_weak); tagBackgroundColor = context.getResources().getColor(R.color.bg_secondary); tagBackgroundRadius = context.getResources().getDimension(R.dimen.tag_radius); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { final View view = LayoutInflater.from(context).inflate(R.layout.li_transaction, parent, false); ViewHolder.setAsTag(view); return view; } @Override public void bindView(View view, Context context, Cursor cursor) { final ViewHolder holder = (ViewHolder) view.getTag(); final Transaction transaction = Transaction.from(cursor); final DateTime date = new DateTime(transaction.getDate()); // Set values holder.weekday_TV.setText(date.dayOfWeek().getAsShortText()); holder.day_TV.setText(date.dayOfMonth().getAsShortText()); holder.color_IV.setColorFilter(getCategoryColor(transaction)); holder.amount_TV.setText(MoneyFormatter.format(transaction)); holder.title_TV.setTextColor(primaryColor); holder.title_TV.setText(getTitle(transaction)); holder.subtitle_TV.setText(getSubtitle(transaction)); bindViewForTransactionType(holder, transaction); if (transaction.getTransactionState() == TransactionState.Pending) { bindViewPending(holder, transaction); } } @Override public View getHeaderView(int position, View convertView, ViewGroup parent) { getCursor().moveToPosition(position); final HeaderViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.li_transaction_header, parent, false); holder = HeaderViewHolder.setAsTag(convertView); holder.arrow_IV.setColorFilter(holder.title_TV.getCurrentTextColor()); } else { holder = (HeaderViewHolder) convertView.getTag(); } final String title; final TransactionState transactionState = TransactionState.fromInt(getCursor().getInt(getCursor().getColumnIndex(Tables.Transactions.STATE.getName()))); if (transactionState == TransactionState.Confirmed) { final BaseInterval.Type type = interval.getType(); final long date = getCursor().getLong(getCursor().getColumnIndex(Tables.Transactions.DATE.getName())); final Period period = BaseInterval.getPeriod(type, interval.getLength()); final Interval interval = BaseInterval.getInterval(date, period, type); title = BaseInterval.getTitle(mContext, interval, type); } else { title = mContext.getString(R.string.pending); } holder.title_TV.setText(title); holder.amount_TV.setText(MoneyFormatter.format(mainCurrency, getTotalExpenseForPosition(position))); return convertView; } @Override public long getHeaderId(int position) { getCursor().moveToPosition(position); final TransactionState transactionState = TransactionState.fromInt(getCursor().getInt(getCursor().getColumnIndex(Tables.Transactions.STATE.getName()))); if (transactionState == TransactionState.Pending) { return 0; } final BaseInterval.Type type = interval.getType(); final long date = getCursor().getLong(getCursor().getColumnIndex(Tables.Transactions.DATE.getName())); final Period period = BaseInterval.getPeriod(type, interval.getLength()); final Interval interval = BaseInterval.getInterval(date, period, type); return interval.getStartMillis(); } @Override public Cursor swapCursor(Cursor newCursor) { totalExpenses.clear(); return super.swapCursor(newCursor); } private String getTitle(Transaction transaction) { if (StringUtils.isEmpty(transaction.getNote())) { if (transaction.getCategory() != null && transaction.getCategory().hasId()) { return transaction.getCategory().getTitle(); } else { switch (transaction.getTransactionType()) { case Expense: return unknownExpenseTitle; case Income: return unknownIncomeTitle; case Transfer: return unknownTransferTitle; default: throw new IllegalArgumentException("Transaction type " + transaction.getTransactionType() + " is not supported."); } } } else { return transaction.getNote(); } } private CharSequence getSubtitle(Transaction transaction) { if (transaction.getTags().size() > 0) { final SpannableStringBuilder subtitle = new SpannableStringBuilder(); for (Tag tag : transaction.getTags()) { subtitle.append(tag.getTitle()); subtitle.setSpan(new TextBackgroundSpan(tagBackgroundColor, tagBackgroundRadius), subtitle.length() - tag.getTitle().length(), subtitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); subtitle.append(" "); } return subtitle; } else if (transaction.getCategory() != null && !StringUtils.isEmpty(transaction.getNote())) { return transaction.getCategory().getTitle(); } return null; } private int getCategoryColor(Transaction transaction) { if (transaction.getCategory() == null) { switch (transaction.getTransactionType()) { case Expense: return unknownExpenseColor; case Income: return unknownIncomeColor; case Transfer: return unknownTransferColor; default: throw new IllegalArgumentException("Transaction type " + transaction.getTransactionType() + " is not supported."); } } else { return transaction.getCategory().getColor(); } } private void bindViewForTransactionType(ViewHolder holder, Transaction transaction) { final String account; final int amountColor; switch (transaction.getTransactionType()) { case Expense: account = transaction.getAccountFrom() != null ? transaction.getAccountFrom().getTitle() : UNKNOWN_VALUE; amountColor = expenseAmountColor; break; case Income: account = transaction.getAccountTo() != null ? transaction.getAccountTo().getTitle() : UNKNOWN_VALUE; amountColor = incomeAmountColor; break; case Transfer: account = (transaction.getAccountFrom() != null ? transaction.getAccountFrom().getTitle() : UNKNOWN_VALUE) + TRANSFER_SYMBOL + (transaction.getAccountTo() != null ? transaction.getAccountTo().getTitle() : UNKNOWN_VALUE); amountColor = transferAmountColor; break; default: throw new IllegalArgumentException("Transaction type " + transaction.getTransactionType() + " is not supported."); } holder.account_TV.setText(account); holder.amount_TV.setTextColor(amountColor); } private void bindViewPending(ViewHolder holder, Transaction transaction) { final boolean isCategoryUnknown = transaction.getCategory() == null || !transaction.getCategory().hasId(); final boolean isTransfer = transaction.getTransactionType() == TransactionType.Transfer; if (isCategoryUnknown && !isTransfer) { holder.title_TV.setTextColor(weakColor); holder.color_IV.setColorFilter(weakColor); } if (transaction.getAmount() == 0) { holder.amount_TV.setTextColor(weakColor); } final boolean isAccountFromUnknown = transaction.getAccountFrom() == null || !transaction.getAccountFrom().hasId(); final boolean isAccountToUnknown = transaction.getAccountTo() == null || !transaction.getAccountTo().hasId(); final boolean isExpense = transaction.getTransactionType() == TransactionType.Expense; final boolean isIncome = transaction.getTransactionType() == TransactionType.Income; if (isExpense) { if (isAccountFromUnknown) { holder.account_TV.setText(UNKNOWN_VALUE); } if (transaction.getAmount() > 0) { holder.amount_TV.setTextColor(expenseAmountColor); } } else if (isIncome) { if (isAccountToUnknown) { holder.account_TV.setText(UNKNOWN_VALUE); } if (transaction.getAmount() > 0) { holder.amount_TV.setTextColor(incomeAmountColor); } } else { final String accountFrom = isAccountFromUnknown ? UNKNOWN_VALUE : transaction.getAccountFrom().getTitle(); final String accountTo = isAccountToUnknown ? UNKNOWN_VALUE : transaction.getAccountTo().getTitle(); holder.account_TV.setText(accountFrom + TRANSFER_SYMBOL + accountTo); if (transaction.getAmount() > 0) { holder.amount_TV.setTextColor(transferAmountColor); } } } private long getTotalExpenseForPosition(int position) { final long headerId = getHeaderId(position); long totalExpense = totalExpenses.get(headerId, -1L); if (totalExpense != -1) { return totalExpense; } totalExpense = 0; final Cursor cursor = getCursor(); final int iTransactionType = cursor.getColumnIndex(Tables.Transactions.TYPE.getName()); final int iAmount = cursor.getColumnIndex(Tables.Transactions.AMOUNT.getName()); final int iAccountFromCurrencyServerId = cursor.getColumnIndex(Tables.Currencies.ID.getName(Tables.Currencies.TEMP_TABLE_NAME_FROM_CURRENCY)); final int iAccountFromCurrencyExchangeRate = cursor.getColumnIndex(Tables.Currencies.EXCHANGE_RATE.getName(Tables.Currencies.TEMP_TABLE_NAME_FROM_CURRENCY)); final int iIncludeInReports = cursor.getColumnIndex(Tables.Transactions.INCLUDE_IN_REPORTS.getName()); do { if (TransactionType.fromInt(cursor.getInt(iTransactionType)) == TransactionType.Expense && cursor.getInt(iIncludeInReports) != 0) { final long amount = cursor.getLong(iAmount); if (mainCurrency.getId().equals(cursor.getString(iAccountFromCurrencyServerId))) { totalExpense += amount; } else { totalExpense += amount * cursor.getDouble(iAccountFromCurrencyExchangeRate); } } } while (cursor.moveToNext() && getHeaderId(cursor.getPosition()) == headerId); totalExpenses.put(headerId, totalExpense); return totalExpense; } private static class ViewHolder { public ImageView color_IV; public TextView weekday_TV; public TextView day_TV; public TextView title_TV; public TextView subtitle_TV; public TextView amount_TV; public TextView account_TV; public static ViewHolder setAsTag(View view) { final ViewHolder holder = new ViewHolder(); holder.color_IV = (ImageView) view.findViewById(R.id.colorImageView); holder.weekday_TV = (TextView) view.findViewById(R.id.weekdayTextView); holder.day_TV = (TextView) view.findViewById(R.id.dayTextView); holder.title_TV = (TextView) view.findViewById(R.id.titleTextView); holder.subtitle_TV = (TextView) view.findViewById(R.id.subtitleTextView); holder.amount_TV = (TextView) view.findViewById(R.id.amountTextView); holder.account_TV = (TextView) view.findViewById(R.id.accountTextView); view.setTag(holder); return holder; } } private static class HeaderViewHolder { public ImageView arrow_IV; public TextView title_TV; public TextView amount_TV; public static HeaderViewHolder setAsTag(View view) { final HeaderViewHolder holder = new HeaderViewHolder(); holder.arrow_IV = (ImageView) view.findViewById(R.id.arrow_IV); holder.title_TV = (TextView) view.findViewById(R.id.titleTextView); holder.amount_TV = (TextView) view.findViewById(R.id.amountTextView); view.setTag(holder); return holder; } } }