package com.mygeopay.wallet.ui; /* * Copyright 2011-2014 the original author or authors. * * 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/>. */ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Typeface; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import com.mygeopay.core.coins.CoinType; import com.mygeopay.core.coins.Value; import com.mygeopay.core.util.GenericUtils; import com.mygeopay.core.wallet.AbstractWallet; import com.mygeopay.wallet.AddressBookProvider; import com.mygeopay.wallet.R; import com.mygeopay.wallet.ui.widget.CurrencyTextView; import com.mygeopay.wallet.util.Fonts; import com.mygeopay.wallet.util.WalletUtils; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionConfidence; import org.bitcoinj.core.TransactionConfidence.ConfidenceType; /** * @author Andreas Schildbach */ public class TransactionsListAdapter extends BaseAdapter { private final Context context; private final LayoutInflater inflater; private final AbstractWallet walletPocket; private final List<Transaction> transactions = new ArrayList<Transaction>(); private final Resources res; private int precision = 0; private int shift = 0; private boolean showEmptyText = false; private final int colorSignificant; private final int colorLessSignificant; private final int colorInsignificant; private final int colorError; private final int colorCircularBuilding = Color.parseColor("#44ff44"); private final String minedTitle; private final String fontIconMined; private final String sentToTitle; private final String fontIconSentTo; private final String receivedWithTitle; private final String fontIconReceivedWith; private final Map<String, String> labelCache = new HashMap<String, String>(); private final static Object CACHE_NULL_MARKER = ""; private static final String CONFIDENCE_SYMBOL_DEAD = "\u271D"; // latin cross private static final String CONFIDENCE_SYMBOL_UNKNOWN = "?"; private static final int VIEW_TYPE_TRANSACTION = 0; public TransactionsListAdapter(final Context context, @Nonnull final AbstractWallet walletPocket) { this.context = context; inflater = LayoutInflater.from(context); this.walletPocket = walletPocket; res = context.getResources(); colorSignificant = res.getColor(R.color.gray_87_text); colorLessSignificant = res.getColor(R.color.gray_54_sec_text_icons); colorInsignificant = res.getColor(R.color.gray_26_hint_text); colorError = res.getColor(R.color.fg_error); minedTitle = res.getString(R.string.wallet_transactions_coinbase); fontIconMined = res.getString(R.string.font_icon_mining); sentToTitle = res.getString(R.string.sent_to); fontIconSentTo = res.getString(R.string.font_icon_send_coins); receivedWithTitle = res.getString(R.string.received_with); fontIconReceivedWith = res.getString(R.string.font_icon_receive_coins); } public void setPrecision(final int precision, final int shift) { this.precision = precision; this.shift = shift; notifyDataSetChanged(); } public void clear() { transactions.clear(); notifyDataSetChanged(); } public void replace(@Nonnull final Transaction tx) { transactions.clear(); transactions.add(tx); notifyDataSetChanged(); } public void replace(@Nonnull final Collection<Transaction> transactions) { this.transactions.clear(); this.transactions.addAll(transactions); showEmptyText = true; notifyDataSetChanged(); } @Override public boolean isEmpty() { return showEmptyText && super.isEmpty(); } @Override public int getCount() { return transactions.size(); } @Override public Transaction getItem(final int position) { if (position == transactions.size()) return null; return transactions.get(position); } @Override public long getItemId(final int position) { if (position == transactions.size()) return 0; return WalletUtils.longHash(transactions.get(position).getHash()); } @Override public boolean hasStableIds() { return true; } @Override public View getView(final int position, View row, final ViewGroup parent) { final int type = getItemViewType(position); if (type == VIEW_TYPE_TRANSACTION) { if (row == null) row = inflater.inflate(R.layout.transaction_row, null); final Transaction tx = getItem(position); bindView(row, tx); } else { throw new IllegalStateException("unknown type: " + type); } return row; } public void bindView(@Nonnull final View row, @Nonnull final Transaction tx) { Resources res = context.getResources(); final TransactionConfidence confidence = tx.getConfidence(); final ConfidenceType confidenceType = confidence.getConfidenceType(); final boolean isOwn = confidence.getSource().equals(TransactionConfidence.Source.SELF); final boolean isMined = tx.isCoinBase() || tx.isCoinStake(); // final boolean isInternal = WalletUtils.isInternal(tx); final Coin value = tx.getValue(walletPocket); final boolean sent = value.signum() < 0; final CoinType type = walletPocket.getCoinType(); // TODO set colors as theme, not here in code final TextView rowDirectionText = (TextView) row.findViewById(R.id.transaction_row_direction_text); final TextView rowDirectionFontIcon = (TextView) row.findViewById(R.id.transaction_row_direction_font_icon); Fonts.setTypeface(rowDirectionFontIcon, Fonts.Font.COINOMI_FONT_ICONS); final TextView rowConfirmationsFontIcon = (TextView) row.findViewById(R.id.transaction_row_confirmations_font_icon); Fonts.setTypeface(rowConfirmationsFontIcon, Fonts.Font.COINOMI_FONT_ICONS); // TODO implement date // final TextView rowDate = (TextView) row.findViewById(R.id.transaction_row_time); final TextView rowLabel = (TextView) row.findViewById(R.id.transaction_row_label); final TextView rowAddress = (TextView) row.findViewById(R.id.transaction_row_address); final CurrencyTextView rowValue = (CurrencyTextView) row.findViewById(R.id.transaction_row_value); // confidence if (confidenceType == ConfidenceType.PENDING) { rowLabel.setTextColor(colorInsignificant); rowValue.setTextColor(colorInsignificant); rowDirectionText.setTextColor(colorInsignificant); rowDirectionFontIcon.setTextColor(colorInsignificant); rowDirectionFontIcon.setBackgroundResource(R.drawable.transaction_row_circle_bg_pending); } else if (confidenceType == ConfidenceType.BUILDING) { rowLabel.setTextColor(colorSignificant); rowValue.setTextColor(colorSignificant); rowDirectionText.setTextColor(colorLessSignificant); rowDirectionFontIcon.setTextColor(colorLessSignificant); if (value.isNegative()) { rowDirectionFontIcon.setBackgroundResource(R.drawable.transaction_row_circle_bg_send); rowValue.setTextColor(res.getColor(R.color.send_color_fg)); } else { rowDirectionFontIcon.setBackgroundResource(R.drawable.transaction_row_circle_bg_receive); rowValue.setTextColor(res.getColor(R.color.receive_color_fg)); } } else if (confidenceType == ConfidenceType.DEAD) { rowLabel.setTextColor(colorSignificant); rowValue.setTextColor(colorSignificant); Fonts.strikeThrough(rowLabel); Fonts.strikeThrough(rowValue); } else { rowDirectionText.setTextColor(colorError); rowLabel.setTextColor(colorInsignificant); rowValue.setTextColor(colorInsignificant); rowDirectionFontIcon.setTextColor(colorInsignificant); rowDirectionFontIcon.setBackgroundResource(R.drawable.transaction_row_circle_bg_pending); } // Confirmations if (confidence.getDepthInBlocks() < 4) { rowConfirmationsFontIcon.setVisibility(View.VISIBLE); rowConfirmationsFontIcon.setTextColor(colorLessSignificant); switch (confidence.getDepthInBlocks()) { case 0: // No confirmations rowConfirmationsFontIcon.setText(res.getString(R.string.font_icon_progress_empty)); rowConfirmationsFontIcon.setTextColor(colorInsignificant); // PENDING break; case 1: // 1 out of 3 confirmations rowConfirmationsFontIcon.setText(res.getString(R.string.font_icon_progress_one)); break; case 2: // 2 out of 3 confirmations rowConfirmationsFontIcon.setText(res.getString(R.string.font_icon_progress_two)); break; case 3: // 3 out of 3 confirmations rowConfirmationsFontIcon.setText(res.getString(R.string.font_icon_progress_full)); break; } } else { rowConfirmationsFontIcon.setVisibility(View.GONE); } // Money direction and icon if (isMined) { rowDirectionText.setText(minedTitle); rowDirectionFontIcon.setText(fontIconMined); } else { if (value.isNegative()) { rowDirectionText.setText(sentToTitle); rowDirectionFontIcon.setText(fontIconSentTo); } else { rowDirectionText.setText(receivedWithTitle); rowDirectionFontIcon.setText(fontIconReceivedWith); } } // date // final Date time = tx.getUpdateTime(); // rowDate.setText(time != null ? (DateUtils.getRelativeTimeSpanString(context, time.getTime())) : null); // address - label final Address address; final String label; if (sent) { // we send payment to those addresses List<Address> sentTo = WalletUtils.getSendToAddress(tx, walletPocket); // For now show only the first address address = sentTo.isEmpty() ? null : sentTo.get(0); } else { // received with those addresses List<Address> receivedWith = WalletUtils.getReceivedWithAddress(tx, walletPocket); // Should be one address = receivedWith.isEmpty() ? null : receivedWith.get(0); } if (address != null) { label = resolveLabel(address.toString()); } else { if (sent) { // If no address found, assume it is an internal transfer label = res.getString(R.string.internal_transfer); } else { label = "?"; } } if (label != null) { rowLabel.setText(label); if (address != null) { rowAddress.setText(GenericUtils.addressSplitToGroups(address.toString())); rowAddress.setVisibility(View.VISIBLE); } else { rowAddress.setVisibility(View.GONE); } } else if (address != null) { rowLabel.setText(GenericUtils.addressSplitToGroups(address.toString())); rowAddress.setVisibility(View.GONE); } else { rowLabel.setText("???"); // should not happen } rowAddress.setVisibility(View.GONE); rowLabel.setTypeface(label != null ? Typeface.DEFAULT : Typeface.MONOSPACE); // value rowValue.setAlwaysSigned(true); rowValue.setAmount(Value.valueOf(type, value)); } private String resolveLabel(@Nonnull final String address) { final String cachedLabel = labelCache.get(address); if (cachedLabel == null) { final String label = AddressBookProvider.resolveLabel(context, walletPocket.getCoinType(), address); if (label != null) { labelCache.put(address, label); } else { labelCache.put(address, (String)CACHE_NULL_MARKER); } return label; } else { return cachedLabel != CACHE_NULL_MARKER ? cachedLabel : null; } } public void clearLabelCache() { labelCache.clear(); notifyDataSetChanged(); } }