package com.mygeopay.wallet.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.mygeopay.core.coins.CoinType;
import com.mygeopay.core.coins.FiatType;
import com.mygeopay.core.coins.Value;
import com.mygeopay.core.uri.CoinURI;
import com.mygeopay.core.util.ExchangeRate;
import com.mygeopay.core.util.GenericUtils;
import com.mygeopay.core.wallet.WalletPocketHD;
import com.mygeopay.core.wallet.exceptions.Bip44KeyLookAheadExceededException;
import com.mygeopay.wallet.AddressBookProvider;
import com.mygeopay.wallet.Configuration;
import com.mygeopay.wallet.Constants;
import com.mygeopay.wallet.ExchangeRatesProvider;
import com.mygeopay.wallet.R;
import com.mygeopay.wallet.WalletApplication;
import com.mygeopay.wallet.ui.widget.AmountEditView;
import com.mygeopay.wallet.util.LayoutUtils;
import com.mygeopay.wallet.util.Qr;
import com.mygeopay.wallet.util.UiUtils;
import com.mygeopay.wallet.util.ThrottlingWalletChangeListener;
import com.mygeopay.wallet.util.WeakHandler;
import org.bitcoinj.core.Address;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static com.mygeopay.core.Preconditions.checkNotNull;
/**
*
*/
public class AddressRequestFragment extends Fragment {
private static final Logger log = LoggerFactory.getLogger(AddressRequestFragment.class);
private static final int UPDATE_VIEW = 0;
private static final int UPDATE_EXCHANGE_RATE = 1;
// Loader IDs
private static final int ID_RATE_LOADER = 0;
@Nullable private Address showAddress;
private Address receiveAddress;
private Value amount;
private String label;
private String message;
private CoinType type;
private TextView addressLabelView;
private TextView addressView;
private ImageView qrView;
private CurrencyCalculatorLink amountCalculatorLink;
private View previousAddressesLink;
private NavigationDrawerFragment mNavigationDrawerFragment;
private String accountId;
private WalletPocketHD pocket;
private int maxQrSize;
private String lastQrContent;
private final Handler handler = new MyHandler(this);
private Configuration config;
private ContentResolver resolver;
private LoaderManager loaderManager;
private static class MyHandler extends WeakHandler<AddressRequestFragment> {
public MyHandler(AddressRequestFragment ref) { super(ref); }
@Override
protected void weakHandleMessage(AddressRequestFragment ref, Message msg) {
switch (msg.what) {
case UPDATE_VIEW:
ref.updateView();
break;
case UPDATE_EXCHANGE_RATE:
ref.amountCalculatorLink.setExchangeRate((ExchangeRate) msg.obj);
}
}
}
private final ContentObserver addressBookObserver = new ContentObserver(handler) {
@Override
public void onChange(final boolean selfChange) {
updateView();
}
};
public static AddressRequestFragment newInstance(Bundle args) {
AddressRequestFragment fragment = new AddressRequestFragment();
fragment.setArguments(args);
return fragment;
}
public static AddressRequestFragment newInstance(String accountId) {
return newInstance(accountId, null);
}
public static AddressRequestFragment newInstance(String accountId, @Nullable Address showAddress) {
Bundle args = new Bundle();
args.putString(Constants.ARG_ACCOUNT_ID, accountId);
if (showAddress != null) {
args.putSerializable(Constants.ARG_ADDRESS, showAddress);
}
return newInstance(args);
}
public AddressRequestFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WalletApplication walletApplication = (WalletApplication) getActivity().getApplication();
Bundle args = getArguments();
if (args != null) {
accountId = args.getString(Constants.ARG_ACCOUNT_ID);
if (args.containsKey(Constants.ARG_ADDRESS)) {
showAddress = (Address) args.getSerializable(Constants.ARG_ADDRESS);
}
}
// TODO
pocket = (WalletPocketHD) checkNotNull(walletApplication.getAccount(accountId));
if (pocket == null) {
Toast.makeText(getActivity(), R.string.no_such_pocket_error, Toast.LENGTH_LONG).show();
return;
}
type = pocket.getCoinType();
setHasOptionsMenu(true);
mNavigationDrawerFragment = (NavigationDrawerFragment)
getFragmentManager().findFragmentById(R.id.navigation_drawer);
maxQrSize = LayoutUtils.calculateMaxQrCodeSize(getResources());
loaderManager.initLoader(ID_RATE_LOADER, null, rateLoaderCallbacks);
}
@Override
public void onDestroy() {
loaderManager.destroyLoader(ID_RATE_LOADER);
super.onDestroy();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_request, container, false);
addressLabelView = (TextView) view.findViewById(R.id.request_address_label);
addressView = (TextView) view.findViewById(R.id.request_address);
View.OnClickListener addressOnClickListener = getAddressOnClickListener();
addressLabelView.setOnClickListener(addressOnClickListener);
addressView.setOnClickListener(addressOnClickListener);
qrView = (ImageView) view.findViewById(R.id.qr_code);
AmountEditView sendCoinAmountView = (AmountEditView) view.findViewById(R.id.send_coin_amount);
sendCoinAmountView.setType(type);
sendCoinAmountView.setFormat(type.getMonetaryFormat());
AmountEditView sendLocalAmountView = (AmountEditView) view.findViewById(R.id.send_local_amount);
sendLocalAmountView.setFormat(FiatType.FRIENDLY_FORMAT);
amountCalculatorLink = new CurrencyCalculatorLink(sendCoinAmountView, sendLocalAmountView);
previousAddressesLink = view.findViewById(R.id.view_previous_addresses);
previousAddressesLink.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), PreviousAddressesActivity.class);
intent.putExtra(Constants.ARG_ACCOUNT_ID, accountId);
startActivity(intent);
}
});
updateView();
pocket.addEventListener(walletListener);
return view;
}
private View.OnClickListener getAddressOnClickListener() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
if (showAddress != null) {
receiveAddress = showAddress;
}
Activity activity = getActivity();
ActionMode actionMode = UiUtils.startAddressActionMode(receiveAddress, activity,
getFragmentManager());
// Hack to dismiss this action mode when back is pressed
if (activity != null && activity instanceof WalletActivity) {
((WalletActivity) activity).registerActionMode(actionMode);
}
}
};
}
@Override
public void onResume() {
super.onResume();
amountCalculatorLink.setListener(amountsListener);
resolver.registerContentObserver(AddressBookProvider.contentUri(
getActivity().getPackageName(), type), true, addressBookObserver);
updateView();
}
@Override
public void onPause() {
resolver.unregisterContentObserver(addressBookObserver);
amountCalculatorLink.setListener(null);
super.onPause();
}
@Override
public void onDestroyView() {
pocket.removeEventListener(walletListener);
walletListener.removeCallbacks();
super.onDestroyView();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Only show items in the action bar relevant to this screen
// if the drawer is not showing. Otherwise, let the drawer
// decide what to show in the action bar.
if (mNavigationDrawerFragment == null || !mNavigationDrawerFragment.isDrawerOpen()) {
if (showAddress == null) {
inflater.inflate(R.menu.request, menu);
} else {
inflater.inflate(R.menu.request_single_address, menu);
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_share:
UiUtils.share(getActivity(), receiveAddress.toString());
return true;
case R.id.action_copy:
UiUtils.copy(getActivity(), receiveAddress.toString());
return true;
case R.id.action_new_address:
createNewAddressDialog.show(getFragmentManager(), null);
return true;
case R.id.action_edit_label:
EditAddressBookEntryFragment.edit(getFragmentManager(), type, receiveAddress.toString());
return true;
default:
// Not one of ours. Perform default menu processing
return super.onOptionsItemSelected(item);
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.resolver = activity.getContentResolver();
this.config = ((WalletApplication) activity.getApplication()).getConfiguration();
this.loaderManager = getLoaderManager();
}
DialogFragment createNewAddressDialog = new DialogFragment() {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog;
DialogInterface.OnClickListener dismissListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dismissAllowingStateLoss();
}
};
if (pocket.canCreateFreshReceiveAddress()) {
final LayoutInflater inflater = LayoutInflater.from(getActivity());
final View view = inflater.inflate(R.layout.new_address_dialog, null);
final TextView viewLabel = (TextView) view.findViewById(R.id.new_address_label);
final DialogBuilder builder = new DialogBuilder(getActivity());
builder.setTitle(R.string.create_new_address);
builder.setView(view);
builder.setNegativeButton(R.string.button_cancel, dismissListener);
builder.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
Address newAddress = pocket.getFreshReceiveAddress(
config.isManualAddressManagement());
final String newLabel = viewLabel.getText().toString().trim();
if (!newLabel.isEmpty()) {
final Uri uri =
AddressBookProvider.contentUri(getActivity().getPackageName(), type)
.buildUpon().appendPath(newAddress.toString()).build();
final ContentValues values = new ContentValues();
values.put(AddressBookProvider.KEY_LABEL, newLabel);
resolver.insert(uri, values);
}
updateView();
} catch (Bip44KeyLookAheadExceededException e) {
// Should not happen as we already checked if we can create a new address
Toast.makeText(getActivity(), R.string.too_many_unused_addresses, Toast.LENGTH_LONG).show();
}
dismissAllowingStateLoss();
}
});
dialog = builder.create();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.too_many_unused_addresses)
.setPositiveButton(R.string.button_ok, dismissListener);
dialog = builder.create();
}
return dialog;
}
};
private void updateView() {
if (isRemoving() || isDetached()) return;
receiveAddress = null;
if (showAddress != null) {
receiveAddress = showAddress;
} else {
receiveAddress = pocket.getReceiveAddress(config.isManualAddressManagement());
}
// Don't show previous addresses link if we are showing a specific address
if (showAddress == null && pocket.getNumberIssuedReceiveAddresses() != 0) {
previousAddressesLink.setVisibility(View.VISIBLE);
} else {
previousAddressesLink.setVisibility(View.GONE);
}
// TODO, get amount and description, update QR if needed
updateLabel();
updateQrCode(CoinURI.convertToCoinURI(receiveAddress, amount, label, message));
}
/**
* Update qr code if the content is different
* @param qrContent
*/
private void updateQrCode(final String qrContent) {
if (lastQrContent == null || !lastQrContent.equals(qrContent)) {
Bitmap qrCodeBitmap = Qr.bitmap(qrContent, maxQrSize);
qrView.setImageBitmap(qrCodeBitmap);
lastQrContent = qrContent;
}
}
private void updateLabel() {
label = resolveLabel(receiveAddress.toString());
if (label != null) {
addressLabelView.setText(label);
addressLabelView.setTypeface(Typeface.DEFAULT);
addressView.setText(
GenericUtils.addressSplitToGroups(receiveAddress.toString()));
addressView.setVisibility(View.VISIBLE);
} else {
addressLabelView.setText(
GenericUtils.addressSplitToGroupsMultiline(receiveAddress.toString()));
addressLabelView.setTypeface(Typeface.MONOSPACE);
addressView.setVisibility(View.GONE);
}
}
private final ThrottlingWalletChangeListener walletListener = new ThrottlingWalletChangeListener() {
@Override
public void onThrottledWalletChanged() {
handler.sendEmptyMessage(UPDATE_VIEW);
}
};
private String resolveLabel(@Nonnull final String address) {
return AddressBookProvider.resolveLabel(getActivity(), type, address);
}
private final LoaderManager.LoaderCallbacks<Cursor> rateLoaderCallbacks = new LoaderManager.LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
String localSymbol = config.getExchangeCurrencyCode();
String coinSymbol = type.getSymbol();
return new ExchangeRateLoader(getActivity(), config, localSymbol, coinSymbol);
}
@Override
public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
if (data != null && data.getCount() > 0) {
data.moveToFirst();
final ExchangeRatesProvider.ExchangeRate exchangeRate = ExchangeRatesProvider.getExchangeRate(data);
handler.sendMessage(handler.obtainMessage(UPDATE_EXCHANGE_RATE, exchangeRate.rate));
}
}
@Override
public void onLoaderReset(final Loader<Cursor> loader) {
}
};
private final AmountEditView.Listener amountsListener = new AmountEditView.Listener() {
boolean isValid(Value amount) {
return amount != null && amount.isPositive()
&& amount.compareTo(type.minNonDust()) >= 0;
}
void checkAndUpdateAmount() {
Value amountParsed = amountCalculatorLink.getPrimaryAmount();
if (isValid(amountParsed)) {
amount = amountParsed;
} else {
amount = null;
}
updateView();
}
@Override
public void changed() {
checkAndUpdateAmount();
}
@Override
public void focusChanged(final boolean hasFocus) {
if (!hasFocus) {
checkAndUpdateAmount();
}
}
};
}