/* * Copyright 2011-2013 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/>. */ package de.schildbach.wallet.digitalcoin.ui; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Typeface; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import com.google.digitalcoin.core.Address; import com.google.digitalcoin.core.ScriptException; import com.google.digitalcoin.core.Transaction; import com.google.digitalcoin.core.TransactionConfidence; import com.google.digitalcoin.core.TransactionConfidence.ConfidenceType; import com.google.digitalcoin.core.Wallet; import de.schildbach.wallet.digitalcoin.AddressBookProvider; import de.schildbach.wallet.digitalcoin.Constants; import de.schildbach.wallet.digitalcoin.util.CircularProgressView; import de.schildbach.wallet.digitalcoin.util.WalletUtils; import de.schildbach.wallet.digitalcoin.R; /** * @author Andreas Schildbach */ public class TransactionsListAdapter extends BaseAdapter { private final Context context; private final LayoutInflater inflater; private final Wallet wallet; private final int maxConnectedPeers; private final List<Transaction> transactions = new ArrayList<Transaction>(); private int precision = Constants.WDC_PRECISION; private final int colorSignificant; private final int colorInsignificant; private final int colorError; private final int colorCircularBuilding = Color.parseColor("#44ff44"); private final String textCoinBase; private final Map<String, String> labelCache = new HashMap<String, String>(); private final static String CACHE_NULL_MARKER = ""; private static final String CONFIDENCE_SYMBOL_NOT_IN_BEST_CHAIN = "!"; private static final String CONFIDENCE_SYMBOL_DEAD = "\u271D"; // latin cross private static final String CONFIDENCE_SYMBOL_UNKNOWN = "?"; public TransactionsListAdapter(final Context context, final Wallet wallet, final int maxConnectedPeers) { this.context = context; inflater = LayoutInflater.from(context); this.wallet = wallet; this.maxConnectedPeers = maxConnectedPeers; final Resources resources = context.getResources(); colorSignificant = resources.getColor(R.color.fg_significant); colorInsignificant = resources.getColor(R.color.fg_insignificant); colorError = resources.getColor(R.color.fg_error); textCoinBase = context.getString(R.string.wallet_transactions_fragment_coinbase); } public void setPrecision(final int precision) { this.precision = precision; notifyDataSetChanged(); } public void clear() { transactions.clear(); notifyDataSetChanged(); } public void replace(final Transaction tx) { transactions.clear(); transactions.add(tx); notifyDataSetChanged(); } public void replace(final Collection<Transaction> transactions) { this.transactions.clear(); this.transactions.addAll(transactions); notifyDataSetChanged(); } public int getCount() { return transactions.size(); } public Transaction getItem(final int position) { return transactions.get(position); } public long getItemId(final int position) { return WalletUtils.longHash(transactions.get(position).getHash()); } @Override public boolean hasStableIds() { return true; } public View getView(final int position, View row, final ViewGroup parent) { if (row == null) row = inflater.inflate(R.layout.transaction_row, null); final Transaction tx = getItem(position); final TransactionConfidence confidence = tx.getConfidence(); final ConfidenceType confidenceType = confidence.getConfidenceType(); final boolean isOwn = confidence.getSource().equals(TransactionConfidence.Source.SELF); try { final BigInteger value = tx.getValue(wallet); final boolean sent = value.signum() < 0; final CircularProgressView rowConfidenceCircular = (CircularProgressView) row.findViewById(R.id.transaction_row_confidence_circular); final TextView rowConfidenceTextual = (TextView) row.findViewById(R.id.transaction_row_confidence_textual); final int textColor; if (confidenceType == ConfidenceType.NOT_SEEN_IN_CHAIN) { final boolean isValid = isOwn && confidence.numBroadcastPeers() > 1; rowConfidenceCircular.setVisibility(View.VISIBLE); rowConfidenceTextual.setVisibility(View.GONE); textColor = isValid ? colorSignificant : colorInsignificant; rowConfidenceCircular.setProgress(1); rowConfidenceCircular.setMaxProgress(1); rowConfidenceCircular.setSize(confidence.numBroadcastPeers()); rowConfidenceCircular.setMaxSize(maxConnectedPeers - 1); rowConfidenceCircular.setColors(colorInsignificant, colorInsignificant); } else if (confidenceType == ConfidenceType.BUILDING) { rowConfidenceCircular.setVisibility(View.VISIBLE); rowConfidenceTextual.setVisibility(View.GONE); textColor = colorSignificant; rowConfidenceCircular.setProgress(confidence.getDepthInBlocks()); rowConfidenceCircular.setMaxProgress(tx.isCoinBase() ? Constants.NETWORK_PARAMETERS.getSpendableCoinbaseDepth() : Constants.MAX_NUM_CONFIRMATIONS); rowConfidenceCircular.setSize(1); rowConfidenceCircular.setMaxSize(1); rowConfidenceCircular.setColors(colorCircularBuilding, Color.DKGRAY); } else if (confidenceType == ConfidenceType.NOT_IN_BEST_CHAIN) { rowConfidenceCircular.setVisibility(View.GONE); rowConfidenceTextual.setVisibility(View.VISIBLE); textColor = colorSignificant; rowConfidenceTextual.setText(CONFIDENCE_SYMBOL_NOT_IN_BEST_CHAIN); rowConfidenceTextual.setTextColor(Color.RED); } else if (confidenceType == ConfidenceType.DEAD) { rowConfidenceCircular.setVisibility(View.GONE); rowConfidenceTextual.setVisibility(View.VISIBLE); textColor = Color.RED; rowConfidenceTextual.setText(CONFIDENCE_SYMBOL_DEAD); rowConfidenceTextual.setTextColor(Color.RED); } else { rowConfidenceCircular.setVisibility(View.GONE); rowConfidenceTextual.setVisibility(View.VISIBLE); textColor = colorInsignificant; rowConfidenceTextual.setText(CONFIDENCE_SYMBOL_UNKNOWN); rowConfidenceTextual.setTextColor(colorInsignificant); } final TextView rowTime = (TextView) row.findViewById(R.id.transaction_row_time); final Date time = tx.getUpdateTime(); rowTime.setText(time != null ? (DateUtils.getRelativeTimeSpanString(context, time.getTime())) : null); rowTime.setTextColor(textColor); final TextView rowFromTo = (TextView) row.findViewById(R.id.transaction_row_fromto); rowFromTo.setText(sent ? R.string.symbol_to : R.string.symbol_from); rowFromTo.setTextColor(textColor); final TextView rowAddress = (TextView) row.findViewById(R.id.transaction_row_address); final Address address = sent ? WalletUtils.getToAddress(tx) : WalletUtils.getFromAddress(tx); final String label; if (tx.isCoinBase()) label = textCoinBase; else if (address != null) label = resolveLabel(address.toString()); else label = "?"; rowAddress.setTextColor(textColor); rowAddress.setText(label != null ? label : address.toString()); rowAddress.setTypeface(label != null ? Typeface.DEFAULT : Typeface.MONOSPACE); final CurrencyTextView rowValue = (CurrencyTextView) row.findViewById(R.id.transaction_row_value); rowValue.setTextColor(textColor); rowValue.setAlwaysSigned(true); rowValue.setPrecision(precision); rowValue.setAmount(value); final View rowExtend = row.findViewById(R.id.transaction_row_extend); final TextView rowMessage = (TextView) row.findViewById(R.id.transaction_row_message); final boolean isLocked = tx.getLockTime() > 0; rowExtend.setVisibility(View.GONE); if (isOwn && confidenceType == ConfidenceType.NOT_SEEN_IN_CHAIN && confidence.numBroadcastPeers() <= 1) { rowExtend.setVisibility(View.VISIBLE); rowMessage.setText(R.string.transaction_row_message_own_unbroadcasted); rowMessage.setTextColor(colorInsignificant); } else if (!sent && confidenceType == ConfidenceType.NOT_SEEN_IN_CHAIN && isLocked) { rowExtend.setVisibility(View.VISIBLE); rowMessage.setText(R.string.transaction_row_message_received_unconfirmed_locked); rowMessage.setTextColor(colorError); } else if (!sent && confidenceType == ConfidenceType.NOT_SEEN_IN_CHAIN && !isLocked) { rowExtend.setVisibility(View.VISIBLE); rowMessage.setText(R.string.transaction_row_message_received_unconfirmed_unlocked); rowMessage.setTextColor(colorInsignificant); } else if (!sent && confidenceType == ConfidenceType.NOT_IN_BEST_CHAIN) { rowExtend.setVisibility(View.VISIBLE); rowMessage.setText(R.string.transaction_row_message_received_unconfirmed_unlocked); rowMessage.setTextColor(colorError); } else if (!sent && confidenceType == ConfidenceType.DEAD) { rowExtend.setVisibility(View.VISIBLE); rowMessage.setText(R.string.transaction_row_message_received_dead); rowMessage.setTextColor(colorError); } return row; } catch (final ScriptException x) { throw new RuntimeException(x); } } private String resolveLabel(final String address) { final String cachedLabel = labelCache.get(address); if (cachedLabel == null) { final String label = AddressBookProvider.resolveLabel(context, address); if (label != null) labelCache.put(address, label); else labelCache.put(address, CACHE_NULL_MARKER); return label; } else { return cachedLabel != CACHE_NULL_MARKER ? cachedLabel : null; } } public void clearLabelCache() { labelCache.clear(); notifyDataSetChanged(); } }