From 0cb8ece336433c50c4efbd825c7bc91fad640f4e Mon Sep 17 00:00:00 2001 From: m2049r <30435443+m2049r@users.noreply.github.com> Date: Sun, 3 Dec 2017 22:17:28 +0100 Subject: [PATCH] Rework of Send flow + refactoring (#146) --- .../m2049r/xmrwallet/GenerateFragment.java | 2 +- .../xmrwallet/GenerateReviewFragment.java | 4 +- .../com/m2049r/xmrwallet/LoginActivity.java | 14 +- .../com/m2049r/xmrwallet/LoginFragment.java | 8 +- .../xmrwallet/OnBackPressedListener.java | 21 + .../com/m2049r/xmrwallet/ReceiveFragment.java | 15 +- .../com/m2049r/xmrwallet/ScannerFragment.java | 14 +- .../xmrwallet/SendAddressWizardFragment.java | 237 ++++++++ .../xmrwallet/SendAmountWizardFragment.java | 132 +++++ .../xmrwallet/SendConfirmWizardFragment.java | 306 ++++++++++ .../com/m2049r/xmrwallet/SendFragment.java | 534 ------------------ .../com/m2049r/xmrwallet/SendFragmentNew.java | 486 ++++++++++++++++ .../xmrwallet/SendSettingsWizardFragment.java | 124 ++++ .../xmrwallet/SendSuccessWizardFragment.java | 146 +++++ .../m2049r/xmrwallet/SendWizardFragment.java | 36 ++ .../java/com/m2049r/xmrwallet/TxFragment.java | 7 +- .../com/m2049r/xmrwallet/WalletActivity.java | 132 +++-- .../com/m2049r/xmrwallet/WalletFragment.java | 3 +- .../xmrwallet/XmrWalletApplication.java | 16 + .../xmrwallet/{util => data}/BarcodeData.java | 2 +- .../com/m2049r/xmrwallet/data/PendingTx.java | 39 ++ .../xmrwallet/{util => data}/TxData.java | 47 +- .../xmrwallet/layout/SpendViewPager.java | 75 +++ .../layout/TransactionInfoAdapter.java | 5 - .../com/m2049r/xmrwallet/model/Transfer.java | 2 - .../com/m2049r/xmrwallet/model/Wallet.java | 11 +- .../m2049r/xmrwallet/model/WalletManager.java | 3 +- .../xmrwallet/service/WalletService.java | 27 +- .../exchange/kraken/ExchangeApiImpl.java | 3 - .../exchange/kraken/ExchangeRateImpl.java | 1 - .../com/m2049r/xmrwallet/util/Helper.java | 29 +- .../{layout => widget}/CTextInputLayout.java | 2 +- .../com/m2049r/xmrwallet/widget/DotBar.java | 156 +++++ .../{layout => widget}/DropDownEditText.java | 2 +- .../xmrwallet/widget/ExchangeTextView.java | 449 +++++++++++++++ .../{layout => widget}/ExchangeView.java | 85 ++- .../xmrwallet/widget/NumberPadView.java | 87 +++ .../xmrwallet/{layout => widget}/Toolbar.java | 15 +- app/src/main/res/anim/cycle_7.xml | 3 + app/src/main/res/anim/shake.xml | 6 + .../res/drawable/ic_backspace_black_36dp.xml | 10 + app/src/main/res/drawable/ic_check_circle.xml | 24 + app/src/main/res/drawable/ic_max_sweep.xml | 9 + .../drawable/ic_navigate_next_black_24dp.xml | 10 +- .../drawable/ic_navigate_next_white_24dp.xml | 9 + .../drawable/ic_navigate_prev_white_24dp.xml | 13 + .../res/drawable/ic_settings_orange_24dp.xml | 2 +- app/src/main/res/layout/activity_login.xml | 2 +- .../main/res/layout/activity_test_send.xml | 22 + app/src/main/res/layout/activity_wallet.xml | 2 +- app/src/main/res/layout/fragment_login.xml | 2 +- app/src/main/res/layout/fragment_receive.xml | 16 +- app/src/main/res/layout/fragment_send.xml | 3 +- .../main/res/layout/fragment_send_address.xml | 92 +++ .../main/res/layout/fragment_send_amount.xml | 32 ++ .../main/res/layout/fragment_send_confirm.xml | 185 ++++++ app/src/main/res/layout/fragment_send_new.xml | 71 +++ .../res/layout/fragment_send_settings.xml | 80 +++ .../main/res/layout/fragment_send_success.xml | 215 +++++++ app/src/main/res/layout/view_exchange.xml | 4 +- .../main/res/layout/view_exchange_text.xml | 91 +++ app/src/main/res/layout/view_number_pad.xml | 170 ++++++ app/src/main/res/values/attrs.xml | 10 + app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 26 +- app/src/main/res/values/styles.xml | 58 +- .../exchange/kraken/ExchangeRateTest.java | 2 +- build.gradle | 2 +- 68 files changed, 3743 insertions(+), 707 deletions(-) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/OnBackPressedListener.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/SendAddressWizardFragment.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/SendAmountWizardFragment.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/SendConfirmWizardFragment.java delete mode 100644 app/src/main/java/com/m2049r/xmrwallet/SendFragment.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/SendFragmentNew.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/SendSettingsWizardFragment.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/SendSuccessWizardFragment.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/SendWizardFragment.java rename app/src/main/java/com/m2049r/xmrwallet/{util => data}/BarcodeData.java (96%) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/data/PendingTx.java rename app/src/main/java/com/m2049r/xmrwallet/{util => data}/TxData.java (74%) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java rename app/src/main/java/com/m2049r/xmrwallet/{layout => widget}/CTextInputLayout.java (97%) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/widget/DotBar.java rename app/src/main/java/com/m2049r/xmrwallet/{layout => widget}/DropDownEditText.java (97%) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java rename app/src/main/java/com/m2049r/xmrwallet/{layout => widget}/ExchangeView.java (87%) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/widget/NumberPadView.java rename app/src/main/java/com/m2049r/xmrwallet/{layout => widget}/Toolbar.java (94%) create mode 100644 app/src/main/res/anim/cycle_7.xml create mode 100644 app/src/main/res/anim/shake.xml create mode 100644 app/src/main/res/drawable/ic_backspace_black_36dp.xml create mode 100644 app/src/main/res/drawable/ic_check_circle.xml create mode 100644 app/src/main/res/drawable/ic_max_sweep.xml create mode 100644 app/src/main/res/drawable/ic_navigate_next_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_navigate_prev_white_24dp.xml create mode 100644 app/src/main/res/layout/activity_test_send.xml create mode 100644 app/src/main/res/layout/fragment_send_address.xml create mode 100644 app/src/main/res/layout/fragment_send_amount.xml create mode 100644 app/src/main/res/layout/fragment_send_confirm.xml create mode 100644 app/src/main/res/layout/fragment_send_new.xml create mode 100644 app/src/main/res/layout/fragment_send_settings.xml create mode 100644 app/src/main/res/layout/fragment_send_success.xml create mode 100644 app/src/main/res/layout/view_exchange_text.xml create mode 100644 app/src/main/res/layout/view_number_pad.xml create mode 100644 app/src/main/res/values/attrs.xml diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java index 29634b9..677e7fc 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java @@ -32,7 +32,7 @@ import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.TextView; -import com.m2049r.xmrwallet.layout.Toolbar; +import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.util.Helper; diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java index 45d7466..6a7e021 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java @@ -34,7 +34,7 @@ import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; -import com.m2049r.xmrwallet.layout.Toolbar; +import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.util.Helper; @@ -295,4 +295,4 @@ public class GenerateReviewFragment extends Fragment { inflater.inflate(R.menu.wallet_details_menu, menu); super.onCreateOptionsMenu(menu, inflater); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index 16ad5fc..e233ff7 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -49,12 +49,12 @@ import com.m2049r.xmrwallet.dialog.AboutFragment; import com.m2049r.xmrwallet.dialog.DonationFragment; import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.PrivacyFragment; -import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; +import com.m2049r.xmrwallet.widget.Toolbar; import java.io.File; import java.io.FileInputStream; @@ -314,7 +314,6 @@ public class LoginActivity extends SecureActivity dialog.show(); } - private class AsyncBackup extends AsyncTask { @Override protected void onPreExecute() { @@ -503,9 +502,9 @@ public class LoginActivity extends SecureActivity public void onClick(View view) { String pass = etPassword.getEditText().getText().toString(); if (processPasswordEntry(wallet, pass, action)) { + Helper.hideKeyboardAlways(LoginActivity.this); passwordDialog.dismiss(); passwordDialog = null; - Helper.hideKeyboardAlways(LoginActivity.this); } else { etPassword.setError(getString(R.string.bad_password)); } @@ -520,10 +519,10 @@ public class LoginActivity extends SecureActivity etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { - Helper.hideKeyboardAlways(LoginActivity.this); String pass = etPassword.getEditText().getText().toString(); if (processPasswordEntry(wallet, pass, action)) { - passwordDialog.cancel(); + Helper.hideKeyboardAlways(LoginActivity.this); + passwordDialog.dismiss(); passwordDialog = null; } else { etPassword.setError(getString(R.string.bad_password)); @@ -646,7 +645,7 @@ public class LoginActivity extends SecureActivity @Override public void onBackPressed() { - //activity.finish(); + // prevent back button } } @@ -718,7 +717,6 @@ public class LoginActivity extends SecureActivity String msg = getString(R.string.message_strorage_not_permitted); Timber.e(msg); Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); - //throw new IllegalStateException(msg); } break; default: @@ -1198,7 +1196,7 @@ public class LoginActivity extends SecureActivity switch (result) { case OK: Timber.d("selected wallet is ." + walletNode.name + "."); - // now it's getting real, check if wallet exists + // now it's getting real, onValidateFields if wallet exists promptAndStart(walletNode); break; case TIMEOUT: diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java index 48666be..60ad6a2 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java @@ -43,12 +43,12 @@ import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; -import com.m2049r.xmrwallet.layout.DropDownEditText; -import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.layout.WalletInfoAdapter; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.NodeList; +import com.m2049r.xmrwallet.widget.DropDownEditText; +import com.m2049r.xmrwallet.widget.Toolbar; import java.io.File; import java.util.ArrayList; @@ -315,7 +315,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter } public boolean onTestnetMenuItem() { - boolean lastState = testnet;//item.isChecked(); + boolean lastState = testnet; setNet(!lastState, true); // set and save return !lastState; } @@ -336,7 +336,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter private static final String PREF_DAEMON_TESTNET = "daemon_testnet"; private static final String PREF_DAEMON_MAINNET = "daemon_mainnet"; - //private static final String PREF_TESTNET = "testnet"; private static final String PREF_DAEMONLIST_MAINNET = "node.moneroworld.com:18089;node.xmrbackb.one;node.xmr.be"; @@ -372,7 +371,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter SharedPreferences sharedPref = activityCallback.getPrefs(); SharedPreferences.Editor editor = sharedPref.edit(); - //editor.putBoolean(PREF_TESTNET, testnet); editor.putString(PREF_DAEMON_MAINNET, daemonMainNet.toString()); editor.putString(PREF_DAEMON_TESTNET, daemonTestNet.toString()); editor.apply(); diff --git a/app/src/main/java/com/m2049r/xmrwallet/OnBackPressedListener.java b/app/src/main/java/com/m2049r/xmrwallet/OnBackPressedListener.java new file mode 100644 index 0000000..eb09125 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/OnBackPressedListener.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet; + +public interface OnBackPressedListener { + boolean onBackPressed(); +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java index cd4014d..09430e3 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java @@ -46,12 +46,12 @@ import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; -import com.m2049r.xmrwallet.layout.ExchangeView; -import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; +import com.m2049r.xmrwallet.widget.ExchangeView; +import com.m2049r.xmrwallet.widget.Toolbar; import java.util.HashMap; import java.util.Map; @@ -71,8 +71,6 @@ public class ReceiveFragment extends Fragment { private EditText etDummy; private ImageButton bCopyAddress; - //String name; - public interface Listener { void setToolbarButton(int type); @@ -151,7 +149,6 @@ public class ReceiveFragment extends Fragment { public void onTextChanged(CharSequence s, int start, int before, int count) { } }); - bPaymentId.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -189,7 +186,7 @@ public class ReceiveFragment extends Fragment { Bundle b = getArguments(); String address = b.getString("address"); String walletName = b.getString("name"); - Timber.d("%s/%s",address, walletName); + Timber.d("%s/%s", address, walletName); if (address == null) { String path = b.getString("path"); String password = b.getString("password"); @@ -300,7 +297,7 @@ public class ReceiveFragment extends Fragment { String address = tvAddress.getText().toString(); String paymentId = etPaymentId.getEditText().getText().toString(); String xmrAmount = evAmount.getAmount(); - Timber.d("%s/%s/%s",xmrAmount, paymentId, address); + Timber.d("%s/%s/%s", xmrAmount, paymentId, address); if ((xmrAmount == null) || !Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet())) { clearQR(); Timber.d("CLEARQR"); @@ -360,7 +357,6 @@ public class ReceiveFragment extends Fragment { return null; } - // TODO check if we can sensibly cache some of this private Bitmap addLogo(Bitmap qrBitmap) { Bitmap logo = getMoneroLogo(); int qrWidth = qrBitmap.getWidth(); @@ -414,9 +410,6 @@ public class ReceiveFragment extends Fragment { } } - static final String PREF_CURRENCY_A = "PREF_CURRENCY_A"; - static final String PREF_CURRENCY_B = "PREF_CURRENCY_B"; - @Override public void onPause() { Timber.d("onPause()"); diff --git a/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java b/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java index 1447880..dcd7fa3 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java @@ -33,10 +33,10 @@ import timber.log.Timber; public class ScannerFragment extends Fragment implements ZXingScannerView.ResultHandler { - private Listener activityCallback; + private OnScannedListener onScannedListener; - public interface Listener { - boolean onAddressScanned(String uri); + public interface OnScannedListener { + boolean onScanned(String uri); } private ZXingScannerView mScannerView; @@ -64,7 +64,7 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result public void handleResult(Result rawResult) { if ((rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE) && (rawResult.getText().startsWith(QR_SCHEME))) { - if (activityCallback.onAddressScanned(rawResult.getText())) { + if (onScannedListener.onScanned(rawResult.getText())) { return; } else { Toast.makeText(getActivity(), getString(R.string.send_qr_address_invalid), Toast.LENGTH_SHORT).show(); @@ -98,11 +98,11 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result @Override public void onAttach(Context context) { super.onAttach(context); - if (context instanceof Listener) { - this.activityCallback = (Listener) context; + if (context instanceof OnScannedListener) { + this.onScannedListener = (OnScannedListener) context; } else { throw new ClassCastException(context.toString() + " must implement Listener"); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendAddressWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SendAddressWizardFragment.java new file mode 100644 index 0000000..4310a9f --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/SendAddressWizardFragment.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet; + +import android.content.Context; +import android.os.Bundle; +import android.support.design.widget.TextInputLayout; +import android.support.v7.widget.CardView; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import com.m2049r.xmrwallet.data.BarcodeData; +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.util.Helper; + +import timber.log.Timber; + +public class SendAddressWizardFragment extends SendWizardFragment { + + public static SendAddressWizardFragment newInstance(Listener listener) { + SendAddressWizardFragment instance = new SendAddressWizardFragment(); + instance.setSendListener(listener); + return instance; + } + + Listener sendListener; + + public SendAddressWizardFragment setSendListener(Listener listener) { + this.sendListener = listener; + return this; + } + + interface Listener { + void setAddress(final String address); + + void setPaymentId(final String paymentId); + + void setBarcodeData(BarcodeData data); + } + + private EditText etDummy; + private TextInputLayout etAddress; + private TextInputLayout etPaymentId; + private Button bPaymentId; + private CardView cvScan; + + private String scannedAmount = null; + + OnScanListener onScanListener; + + public interface OnScanListener { + void onScan(); + + BarcodeData popScannedData(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); + + View view = inflater.inflate(R.layout.fragment_send_address, container, false); + + etAddress = (TextInputLayout) view.findViewById(R.id.etAddress); + etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + + etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId); + etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { + if (checkPaymentId()) { + etDummy.requestFocus(); + Helper.hideKeyboard(getActivity()); + } + return true; + } + return false; + } + }); + etPaymentId.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + etPaymentId.setError(null); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }); + + bPaymentId = (Button) view.findViewById(R.id.bPaymentId); + bPaymentId.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + etPaymentId.getEditText().setText((Wallet.generatePaymentId())); + } + }); + + cvScan = (CardView) view.findViewById(R.id.bScan); + cvScan.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onScanListener.onScan(); + } + }); + + + etDummy = (EditText) view.findViewById(R.id.etDummy); + etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + etDummy.requestFocus(); + Helper.hideKeyboard(getActivity()); + + return view; + } + + private boolean checkAddressNoError() { + String address = etAddress.getEditText().getText().toString(); + return Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet()); + } + + private boolean checkAddress() { + boolean ok = checkAddressNoError(); + if (!ok) { + etAddress.setError(getString(R.string.send_qr_address_invalid)); + } else { + etAddress.setError(null); + } + return ok; + } + + private boolean checkPaymentId() { + String paymentId = etPaymentId.getEditText().getText().toString(); + boolean ok = paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId); + if (!ok) { + etPaymentId.setError(getString(R.string.receive_paymentid_invalid)); + } else { + etPaymentId.setError(null); + } + return ok; + } + + @Override + public boolean onValidateFields() { + boolean ok = true; + if (!checkAddressNoError()) { + etAddress.startAnimation(Helper.getShakeAnimation(getContext())); + ok = false; + } + if (!checkPaymentId()) { + etPaymentId.startAnimation(Helper.getShakeAnimation(getContext())); + ok = false; + } + if (!ok) return false; + if (sendListener != null) { + sendListener.setAddress(etAddress.getEditText().getText().toString()); + sendListener.setPaymentId(etPaymentId.getEditText().getText().toString()); + } + return true; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnScanListener) { + onScanListener = (OnScanListener) context; + } else { + throw new ClassCastException(context.toString() + + " must implement ScanListener"); + } + } + + // QR Scan Stuff + + @Override + public void onResume() { + super.onResume(); + Timber.d("onResume"); + BarcodeData data = onScanListener.popScannedData(); + sendListener.setBarcodeData(data); + if (data != null) { + Timber.d("GOT DATA"); + String scannedAddress = data.address; + if (scannedAddress != null) { + etAddress.getEditText().setText(scannedAddress); + checkAddress(); + } else { + etAddress.getEditText().getText().clear(); + etAddress.setError(null); + } + String scannedPaymenId = data.paymentId; + if (scannedPaymenId != null) { + etPaymentId.getEditText().setText(scannedPaymenId); + checkPaymentId(); + } else { + etPaymentId.getEditText().getText().clear(); + etPaymentId.setError(null); + } + } + } + + @Override + public void onResumeFragment() { + super.onResumeFragment(); + Timber.d("onResumeFragment()"); + Helper.hideKeyboard(getActivity()); + etDummy.requestFocus(); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendAmountWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SendAmountWizardFragment.java new file mode 100644 index 0000000..c82e2af --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/SendAmountWizardFragment.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.m2049r.xmrwallet.data.BarcodeData; +import com.m2049r.xmrwallet.widget.ExchangeTextView; +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.util.Helper; +import com.m2049r.xmrwallet.widget.NumberPadView; + +import timber.log.Timber; + +public class SendAmountWizardFragment extends SendWizardFragment { + + public static SendAmountWizardFragment newInstance(Listener listener) { + SendAmountWizardFragment instance = new SendAmountWizardFragment(); + instance.setSendListener(listener); + return instance; + } + + Listener sendListener; + + public SendAmountWizardFragment setSendListener(Listener listener) { + this.sendListener = listener; + return this; + } + + interface Listener { + SendFragmentNew.Listener getActivityCallback(); + + void setAmount(final long amount); + + BarcodeData popBarcodeData(); + } + + private TextView tvFunds; + private ExchangeTextView evAmount; + //private Button bSendAll; + private NumberPadView numberPad; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); + + sendListener = (Listener) getParentFragment(); + + View view = inflater.inflate(R.layout.fragment_send_amount, container, false); + + tvFunds = (TextView) view.findViewById(R.id.tvFunds); + + evAmount = (ExchangeTextView) view.findViewById(R.id.evAmount); + numberPad = (NumberPadView) view.findViewById(R.id.numberPad); + numberPad.setListener(evAmount); + + /* + bSendAll = (Button) view.findViewById(R.id.bSendAll); + bSendAll.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // TODO: send all - figure out how to display this + } + }); +*/ + + Helper.hideKeyboard(getActivity()); + + return view; + } + + + @Override + public boolean onValidateFields() { + if (!evAmount.validate(maxFunds)) { + return false; + } + + if (sendListener != null) { + String xmr = evAmount.getAmount(); + if (xmr != null) { + sendListener.setAmount(Wallet.getAmountFromString(xmr)); + } else { + sendListener.setAmount(0L); + } + } + return true; + } + + double maxFunds = 0; + + @Override + public void onResumeFragment() { + super.onResumeFragment(); + Timber.d("onResumeFragment()"); + Helper.hideKeyboard(getActivity()); + final long funds = getTotalFunds(); + maxFunds = funds / 1000000000000L; + tvFunds.setText(getString(R.string.send_available, + Wallet.getDisplayAmount(funds))); + if (evAmount.getAmount().isEmpty()) { + final BarcodeData data = sendListener.popBarcodeData(); + if ((data != null) && (data.amount > 0)) { + evAmount.setAmount(Wallet.getDisplayAmount(data.amount)); + } + } + } + + long getTotalFunds() { + return sendListener.getActivityCallback().getTotalFunds(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendConfirmWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SendConfirmWizardFragment.java new file mode 100644 index 0000000..0c37901 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/SendConfirmWizardFragment.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet; + +import android.app.Activity; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.design.widget.TextInputLayout; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.TextView; + +import com.m2049r.xmrwallet.data.TxData; +import com.m2049r.xmrwallet.model.PendingTransaction; +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.util.Helper; + +import timber.log.Timber; + +public class SendConfirmWizardFragment extends SendWizardFragment { + + public static SendConfirmWizardFragment newInstance(Listener listener) { + SendConfirmWizardFragment instance = new SendConfirmWizardFragment(); + instance.setSendListener(listener); + return instance; + } + + Listener sendListener; + + public SendConfirmWizardFragment setSendListener(Listener listener) { + this.sendListener = listener; + return this; + } + + interface Listener { + SendFragmentNew.Listener getActivityCallback(); + + TxData getTxData(); + + String getNotes(); + + void commitTransaction(); + + void disposeTransaction(); + } + + private TextView tvTxAddress; + private TextView tvTxPaymentId; + private TextView tvTxNotes; + private TextView tvTxAmount; + private TextView tvTxFee; + private TextView tvTxTotal; + private View pbProgress; + private View bSend; + private View llConfirmSend; + private View pbProgressSend; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); + + View view = inflater.inflate( + R.layout.fragment_send_confirm, container, false); + + tvTxAddress = (TextView) view.findViewById(R.id.tvTxAddress); + tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId); + tvTxNotes = (TextView) view.findViewById(R.id.tvTxNotes); + tvTxAmount = ((TextView) view.findViewById(R.id.tvTxAmount)); + tvTxFee = (TextView) view.findViewById(R.id.tvTxFee); + tvTxTotal = (TextView) view.findViewById(R.id.tvTxTotal); + + pbProgress = view.findViewById(R.id.pbProgress); + pbProgressSend = view.findViewById(R.id.pbProgressSend); + llConfirmSend = view.findViewById(R.id.llConfirmSend); + + bSend = view.findViewById(R.id.bSend); + bSend.setEnabled(false); + bSend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Timber.d("bSend.setOnClickListener"); + bSend.setEnabled(false); + preSend(); + } + }); + return view; + } + + boolean inProgress = false; + + public void hideProgress() { + pbProgress.setVisibility(View.INVISIBLE); + inProgress = false; + } + + public void showProgress() { + pbProgress.setVisibility(View.VISIBLE); + inProgress = true; + } + + PendingTransaction pendingTransaction = null; + + // callback from wallet when PendingTransaction created + void transactionCreated(PendingTransaction pendingTransaction) { + hideProgress(); + if (isResumed) { + this.pendingTransaction = pendingTransaction; + refreshTransactionDetails(); + } else { + sendListener.disposeTransaction(); + } + } + + void send() { + sendListener.commitTransaction(); + pbProgressSend.setVisibility(View.VISIBLE); + } + + void sendFailed() { + pbProgressSend.setVisibility(View.INVISIBLE); + } + + @Override + public boolean onValidateFields() { + return true; + } + + private boolean isResumed = false; + + @Override + public void onPauseFragment() { + isResumed = false; + pendingTransaction = null; + sendListener.disposeTransaction(); + refreshTransactionDetails(); + super.onPauseFragment(); + } + + @Override + public void onResumeFragment() { + super.onResumeFragment(); + Timber.d("onResumeFragment()"); + Helper.hideKeyboard(getActivity()); + isResumed = true; + + final TxData txData = sendListener.getTxData(); + tvTxAddress.setText(txData.getDestinationAddress()); + String paymentId = txData.getPaymentId(); + if ((paymentId != null) && (!paymentId.isEmpty())) { + tvTxPaymentId.setText(txData.getPaymentId()); + } else { + tvTxPaymentId.setText("-"); + } + String notes = sendListener.getNotes(); + if ((notes != null) && (!notes.isEmpty())) { + tvTxNotes.setText(sendListener.getNotes()); + } else { + tvTxNotes.setText("-"); + } + refreshTransactionDetails(); + if ((pendingTransaction == null) && (!inProgress)) { + showProgress(); + prepareSend(txData); + } + } + + void refreshTransactionDetails() { + Timber.d("refreshTransactionDetails()"); + if (pendingTransaction != null) { + llConfirmSend.setVisibility(View.VISIBLE); + bSend.setEnabled(true); + tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount())); + tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee())); + //tvTxDust.setText(Wallet.getDisplayAmount(pendingTransaction.getDust())); + tvTxTotal.setText(Wallet.getDisplayAmount( + pendingTransaction.getFee() + pendingTransaction.getAmount())); + } else { + llConfirmSend.setVisibility(View.GONE); + bSend.setEnabled(false); + } + } + + public void preSend() { + final Activity activity = getActivity(); + View promptsView = getLayoutInflater().inflate(R.layout.prompt_password, null); + android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity); + alertDialogBuilder.setView(promptsView); + + final TextInputLayout etPassword = (TextInputLayout) promptsView.findViewById(R.id.etPassword); + etPassword.setHint(getString(R.string.prompt_send_password)); + + etPassword.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable s) { + if (etPassword.getError() != null) { + etPassword.setError(null); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, + int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, + int before, int count) { + } + }); + + alertDialogBuilder + .setCancelable(false) + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + String pass = etPassword.getEditText().getText().toString(); + if (getActivityCallback().verifyWalletPassword(pass)) { + dialog.dismiss(); + Helper.hideKeyboardAlways(activity); + send(); + } else { + etPassword.setError(getString(R.string.bad_password)); + } + } + }) + .setNegativeButton("Cancel", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Helper.hideKeyboardAlways(activity); + dialog.cancel(); + bSend.setEnabled(true); // allow to try again + } + }); + + final android.app.AlertDialog passwordDialog = alertDialogBuilder.create(); + passwordDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + Button button = ((android.app.AlertDialog) dialog).getButton(android.app.AlertDialog.BUTTON_POSITIVE); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String pass = etPassword.getEditText().getText().toString(); + if (getActivityCallback().verifyWalletPassword(pass)) { + Helper.hideKeyboardAlways(activity); + passwordDialog.dismiss(); + send(); + } else { + etPassword.setError(getString(R.string.bad_password)); + } + } + }); + } + }); + + Helper.showKeyboard(passwordDialog); + + // accept keyboard "ok" + etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { + String pass = etPassword.getEditText().getText().toString(); + if (getActivityCallback().verifyWalletPassword(pass)) { + Helper.hideKeyboardAlways(activity); + passwordDialog.dismiss(); + send(); + } else { + etPassword.setError(getString(R.string.bad_password)); + } + return true; + } + return false; + } + }); + passwordDialog.show(); + } + + void prepareSend(TxData txData) { + getActivityCallback().onPrepareSend(txData); + } + + SendFragmentNew.Listener getActivityCallback() { + return sendListener.getActivityCallback(); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java deleted file mode 100644 index 1182965..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java +++ /dev/null @@ -1,534 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.Fragment; -import android.support.v7.app.AlertDialog; -import android.text.Editable; -import android.text.InputType; -import android.text.TextWatcher; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.widget.AdapterView; -import android.widget.Button; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.ScrollView; -import android.widget.Spinner; -import android.widget.TextView; - -import com.m2049r.xmrwallet.layout.ExchangeView; -import com.m2049r.xmrwallet.layout.Toolbar; -import com.m2049r.xmrwallet.model.PendingTransaction; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.util.BarcodeData; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.TxData; - -import timber.log.Timber; - -public class SendFragment extends Fragment { - - private EditText etDummy; - - private ScrollView scrollview; - - private TextInputLayout etAddress; - private TextInputLayout etPaymentId; - - private ExchangeView evAmount; - - private Button bScan; - private Spinner sMixin; - private Spinner sPriority; - private Button bPrepareSend; - private Button bDispose; - private Button bPaymentId; - private LinearLayout llConfirmSend; - private TextView tvTxAmount; - private TextView tvTxFee; - //TextView tvTxDust; - private TextView tvTxTotal; - private EditText etNotes; - private Button bSend; - private Button bReallySend; - private ProgressBar pbProgress; - - final static int Mixins[] = {4, 7, 12, 25}; // must match the layout XML - final static PendingTransaction.Priority Priorities[] = - {PendingTransaction.Priority.Priority_Default, - PendingTransaction.Priority.Priority_Low, - PendingTransaction.Priority.Priority_Medium, - PendingTransaction.Priority.Priority_High}; // must match the layout XML - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - View view = inflater.inflate(R.layout.fragment_send, container, false); - - etDummy = (EditText) view.findViewById(R.id.etDummy); - etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - - scrollview = (ScrollView) view.findViewById(R.id.scrollview); - - sMixin = (Spinner) view.findViewById(R.id.sMixin); - sPriority = (Spinner) view.findViewById(R.id.sPriority); - etAddress = (TextInputLayout) view.findViewById(R.id.etAddress); - etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId); - evAmount = (ExchangeView) view.findViewById(R.id.evAmount); - - bScan = (Button) view.findViewById(R.id.bScan); - bPrepareSend = (Button) view.findViewById(R.id.bPrepareSend); - bPaymentId = (Button) view.findViewById(R.id.bPaymentId); - bDispose = (Button) view.findViewById(R.id.bDispose); - - llConfirmSend = (LinearLayout) view.findViewById(R.id.llConfirmSend); - tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount); - tvTxFee = (TextView) view.findViewById(R.id.tvTxFee); - //tvTxDust = (TextView) view.findViewById(R.id.tvTxDust); - tvTxTotal = (TextView) view.findViewById(R.id.tvTxTotal); - etNotes = (EditText) view.findViewById(R.id.etNotes); - bSend = (Button) view.findViewById(R.id.bSend); - bReallySend = (Button) view.findViewById(R.id.bReallySend); - - pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); - - etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - etNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); - - Helper.showKeyboard(getActivity()); - etAddress.getEditText().requestFocus(); - etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { - if (checkAddress()) { - evAmount.focus(); - } // otherwise ignore - return true; - } - return false; - } - }); - - etAddress.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - etAddress.setError(null); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }); - - etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { - if (checkPaymentId()) { - etDummy.requestFocus(); - Helper.hideKeyboard(getActivity()); - } - return true; - } - return false; - } - }); - - etPaymentId.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - etPaymentId.setError(null); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }); - - bPrepareSend.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (checkAddress() && checkAmountWithError() && checkPaymentId()) { - Helper.hideKeyboard(getActivity()); - prepareSend(); - } - } - }); - - bDispose.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - activityCallback.onDisposeRequest(); - enableEdit(); - } - }); - - bScan.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - activityCallback.onScanAddress(); - } - }); - - bPaymentId.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - etPaymentId.getEditText().setText((Wallet.generatePaymentId())); - etPaymentId.getEditText().setSelection(etPaymentId.getEditText().getText().length()); - } - }); - - etNotes.setOnEditorActionListener(new TextView.OnEditorActionListener() { - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { - Helper.hideKeyboard(getActivity()); - return true; - } - return false; - } - }); - - bSend.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - bSend.setEnabled(false); - boolean testnet = WalletManager.getInstance().isTestNet(); - if (testnet) { - send(); - } else { - etNotes.setEnabled(false); - Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - bReallySend.setVisibility(View.VISIBLE); - bReallySend.setEnabled(true); - scrollview.post(new Runnable() { - @Override - public void run() { - scrollview.fullScroll(ScrollView.FOCUS_DOWN); - } - }); - } - }, 1000); - } - } - }); - - bReallySend.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - bReallySend.setEnabled(false); - send(); - } - }); - - sMixin.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(final AdapterView parentView, View selectedItemView, int position, long id) { - parentView.post(new Runnable() { - @Override - public void run() { - if (isAdded()) - ((TextView) parentView.getChildAt(0)).setTextColor(getResources().getColor(R.color.moneroGray)); - } - }); - } - - @Override - public void onNothingSelected(AdapterView parentView) { - // nothing (yet?) - } - }); - sPriority.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(final AdapterView parentView, View selectedItemView, int position, long id) { - parentView.post(new Runnable() { - @Override - public void run() { - if (isAdded()) - ((TextView) parentView.getChildAt(0)).setTextColor(getResources().getColor(R.color.moneroGray)); - } - }); - } - - @Override - public void onNothingSelected(AdapterView parentView) { - // nothing (yet?) - } - }); - - - etDummy.requestFocus(); - Helper.hideKeyboard(getActivity()); - - return view; - } - - private boolean checkAddressNoError() { - String address = etAddress.getEditText().getText().toString(); - return Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet()); - } - - private boolean checkAddress() { - boolean ok = checkAddressNoError(); - if (!ok) { - etAddress.setError(getString(R.string.send_qr_address_invalid)); - } else { - etAddress.setError(null); - } - return ok; - } - - private boolean checkAmount() { - String xmr = evAmount.getAmount(); - return (xmr != null) && (Wallet.getAmountFromString(xmr) > 0); - } - - private boolean checkAmountWithError() { - boolean ok = checkAmount(); - if (!ok) { - evAmount.setError(getString(R.string.receive_amount_empty)); - } else { - evAmount.setError(null); - } - return ok; - } - - private boolean checkPaymentId() { - String paymentId = etPaymentId.getEditText().getText().toString(); - boolean ok = paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId); - if (!ok) { - etPaymentId.setError(getString(R.string.receive_paymentid_invalid)); - } else { - etPaymentId.setError(null); - } - return ok; - } - - private void prepareSend() { - etDummy.requestFocus(); - disableEdit(); - String dst_addr = etAddress.getEditText().getText().toString(); - String paymentId = etPaymentId.getEditText().getText().toString(); - long amount = Wallet.getAmountFromString(evAmount.getAmount()); - int mixin = Mixins[sMixin.getSelectedItemPosition()]; - int priorityIndex = sPriority.getSelectedItemPosition(); - PendingTransaction.Priority priority = Priorities[priorityIndex]; - Timber.d("%s/%s/%d/%d/%s", dst_addr, paymentId, amount, mixin, priority.toString()); - TxData txData = new TxData( - dst_addr, - paymentId, - amount, - mixin, - priority); - showProgress(); - activityCallback.onPrepareSend(txData); - } - - private void disableEdit() { - sMixin.setEnabled(false); - sPriority.setEnabled(false); - etAddress.getEditText().setEnabled(false); - etPaymentId.getEditText().setEnabled(false); - evAmount.enable(false); - bScan.setEnabled(false); - bPaymentId.setEnabled(false); - bPrepareSend.setEnabled(false); - bPrepareSend.setVisibility(View.GONE); - } - - private void enableEdit() { - sMixin.setEnabled(true); - sPriority.setEnabled(true); - etAddress.getEditText().setEnabled(true); - etPaymentId.getEditText().setEnabled(true); - evAmount.enable(true); - bScan.setEnabled(true); - bPaymentId.setEnabled(true); - bPrepareSend.setEnabled(true); - bPrepareSend.setVisibility(View.VISIBLE); - - llConfirmSend.setVisibility(View.GONE); - etNotes.setEnabled(true); - bSend.setEnabled(false); - bReallySend.setVisibility(View.GONE); - bReallySend.setEnabled(false); - etDummy.requestFocus(); - } - - private void send() { - etNotes.setEnabled(false); - etDummy.requestFocus(); - String notes = etNotes.getText().toString(); - activityCallback.onSend(notes); - } - - Listener activityCallback; - - public interface Listener { - void onPrepareSend(TxData data); - - void onSend(String notes); - - String getWalletAddress(); - - String getWalletName(); - - void onDisposeRequest(); - - void onScanAddress(); - - BarcodeData popScannedData(); - - void setSubtitle(String subtitle); - - void setToolbarButton(int type); - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume"); - activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); - activityCallback.setSubtitle(getString(R.string.send_title)); - BarcodeData data = activityCallback.popScannedData(); - if (data != null) { - Timber.d("GOT DATA"); - String scannedAddress = data.address; - if (scannedAddress != null) { - etAddress.getEditText().setText(scannedAddress); - checkAddress(); - } else { - etAddress.getEditText().getText().clear(); - etAddress.setError(null); - } - String scannedPaymenId = data.paymentId; - if (scannedPaymenId != null) { - etPaymentId.getEditText().setText(scannedPaymenId); - checkPaymentId(); - } else { - etPaymentId.getEditText().getText().clear(); - etPaymentId.setError(null); - } - if (data.amount > 0) { - String scannedAmount = Helper.getDisplayAmount(data.amount); - evAmount.setAmount(scannedAmount); - } else { - evAmount.setAmount(""); - } - } - if ((data != null) && (data.amount <= 0)) { - evAmount.focus(); - } else { - etDummy.requestFocus(); - Helper.hideKeyboard(getActivity()); - } - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (context instanceof SendFragment.Listener) { - this.activityCallback = (SendFragment.Listener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } - - public void onCreatedTransaction(PendingTransaction pendingTransaction) { - hideProgress(); - if (pendingTransaction == null) { - enableEdit(); - return; - } - llConfirmSend.setVisibility(View.VISIBLE); - bSend.setEnabled(true); - tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount())); - tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee())); - //tvTxDust.setText(Wallet.getDisplayAmount(pendingTransaction.getDust())); - tvTxTotal.setText(Wallet.getDisplayAmount( - pendingTransaction.getFee() + pendingTransaction.getAmount())); - scrollview.post(new Runnable() { - @Override - public void run() { - scrollview.fullScroll(ScrollView.FOCUS_DOWN); - } - }); - } - - public void onCreatedTransactionFailed(String errorText) { - hideProgress(); - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle(getString(R.string.send_error_title)); - builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - enableEdit(); - } - }); - builder.setMessage(errorText); - builder.setCancelable(false); - builder.create().show(); - } - - public void showProgress() { - pbProgress.setIndeterminate(true); - pbProgress.setVisibility(View.VISIBLE); - } - - public void hideProgress() { - pbProgress.setVisibility(View.GONE); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.send_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendFragmentNew.java b/app/src/main/java/com/m2049r/xmrwallet/SendFragmentNew.java new file mode 100644 index 0000000..56aeba8 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/SendFragmentNew.java @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AlertDialog; +import android.text.InputType; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import com.m2049r.xmrwallet.data.BarcodeData; +import com.m2049r.xmrwallet.data.PendingTx; +import com.m2049r.xmrwallet.data.TxData; +import com.m2049r.xmrwallet.layout.SpendViewPager; +import com.m2049r.xmrwallet.model.PendingTransaction; +import com.m2049r.xmrwallet.util.Helper; +import com.m2049r.xmrwallet.widget.DotBar; +import com.m2049r.xmrwallet.widget.Toolbar; + +import java.lang.ref.WeakReference; + +import timber.log.Timber; + +public class SendFragmentNew extends Fragment + implements SendAddressWizardFragment.Listener, + SendAmountWizardFragment.Listener, + SendSettingsWizardFragment.Listener, + SendConfirmWizardFragment.Listener, + SendSuccessWizardFragment.Listener, + OnBackPressedListener { + + private Listener activityCallback; + + public interface Listener { + long getTotalFunds(); + + void onPrepareSend(TxData data); + + boolean verifyWalletPassword(String password); + + void onSend(String notes); + + void onDisposeRequest(); + + void onFragmentDone(); + + void setToolbarButton(int type); + + void setTitle(String title); + + void setSubtitle(String subtitle); + } + + private EditText etDummy; + private Drawable arrowPrev; + private Drawable arrowNext; + + private View llNavBar; + private DotBar dotBar; + private Button bPrev; + private Button bNext; + + private Button bDone; + + static private int MAX_FALLBACK = Integer.MAX_VALUE; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + final View view = inflater.inflate(R.layout.fragment_send_new, container, false); + + llNavBar = view.findViewById(R.id.llNavBar); + bDone = (Button) view.findViewById(R.id.bDone); + + dotBar = (DotBar) view.findViewById(R.id.dotBar); + bPrev = (Button) view.findViewById(R.id.bPrev); + bNext = (Button) view.findViewById(R.id.bNext); + arrowPrev = getResources().getDrawable(R.drawable.ic_navigate_prev_white_24dp); + arrowNext = getResources().getDrawable(R.drawable.ic_navigate_next_white_24dp); + + spendViewPager = (SpendViewPager) view.findViewById(R.id.pager); + pagerAdapter = new SpendPagerAdapter(getChildFragmentManager()); + spendViewPager.setOffscreenPageLimit(pagerAdapter.getCount()); // load & keep all pages in cache + spendViewPager.setAdapter(pagerAdapter); + + spendViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + private int fallbackPosition = MAX_FALLBACK; + private int currentPosition = 0; + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int newPosition) { + Timber.d("onPageSelected=%d/%d", newPosition, fallbackPosition); + if (fallbackPosition < newPosition) { + spendViewPager.setCurrentItem(fallbackPosition); + } else { + pagerAdapter.getFragment(currentPosition).onPauseFragment(); + pagerAdapter.getFragment(newPosition).onResumeFragment(); + updatePosition(newPosition); + currentPosition = newPosition; + fallbackPosition = MAX_FALLBACK; + } + } + + @Override + public void onPageScrollStateChanged(int state) { + if (state == ViewPager.SCROLL_STATE_DRAGGING) { + if (!spendViewPager.validateFields(spendViewPager.getCurrentItem())) { + fallbackPosition = spendViewPager.getCurrentItem(); + } else { + fallbackPosition = spendViewPager.getCurrentItem() + 1; + } + } + } + }); + + bPrev.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + spendViewPager.previous(); + } + }); + + bNext.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + spendViewPager.next(); + } + }); + + bDone.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + Timber.d("bDone.onClick"); + activityCallback.onFragmentDone(); + } + }); + + updatePosition(0); + + etDummy = (EditText) view.findViewById(R.id.etDummy); + etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + etDummy.requestFocus(); + Helper.hideKeyboard(getActivity()); + + return view; + } + + void updatePosition(int position) { + dotBar.setActiveDot(position); + CharSequence nextLabel = pagerAdapter.getPageTitle(position + 1); + bNext.setText(nextLabel); + if (nextLabel != null) { + bNext.setCompoundDrawablesWithIntrinsicBounds(null, null, arrowNext, null); + } else { + bNext.setCompoundDrawables(null, null, null, null); + } + CharSequence prevLabel = pagerAdapter.getPageTitle(position - 1); + bPrev.setText(prevLabel); + if (prevLabel != null) { + bPrev.setCompoundDrawablesWithIntrinsicBounds(arrowPrev, null, null, null); + } else { + bPrev.setCompoundDrawables(null, null, null, null); + } + } + + @Override + public void onResume() { + super.onResume(); + Timber.d("onResume"); + activityCallback.setSubtitle(getString(R.string.send_title)); + if (getConfirmFragment() != null) { + activityCallback.setToolbarButton(Toolbar.BUTTON_NONE); + } else { + activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); + } + } + + @Override + public void onAttach(Context context) { + Timber.d("onAttach %s", context); + super.onAttach(context); + if (context instanceof Listener) { + this.activityCallback = (Listener) context; + } else { + throw new ClassCastException(context.toString() + + " must implement Listener"); + } + } + + private SpendViewPager spendViewPager; + private SpendPagerAdapter pagerAdapter; + + @Override + public boolean onBackPressed() { + if (isComitted()) return true; // no going back + if (spendViewPager.getCurrentItem() == 0) { + return false; + } else { + spendViewPager.previous(); + return true; + } + } + + public class SpendPagerAdapter extends FragmentPagerAdapter { + private static final int POS_ADDRESS = 0; + private static final int POS_AMOUNT = 1; + private static final int POS_SETTINGS = 2; + private static final int POS_CONFIRM = 3; + private static final int POS_SUCCESS = 4; + private int numPages = 4; + + SparseArray> myFragments = new SparseArray<>(); + + public SpendPagerAdapter(FragmentManager fm) { + super(fm); + } + + public void addSuccess() { + numPages++; + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return numPages; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + SendWizardFragment fragment = (SendWizardFragment) super.instantiateItem(container, position); + myFragments.put(position, new WeakReference<>(fragment)); + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + myFragments.remove(position); + super.destroyItem(container, position, object); + } + + public SendWizardFragment getFragment(int position) { + WeakReference ref = myFragments.get(position); + if (ref != null) + return myFragments.get(position).get(); + else + return null; + } + + @Override + public SendWizardFragment getItem(int position) { + Timber.d("getItem(%d) CREATE", position); + switch (position) { + case POS_ADDRESS: + return SendAddressWizardFragment.newInstance(SendFragmentNew.this); + case POS_AMOUNT: + return SendAmountWizardFragment.newInstance(SendFragmentNew.this); + case POS_SETTINGS: + return SendSettingsWizardFragment.newInstance(SendFragmentNew.this); + case POS_CONFIRM: + return SendConfirmWizardFragment.newInstance(SendFragmentNew.this); + case POS_SUCCESS: + return SendSuccessWizardFragment.newInstance(SendFragmentNew.this); + default: + throw new IllegalArgumentException("no such send position(" + position + ")"); + } + } + + @Override + public CharSequence getPageTitle(int position) { + Timber.d("getPageTitle(%d)", position); + if (position >= numPages) return null; + switch (position) { + case POS_ADDRESS: + return getString(R.string.send_address_title); + case POS_AMOUNT: + return getString(R.string.send_amount_title); + case POS_SETTINGS: + return getString(R.string.send_settings_title); + case POS_CONFIRM: + return getString(R.string.send_confirm_title); + case POS_SUCCESS: + return getString(R.string.send_success_title); + default: + return null; + } + } + } + + @Override + public TxData getTxData() { + return new TxData(sendAddress, sendPaymentId, sendAmount, sendMixin, sendPriority); + } + + @Override + public String getNotes() { + return sendNotes; + } + + private String sendAddress; + private String sendPaymentId; + private long sendAmount; + private PendingTransaction.Priority sendPriority; + private int sendMixin; + private String sendNotes; + private BarcodeData barcodeData; + + // Listeners + @Override + public void setBarcodeData(BarcodeData data) { + barcodeData = data; + } + + @Override + public BarcodeData popBarcodeData() { + BarcodeData data = barcodeData; + barcodeData = null; + return data; + } + + @Override + public void setAddress(final String address) { + sendAddress = address; + } + + @Override + public void setPaymentId(final String paymentId) { + sendPaymentId = paymentId; + } + + @Override + public void setAmount(final long amount) { + sendAmount = amount; + } + + @Override + public void setPriority(final PendingTransaction.Priority priority) { + sendPriority = priority; + } + + @Override + public void setMixin(final int mixin) { + sendMixin = mixin; + } + + @Override + public void setNotes(final String notes) { + sendNotes = notes; + } + + boolean isComitted() { + return committedTx != null; + } + + PendingTx committedTx; + + @Override + public PendingTx getCommittedTx() { + return committedTx; + } + + + @Override + public void commitTransaction() { + Timber.d("REALLY SEND A %s", getNotes()); + disableNavigation(); // committed - disable all navigation + activityCallback.onSend(getNotes()); + committedTx = pendingTx; + } + + void disableNavigation() { + spendViewPager.allowSwipe(false); + } + + void enableNavigation() { + spendViewPager.allowSwipe(true); + } + + @Override + public void enableDone() { + llNavBar.setVisibility(View.INVISIBLE); + bDone.setVisibility(View.VISIBLE); + } + + public Listener getActivityCallback() { + return activityCallback; + } + + + // callbacks from send service + + public void onTransactionCreated(PendingTransaction pendingTransaction) { + //public void onTransactionCreated(TestTransaction pendingTransaction) { + final SendConfirmWizardFragment confirmFragment = getConfirmFragment(); + if (confirmFragment != null) { + pendingTx = new PendingTx(pendingTransaction); + confirmFragment.transactionCreated(pendingTransaction); + } else { + // not in confirm fragment => dispose & move on + disposeTransaction(); + } + } + + @Override + public void disposeTransaction() { + pendingTx = null; + activityCallback.onDisposeRequest(); + } + + PendingTx pendingTx; + + public PendingTx getPendingTx() { + return pendingTx; + } + + public void onCreateTransactionFailed(String errorText) { + final SendConfirmWizardFragment confirmFragment = getConfirmFragment(); + if (confirmFragment != null) { + confirmFragment.hideProgress(); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setCancelable(false). + setTitle(getString(R.string.send_error_title)). + setMessage(errorText). + create(). + show(); + } + } + + SendConfirmWizardFragment getConfirmFragment() { + final SendWizardFragment fragment = pagerAdapter.getFragment(SpendPagerAdapter.POS_CONFIRM); + if (fragment instanceof SendConfirmWizardFragment) { + return (SendConfirmWizardFragment) fragment; + } else { + return null; + } + } + + public void onTransactionSent(final String txId) { + Timber.d("txid=%s", txId); + pagerAdapter.addSuccess(); + Timber.d("numPages=%d", spendViewPager.getAdapter().getCount()); + spendViewPager.setCurrentItem(SpendPagerAdapter.POS_SUCCESS); + activityCallback.setToolbarButton(Toolbar.BUTTON_NONE); + } + + public void onSendTransactionFailed(final String error) { + Timber.d("error=%s", error); + committedTx = null; + Toast.makeText(getContext(), getString(R.string.status_transaction_failed, error), Toast.LENGTH_SHORT).show(); + enableNavigation(); + final SendConfirmWizardFragment fragment = getConfirmFragment(); + if (fragment != null) { + fragment.sendFailed(); + } + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendSettingsWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SendSettingsWizardFragment.java new file mode 100644 index 0000000..8746c22 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/SendSettingsWizardFragment.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet; + +import android.os.Bundle; +import android.text.InputType; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; + +import com.m2049r.xmrwallet.model.PendingTransaction; +import com.m2049r.xmrwallet.util.Helper; + +import timber.log.Timber; + +public class SendSettingsWizardFragment extends SendWizardFragment { + + public static SendSettingsWizardFragment newInstance(Listener listener) { + SendSettingsWizardFragment instance = new SendSettingsWizardFragment(); + instance.setSendListener(listener); + return instance; + } + + Listener sendListener; + + public SendSettingsWizardFragment setSendListener(Listener listener) { + this.sendListener = listener; + return this; + } + + interface Listener { + + void setPriority(final PendingTransaction.Priority priority); + + void setMixin(final int mixin); + + void setNotes(final String notes); + } + + final static int Mixins[] = {4, 7, 12, 25}; // must match the layout XML + final static PendingTransaction.Priority Priorities[] = + {PendingTransaction.Priority.Priority_Default, + PendingTransaction.Priority.Priority_Low, + PendingTransaction.Priority.Priority_Medium, + PendingTransaction.Priority.Priority_High}; // must match the layout XML + + private Spinner sMixin; + private Spinner sPriority; + private EditText etNotes; + private EditText etDummy; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); + + View view = inflater.inflate( + R.layout.fragment_send_settings, container, false); + + sMixin = (Spinner) view.findViewById(R.id.sMixin); + sPriority = (Spinner) view.findViewById(R.id.sPriority); + + etNotes = (EditText) view.findViewById(R.id.etNotes); + etNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); + etNotes.setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { + etDummy.requestFocus(); + Helper.hideKeyboard(getActivity()); + return true; + } + return false; + } + }); + + etDummy = (EditText) view.findViewById(R.id.etDummy); + etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + + return view; + } + + @Override + public boolean onValidateFields() { + if (sendListener != null) { + int mixin = Mixins[sMixin.getSelectedItemPosition()]; + int priorityIndex = sPriority.getSelectedItemPosition(); + PendingTransaction.Priority priority = Priorities[priorityIndex]; + sendListener.setPriority(priority); + sendListener.setMixin(mixin); + String notes = etNotes.getText().toString(); + sendListener.setNotes(notes); + } + return true; + } + + @Override + public void onResumeFragment() { + super.onResumeFragment(); + Timber.d("onResumeFragment()"); + Helper.hideKeyboard(getActivity()); + etDummy.requestFocus(); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendSuccessWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SendSuccessWizardFragment.java new file mode 100644 index 0000000..9a21dcf --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/SendSuccessWizardFragment.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.TextView; +import android.widget.Toast; + +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.util.Helper; +import com.m2049r.xmrwallet.data.PendingTx; +import com.m2049r.xmrwallet.data.TxData; + +import timber.log.Timber; + +public class SendSuccessWizardFragment extends SendWizardFragment { + + public static SendSuccessWizardFragment newInstance(Listener listener) { + SendSuccessWizardFragment instance = new SendSuccessWizardFragment(); + instance.setSendListener(listener); + return instance; + } + + Listener sendListener; + + public SendSuccessWizardFragment setSendListener(Listener listener) { + this.sendListener = listener; + return this; + } + + interface Listener { + String getNotes(); + + TxData getTxData(); + + PendingTx getCommittedTx(); + + void enableDone(); + } + + ImageButton bCopyAddress; + private TextView tvTxId; + private TextView tvTxAddress; + private TextView tvTxPaymentId; + private TextView tvTxNotes; + private TextView tvTxAmount; + private TextView tvTxFee; + private TextView tvTxTotal; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); + + View view = inflater.inflate( + R.layout.fragment_send_success, container, false); + + bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress); + bCopyAddress.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + copyAddress(); + } + }); + + tvTxId = (TextView) view.findViewById(R.id.tvTxId); + tvTxAddress = (TextView) view.findViewById(R.id.tvTxAddress); + tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId); + tvTxNotes = (TextView) view.findViewById(R.id.tvTxNotes); + tvTxAmount = ((TextView) view.findViewById(R.id.tvTxAmount)); + tvTxFee = (TextView) view.findViewById(R.id.tvTxFee); + tvTxTotal = (TextView) view.findViewById(R.id.tvTxTotal); + + return view; + } + + @Override + public boolean onValidateFields() { + return true; + } + + @Override + public void onPauseFragment() { + super.onPauseFragment(); + } + + @Override + public void onResumeFragment() { + super.onResumeFragment(); + Timber.d("onResumeFragment()"); + Helper.hideKeyboard(getActivity()); + + final TxData txData = sendListener.getTxData(); + tvTxAddress.setText(txData.getDestinationAddress()); + String paymentId = txData.getPaymentId(); + if ((paymentId != null) && (!paymentId.isEmpty())) { + tvTxPaymentId.setText(txData.getPaymentId()); + } else { + tvTxPaymentId.setText("-"); + } + String notes = sendListener.getNotes(); + if ((notes != null) && (!notes.isEmpty())) { + tvTxNotes.setText(sendListener.getNotes()); + } else { + tvTxNotes.setText("-"); + } + + final PendingTx committedTx = sendListener.getCommittedTx(); + if (committedTx != null) { + tvTxId.setText(committedTx.txId); + bCopyAddress.setEnabled(true); + bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp); + tvTxAmount.setText(Wallet.getDisplayAmount(committedTx.amount)); + tvTxFee.setText(Wallet.getDisplayAmount(committedTx.fee)); + //tvTxDust.setText(Wallet.getDisplayAmount(pendingTransaction.getDust())); + tvTxTotal.setText(Wallet.getDisplayAmount( + committedTx.fee + committedTx.amount)); + } + sendListener.enableDone(); + } + + void copyAddress() { + Helper.clipBoardCopy(getActivity(), getString(R.string.label_send_txid), tvTxId.getText().toString()); + Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show(); + } + +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SendWizardFragment.java new file mode 100644 index 0000000..ae99bd7 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/SendWizardFragment.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet; + +import android.support.v4.app.Fragment; + +import com.m2049r.xmrwallet.layout.SpendViewPager; + +abstract public class SendWizardFragment extends Fragment + implements SpendViewPager.OnValidateFieldsListener { + + @Override + public boolean onValidateFields() { + return true; + } + + public void onPauseFragment() { + } + + public void onResumeFragment() { + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java index b30db1b..b530587 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java @@ -31,10 +31,10 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; -import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.Transfer; import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.widget.Toolbar; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -166,8 +166,7 @@ public class TxFragment extends Fragment { sb.append("-"); } sb.append("\n\n"); - //Helper.clipBoardCopy(getActivity(), getString(R.string.tx_copy_label), sb.toString()); - //Toast.makeText(getActivity(), getString(R.string.tx_copy_message), Toast.LENGTH_SHORT).show(); + Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, sb.toString()); @@ -308,4 +307,4 @@ public class TxFragment extends Fragment { + " must implement Listener"); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java index 3c0d2bf..2a8dd46 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java @@ -34,28 +34,30 @@ import android.support.v4.app.FragmentTransaction; import android.view.MenuItem; import android.widget.Toast; +import com.m2049r.xmrwallet.data.BarcodeData; +import com.m2049r.xmrwallet.data.TxData; import com.m2049r.xmrwallet.dialog.DonationFragment; import com.m2049r.xmrwallet.dialog.HelpFragment; -import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.service.WalletService; -import com.m2049r.xmrwallet.util.BarcodeData; import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.TxData; +import com.m2049r.xmrwallet.widget.Toolbar; +import java.io.File; import java.util.HashMap; import java.util.Map; import timber.log.Timber; public class WalletActivity extends SecureActivity implements WalletFragment.Listener, - WalletService.Observer, SendFragment.Listener, TxFragment.Listener, + WalletService.Observer, SendFragmentNew.Listener, TxFragment.Listener, GenerateReviewFragment.ListenerWithWallet, GenerateReviewFragment.Listener, - ScannerFragment.Listener, ReceiveFragment.Listener { + ScannerFragment.OnScannedListener, ReceiveFragment.Listener, + SendAddressWizardFragment.OnScanListener { public static final String REQUEST_ID = "id"; public static final String REQUEST_PW = "pw"; @@ -108,7 +110,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis @Override protected void onStart() { super.onStart(); - Timber.d( "onStart()"); + Timber.d("onStart()"); } private void startWalletService() { @@ -176,7 +178,6 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis } } - @Override protected void onCreate(Bundle savedInstanceState) { Timber.d("onCreate()"); @@ -254,7 +255,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis } } updateProgress(); - Timber.d( "CONNECTED"); + Timber.d("CONNECTED"); } public void onServiceDisconnected(ComponentName className) { @@ -264,7 +265,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis // see this happen. mBoundService = null; setTitle(getString(R.string.wallet_activity_name), getString(R.string.status_wallet_disconnected)); - Timber.d( "DISCONNECTED"); + Timber.d("DISCONNECTED"); } }; @@ -280,7 +281,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis startService(intent); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; - Timber.d( "BOUND"); + Timber.d("BOUND"); } void disconnectWalletService() { @@ -289,20 +290,20 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis mBoundService.setObserver(null); unbindService(mConnection); mIsBound = false; - Timber.d( "UNBOUND"); + Timber.d("UNBOUND"); } } @Override protected void onPause() { - Timber.d( "onPause()"); + Timber.d("onPause()"); super.onPause(); } @Override protected void onResume() { super.onResume(); - Timber.d( "onResume()"); + Timber.d("onResume()"); } private PowerManager.WakeLock wl = null; @@ -313,9 +314,9 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis this.wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name)); try { wl.acquire(); - Timber.d( "WakeLock acquired"); + Timber.d("WakeLock acquired"); } catch (SecurityException ex) { - Timber.w( "WakeLock NOT acquired: %s", ex.getLocalizedMessage()); + Timber.w("WakeLock NOT acquired: %s", ex.getLocalizedMessage()); wl = null; } } @@ -324,7 +325,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis if ((wl == null) || !wl.isHeld()) return; wl.release(); wl = null; - Timber.d( "WakeLock released"); + Timber.d("WakeLock released"); } public void saveWallet() { @@ -332,9 +333,9 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis Intent intent = new Intent(getApplicationContext(), WalletService.class); intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_STORE); startService(intent); - Timber.d( "STORE request sent"); + Timber.d("STORE request sent"); } else { - Timber.e( "Service not bound"); + Timber.e("Service not bound"); } } @@ -359,7 +360,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis @Override public void onSendRequest() { - replaceFragment(new SendFragment(), null, null); + replaceFragment(new SendFragmentNew(), null, null); } @Override @@ -374,7 +375,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis try { onRefreshed(getWallet(), true); } catch (IllegalStateException ex) { - Timber.e( ex.getLocalizedMessage()); + Timber.e(ex.getLocalizedMessage()); } } @@ -382,10 +383,10 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis // WalletService.Observer /////////////////////////// - // refresh and return if successful + // refresh and return true if successful @Override public boolean onRefreshed(final Wallet wallet, final boolean full) { - Timber.d( "onRefreshed()"); + Timber.d("onRefreshed()"); try { final WalletFragment walletFragment = (WalletFragment) getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName()); @@ -460,9 +461,9 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis } @Override - public void onCreatedTransaction(final PendingTransaction pendingTransaction) { + public void onTransactionCreated(final PendingTransaction pendingTransaction) { try { - final SendFragment sendFragment = (SendFragment) + final SendFragmentNew sendFragment = (SendFragmentNew) getSupportFragmentManager().findFragmentById(R.id.fragment_container); runOnUiThread(new Runnable() { public void run() { @@ -470,9 +471,9 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis if (status != PendingTransaction.Status.Status_Ok) { String errorText = pendingTransaction.getErrorString(); getWallet().disposePendingTransaction(); - sendFragment.onCreatedTransactionFailed(errorText); + sendFragment.onCreateTransactionFailed(errorText); } else { - sendFragment.onCreatedTransaction(pendingTransaction); + sendFragment.onTransactionCreated(pendingTransaction); } } }); @@ -485,17 +486,35 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis } @Override - public void onSentTransaction(final boolean success) { - runOnUiThread(new Runnable() { - public void run() { - if (success) { - Toast.makeText(WalletActivity.this, getString(R.string.status_transaction_sent), Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(WalletActivity.this, getString(R.string.status_transaction_failed), Toast.LENGTH_SHORT).show(); + public void onSendTransactionFailed(final String error) { + try { + final SendFragmentNew sendFragment = (SendFragmentNew) + getSupportFragmentManager().findFragmentById(R.id.fragment_container); + runOnUiThread(new Runnable() { + public void run() { + sendFragment.onSendTransactionFailed(error); } - popFragmentStack(null); - } - }); + }); + } catch (ClassCastException ex) { + // not in spend fragment + Timber.d(ex.getLocalizedMessage()); + } + } + + @Override + public void onTransactionSent(final String txId) { + try { + final SendFragmentNew sendFragment = (SendFragmentNew) + getSupportFragmentManager().findFragmentById(R.id.fragment_container); + runOnUiThread(new Runnable() { + public void run() { + sendFragment.onTransactionSent(txId); + } + }); + } catch (ClassCastException ex) { + // not in spend fragment + Timber.d(ex.getLocalizedMessage()); + } } @Override @@ -514,7 +533,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis } catch (ClassCastException ex) { // not in tx fragment Timber.d(ex.getLocalizedMessage()); - // never min + // never mind } } @@ -611,7 +630,6 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis return getWallet().getAddress(); } - @Override public String getWalletName() { return getWallet().getName(); } @@ -693,7 +711,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis /// QR scanner callbacks @Override - public void onScanAddress() { + public void onScan() { if (Helper.getCameraPermission(this)) { startScanFragment(); } else { @@ -705,7 +723,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis private BarcodeData scannedData = null; @Override - public boolean onAddressScanned(String uri) { + public boolean onScanned(String uri) { BarcodeData bcData = parseMoneroUri(uri); if (bcData != null) { this.scannedData = bcData; @@ -759,7 +777,6 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis return null; } - @Override public BarcodeData popScannedData() { BarcodeData data = scannedData; @@ -793,7 +810,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis } void startReceive(String address) { - Timber.d( "startReceive()"); + Timber.d("startReceive()"); Bundle b = new Bundle(); b.putString("address", address); b.putString("name", getWalletName()); @@ -802,6 +819,35 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis void startReceiveFragment(Bundle extras) { replaceFragment(new ReceiveFragment(), null, extras); - Timber.d( "ReceiveFragment placed"); + Timber.d("ReceiveFragment placed"); + } + + @Override + public long getTotalFunds() { + return getWallet().getUnlockedBalance(); + } + + @Override + public boolean verifyWalletPassword(String password) { + String walletPath = new File(Helper.getStorageRoot(this), + getWalletName() + ".keys").getAbsolutePath(); + return WalletManager.getInstance().verifyWalletPassword(walletPath, password, true); + } + + @Override + public void onBackPressed() { + final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container); + if (fragment instanceof OnBackPressedListener) { + if (!((OnBackPressedListener) fragment).onBackPressed()) { + super.onBackPressed(); + } + } else { + super.onBackPressed(); + } + } + + @Override + public void onFragmentDone() { + popFragmentStack(null); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java index 72b74ed..d497d40 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java @@ -37,7 +37,6 @@ import android.widget.ProgressBar; import android.widget.Spinner; import android.widget.TextView; -import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.Wallet; @@ -47,6 +46,7 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; import com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.OkHttpClientSingleton; +import com.m2049r.xmrwallet.widget.Toolbar; import java.text.NumberFormat; import java.util.List; @@ -136,7 +136,6 @@ public class WalletFragment extends Fragment } }); - if (activityCallback.isSynced()) { onSynced(); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java b/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java index fdc340d..520a817 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java +++ b/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2017 m2049r et al. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.m2049r.xmrwallet; diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/BarcodeData.java b/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java similarity index 96% rename from app/src/main/java/com/m2049r/xmrwallet/util/BarcodeData.java rename to app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java index e035e33..e0fd433 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/BarcodeData.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.m2049r.xmrwallet.util; +package com.m2049r.xmrwallet.data; public class BarcodeData { public String address = null; diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/PendingTx.java b/app/src/main/java/com/m2049r/xmrwallet/data/PendingTx.java new file mode 100644 index 0000000..7f99ad9 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/data/PendingTx.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.data; + +import com.m2049r.xmrwallet.model.PendingTransaction; + +public class PendingTx { + final public PendingTransaction.Status status; + final public String error; + final public long amount; + final public long dust; + final public long fee; + final public String txId; + final public long txCount; + + public PendingTx(PendingTransaction pendingTransaction) { + status = pendingTransaction.getStatus(); + error = pendingTransaction.getErrorString(); + amount = pendingTransaction.getAmount(); + dust = pendingTransaction.getDust(); + fee = pendingTransaction.getFee(); + txId = pendingTransaction.getFirstTxId(); + txCount = pendingTransaction.getTxCount(); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/TxData.java b/app/src/main/java/com/m2049r/xmrwallet/data/TxData.java similarity index 74% rename from app/src/main/java/com/m2049r/xmrwallet/util/TxData.java rename to app/src/main/java/com/m2049r/xmrwallet/data/TxData.java index 147b0ac..d15c54d 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/TxData.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/TxData.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.m2049r.xmrwallet.util; +package com.m2049r.xmrwallet.data; import android.os.Parcel; import android.os.Parcelable; @@ -23,6 +23,15 @@ import com.m2049r.xmrwallet.model.PendingTransaction; // https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents public class TxData implements Parcelable { + + public TxData(TxData txData) { + this.dst_addr = txData.dst_addr; + this.paymentId = txData.paymentId; + this.amount = txData.amount; + this.mixin = txData.mixin; + this.priority = txData.priority; + } + public TxData(String dst_addr, String paymentId, long amount, @@ -35,11 +44,35 @@ public class TxData implements Parcelable { this.priority = priority; } - public String dst_addr; - public String paymentId; - public long amount; - public int mixin; - public PendingTransaction.Priority priority; + public long getFee() { + return 0L; + } + + public String getDestinationAddress() { + return dst_addr; + } + + public String getPaymentId() { + return paymentId; + } + + public long getAmount() { + return amount; + } + + public int getMixin() { + return mixin; + } + + public PendingTransaction.Priority getPriority() { + return priority; + } + + final private String dst_addr; + final private String paymentId; + final private long amount; + final private int mixin; + final private PendingTransaction.Priority priority; @Override public void writeToParcel(Parcel out, int flags) { @@ -87,7 +120,7 @@ public class TxData implements Parcelable { sb.append(",mixin:"); sb.append(mixin); sb.append(",priority:"); - sb.append(priority.toString()); + sb.append(String.valueOf(priority)); return sb.toString(); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java b/app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java new file mode 100644 index 0000000..e67d72d --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.layout; + +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import com.m2049r.xmrwallet.SendFragmentNew; + +import timber.log.Timber; + +public class SpendViewPager extends ViewPager { + + public interface OnValidateFieldsListener { + boolean onValidateFields(); + } + + public SpendViewPager(Context context) { + super(context); + } + + public SpendViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void next() { + int pos = getCurrentItem(); + if (validateFields(pos)) { + setCurrentItem(pos + 1); + } + } + + public void previous() { + setCurrentItem(getCurrentItem() - 1); + } + + private boolean allowSwipe = true; + + public void allowSwipe(boolean allow) { + allowSwipe = allow; + } + + public boolean validateFields(int position) { + OnValidateFieldsListener c = ((SendFragmentNew.SpendPagerAdapter) getAdapter()).getFragment(position); + return c.onValidateFields(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (allowSwipe) return super.onInterceptTouchEvent(event); + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (allowSwipe) return super.onTouchEvent(event); + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java index 2c58436..8c4157c 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java @@ -42,11 +42,6 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter= android.os.Build.VERSION_CODES.M) { @@ -174,9 +175,14 @@ public class Helper { static public String getFormattedAmount(double amount, boolean isXmr) { // at this point selection is XMR in case of error String displayB; - if (isXmr) { // not XMR - displayB = String.format(Locale.US, "%,.5f", amount); - } else { // XMR + if (isXmr) { // XMR + long xmr = Wallet.getAmountFromDouble(amount); + if ((xmr > 0) || (amount == 0)) { + displayB = String.format(Locale.US, "%,.5f", amount); + } else { + displayB = null; + } + } else { // not XMR displayB = String.format(Locale.US, "%,.2f", amount); } return displayB; @@ -241,4 +247,17 @@ public class Helper { clipboardManager.setPrimaryClip(clip); } + static private Animation ShakeAnimation; + + static public Animation getShakeAnimation(Context context) { + if (ShakeAnimation == null) { + synchronized (Helper.class) { + if (ShakeAnimation == null) { + ShakeAnimation = AnimationUtils.loadAnimation(context, R.anim.shake); + } + } + } + return ShakeAnimation; + } + } diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/CTextInputLayout.java b/app/src/main/java/com/m2049r/xmrwallet/widget/CTextInputLayout.java similarity index 97% rename from app/src/main/java/com/m2049r/xmrwallet/layout/CTextInputLayout.java rename to app/src/main/java/com/m2049r/xmrwallet/widget/CTextInputLayout.java index 6be40f0..8933509 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/CTextInputLayout.java +++ b/app/src/main/java/com/m2049r/xmrwallet/widget/CTextInputLayout.java @@ -16,7 +16,7 @@ // based on from https://stackoverflow.com/a/45325876 (which did not work for me) -package com.m2049r.xmrwallet.layout; +package com.m2049r.xmrwallet.widget; import android.content.Context; import android.support.design.widget.TextInputLayout; diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/DotBar.java b/app/src/main/java/com/m2049r/xmrwallet/widget/DotBar.java new file mode 100644 index 0000000..0baf2d3 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/widget/DotBar.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2017 m2049r et al. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// based on https://github.com/marcokstephen/StepProgressBar + +package com.m2049r.xmrwallet.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +import com.m2049r.xmrwallet.R; + +import timber.log.Timber; + +public class DotBar extends View { + + final private int inactiveColor; + final private int activeColor; + + final private float dotSize; + private float dotSpacing; + + final private int numDots; + private int activeDot; + + final private Paint paint; + + public DotBar(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DotBar, 0, 0); + try { + inactiveColor = ta.getInt(R.styleable.DotBar_inactiveColor, 0); + activeColor = ta.getInt(R.styleable.DotBar_activeColor, 0); + dotSize = ta.getDimensionPixelSize(R.styleable.DotBar_dotSize, 8); + numDots = ta.getInt(R.styleable.DotBar_numberDots, 5); + activeDot = ta.getInt(R.styleable.DotBar_activeDot, 0); + } finally { + ta.recycle(); + } + + paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setStyle(Paint.Style.FILL); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int desiredWidth = (int) ((numDots * dotSize) + getPaddingLeft() + getPaddingRight()); + int desiredHeight = (int) (dotSize + getPaddingBottom() + getPaddingTop()); + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + int width; + int height; + + //Measure Width + if (widthMode == MeasureSpec.EXACTLY) { + //Must be this size + width = widthSize; + } else if (widthMode == MeasureSpec.AT_MOST) { + //Can't be bigger than... + width = Math.min(desiredWidth, widthSize); + } else { + //Be whatever you want + width = desiredWidth; + } + + //Measure Height + if (heightMode == MeasureSpec.EXACTLY) { + //Must be this size + height = heightSize; + } else if (heightMode == MeasureSpec.AT_MOST) { + //Can't be bigger than... + height = Math.min(desiredHeight, heightSize); + } else { + //Be whatever you want + height = desiredHeight; + } + + dotSpacing = (int) (((1.0 * width - (getPaddingLeft() + getPaddingRight())) / numDots - dotSize) / (numDots - 1)); + + Timber.d("dotSpacing=%f", dotSpacing); + //MUST CALL THIS + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // Centering the dots in the middle of the canvas + float singleDotSize = dotSpacing + dotSize; + float combinedDotSize = singleDotSize * numDots - dotSpacing; + int startingX = (int) ((canvas.getWidth() - combinedDotSize) / 2); + int startingY = (int) ((canvas.getHeight() - dotSize) / 2); + + for (int i = 0; i < numDots; i++) { + int x = (int) (startingX + i * singleDotSize); + if (i == activeDot) { + paint.setColor(activeColor); + } else { + paint.setColor(inactiveColor); + } + canvas.drawCircle(x + dotSize / 2, startingY + dotSize / 2, dotSize / 2, paint); + } + } + + public void next() { + if (activeDot < numDots - 2) { + activeDot++; + invalidate(); + } // else no next - stay stuck at end + } + + public void previous() { + if (activeDot >= 0) { + activeDot--; + invalidate(); + } // else no previous - stay stuck at beginning + } + + public void setActiveDot(int i) { + if ((i >= 0) && (i < numDots)) { + activeDot = i; + invalidate(); + } + } + + public int getActiveDot() { + return activeDot; + } + + public int getNumDots() { + return numDots; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/DropDownEditText.java b/app/src/main/java/com/m2049r/xmrwallet/widget/DropDownEditText.java similarity index 97% rename from app/src/main/java/com/m2049r/xmrwallet/layout/DropDownEditText.java rename to app/src/main/java/com/m2049r/xmrwallet/widget/DropDownEditText.java index 43f8b76..e6cd404 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/DropDownEditText.java +++ b/app/src/main/java/com/m2049r/xmrwallet/widget/DropDownEditText.java @@ -16,7 +16,7 @@ // https://stackoverflow.com/questions/2126717/android-autocompletetextview-show-suggestions-when-no-text-entered -package com.m2049r.xmrwallet.layout; +package com.m2049r.xmrwallet.widget; import android.content.Context; import android.graphics.Rect; diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java new file mode 100644 index 0000000..22908ac --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889 + +package com.m2049r.xmrwallet.widget; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.Spinner; +import android.widget.TextView; + +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; +import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; +import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; +import com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl; +import com.m2049r.xmrwallet.util.Helper; +import com.m2049r.xmrwallet.util.OkHttpClientSingleton; + +import java.util.Locale; + +import timber.log.Timber; + +public class ExchangeTextView extends LinearLayout + implements NumberPadView.NumberPadListener { + + String xmrAmount = null; + String notXmrAmount = null; + + void setXmr(String xmr) { + xmrAmount = xmr; + if (onNewAmountListener != null) { + onNewAmountListener.onNewAmount(xmr); + } + } + + public boolean validate(double max) { + boolean ok = true; + if (xmrAmount != null) { + try { + double amount = Double.parseDouble(xmrAmount); + if (amount > max) { + ok = false; + } + if (amount <= 0) { + ok = false; + } + } catch (NumberFormatException ex) { + // this cannot be + Timber.e(ex.getLocalizedMessage()); + ok = false; + } + } else { + ok = false; + } + if (!ok) { + shakeAmountField(); + } + return ok; + } + + void shakeAmountField() { + tvAmountA.startAnimation(Helper.getShakeAnimation(getContext())); + } + + public void setAmount(String xmrAmount) { + if (xmrAmount != null) { + setCurrencyA(0); + tvAmountA.setText(xmrAmount); + setXmr(xmrAmount); + this.notXmrAmount = null; + doExchange(); + } else { + setXmr(null); + this.notXmrAmount = null; + tvAmountB.setText(null); + } + } + + public String getAmount() { + return xmrAmount; + } + + TextView tvAmountA; + TextView tvAmountB; + Spinner sCurrencyA; + Spinner sCurrencyB; + ImageView evExchange; + ProgressBar pbExchange; + + + public void setCurrencyA(int currency) { + if ((currency != 0) && (getCurrencyB() != 0)) { + setCurrencyB(0); + } + sCurrencyA.setSelection(currency, true); + doExchange(); + } + + public void setCurrencyB(int currency) { + if ((currency != 0) && (getCurrencyA() != 0)) { + setCurrencyA(0); + } + sCurrencyB.setSelection(currency, true); + doExchange(); + } + + public int getCurrencyA() { + return sCurrencyA.getSelectedItemPosition(); + } + + public int getCurrencyB() { + return sCurrencyB.getSelectedItemPosition(); + } + + public ExchangeTextView(Context context) { + super(context); + initializeViews(context); + } + + public ExchangeTextView(Context context, AttributeSet attrs) { + super(context, attrs); + initializeViews(context); + } + + public ExchangeTextView(Context context, + AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + initializeViews(context); + } + + /** + * Inflates the views in the layout. + * + * @param context the current context for the view. + */ + private void initializeViews(Context context) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.view_exchange_text, this); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + tvAmountA = (TextView) findViewById(R.id.tvAmountA); + tvAmountB = (TextView) findViewById(R.id.tvAmountB); + sCurrencyA = (Spinner) findViewById(R.id.sCurrencyA); + sCurrencyB = (Spinner) findViewById(R.id.sCurrencyB); + evExchange = (ImageView) findViewById(R.id.evExchange); + pbExchange = (ProgressBar) findViewById(R.id.pbExchange); + + // make progress circle gray + pbExchange.getIndeterminateDrawable(). + setColorFilter(getResources().getColor(R.color.trafficGray), + android.graphics.PorterDuff.Mode.MULTIPLY); + + + sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parentView, View selectedItemView, int position, long id) { + if (position != 0) { // if not XMR, select XMR on other + sCurrencyB.setSelection(0, true); + } + doExchange(); + } + + @Override + public void onNothingSelected(AdapterView parentView) { + // nothing + } + }); + + sCurrencyB.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(final AdapterView parentView, View selectedItemView, int position, long id) { + if (position != 0) { // if not XMR, select XMR on other + sCurrencyA.setSelection(0, true); + } + doExchange(); + } + + @Override + public void onNothingSelected(AdapterView parentView) { + // nothing + } + }); + } + + public void doExchange() { + tvAmountB.setText(null); + // use cached exchange rate if we have it + if (!isExchangeInProgress()) { + String enteredCurrencyA = (String) sCurrencyA.getSelectedItem(); + String enteredCurrencyB = (String) sCurrencyB.getSelectedItem(); + if ((enteredCurrencyA + enteredCurrencyB).equals(assetPair)) { + if (prepareExchange()) { + exchange(assetRate); + } else { + clearAmounts(); + } + } else { + clearAmounts(); + startExchange(); + } + } else { + clearAmounts(); + } + } + + private void clearAmounts() { + Timber.d("clearAmounts"); + if ((xmrAmount != null) || (notXmrAmount != null)) { + tvAmountB.setText(null); + setXmr(null); + notXmrAmount = null; + } + } + + private final ExchangeApi exchangeApi = new ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient()); + + void startExchange() { + showProgress(); + String currencyA = (String) sCurrencyA.getSelectedItem(); + String currencyB = (String) sCurrencyB.getSelectedItem(); + exchangeApi.queryExchangeRate(currencyA, currencyB, + new ExchangeCallback() { + @Override + public void onSuccess(final ExchangeRate exchangeRate) { + if (isAttachedToWindow()) + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + exchange(exchangeRate); + } + }); + } + + @Override + public void onError(final Exception e) { + Timber.e(e.getLocalizedMessage()); + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + exchangeFailed(); + } + }); + } + }); + } + + public void exchange(double rate) { + Timber.d("%s / %s", xmrAmount, notXmrAmount); + if (getCurrencyA() == 0) { + if (xmrAmount == null) return; + if (!xmrAmount.isEmpty() && (rate > 0)) { + double amountB = rate * Double.parseDouble(xmrAmount); + notXmrAmount = Helper.getFormattedAmount(amountB, getCurrencyB() == 0); + } else { + notXmrAmount = ""; + } + tvAmountB.setText(notXmrAmount); + Timber.d("%s / %s", xmrAmount, notXmrAmount); + } else if (getCurrencyB() == 0) { + if (notXmrAmount == null) return; + if (!notXmrAmount.isEmpty() && (rate > 0)) { + double amountB = rate * Double.parseDouble(notXmrAmount); + setXmr(Helper.getFormattedAmount(amountB, true)); + } else { + setXmr(""); + } + tvAmountB.setText(xmrAmount); + if (xmrAmount == null) { + shakeAmountField(); + } + } else { // no XMR currency - cannot happen! + Timber.e("No XMR currency!"); + setXmr(null); + notXmrAmount = null; + return; + } + } + + boolean prepareExchange() { + Timber.d("prepareExchange()"); + String enteredAmount = tvAmountA.getText().toString(); + if (!enteredAmount.isEmpty()) { + String cleanAmount = ""; + if (getCurrencyA() == 0) { + // sanitize the input + long xmr = Wallet.getAmountFromString(enteredAmount); + if (xmr >= 0) { + cleanAmount = Helper.getDisplayAmount(xmr); + } else { + cleanAmount = null; + } + setXmr(cleanAmount); + notXmrAmount = null; + Timber.d("cleanAmount = %s", cleanAmount); + if (cleanAmount == null) { + shakeAmountField(); + return false; + } + } else if (getCurrencyB() == 0) { // we use B & 0 here for the else below ... + // sanitize the input + double amountA = Double.parseDouble(enteredAmount); + cleanAmount = String.format(Locale.US, "%.2f", amountA); + setXmr(null); + notXmrAmount = cleanAmount; + } else { // no XMR currency - cannot happen! + Timber.e("No XMR currency!"); + setXmr(null); + notXmrAmount = null; + return false; + } + Timber.d("prepareExchange() %s", cleanAmount); + } else { + setXmr(""); + notXmrAmount = ""; + } + return true; + } + + public void exchangeFailed() { + hideProgress(); + exchange(0); + if (onFailedExchangeListener != null) { + onFailedExchangeListener.onFailedExchange(); + } + } + + String assetPair = null; + double assetRate = 0; + + public void exchange(ExchangeRate exchangeRate) { + hideProgress(); + // first, make sure this is what we want + String enteredCurrencyA = (String) sCurrencyA.getSelectedItem(); + String enteredCurrencyB = (String) sCurrencyB.getSelectedItem(); + if (!exchangeRate.getBaseCurrency().equals(enteredCurrencyA) + || !exchangeRate.getQuoteCurrency().equals(enteredCurrencyB)) { + // something's wrong + Timber.e("Currencies don't match!"); + return; + } + assetPair = enteredCurrencyA + enteredCurrencyB; + assetRate = exchangeRate.getRate(); + if (prepareExchange()) { + exchange(exchangeRate.getRate()); + } + } + + private void showProgress() { + pbExchange.setVisibility(View.VISIBLE); + } + + private boolean isExchangeInProgress() { + return pbExchange.getVisibility() == View.VISIBLE; + } + + private void hideProgress() { + pbExchange.setVisibility(View.INVISIBLE); + } + + // Hooks + public interface OnNewAmountListener { + void onNewAmount(String xmr); + } + + OnNewAmountListener onNewAmountListener; + + public void setOnNewAmountListener(OnNewAmountListener listener) { + onNewAmountListener = listener; + } + + public interface OnAmountInvalidatedListener { + void onAmountInvalidated(); + } + + OnAmountInvalidatedListener onAmountInvalidatedListener; + + public void setOnAmountInvalidatedListener(OnAmountInvalidatedListener listener) { + onAmountInvalidatedListener = listener; + } + + public interface OnFailedExchangeListener { + void onFailedExchange(); + } + + OnFailedExchangeListener onFailedExchangeListener; + + public void setOnFailedExchangeListener(OnFailedExchangeListener listener) { + onFailedExchangeListener = listener; + } + + @Override + public void onDigitPressed(final int digit) { + tvAmountA.append(String.valueOf(digit)); + doExchange(); + } + + @Override + public void onPointPressed() { + //TODO locale? + if (tvAmountA.getText().toString().indexOf('.') == -1) { + tvAmountA.append("."); + } + } + + @Override + public void onBackSpacePressed() { + String entry = tvAmountA.getText().toString(); + int length = entry.length(); + if (length > 0) { + tvAmountA.setText(entry.substring(0, entry.length() - 1)); + doExchange(); + } + } + + @Override + public void onClearAll() { + tvAmountA.setText(null); + doExchange(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/ExchangeView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java similarity index 87% rename from app/src/main/java/com/m2049r/xmrwallet/layout/ExchangeView.java rename to app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java index d03f732..0c78957 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/ExchangeView.java +++ b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java @@ -16,7 +16,7 @@ // based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889 -package com.m2049r.xmrwallet.layout; +package com.m2049r.xmrwallet.widget; import android.content.Context; import android.os.Handler; @@ -49,7 +49,14 @@ import java.util.Locale; import timber.log.Timber; -public class ExchangeView extends LinearLayout { +// TODO combine this with ExchangeTextView + +public class ExchangeView extends LinearLayout + implements NumberPadView.NumberPadListener { + + public void enableSoftKeyboard(final boolean isEnabled) { + etAmount.getEditText().setShowSoftInputOnFocus(isEnabled); + } public boolean focus() { return etAmount.requestFocus(); @@ -201,7 +208,7 @@ public class ExchangeView extends LinearLayout { @Override public void onNothingSelected(AdapterView parentView) { - // nothing (yet?) + // nothing } }); @@ -229,11 +236,7 @@ public class ExchangeView extends LinearLayout { @Override public void afterTextChanged(Editable editable) { etAmount.setError(null); - if ((xmrAmount != null) || (notXmrAmount != null)) { - tvAmountB.setText("--"); - setXmr(null); - notXmrAmount = null; - } + //doExchange(); } @Override @@ -280,8 +283,31 @@ public class ExchangeView extends LinearLayout { public void doExchange() { tvAmountB.setText("--"); - // TODO cache & use cached exchange rate here - startExchange(); + // use cached exchange rate if we have it + if (!isExchangeInProgress()) { + String enteredCurrencyA = (String) sCurrencyA.getSelectedItem(); + String enteredCurrencyB = (String) sCurrencyB.getSelectedItem(); + if ((enteredCurrencyA + enteredCurrencyB).equals(assetPair)) { + if (prepareExchange()) { + exchange(assetRate); + } else { + clearAmounts(); + } + } else { + clearAmounts(); + startExchange(); + } + } else { + clearAmounts(); + } + } + + private void clearAmounts() { + if ((xmrAmount != null) || (notXmrAmount != null)) { + tvAmountB.setText("--"); + setXmr(null); + notXmrAmount = null; + } } private final ExchangeApi exchangeApi = new ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient()); @@ -368,7 +394,6 @@ public class ExchangeView extends LinearLayout { return false; } Timber.d("prepareExchange() %s", cleanAmount); - //etAmount.getEditText().setText(cleanAmount); // display what we use } else { setXmr(""); notXmrAmount = ""; @@ -389,6 +414,9 @@ public class ExchangeView extends LinearLayout { } } + String assetPair = null; + double assetRate = 0; + public void exchange(ExchangeRate exchangeRate) { hideProgress(); // first, make sure this is what we want @@ -400,6 +428,8 @@ public class ExchangeView extends LinearLayout { Timber.e("Currencies don't match!"); return; } + assetPair = enteredCurrencyA + enteredCurrencyB; + assetRate = exchangeRate.getRate(); if (prepareExchange()) { exchange(exchangeRate.getRate()); } @@ -409,6 +439,10 @@ public class ExchangeView extends LinearLayout { pbExchange.setVisibility(View.VISIBLE); } + private boolean isExchangeInProgress() { + return pbExchange.getVisibility() == View.VISIBLE; + } + private void hideProgress() { pbExchange.setVisibility(View.INVISIBLE); } @@ -443,4 +477,31 @@ public class ExchangeView extends LinearLayout { public void setOnFailedExchangeListener(OnFailedExchangeListener listener) { onFailedExchangeListener = listener; } -} \ No newline at end of file + + @Override + public void onDigitPressed(final int digit) { + etAmount.getEditText().append(String.valueOf(digit)); + } + + @Override + public void onPointPressed() { + //TODO locale? + if (etAmount.getEditText().getText().toString().indexOf('.') == -1) { + etAmount.getEditText().append("."); + } + } + + @Override + public void onBackSpacePressed() { + Editable editable = etAmount.getEditText().getText(); + int length = editable.length(); + if (length > 0) { + editable.delete(length - 1, length); + } + } + + @Override + public void onClearAll() { + etAmount.getEditText().getText().clear(); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/NumberPadView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/NumberPadView.java new file mode 100644 index 0000000..6a16474 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/widget/NumberPadView.java @@ -0,0 +1,87 @@ +package com.m2049r.xmrwallet.widget; + + +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; + +import com.m2049r.xmrwallet.R; + +public class NumberPadView extends LinearLayout + implements View.OnClickListener, View.OnLongClickListener { + + @Override + public void onClick(final View view) { + if (listener == null) { + throw new IllegalArgumentException("NumberPadListener has to be set, use setListener() to set it."); + } + switch (view.getId()) { + case R.id.numberPadPoint: + listener.onPointPressed(); + break; + case R.id.numberPadBackSpace: + listener.onBackSpacePressed(); + break; + default: + if (view.getTag() != null) { + listener.onDigitPressed(Integer.parseInt(view.getTag().toString())); + } + } + } + + @Override + public boolean onLongClick(final View view) { + if (view.getId() == R.id.numberPadBackSpace) { + listener.onClearAll(); + return true; + } + return false; + } + + public void setListener(final NumberPadListener listener) { + this.listener = listener; + } + + public interface NumberPadListener { + void onDigitPressed(final int digit); + + void onBackSpacePressed(); + + void onPointPressed(); + + void onClearAll(); + } + + private NumberPadListener listener; + + public NumberPadView(final Context context) { + this(context, null); + } + + public NumberPadView(final Context context, + @Nullable final AttributeSet attrs) { + this(context, attrs, 0); + } + + public NumberPadView(final Context context, @Nullable final AttributeSet attrs, + final int defStyleAttr) { + super(context, attrs, defStyleAttr); + final View view = View.inflate(context, R.layout.view_number_pad, this); + setOrientation(VERTICAL); + view.findViewById(R.id.numberPad0).setOnClickListener(this); + view.findViewById(R.id.numberPad1).setOnClickListener(this); + view.findViewById(R.id.numberPad2).setOnClickListener(this); + view.findViewById(R.id.numberPad3).setOnClickListener(this); + view.findViewById(R.id.numberPad4).setOnClickListener(this); + view.findViewById(R.id.numberPad5).setOnClickListener(this); + view.findViewById(R.id.numberPad6).setOnClickListener(this); + view.findViewById(R.id.numberPad7).setOnClickListener(this); + view.findViewById(R.id.numberPad8).setOnClickListener(this); + view.findViewById(R.id.numberPad9).setOnClickListener(this); + view.findViewById(R.id.numberPadPoint).setOnClickListener(this); + view.findViewById(R.id.numberPadBackSpace).setOnClickListener(this); + view.findViewById(R.id.numberPadBackSpace).setOnLongClickListener(this); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/Toolbar.java b/app/src/main/java/com/m2049r/xmrwallet/widget/Toolbar.java similarity index 94% rename from app/src/main/java/com/m2049r/xmrwallet/layout/Toolbar.java rename to app/src/main/java/com/m2049r/xmrwallet/widget/Toolbar.java index d9cf302..f36d8dc 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/Toolbar.java +++ b/app/src/main/java/com/m2049r/xmrwallet/widget/Toolbar.java @@ -16,12 +16,11 @@ // based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889 -package com.m2049r.xmrwallet.layout; +package com.m2049r.xmrwallet.widget; import android.content.Context; import android.os.Build; import android.util.AttributeSet; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; @@ -30,9 +29,9 @@ import android.widget.TextView; import com.m2049r.xmrwallet.R; -public class Toolbar extends android.support.v7.widget.Toolbar { - static final String TAG = "Toolbar"; +import timber.log.Timber; +public class Toolbar extends android.support.v7.widget.Toolbar { public interface OnButtonListener { void onButton(int type); } @@ -124,26 +123,26 @@ public class Toolbar extends android.support.v7.widget.Toolbar { public void setButton(int type) { switch (type) { case BUTTON_BACK: - Log.d(TAG, "BUTTON_BACK"); + Timber.d("BUTTON_BACK"); bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_arrow_back_white_24dp, 0, 0, 0); bDonate.setText(null); bDonate.setVisibility(View.VISIBLE); break; case BUTTON_CLOSE: - Log.d(TAG, "BUTTON_CLOSE"); + Timber.d("BUTTON_CLOSE"); bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0); bDonate.setText(R.string.label_close); bDonate.setVisibility(View.VISIBLE); break; case BUTTON_DONATE: - Log.d(TAG, "BUTTON_DONATE"); + Timber.d("BUTTON_DONATE"); bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_favorite_white_24dp, 0, 0, 0); bDonate.setText(R.string.label_donate); bDonate.setVisibility(View.VISIBLE); break; case BUTTON_NONE: default: - Log.d(TAG, "BUTTON_NONE"); + Timber.d("BUTTON_NONE"); bDonate.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); bDonate.setText(null); bDonate.setVisibility(View.INVISIBLE); diff --git a/app/src/main/res/anim/cycle_7.xml b/app/src/main/res/anim/cycle_7.xml new file mode 100644 index 0000000..4bfb143 --- /dev/null +++ b/app/src/main/res/anim/cycle_7.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/shake.xml b/app/src/main/res/anim/shake.xml new file mode 100644 index 0000000..816ef0a --- /dev/null +++ b/app/src/main/res/anim/shake.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_backspace_black_36dp.xml b/app/src/main/res/drawable/ic_backspace_black_36dp.xml new file mode 100644 index 0000000..fdb5948 --- /dev/null +++ b/app/src/main/res/drawable/ic_backspace_black_36dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_circle.xml b/app/src/main/res/drawable/ic_check_circle.xml new file mode 100644 index 0000000..9afcc71 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_circle.xml @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_max_sweep.xml b/app/src/main/res/drawable/ic_max_sweep.xml new file mode 100644 index 0000000..18ffccb --- /dev/null +++ b/app/src/main/res/drawable/ic_max_sweep.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/ic_navigate_next_black_24dp.xml b/app/src/main/res/drawable/ic_navigate_next_black_24dp.xml index 2483512..f7774f8 100644 --- a/app/src/main/res/drawable/ic_navigate_next_black_24dp.xml +++ b/app/src/main/res/drawable/ic_navigate_next_black_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z" /> diff --git a/app/src/main/res/drawable/ic_navigate_next_white_24dp.xml b/app/src/main/res/drawable/ic_navigate_next_white_24dp.xml new file mode 100644 index 0000000..805d677 --- /dev/null +++ b/app/src/main/res/drawable/ic_navigate_next_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_navigate_prev_white_24dp.xml b/app/src/main/res/drawable/ic_navigate_prev_white_24dp.xml new file mode 100644 index 0000000..1c485e7 --- /dev/null +++ b/app/src/main/res/drawable/ic_navigate_prev_white_24dp.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_settings_orange_24dp.xml b/app/src/main/res/drawable/ic_settings_orange_24dp.xml index 42e98f0..d36fbe3 100644 --- a/app/src/main/res/drawable/ic_settings_orange_24dp.xml +++ b/app/src/main/res/drawable/ic_settings_orange_24dp.xml @@ -4,6 +4,6 @@ android:viewportHeight="24.0" android:viewportWidth="24.0"> diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 7bd1052..4df48d5 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -6,7 +6,7 @@ android:gravity="center_horizontal" android:orientation="vertical"> - + + + + + + + diff --git a/app/src/main/res/layout/activity_wallet.xml b/app/src/main/res/layout/activity_wallet.xml index 7bd1052..4df48d5 100644 --- a/app/src/main/res/layout/activity_wallet.xml +++ b/app/src/main/res/layout/activity_wallet.xml @@ -6,7 +6,7 @@ android:gravity="center_horizontal" android:orientation="vertical"> - - + + - + android:visibility="invisible" /> - - @@ -49,7 +48,7 @@ android:text="@string/send_qr_hint" /> - + + + + + + + + + + + + + + + + +