package com.mygeopay.wallet.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.mygeopay.core.coins.CoinType;
import com.mygeopay.core.uri.CoinURI;
import com.mygeopay.core.uri.CoinURIParseException;
import com.mygeopay.core.util.GenericUtils;
import com.mygeopay.core.wallet.WalletAccount;
import com.mygeopay.wallet.Constants;
import com.mygeopay.wallet.R;
import com.mygeopay.wallet.WalletApplication;
import com.mygeopay.wallet.service.CoinService;
import com.mygeopay.wallet.service.CoinServiceImpl;
import com.mygeopay.wallet.tasks.CheckUpdateTask;
import com.mygeopay.wallet.ui.widget.CoinListItem;
import com.mygeopay.wallet.util.Keyboard;
import com.mygeopay.wallet.util.SystemUtils;
import com.mygeopay.wallet.util.UiUtils;
import com.mygeopay.wallet.util.WeakHandler;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import static com.mygeopay.wallet.ui.NavDrawerItemType.ITEM_SECTION_TITLE;
import static com.mygeopay.wallet.ui.NavDrawerItemType.ITEM_COIN;
import static com.mygeopay.wallet.ui.NavDrawerItemType.ITEM_TRADE;
import static com.mygeopay.wallet.util.UiUtils.setGone;
import static com.mygeopay.wallet.util.UiUtils.setVisible;
/**
* @author John L. Jegutanis
* @author Andreas Schildbach
*/
final public class WalletActivity extends BaseWalletActivity implements
NavigationDrawerFragment.NavigationDrawerCallbacks, BalanceFragment.Listener,
SendFragment.Listener {
private static final Logger log = LoggerFactory.getLogger(WalletActivity.class);
private static final int RECEIVE = 0;
private static final int BALANCE = 1;
private static final int SEND = 2;
private static final int REQUEST_CODE_SCAN = 0;
private static final int ADD_COIN = 1;
private static final int TX_BROADCAST_OK = 0;
private static final int TX_BROADCAST_ERROR = 1;
/**
* Fragment managing the behaviors, interactions and presentation of the navigation drawer.
*/
private NavigationDrawerFragment mNavigationDrawerFragment;
/**
* Used to store the last screen title. For use in {@link #restoreActionBar()}.
*/
private CharSequence mTitle;
/**
* For SharedPreferences, used to check if first launch ever.
*/
private ViewPager mViewPager;
private String currentAccountId;
private Intent connectCoinIntent;
private List<NavDrawerItem> navDrawerItems = new ArrayList<>();
private ActionMode lastActionMode;
private final Handler handler = new MyHandler(this);
public WalletActivity() {}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wallet);
if (getWalletApplication().getWallet() == null) {
startIntro();
finish();
return;
}
if (getIntent().getBooleanExtra(Constants.ARG_TEST_WALLET, false)) {
new AlertDialog.Builder(this)
.setTitle(R.string.test_wallet)
.setMessage(R.string.test_wallet_message)
.setPositiveButton(R.string.button_ok, null)
.create().show();
}
if (savedInstanceState == null) {
checkAlerts();
}
mTitle = getTitle();
// Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
// setSupportActionBar(toolbar);
getSupportActionBar().setElevation(0);
mNavigationDrawerFragment = (NavigationDrawerFragment)
getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
// Set up the drawer.
createNavDrawerItems();
mNavigationDrawerFragment.setUp(
R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout),
navDrawerItems);
// Set up the ViewPager, attaching the adapter and setting up a listener for when the
// user swipes between sections.
mViewPager = (ViewPager) findViewById(R.id.pager);
// Set OffscreenPageLimit to 2 because receive fragment draws a QR code and we don't
// want to re-render that if we go to the SendFragment and back
mViewPager.setOffscreenPageLimit(2);
mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override public void onPageScrolled(int pos, float posOffset, int posOffsetPixels) { }
@Override
public void onPageSelected(int position) {
if (position == BALANCE) Keyboard.hideKeyboard(WalletActivity.this);
finishActionMode();
}
@Override public void onPageScrollStateChanged(int state) {}
});
// Get the last used wallet pocket and select it
WalletAccount lastAccount = getAccount(getWalletApplication().getConfiguration().getLastAccountId());
if (lastAccount == null && !getAllAccounts().isEmpty()) {
lastAccount = getAllAccounts().get(0);
}
// TODO, when we are able to remove accounts, show a message when no accounts found
if (lastAccount != null) {
navDrawerSelectAccount(lastAccount, false);
openPocket(lastAccount, false);
}
}
private void navDrawerSelectAccount(@Nullable WalletAccount account, boolean closeDrawer) {
if (mNavigationDrawerFragment != null && account != null) {
int position = 0;
for (NavDrawerItem item : navDrawerItems) {
if (item.itemType == ITEM_COIN && account.getId().equals(item.itemData)) {
mNavigationDrawerFragment.setSelectedItem(position, closeDrawer);
}
position++;
}
}
}
private void createNavDrawerItems() {
navDrawerItems.clear();
NavDrawerItem.addItem(navDrawerItems, ITEM_SECTION_TITLE, getString(R.string.navigation_drawer_services));
NavDrawerItem.addItem(navDrawerItems, ITEM_TRADE, getString(R.string.title_activity_trade), R.drawable.trade, null);
NavDrawerItem.addItem(navDrawerItems, ITEM_SECTION_TITLE, getString(R.string.navigation_drawer_wallet));
for (WalletAccount account : getAllAccounts()) {
CoinType type = account.getCoinType();
NavDrawerItem.addItem(navDrawerItems, ITEM_COIN, type.getName(), Constants.COINS_ICONS.get(type), account.getId());
}
}
@Override
protected void onResume() {
super.onResume();
getWalletApplication().startBlockchainService(CoinService.ServiceMode.CANCEL_COINS_RECEIVED);
connectCoinService();
//TODO
// checkLowStorageAlert();
}
@Override
public void onLocalAmountClick() {
startExchangeRates();
}
@Override
public void onTransactionBroadcastSuccess(WalletAccount pocket, Transaction transaction) {
handler.sendMessage(handler.obtainMessage(TX_BROADCAST_OK, transaction));
}
@Override
public void onTransactionBroadcastFailure(WalletAccount pocket, Transaction transaction) {
handler.sendMessage(handler.obtainMessage(TX_BROADCAST_ERROR, transaction));
}
@Override
public void onAccountSelected(String accountId) {
log.info("Coin selected {}", accountId);
openPocket(getAccount(accountId), false);
}
@Override
public void onAddCoinsSelected() {
startActivityForResult(new Intent(WalletActivity.this, AddCoinsActivity.class), ADD_COIN);
}
@Override
public void onTradeSelected() {
startActivity(new Intent(WalletActivity.this, TradeActivity.class));
// Reselect the current account as the trade is a separate activity
navDrawerSelectAccount(getAccount(currentAccountId), true);
}
private void openPocket(WalletAccount account) {
openPocket(account, true);
}
private void openPocket(String accountId) {
openPocket(getAccount(accountId), true);
}
private void openPocket(WalletAccount account, boolean selectInNavDrawer) {
if (account != null && mViewPager != null && !account.getId().equals(currentAccountId)) {
currentAccountId = account.getId();
CoinType type = account.getCoinType();
// TODO display fullname for coins changed mTitle from getName to getFullname
mTitle = type.getFullname();
AppSectionsPagerAdapter adapter = new AppSectionsPagerAdapter(this, account);
mViewPager.setAdapter(adapter);
mViewPager.setCurrentItem(BALANCE);
mViewPager.getAdapter().notifyDataSetChanged();
getWalletApplication().getConfiguration().touchLastAccountId(currentAccountId);
connectCoinService();
if (selectInNavDrawer) {
navDrawerSelectAccount(account, true);
}
}
}
private void connectCoinService() {
if (connectCoinIntent == null) {
connectCoinIntent = new Intent(CoinService.ACTION_CONNECT_COIN, null,
getWalletApplication(), CoinServiceImpl.class);
}
// Open connection if needed or possible
connectCoinIntent.putExtra(Constants.ARG_ACCOUNT_ID, currentAccountId);
getWalletApplication().startService(connectCoinIntent);
}
public void restoreActionBar() {
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setTitle(mTitle);
}
private void checkAlerts() {
// If not store version, show update dialog if needed
if (!SystemUtils.isStoreVersion(this)) {
final PackageInfo packageInfo = getWalletApplication().packageInfo();
new CheckUpdateTask() {
@Override
protected void onPostExecute(Integer serverVersionCode) {
if (serverVersionCode != null && serverVersionCode > packageInfo.versionCode) {
showUpdateDialog();
}
}
}.execute();
}
}
private void showUpdateDialog() {
final PackageManager pm = getPackageManager();
// final Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format(Constants.MARKET_APP_URL, getPackageName())));
final Intent binaryIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.BINARY_URL));
final AlertDialog.Builder builder = new AlertDialog.Builder(WalletActivity.this);
builder.setTitle(R.string.wallet_update_title);
builder.setMessage(R.string.wallet_update_message);
// Disable market link for now
// http://developer.android.com/distribute/tools/promote/linking.html
// TODO Create MarketIntent
// if (pm.resolveActivity(marketIntent, 0) != null)
// {
// builder.setPositiveButton("Play Store", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(final DialogInterface dialog, final int id) {
// startActivity(marketIntent);
// finish();
// }
// });
// }
if (pm.resolveActivity(binaryIntent, 0) != null)
{
builder.setPositiveButton(R.string.button_download, new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int id) {
startActivity(binaryIntent);
finish();
}
});
}
builder.setNegativeButton(R.string.button_dismiss, null);
builder.create().show();
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
handler.post(new Runnable() {
@Override
public void run() {
if (requestCode == REQUEST_CODE_SCAN) {
if (resultCode == Activity.RESULT_OK) {
try {
processInput(intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT));
} catch (final Exception e) {
showScanFailedMessage(e);
}
}
} else if (requestCode == ADD_COIN && resultCode == Activity.RESULT_OK) {
final String accountId = intent.getStringExtra(Constants.ARG_ACCOUNT_ID);
createNavDrawerItems();
mNavigationDrawerFragment.setItems(navDrawerItems);
openPocket(accountId);
}
}
});
}
private void showScanFailedMessage(Exception e) {
String error = getResources().getString(R.string.scan_error, e.getMessage());
Toast.makeText(this, error, Toast.LENGTH_LONG).show();
}
private void processInput(String input) throws CoinURIParseException, AddressFormatException {
input = input.trim();
try {
processUri(input);
} catch (final CoinURIParseException x) {
processAddress(input);
}
}
private void processUri(String input) throws CoinURIParseException {
CoinURI coinUri = new CoinURI(input);
CoinType scannedType = coinUri.getType();
if (!Constants.SUPPORTED_COINS.contains(scannedType)) {
String error = getResources().getString(R.string.unsupported_coin, scannedType.getName());
throw new CoinURIParseException(error);
}
List<WalletAccount> allAccounts = getAllAccounts();
List<WalletAccount> sendFromAccounts = getAccounts(coinUri.getType());
if (sendFromAccounts.size() == 1) {
setSendFromCoin(sendFromAccounts.get(0), coinUri);
} else if (allAccounts.size() == 1) {
setSendFromCoin(allAccounts.get(0), coinUri);
} else {
showPayWithDialog(coinUri);
}
}
private void processAddress(String addressStr) throws AddressFormatException, CoinURIParseException {
List<CoinType> possibleTypes = GenericUtils.getPossibleTypes(addressStr);
WalletAccount currentAccount = getAccount(currentAccountId);
if (currentAccount != null && possibleTypes.contains(currentAccount.getCoinType())) {
Address address = new Address(currentAccount.getCoinType(), addressStr);
processUri(CoinURI.convertToCoinURI(address, null, null, null));
} else if (possibleTypes.size() == 1) {
Address address = new Address(possibleTypes.get(0), addressStr);
processUri(CoinURI.convertToCoinURI(address, null, null, null));
} else {
// This address string could be more that one coin type so first check if this address
// comes from an account to determine the type.
List<WalletAccount> possibleAccounts = getAccounts(possibleTypes);
Address addressOfAccount = null;
for (WalletAccount account : possibleAccounts) {
Address testAddress = new Address(account.getCoinType(), addressStr);
if (account.isAddressMine(testAddress)) {
addressOfAccount = testAddress;
break;
}
}
if (addressOfAccount != null) {
// If address is from an account don't show a dialog.
processUri(CoinURI.convertToCoinURI(addressOfAccount, null, null, null));
} else {
// As a last resort let the use choose the correct coin type
showPayToDialog(addressStr);
}
}
}
private void showPayToDialog(String addressStr) {
if (selectCoinTypeDialog.getArguments() == null) {
selectCoinTypeDialog.setArguments(new Bundle());
}
selectCoinTypeDialog.getArguments().putString(Constants.ARG_ADDRESS_STRING, addressStr);
selectCoinTypeDialog.show(getSupportFragmentManager(), null);
}
SelectCoinTypeDialog selectCoinTypeDialog = new SelectCoinTypeDialog() {
// FIXME crash when this dialog being restored from saved state
@Override
public void onAddressSelected(Address selectedAddress) {
try {
processUri(CoinURI.convertToCoinURI(selectedAddress, null, null, null));
} catch (CoinURIParseException e) {
showScanFailedMessage(e);
}
}
};
private void showPayWithDialog(CoinURI uri) {
if (payWithDialog.getArguments() == null) {
payWithDialog.setArguments(new Bundle());
}
payWithDialog.getArguments().putString(Constants.ARG_URI, uri.toUriString());
payWithDialog.show(getSupportFragmentManager(), null);
}
PayWithDialog payWithDialog = new PayWithDialog() {
// FIXME crash when this dialog being restored from saved state
@Override
public void payWith(WalletAccount account, CoinURI uri) {
setSendFromCoin(account, uri);
}
};
private void setSendFromCoin(final WalletAccount account, final CoinURI coinUri) {
if (mViewPager != null && mNavigationDrawerFragment != null) {
openPocket(account);
mViewPager.setCurrentItem(SEND);
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
if (fragment instanceof SendFragment) {
try {
((SendFragment) fragment).updateStateFrom(coinUri);
} catch (CoinURIParseException e) {
showScanFailedMessage(e);
}
break;
}
}
} else {
// Should not happen
Toast.makeText(this, R.string.error_generic, Toast.LENGTH_SHORT).show();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (mNavigationDrawerFragment != null && !mNavigationDrawerFragment.isDrawerOpen()) {
// 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.
getMenuInflater().inflate(R.menu.global, menu);
restoreActionBar();
return true;
}
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
startActivity(new Intent(WalletActivity.this, SettingsActivity.class));
return true;
} else if (id == R.id.action_scan_qr_code) {
startActivityForResult(new Intent(this, ScanActivity.class), REQUEST_CODE_SCAN);
return true;
} else if (id == R.id.action_refresh_wallet) {
refreshWallet();
return true;
} else if (id == R.id.action_balance) {
//TODO Add Individual coin balances
refreshWallet();
return true;
} else if (id == R.id.action_about) {
startActivity(new Intent(WalletActivity.this, AboutActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
void startExchangeRates() {
WalletAccount account = getAccount(currentAccountId);
if (account != null) {
Intent intent = new Intent(this, ExchangeRatesActivity.class);
intent.putExtra(Constants.ARG_COIN_ID, account.getCoinType().getId());
startActivity(intent);
} else {
Toast.makeText(this, R.string.no_wallet_pocket_selected, Toast.LENGTH_LONG).show();
}
}
private void refreshWallet() {
if (getWalletApplication().getWallet() != null) {
Intent intent = new Intent(CoinService.ACTION_RESET_WALLET, null,
getWalletApplication(), CoinServiceImpl.class);
intent.putExtra(Constants.ARG_ACCOUNT_ID, currentAccountId);
getWalletApplication().startService(intent);
// FIXME, we get a crash if the activity is not restarted
Intent introIntent = new Intent(WalletActivity.this, WalletActivity.class);
startActivity(introIntent);
finish();
}
}
private void startIntro() {
Intent introIntent = new Intent(this, IntroActivity.class);
startActivity(introIntent);
}
@Override
public void onBackPressed() {
if (mNavigationDrawerFragment != null && mNavigationDrawerFragment.isDrawerOpen()) {
mNavigationDrawerFragment.closeDrawer();
return;
}
// If not in balance screen, back button brings us there
boolean screenChanged = goToBalance();
if (!screenChanged) {
super.onBackPressed();
}
finishActionMode();
}
private boolean goToBalance() {
if (mViewPager != null && mViewPager.getCurrentItem() != BALANCE) {
mViewPager.setCurrentItem(BALANCE);
return true;
}
return false;
}
public void registerActionMode(ActionMode actionMode) {
finishActionMode();
lastActionMode = actionMode;
}
private void finishActionMode() {
if (lastActionMode != null) {
lastActionMode.finish();
lastActionMode = null;
}
}
private static class MyHandler extends WeakHandler<WalletActivity> {
public MyHandler(WalletActivity ref) { super(ref); }
@Override
protected void weakHandleMessage(WalletActivity ref, Message msg) {
switch (msg.what) {
case TX_BROADCAST_OK:
Toast.makeText(ref, ref.getString(R.string.sent_msg),
Toast.LENGTH_LONG).show();
ref.goToBalance();
break;
case TX_BROADCAST_ERROR:
Toast.makeText(ref, ref.getString(R.string.get_tx_broadcast_error),
Toast.LENGTH_LONG).show();
ref.goToBalance();
break;
}
}
}
private static class AppSectionsPagerAdapter extends FragmentStatePagerAdapter {
private final WalletActivity walletActivity;
private final String accountId;
private AddressRequestFragment request;
private SendFragment send;
private BalanceFragment balance;
public AppSectionsPagerAdapter(WalletActivity walletActivity, WalletAccount account) {
super(walletActivity.getSupportFragmentManager());
this.walletActivity = walletActivity;
this.accountId = account.getId();
}
@Override
public Fragment getItem(int i) {
switch (i) {
case RECEIVE:
if (request == null) request = AddressRequestFragment.newInstance(accountId);
return request;
case SEND:
if (send == null) send = SendFragment.newInstance(accountId);
return send;
case BALANCE:
default:
if (balance == null) balance = BalanceFragment.newInstance(accountId);
return balance;
}
}
@Override
public int getCount() {
return 3;
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case RECEIVE:
return walletActivity.getString(R.string.wallet_title_request);
case SEND:
return walletActivity.getString(R.string.wallet_title_send);
case BALANCE:
default:
return walletActivity.getString(R.string.wallet_title_balance);
}
}
}
}