package com.greenaddress.greenbits.ui;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.nfc.Tag;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.blockstream.libwally.Wally;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.greenaddress.greenapi.ConfidentialAddress;
import com.greenaddress.greenapi.JSONMap;
import com.greenaddress.greenapi.Network;
import com.greenaddress.greenbits.GaService;
import com.greenaddress.greenbits.QrBitmap;
import java.util.concurrent.Callable;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.uri.BitcoinURI;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Observer;
import nordpol.android.OnDiscoveredTagListener;
import nordpol.android.TagDispatcher;
public class ReceiveFragment extends SubaccountFragment implements OnDiscoveredTagListener, AmountFields.OnConversionFinishListener, Exchanger.OnCalculateCommissionFinishListener {
private static final String TAG = ReceiveFragment.class.getSimpleName();
private FutureCallback<QrBitmap> mNewAddressCallback;
private FutureCallback<Void> mNewAddressFinished;
private QrBitmap mQrCodeBitmap;
private int mSubaccount;
private Dialog mQrCodeDialog;
private TagDispatcher mTagDispatcher;
private TextView mAddressText;
private ImageView mAddressImage;
private TextView mCopyIcon;
private final Runnable mDialogCB = new Runnable() { public void run() { mQrCodeDialog = null; } };
private EditText mAmountEdit;
private EditText mAmountFiatEdit;
private TextView mAmountFiatWithCommission;
private String mCurrentAddress = "";
private Coin mCurrentAmount;
private BitmapWorkerTask mBitmapWorkerTask;
private AmountFields mAmountFields;
private Exchanger mExchanger;
private boolean mIsExchanger;
private Button mShowQrCode;
private Observer mNewTxObserver;
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume -> " + TAG);
if (mIsExchanger && getGAService() != null)
attachObservers();
if (mAmountFields != null)
mAmountFields.setIsPausing(false);
}
@Override
public void onPause() {
super.onPause();
if (mAmountFields != null)
mAmountFields.setIsPausing(true);
Log.d(TAG, "onPause -> " + TAG);
if (mQrCodeDialog != null) {
mQrCodeDialog.dismiss();
mQrCodeDialog = null;
}
if (mTagDispatcher != null)
mTagDispatcher.disableExclusiveNfc();
detachObservers();
}
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
Log.d(TAG, "onCreateView -> " + TAG);
if (isZombieNoView())
return null;
final GaActivity gaActivity = getGaActivity();
mTagDispatcher = TagDispatcher.get(gaActivity, this);
mTagDispatcher.enableExclusiveNfc();
mSubaccount = getGAService().getCurrentSubAccount();
if (savedInstanceState != null)
mIsExchanger = savedInstanceState.getBoolean("isExchanger", false);
if (mIsExchanger)
mView = inflater.inflate(R.layout.fragment_buy, container, false);
else
mView = inflater.inflate(R.layout.fragment_receive, container, false);
mAmountFields = new AmountFields(getGAService(), getContext(), mView, this);
if (savedInstanceState != null) {
final Boolean pausing = savedInstanceState.getBoolean("pausing", false);
mAmountFields.setIsPausing(pausing);
}
mAddressText = UI.find(mView, R.id.receiveAddressText);
mAddressImage = UI.find(mView, R.id.receiveQrImageView);
mCopyIcon = UI.find(mView, R.id.receiveCopyIcon);
UI.disable(mCopyIcon);
mCopyIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
onCopyClicked();
}
});
mNewAddressCallback = new FutureCallback<QrBitmap>() {
@Override
public void onSuccess(final QrBitmap result) {
if (getActivity() != null)
getActivity().runOnUiThread(new Runnable() {
public void run() {
onNewAddressGenerated(result);
}
});
}
@Override
public void onFailure(final Throwable t) {
t.printStackTrace();
if (getActivity() != null)
getActivity().runOnUiThread(new Runnable() {
public void run() {
hideWaitDialog();
UI.enable(mCopyIcon);
}
});
}
};
final TextView newAddressIcon = UI.find(mView, R.id.receiveNewAddressIcon);
newAddressIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
generateNewAddress();
}
});
mAmountEdit = UI.find(mView, R.id.sendAmountEditText);
mAmountFiatEdit = UI.find(mView, R.id.sendAmountFiatEditText);
final View amountFields = UI.find(mView, R.id.amountFields);
UI.showIf(getGAService().cfg().getBoolean("showAmountInReceive", false) || mIsExchanger, amountFields);
if (mIsExchanger) {
setPageSelected(true);
mAmountFiatWithCommission = UI.find(mView, R.id.amountFiatWithCommission);
mExchanger = new Exchanger(getContext(), getGAService(), mView, true, this);
mShowQrCode = UI.find(mView, R.id.showQrCode);
mShowQrCode.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
final String amountStr = UI.getText(mAmountFiatWithCommission);
final float amount = Float.valueOf(amountStr);
if (amount > mExchanger.getFiatInBill()) {
UI.toast(getGaActivity(), R.string.noEnoughMoneyInPocket, Toast.LENGTH_LONG);
return;
}
final String amountBtc = mAmountEdit.getText().toString();
if (amountBtc.isEmpty() || Float.valueOf(amountBtc) <= 0) {
UI.toast(getGaActivity(), R.string.invalidAmount, Toast.LENGTH_LONG);
return;
}
generateNewAddress(false, new FutureCallback<Void>() {
@Override
public void onSuccess(final Void aVoid) {
String exchangerAddress = mCurrentAddress;
if (GaService.IS_ELEMENTS) {
final String currentBtcAddress = mCurrentAddress.replace("bitcoin:", "").split("\\?")[0];
exchangerAddress = ConfidentialAddress.fromBase58(Network.NETWORK, currentBtcAddress).getBitcoinAddress().toString();
}
getGAService().cfg().edit().putBoolean("exchanger_address_" + exchangerAddress, true).apply();
final BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), mQrCodeBitmap.getQRCode());
bitmapDrawable.setFilterBitmap(false);
mAddressImage.setImageDrawable(bitmapDrawable);
onAddressImageClicked(bitmapDrawable);
}
@Override
public void onFailure(final Throwable t) {
t.printStackTrace();
}
});
}
});
}
registerReceiver();
return mView;
}
@Override
public void conversionFinish() {
if (mIsExchanger && mExchanger != null) {
mExchanger.conversionFinish();
} else {
if (mBitmapWorkerTask != null)
mBitmapWorkerTask.cancel(true);
mBitmapWorkerTask = new BitmapWorkerTask();
mBitmapWorkerTask.execute();
}
}
@Override
public void calculateCommissionFinish() {
if (mBitmapWorkerTask != null)
mBitmapWorkerTask.cancel(true);
mBitmapWorkerTask = new BitmapWorkerTask();
mBitmapWorkerTask.execute();
}
class BitmapWorkerTask extends AsyncTask<Object, Object, Bitmap> {
@Override
protected Bitmap doInBackground(final Object... integers) {
final String amount = UI.getText(mAmountEdit);
mCurrentAmount = null;
if (amount.isEmpty())
return mQrCodeBitmap == null ? null : resetBitmap(mCurrentAddress);
try {
mCurrentAmount = UI.parseCoinValue(getGAService(), amount);
final Address address = Address.fromBase58(Network.NETWORK, mCurrentAddress);
final String qrCodeText = BitcoinURI.convertToBitcoinURI(address, mCurrentAmount, null, null);
return resetBitmap(qrCodeText);
} catch (final ArithmeticException | IllegalArgumentException e) {
return resetBitmap(mCurrentAddress);
}
}
@Override
protected void onPostExecute(final Bitmap bitmap) {
if (bitmap == null)
return;
final BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap);
bitmapDrawable.setFilterBitmap(false);
mAddressImage.setImageDrawable(bitmapDrawable);
mAddressImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
onAddressImageClicked(bitmapDrawable);
}
});
}
private Bitmap resetBitmap(final String address) {
final int TRANSPARENT = 0; // Transparent background
mQrCodeBitmap = new QrBitmap(address, TRANSPARENT);
return mQrCodeBitmap.getQRCode();
}
}
private void generateNewAddress() {
generateNewAddress(true, null);
}
private void generateNewAddress(final boolean clear, final FutureCallback<Void> onDone) {
Log.d(TAG, "Generating new address for subaccount " + mSubaccount);
if (isZombie())
return;
Long amount = null;
if (clear)
UI.clear(mAmountEdit, mAmountFiatEdit);
if (mIsExchanger && GaService.IS_ELEMENTS) {
// TODO: non-fiat / non-assets values
if (mAmountEdit.getText().toString().isEmpty())
return;
amount = (long) (Float.valueOf(mAmountEdit.getText().toString()).floatValue() * 100);
}
mCurrentAddress = "";
UI.disable(mCopyIcon);
destroyCurrentAddress(clear);
mNewAddressFinished = onDone;
final Callable waitFn = new Callable<Void>() {
@Override
public Void call() {
popupWaitDialog(R.string.generating_address);
return null;
}
};
Futures.addCallback(getGAService().getNewAddressBitmap(mSubaccount, waitFn, amount),
mNewAddressCallback, getGAService().getExecutor());
}
private void destroyCurrentAddress(final boolean clear) {
Log.d(TAG, "Destroying address for subaccount " + mSubaccount);
if (isZombie())
return;
mCurrentAddress = "";
if (clear)
UI.clear(mAmountEdit, mAmountFiatEdit, mAddressText);
mAddressImage.setImageBitmap(null);
UI.hide(mView);
}
private void onCopyClicked() {
// Gets a handle to the clipboard service.
final GaActivity gaActivity = getGaActivity();
final ClipboardManager cm;
cm = (ClipboardManager) gaActivity.getSystemService(Context.CLIPBOARD_SERVICE);
final ClipData data = ClipData.newPlainText("data", mQrCodeBitmap.getData());
cm.setPrimaryClip(data);
final String text = gaActivity.getString(R.string.toastOnCopyAddress) +
' ' + gaActivity.getString(R.string.warnOnPaste);
gaActivity.toast(text);
}
private void onAddressImageClicked(final BitmapDrawable bd) {
if (mQrCodeDialog != null)
mQrCodeDialog.dismiss();
final View v;
if (mIsExchanger) {
v = getActivity().getLayoutInflater().inflate(R.layout.dialog_qrcode_exchanger, null, false);
final Button cancelBtn = UI.find(v, R.id.cancelBtn);
cancelBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
mQrCodeDialog.dismiss();
}
});
} else {
v = getActivity().getLayoutInflater().inflate(R.layout.dialog_qrcode, null, false);
}
final ImageView qrCode = UI.find(v, R.id.qrInDialogImageView);
qrCode.setLayoutParams(UI.getScreenLayout(getActivity(), 0.8));
final Dialog dialog = new Dialog(getActivity());
dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(v);
UI.setDialogCloseHandler(dialog, mDialogCB);
qrCode.setImageDrawable(bd);
mQrCodeDialog = dialog;
mQrCodeDialog.show();
}
private void onNewAddressGenerated(final QrBitmap result) {
if (getActivity() == null)
return;
if (mBitmapWorkerTask != null) {
mBitmapWorkerTask.cancel(true);
mBitmapWorkerTask = null;
}
mQrCodeBitmap = result;
final BitmapDrawable bd = new BitmapDrawable(getResources(), result.getQRCode());
bd.setFilterBitmap(false);
mAddressImage.setImageDrawable(bd);
final String qrData = result.getData();
if (GaService.IS_ELEMENTS) {
mAddressText.setText(String.format("%s\n" +
"%s\n%s\n" +
"%s\n%s\n" +
"%s\n%s",
qrData.substring(0, 12),
qrData.substring(12, 24),
qrData.substring(24, 36),
qrData.substring(36, 48),
qrData.substring(48, 60),
qrData.substring(60, 72),
qrData.substring(72)
));
mAddressText.setLines(7);
mAddressText.setMaxLines(7);
} else {
mAddressText.setText(String.format("%s\n%s\n%s", qrData.substring(0, 12),
qrData.substring(12, 24), qrData.substring(24)));
}
mCurrentAddress = result.getData();
mAddressImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
onAddressImageClicked(bd);
}
});
hideWaitDialog();
UI.enable(mCopyIcon);
UI.show(mView);
if (mNewAddressFinished != null)
mNewAddressFinished.onSuccess(null);
}
@Override
public void tagDiscovered(final Tag t) {
Log.d(TAG, "Tag discovered " + t);
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate -> " + TAG);
}
@Override
protected void onSubaccountChanged(final int newSubAccount) {
mSubaccount = newSubAccount;
if (IsPageSelected())
generateNewAddress();
else
destroyCurrentAddress(true);
}
private String getAddressUri() {
final String addr;
if (GaService.IS_ELEMENTS)
addr = ConfidentialAddress.fromBase58(Network.NETWORK, mCurrentAddress).toString();
else
addr = Address.fromBase58(Network.NETWORK, mCurrentAddress).toString();
return BitcoinURI.convertToBitcoinURI(Network.NETWORK, addr, mCurrentAmount, null, null);
}
@Override
public void onShareClicked() {
if (mQrCodeBitmap == null || mQrCodeBitmap.getData().isEmpty())
return;
final Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, getAddressUri());
intent.setType("text/plain");
startActivity(intent);
}
public void setPageSelected(final boolean isSelected) {
final boolean needToRegenerate = isSelected && !IsPageSelected();
super.setPageSelected(isSelected);
if (needToRegenerate)
generateNewAddress();
else if (!isSelected)
destroyCurrentAddress(true);
}
@Override
public void onViewStateRestored(final Bundle savedInstanceState) {
Log.d(TAG, "onViewStateRestored -> " + TAG);
super.onViewStateRestored(savedInstanceState);
if (mAmountFields != null)
mAmountFields.setIsPausing(false);
if (mIsExchanger)
mExchanger.conversionFinish();
}
@Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
if (mAmountFields != null)
outState.putBoolean("pausing", mAmountFields.isPausing());
outState.putBoolean("isExchanger", mIsExchanger);
}
public void setIsExchanger(final boolean isExchanger) {
mIsExchanger = isExchanger;
}
@Override
public void attachObservers() {
if (mNewTxObserver == null) {
mNewTxObserver = makeUiObserver(new Runnable() { public void run() { onNewTx(); } });
getGAService().addNewTxObserver(mNewTxObserver);
}
super.attachObservers();
}
@Override
public void detachObservers() {
super.detachObservers();
if (mNewTxObserver!= null) {
getGAService().deleteNewTxObserver(mNewTxObserver);
mNewTxObserver = null;
}
}
private void onNewTx() {
final GaService service = getGAService();
Futures.addCallback(service.getMyTransactions(mSubaccount),
new FutureCallback<Map<String, Object>>() {
@Override
public void onSuccess(final Map<String, Object> result) {
final List txList = (List) result.get("list");
final int currentBlock = ((Integer) result.get("cur_block"));
for (final Object tx : txList) {
try {
final JSONMap txJSON = (JSONMap) tx;
final ArrayList<String> replacedList = txJSON.get("replaced_by");
if (replacedList == null) {
final TransactionItem txItem = new TransactionItem(service, txJSON, currentBlock);
final boolean matches;
if (!GaService.IS_ELEMENTS)
matches = txItem.receivedOn != null && txItem.receivedOn.equals(mCurrentAddress);
else {
final int subaccount = txItem.receivedOnEp.getInt("subaccount", 0);
final int pointer = txItem.receivedOnEp.getInt("pubkey_pointer");
final String receivedOn = ConfidentialAddress.fromP2SHHash(
Network.NETWORK,
Wally.hash160(service.createOutScript(subaccount, pointer)),
service.getBlindingPubKey(subaccount, pointer)
).toString();
final String currentBtcAddress = mCurrentAddress.replace("bitcoin:", "").split("\\?")[0];
matches = receivedOn.equals(currentBtcAddress);
}
if (matches) {
final float amountFiat = Float.valueOf(mExchanger.getAmountWithCommission());
mExchanger.buyBtc(amountFiat);
getGaActivity().toast(R.string.transactionCompleted);
getGaActivity().finish();
}
}
} catch (final ParseException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(final Throwable throwable) {
throwable.printStackTrace();
}
});
}
}