From 03c5569f91b05ad96cf88f405a20f4c167b5a1d9 Mon Sep 17 00:00:00 2001 From: m2049r <30435443+m2049r@users.noreply.github.com> Date: Tue, 31 Oct 2017 22:35:23 +0100 Subject: [PATCH] Sexy UI (#104) Version 29/1.1.2-alpha --- app/build.gradle | 4 +- .../m2049r/xmrwallet/GenerateFragment.java | 496 +++++++++--------- .../xmrwallet/GenerateReviewFragment.java | 138 +++-- .../com/m2049r/xmrwallet/LoginActivity.java | 366 +++++++++---- .../com/m2049r/xmrwallet/LoginFragment.java | 304 +++++++---- .../com/m2049r/xmrwallet/ReceiveFragment.java | 316 ++++------- .../com/m2049r/xmrwallet/ScannerFragment.java | 3 - .../com/m2049r/xmrwallet/SendFragment.java | 333 ++++++------ .../java/com/m2049r/xmrwallet/TxFragment.java | 133 +++-- .../com/m2049r/xmrwallet/WalletActivity.java | 129 +++-- .../com/m2049r/xmrwallet/WalletFragment.java | 225 ++++++-- .../m2049r/xmrwallet/dialog/HelpFragment.java | 9 +- .../xmrwallet/dialog/LicensesFragment.java | 19 +- .../xmrwallet/layout/AsyncExchangeRate.java | 159 ++++++ .../xmrwallet/layout/CTextInputLayout.java | 44 ++ .../m2049r/xmrwallet/layout/ExchangeView.java | 399 ++++++++++++++ .../com/m2049r/xmrwallet/layout/Toolbar.java | 155 ++++++ .../layout/TransactionInfoAdapter.java | 63 +-- .../xmrwallet/layout/WalletInfoAdapter.java | 157 ++++++ .../xmrwallet/model/TransactionHistory.java | 11 + .../xmrwallet/model/TransactionInfo.java | 16 +- .../m2049r/xmrwallet/model/WalletManager.java | 12 +- .../xmrwallet/service/WalletService.java | 17 +- .../com/m2049r/xmrwallet/util/Helper.java | 62 ++- .../com/m2049r/xmrwallet/util/NodeList.java | 2 - app/src/main/res/anim/fab_close.xml | 18 + app/src/main/res/anim/fab_close_screen.xml | 9 + app/src/main/res/anim/fab_open.xml | 18 + app/src/main/res/anim/fab_open_screen.xml | 9 + app/src/main/res/anim/rotate_backward.xml | 11 + app/src/main/res/anim/rotate_forward.xml | 11 + app/src/main/res/color/text_color.xml | 2 +- app/src/main/res/drawable/backgound_all.xml | 352 +++++++++++++ .../main/res/drawable/backgound_amount.xml | 10 + app/src/main/res/drawable/backgound_scan.xml | 15 + app/src/main/res/drawable/backgound_seed.xml | 15 + .../main/res/drawable/backgound_spinner.xml | 12 + .../drawable/backgound_toolbar_mainnet.xml | 7 + app/src/main/res/drawable/button_default.xml | 4 + app/src/main/res/drawable/button_disabled.xml | 4 + app/src/main/res/drawable/button_selector.xml | 6 + app/src/main/res/drawable/gradient_all.xml | 9 + app/src/main/res/drawable/gradient_oval.xml | 9 + app/src/main/res/drawable/gunther_wallets.xml | 73 +++ .../main/res/drawable/gunther_wallets_00.png | Bin 0 -> 2424 bytes .../main/res/drawable/gunther_wallets_01.png | Bin 0 -> 2438 bytes .../main/res/drawable/gunther_wallets_02.png | Bin 0 -> 2451 bytes .../main/res/drawable/gunther_wallets_03.png | Bin 0 -> 2438 bytes .../main/res/drawable/gunther_wallets_04.png | Bin 0 -> 2451 bytes .../main/res/drawable/gunther_wallets_05.png | Bin 0 -> 2460 bytes .../main/res/drawable/gunther_wallets_06.png | Bin 0 -> 2463 bytes .../main/res/drawable/gunther_wallets_07.png | Bin 0 -> 2464 bytes .../main/res/drawable/gunther_wallets_08.png | Bin 0 -> 2472 bytes .../main/res/drawable/gunther_wallets_09.png | Bin 0 -> 2469 bytes .../main/res/drawable/gunther_wallets_10.png | Bin 0 -> 2468 bytes .../main/res/drawable/gunther_wallets_11.png | Bin 0 -> 2474 bytes .../main/res/drawable/gunther_wallets_12.png | Bin 0 -> 2483 bytes .../main/res/drawable/gunther_wallets_13.png | Bin 0 -> 2488 bytes .../main/res/drawable/gunther_wallets_14.png | Bin 0 -> 2490 bytes .../main/res/drawable/gunther_wallets_15.png | Bin 0 -> 2493 bytes .../main/res/drawable/gunther_wallets_16.png | Bin 0 -> 2497 bytes .../main/res/drawable/gunther_wallets_17.png | Bin 0 -> 2497 bytes .../main/res/drawable/gunther_wallets_18.png | Bin 0 -> 2497 bytes .../main/res/drawable/gunther_wallets_19.png | Bin 0 -> 2493 bytes .../main/res/drawable/gunther_wallets_20.png | Bin 0 -> 2483 bytes .../main/res/drawable/gunther_wallets_21.png | Bin 0 -> 2468 bytes .../main/res/drawable/gunther_wallets_22.png | Bin 0 -> 2438 bytes .../res/drawable/ic_add_circle_outline.xml | 9 + .../main/res/drawable/ic_add_white_24dp.xml | 9 + .../res/drawable/ic_arrow_back_white_24dp.xml | 9 + .../main/res/drawable/ic_close_white_24dp.xml | 9 + .../drawable/ic_content_copy_black_24dp.xml | 9 + .../drawable/ic_content_nocopy_black_24dp.xml | 9 + app/src/main/res/drawable/ic_done_all.xml | 13 + app/src/main/res/drawable/ic_eye.xml | 28 + .../res/drawable/ic_favorite_white_14dp.xml | 9 + .../res/drawable/ic_favorite_white_24dp.xml | 9 + app/src/main/res/drawable/ic_hand.xml | 21 + ..._black_24dp.xml => ic_help_white_24dp.xml} | 12 +- .../drawable/ic_info_outline_gray_24dp.xml | 9 + ..._black_24dp.xml => ic_info_white_24dp.xml} | 2 +- app/src/main/res/drawable/ic_key.xml | 31 ++ .../ic_keyboard_arrow_right_black_24dp.xml | 9 + .../drawable/ic_logo_horizontol_xmrujo.xml | 36 ++ .../drawable/ic_navigate_next_black_24dp.xml | 9 + app/src/main/res/drawable/ic_new.xml | 41 ++ app/src/main/res/drawable/ic_scan.xml | 13 + app/src/main/res/drawable/ic_seed.xml | 21 + app/src/main/res/drawable/ic_send.xml | 9 + .../res/drawable/ic_settings_orange_24dp.xml | 9 + .../main/res/drawable/ic_share_white_24dp.xml | 9 + app/src/main/res/drawable/ic_traffic.xml | 10 + app/src/main/res/drawable/selector_login.xml | 14 + app/src/main/res/drawable/texture_cash.png | Bin 0 -> 49327 bytes ...{login_activity.xml => activity_login.xml} | 10 +- ...allet_activity.xml => activity_wallet.xml} | 11 +- app/src/main/res/layout/fragment_generate.xml | 160 ++++++ .../{help_fragment.xml => fragment_help.xml} | 0 ...ses_fragment.xml => fragment_licenses.xml} | 0 app/src/main/res/layout/fragment_login.xml | 71 +++ app/src/main/res/layout/fragment_receive.xml | 119 +++++ app/src/main/res/layout/fragment_review.xml | 222 ++++++++ .../{send_fragment.xml => fragment_send.xml} | 212 ++++---- .../{tx_fragment.xml => fragment_tx_info.xml} | 270 +++++----- app/src/main/res/layout/fragment_wallet.xml | 162 ++++++ app/src/main/res/layout/gen_fragment.xml | 112 ---- .../main/res/layout/gen_review_fragment.xml | 146 ------ app/src/main/res/layout/item_spinner.xml | 11 + app/src/main/res/layout/item_transaction.xml | 71 +++ app/src/main/res/layout/item_wallet.xml | 50 ++ app/src/main/res/layout/layout_fabmenu.xml | 166 ++++++ app/src/main/res/layout/login_fragment.xml | 47 -- app/src/main/res/layout/prompt_password.xml | 25 +- app/src/main/res/layout/receive_fragment.xml | 129 ----- app/src/main/res/layout/toolbar.xml | 10 - app/src/main/res/layout/transaction_item.xml | 70 --- app/src/main/res/layout/view_exchange.xml | 86 +++ app/src/main/res/layout/view_toolbar.xml | 54 ++ app/src/main/res/layout/wallet_fragment.xml | 110 ---- app/src/main/res/menu/create_wallet_menu.xml | 2 +- app/src/main/res/menu/list_context_menu.xml | 2 +- app/src/main/res/menu/list_menu.xml | 2 +- app/src/main/res/menu/tx_info_menu.xml | 12 + app/src/main/res/menu/wallet_details_menu.xml | 2 +- app/src/main/res/menu/wallet_menu.xml | 18 +- app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 6962 -> 7294 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 3747 -> 3867 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 12348 -> 12691 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 26396 -> 26763 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 45016 -> 45368 bytes app/src/main/res/values/colors.xml | 25 +- app/src/main/res/values/dimens.xml | 12 +- app/src/main/res/values/ids.xml | 5 + app/src/main/res/values/integers.xml | 5 + app/src/main/res/values/strings.xml | 100 +++- app/src/main/res/values/styles.xml | 212 +++++++- 136 files changed, 5208 insertions(+), 2047 deletions(-) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/layout/AsyncExchangeRate.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/layout/CTextInputLayout.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/layout/ExchangeView.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/layout/Toolbar.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java create mode 100644 app/src/main/res/anim/fab_close.xml create mode 100644 app/src/main/res/anim/fab_close_screen.xml create mode 100644 app/src/main/res/anim/fab_open.xml create mode 100644 app/src/main/res/anim/fab_open_screen.xml create mode 100644 app/src/main/res/anim/rotate_backward.xml create mode 100644 app/src/main/res/anim/rotate_forward.xml create mode 100644 app/src/main/res/drawable/backgound_all.xml create mode 100644 app/src/main/res/drawable/backgound_amount.xml create mode 100644 app/src/main/res/drawable/backgound_scan.xml create mode 100644 app/src/main/res/drawable/backgound_seed.xml create mode 100644 app/src/main/res/drawable/backgound_spinner.xml create mode 100644 app/src/main/res/drawable/backgound_toolbar_mainnet.xml create mode 100644 app/src/main/res/drawable/button_default.xml create mode 100644 app/src/main/res/drawable/button_disabled.xml create mode 100644 app/src/main/res/drawable/button_selector.xml create mode 100644 app/src/main/res/drawable/gradient_all.xml create mode 100644 app/src/main/res/drawable/gradient_oval.xml create mode 100644 app/src/main/res/drawable/gunther_wallets.xml create mode 100644 app/src/main/res/drawable/gunther_wallets_00.png create mode 100644 app/src/main/res/drawable/gunther_wallets_01.png create mode 100644 app/src/main/res/drawable/gunther_wallets_02.png create mode 100644 app/src/main/res/drawable/gunther_wallets_03.png create mode 100644 app/src/main/res/drawable/gunther_wallets_04.png create mode 100644 app/src/main/res/drawable/gunther_wallets_05.png create mode 100644 app/src/main/res/drawable/gunther_wallets_06.png create mode 100644 app/src/main/res/drawable/gunther_wallets_07.png create mode 100644 app/src/main/res/drawable/gunther_wallets_08.png create mode 100644 app/src/main/res/drawable/gunther_wallets_09.png create mode 100644 app/src/main/res/drawable/gunther_wallets_10.png create mode 100644 app/src/main/res/drawable/gunther_wallets_11.png create mode 100644 app/src/main/res/drawable/gunther_wallets_12.png create mode 100644 app/src/main/res/drawable/gunther_wallets_13.png create mode 100644 app/src/main/res/drawable/gunther_wallets_14.png create mode 100644 app/src/main/res/drawable/gunther_wallets_15.png create mode 100644 app/src/main/res/drawable/gunther_wallets_16.png create mode 100644 app/src/main/res/drawable/gunther_wallets_17.png create mode 100644 app/src/main/res/drawable/gunther_wallets_18.png create mode 100644 app/src/main/res/drawable/gunther_wallets_19.png create mode 100644 app/src/main/res/drawable/gunther_wallets_20.png create mode 100644 app/src/main/res/drawable/gunther_wallets_21.png create mode 100644 app/src/main/res/drawable/gunther_wallets_22.png create mode 100644 app/src/main/res/drawable/ic_add_circle_outline.xml create mode 100644 app/src/main/res/drawable/ic_add_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_arrow_back_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_close_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_content_copy_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_content_nocopy_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_done_all.xml create mode 100644 app/src/main/res/drawable/ic_eye.xml create mode 100644 app/src/main/res/drawable/ic_favorite_white_14dp.xml create mode 100644 app/src/main/res/drawable/ic_favorite_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_hand.xml rename app/src/main/res/drawable/{ic_help_black_24dp.xml => ic_help_white_24dp.xml} (67%) create mode 100644 app/src/main/res/drawable/ic_info_outline_gray_24dp.xml rename app/src/main/res/drawable/{ic_info_black_24dp.xml => ic_info_white_24dp.xml} (89%) create mode 100644 app/src/main/res/drawable/ic_key.xml create mode 100644 app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_logo_horizontol_xmrujo.xml create mode 100644 app/src/main/res/drawable/ic_navigate_next_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_new.xml create mode 100644 app/src/main/res/drawable/ic_scan.xml create mode 100644 app/src/main/res/drawable/ic_seed.xml create mode 100644 app/src/main/res/drawable/ic_send.xml create mode 100644 app/src/main/res/drawable/ic_settings_orange_24dp.xml create mode 100644 app/src/main/res/drawable/ic_share_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_traffic.xml create mode 100644 app/src/main/res/drawable/selector_login.xml create mode 100644 app/src/main/res/drawable/texture_cash.png rename app/src/main/res/layout/{login_activity.xml => activity_login.xml} (57%) rename app/src/main/res/layout/{wallet_activity.xml => activity_wallet.xml} (57%) create mode 100644 app/src/main/res/layout/fragment_generate.xml rename app/src/main/res/layout/{help_fragment.xml => fragment_help.xml} (100%) rename app/src/main/res/layout/{licenses_fragment.xml => fragment_licenses.xml} (100%) create mode 100644 app/src/main/res/layout/fragment_login.xml create mode 100644 app/src/main/res/layout/fragment_receive.xml create mode 100644 app/src/main/res/layout/fragment_review.xml rename app/src/main/res/layout/{send_fragment.xml => fragment_send.xml} (60%) rename app/src/main/res/layout/{tx_fragment.xml => fragment_tx_info.xml} (53%) create mode 100644 app/src/main/res/layout/fragment_wallet.xml delete mode 100644 app/src/main/res/layout/gen_fragment.xml delete mode 100644 app/src/main/res/layout/gen_review_fragment.xml create mode 100644 app/src/main/res/layout/item_spinner.xml create mode 100644 app/src/main/res/layout/item_transaction.xml create mode 100644 app/src/main/res/layout/item_wallet.xml create mode 100644 app/src/main/res/layout/layout_fabmenu.xml delete mode 100644 app/src/main/res/layout/login_fragment.xml delete mode 100644 app/src/main/res/layout/receive_fragment.xml delete mode 100644 app/src/main/res/layout/toolbar.xml delete mode 100644 app/src/main/res/layout/transaction_item.xml create mode 100644 app/src/main/res/layout/view_exchange.xml create mode 100644 app/src/main/res/layout/view_toolbar.xml delete mode 100644 app/src/main/res/layout/wallet_fragment.xml create mode 100644 app/src/main/res/menu/tx_info_menu.xml create mode 100644 app/src/main/res/values/ids.xml create mode 100644 app/src/main/res/values/integers.xml diff --git a/app/build.gradle b/app/build.gradle index 999189e..8ab7b8a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "com.m2049r.xmrwallet" minSdkVersion 21 targetSdkVersion 25 - versionCode 26 - versionName "1.0.3" + versionCode 29 + versionName "1.1.2-alpha" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java index 8d7c643..16387bf 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java @@ -19,10 +19,9 @@ package com.m2049r.xmrwallet; import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.design.widget.TextInputLayout; import android.support.v4.app.Fragment; -import android.text.Editable; import android.text.InputType; -import android.text.TextWatcher; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -32,10 +31,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.Button; -import android.widget.EditText; -import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; @@ -46,60 +42,93 @@ import java.io.File; public class GenerateFragment extends Fragment { static final String TAG = "GenerateFragment"; - EditText etWalletName; - EditText etWalletPassword; - EditText etWalletAddress; - EditText etWalletMnemonic; - LinearLayout llRestoreKeys; - EditText etWalletViewKey; - EditText etWalletSpendKey; - EditText etWalletRestoreHeight; + static final String TYPE = "type"; + static final String TYPE_NEW = "new"; + static final String TYPE_KEY = "key"; + static final String TYPE_SEED = "seed"; + static final String TYPE_VIEWONLY = "view"; + + TextInputLayout etWalletName; + TextInputLayout etWalletPassword; + TextInputLayout etWalletAddress; + TextInputLayout etWalletMnemonic; + TextInputLayout etWalletViewKey; + TextInputLayout etWalletSpendKey; + TextInputLayout etWalletRestoreHeight; Button bGenerate; + String type = null; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.gen_fragment, container, false); + Bundle args = getArguments(); + this.type = args.getString(TYPE); - etWalletName = (EditText) view.findViewById(R.id.etWalletName); - etWalletPassword = (EditText) view.findViewById(R.id.etWalletPassword); - etWalletMnemonic = (EditText) view.findViewById(R.id.etWalletMnemonic); - etWalletAddress = (EditText) view.findViewById(R.id.etWalletAddress); - llRestoreKeys = (LinearLayout) view.findViewById(R.id.llRestoreKeys); - etWalletViewKey = (EditText) view.findViewById(R.id.etWalletViewKey); - etWalletSpendKey = (EditText) view.findViewById(R.id.etWalletSpendKey); - etWalletRestoreHeight = (EditText) view.findViewById(R.id.etWalletRestoreHeight); + View view = inflater.inflate(R.layout.fragment_generate, container, false); + + etWalletName = (TextInputLayout) view.findViewById(R.id.etWalletName); + etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword); + etWalletMnemonic = (TextInputLayout) view.findViewById(R.id.etWalletMnemonic); + etWalletAddress = (TextInputLayout) view.findViewById(R.id.etWalletAddress); + etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey); + etWalletSpendKey = (TextInputLayout) view.findViewById(R.id.etWalletSpendKey); + etWalletRestoreHeight = (TextInputLayout) view.findViewById(R.id.etWalletRestoreHeight); bGenerate = (Button) view.findViewById(R.id.bGenerate); - etWalletMnemonic.setRawInputType(InputType.TYPE_CLASS_TEXT); - etWalletAddress.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - etWalletViewKey.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - etWalletSpendKey.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT); + etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + etWalletViewKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + etWalletSpendKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - Helper.showKeyboard(getActivity()); - etWalletName.addTextChangedListener(new TextWatcher() { + etWalletName.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override - public void afterTextChanged(Editable editable) { - if (etWalletName.length() > 0) { - bGenerate.setEnabled(true); - } else { - bGenerate.setEnabled(false); + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + checkName(); } } - + }); + etWalletMnemonic.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + checkMnemonic(); + } } }); - etWalletName.setOnEditorActionListener(new TextView.OnEditorActionListener() { + etWalletAddress.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + checkAddress(); + } + } + }); + etWalletViewKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + checkViewKey(); + } + } + }); + etWalletSpendKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + checkSpendKey(); + } + } + }); + + Helper.showKeyboard(getActivity()); +//############## + etWalletName.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 (etWalletName.length() > 0) { + if (checkName()) { etWalletPassword.requestFocus(); } // otherwise ignore return true; @@ -108,159 +137,112 @@ public class GenerateFragment extends Fragment { } }); - etWalletPassword.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 (etWalletAddress.length() > 0) { - etWalletAddress.requestFocus(); - } else { - etWalletMnemonic.requestFocus(); - } - return true; - } - return false; - } - }); - - etWalletMnemonic.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 (etWalletMnemonic.length() == 0) { - etWalletAddress.requestFocus(); - } else if (mnemonicOk()) { - etWalletRestoreHeight.requestFocus(); - } else { - Toast.makeText(getActivity(), getString(R.string.generate_check_mnemonic), Toast.LENGTH_LONG).show(); - } - return true; - } - return false; - } - }); - etWalletMnemonic.addTextChangedListener(new - - TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - if (etWalletMnemonic.length() > 0) { - etWalletRestoreHeight.setVisibility(View.VISIBLE); - etWalletAddress.setVisibility(View.GONE); - } else { - etWalletAddress.setVisibility(View.VISIBLE); - if (etWalletAddress.length() == 0) { - etWalletRestoreHeight.setVisibility(View.GONE); - } else { - etWalletRestoreHeight.setVisibility(View.VISIBLE); - } - - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }); - - etWalletAddress.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 (etWalletAddress.length() == 0) { - if (bGenerate.getVisibility() == View.VISIBLE) { - Helper.hideKeyboard(getActivity()); - generateWallet(); - } - } else if (addressOk()) { - etWalletViewKey.requestFocus(); - } else { - Toast.makeText(getActivity(), getString(R.string.generate_check_address), Toast.LENGTH_LONG).show(); - } - return true; - } - return false; - } - }); - etWalletAddress.addTextChangedListener(new - - TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - if (etWalletAddress.length() > 0) { - llRestoreKeys.setVisibility(View.VISIBLE); - etWalletMnemonic.setVisibility(View.INVISIBLE); - etWalletRestoreHeight.setVisibility(View.VISIBLE); - } else { - llRestoreKeys.setVisibility(View.GONE); - etWalletMnemonic.setVisibility(View.VISIBLE); - if (etWalletMnemonic.length() == 0) { - etWalletRestoreHeight.setVisibility(View.GONE); - } else { - etWalletRestoreHeight.setVisibility(View.VISIBLE); - } - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }); - - etWalletViewKey.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 (viewKeyOk()) { - etWalletSpendKey.requestFocus(); - } else { - Toast.makeText(getActivity(), getString(R.string.generate_check_key), Toast.LENGTH_LONG).show(); - } - return true; - } - return false; - } - }); - etWalletSpendKey.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 (spendKeyOk()) { - etWalletRestoreHeight.requestFocus(); - } else { - Toast.makeText(getActivity(), getString(R.string.generate_check_key), Toast.LENGTH_LONG).show(); - } - return true; - } - return false; - } - }); - etWalletRestoreHeight.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 (bGenerate.getVisibility() == View.VISIBLE) { + if (type.equals(TYPE_NEW)) { + etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE); + etWalletPassword.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.hideKeyboard(getActivity()); generateWallet(); - } else { - Toast.makeText(getActivity(), getString(R.string.generate_check_something), Toast.LENGTH_LONG).show(); + return true; } - return true; + return false; } - return false; - } - }); + }); + } else if (type.equals(TYPE_SEED)) { + etWalletPassword.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)) { + etWalletMnemonic.requestFocus(); + return true; + } + return false; + } + }); + etWalletMnemonic.setVisibility(View.VISIBLE); + etWalletMnemonic.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 (checkMnemonic()) { + etWalletRestoreHeight.requestFocus(); + } + return true; + } + return false; + } + }); + } else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) { + etWalletPassword.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)) { + etWalletAddress.requestFocus(); + return true; + } + return false; + } + }); + etWalletAddress.setVisibility(View.VISIBLE); + etWalletAddress.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()) { + etWalletViewKey.requestFocus(); + } + return true; + } + return false; + } + }); + etWalletViewKey.setVisibility(View.VISIBLE); + etWalletViewKey.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 (checkViewKey()) { + if (type.equals(TYPE_KEY)) { + etWalletSpendKey.requestFocus(); + } else { + etWalletRestoreHeight.requestFocus(); + } + } + return true; + } + return false; + } + }); + } + if (type.equals(TYPE_KEY)) { + etWalletSpendKey.setVisibility(View.VISIBLE); + etWalletSpendKey.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 (checkSpendKey()) { + etWalletRestoreHeight.requestFocus(); + } + return true; + } + return false; + } + }); + } + if (!type.equals(TYPE_NEW)) { + etWalletRestoreHeight.setVisibility(View.VISIBLE); + etWalletRestoreHeight.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.hideKeyboard(getActivity()); + generateWallet(); + return true; + } + return false; + } + }); + } bGenerate.setOnClickListener(new View.OnClickListener() { @@ -272,81 +254,112 @@ public class GenerateFragment extends Fragment { }); etWalletName.requestFocus(); + return view; } - private boolean mnemonicOk() { - String seed = etWalletMnemonic.getText().toString(); - return (seed.split("\\s").length == 25); // 25 words + private boolean checkName() { + String name = etWalletName.getEditText().getText().toString(); + boolean ok = true; + if (name.length() == 0) { + etWalletName.setError(getString(R.string.generate_wallet_name)); + ok = false; + } else if (name.charAt(0) == '.') { + etWalletName.setError(getString(R.string.generate_wallet_dot)); + ok = false; + } else { + File walletFile = Helper.getWalletFile(getActivity(), name); + if (WalletManager.getInstance().walletExists(walletFile)) { + etWalletName.setError(getString(R.string.generate_wallet_exists)); + ok = false; + } + } + if (ok) { + etWalletName.setError(null); + } + return ok; } - private boolean addressOk() { - String address = etWalletAddress.getText().toString(); - return Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet()); + private boolean checkMnemonic() { + String seed = etWalletMnemonic.getEditText().getText().toString(); + boolean ok = (seed.split("\\s").length == 25); // 25 words + if (!ok) { + etWalletMnemonic.setError(getString(R.string.generate_check_mnemonic)); + } else { + etWalletMnemonic.setError(null); + } + return ok; } - private boolean viewKeyOk() { - String viewKey = etWalletViewKey.getText().toString(); - return (viewKey.length() == 64) && (viewKey.matches("^[0-9a-fA-F]+$")); + private boolean checkAddress() { + String address = etWalletAddress.getEditText().getText().toString(); + boolean ok = Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet()); + if (!ok) { + etWalletAddress.setError(getString(R.string.generate_check_address)); + } else { + etWalletAddress.setError(null); + } + return ok; } - private boolean spendKeyOk() { - String spendKey = etWalletSpendKey.getText().toString(); - return ((spendKey.length() == 0) || ((spendKey.length() == 64) && (spendKey.matches("^[0-9a-fA-F]+$")))); + private boolean checkViewKey() { + String viewKey = etWalletViewKey.getEditText().getText().toString(); + boolean ok = (viewKey.length() == 64) && (viewKey.matches("^[0-9a-fA-F]+$")); + if (!ok) { + etWalletViewKey.setError(getString(R.string.generate_check_key)); + } else { + etWalletViewKey.setError(null); + } + return ok; + } + + private boolean checkSpendKey() { + String spendKey = etWalletSpendKey.getEditText().getText().toString(); + boolean ok = ((spendKey.length() == 0) || ((spendKey.length() == 64) && (spendKey.matches("^[0-9a-fA-F]+$")))); + if (!ok) { + etWalletSpendKey.setError(getString(R.string.generate_check_key)); + } else { + etWalletSpendKey.setError(null); + } + return ok; } private void generateWallet() { - String name = etWalletName.getText().toString(); - if (name.length() == 0) return; - if (name.charAt(0) == '.') { - Toast.makeText(getActivity(), getString(R.string.generate_wallet_dot), Toast.LENGTH_LONG).show(); - etWalletName.requestFocus(); - } - File walletFile = Helper.getWalletFile(getActivity(), name); - if (WalletManager.getInstance().walletExists(walletFile)) { - Toast.makeText(getActivity(), getString(R.string.generate_wallet_exists), Toast.LENGTH_LONG).show(); - etWalletName.requestFocus(); - return; - } - String password = etWalletPassword.getText().toString(); - - String seed = etWalletMnemonic.getText().toString(); - String address = etWalletAddress.getText().toString(); + if (!checkName()) return; + String name = etWalletName.getEditText().getText().toString(); + String password = etWalletPassword.getEditText().getText().toString(); long height; try { - height = Long.parseLong(etWalletRestoreHeight.getText().toString()); + height = Long.parseLong(etWalletRestoreHeight.getEditText().getText().toString()); } catch (NumberFormatException ex) { height = 0; // Keep calm and carry on! } - // figure out how we want to create this wallet - // A. from scratch - if ((seed.length() == 0) && (address.length() == 0)) { - bGenerate.setVisibility(View.GONE); + if (type.equals(TYPE_NEW)) { + bGenerate.setEnabled(false); activityCallback.onGenerate(name, password); - } else - // B. from seed - if (mnemonicOk()) { - bGenerate.setVisibility(View.GONE); - activityCallback.onGenerate(name, password, seed, height); - } else - // C. from keys - if (addressOk() && viewKeyOk() && (spendKeyOk())) { - String viewKey = etWalletViewKey.getText().toString(); - String spendKey = etWalletSpendKey.getText().toString(); - bGenerate.setVisibility(View.GONE); - activityCallback.onGenerate(name, password, address, viewKey, spendKey, height); - } else - // D. none of the above :) - { - Toast.makeText(getActivity(), getString(R.string.generate_check_something), Toast.LENGTH_LONG).show(); + } else if (type.equals(TYPE_SEED)) { + if (!checkMnemonic()) return; + String seed = etWalletMnemonic.getEditText().getText().toString(); + bGenerate.setEnabled(false); + activityCallback.onGenerate(name, password, seed, height); + } else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) { + if (checkAddress() && checkViewKey() && checkSpendKey()) { + bGenerate.setEnabled(false); + String address = etWalletAddress.getEditText().getText().toString(); + String viewKey = etWalletViewKey.getEditText().getText().toString(); + String spendKey = ""; + if (type.equals(TYPE_KEY)) { + spendKey = etWalletSpendKey.getEditText().getText().toString(); } + activityCallback.onGenerate(name, password, address, viewKey, spendKey, height); + } + } } public void walletGenerateError() { - bGenerate.setEnabled(etWalletName.length() > 0); - bGenerate.setVisibility(View.VISIBLE); + bGenerate.setEnabled(true); } @Override @@ -365,10 +378,7 @@ public class GenerateFragment extends Fragment { void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height); - File getStorageRoot(); - void setTitle(String title); - } @Override diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java index 4891fde..571c26d 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java @@ -28,12 +28,17 @@ import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.ImageButton; +import android.widget.LinearLayout; import android.widget.ProgressBar; +import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; +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; public class GenerateReviewFragment extends Fragment { @@ -42,6 +47,8 @@ public class GenerateReviewFragment extends Fragment { static final public String VIEW_TYPE_ACCEPT = "accept"; static final public String VIEW_TYPE_WALLET = "wallet"; + ScrollView scrollview; + ProgressBar pbProgress; TextView tvWalletName; TextView tvWalletPassword; @@ -49,14 +56,18 @@ public class GenerateReviewFragment extends Fragment { TextView tvWalletMnemonic; TextView tvWalletViewKey; TextView tvWalletSpendKey; + ImageButton bCopyAddress; + LinearLayout llAdvancedInfo; + Button bAdvancedInfo; Button bAccept; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.gen_review_fragment, container, false); + View view = inflater.inflate(R.layout.fragment_review, container, false); + scrollview = (ScrollView) view.findViewById(R.id.scrollview); pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); tvWalletName = (TextView) view.findViewById(R.id.tvWalletName); tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword); @@ -64,26 +75,15 @@ public class GenerateReviewFragment extends Fragment { tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey); tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey); tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic); + bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress); + bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo); + llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo); bAccept = (Button) view.findViewById(R.id.bAccept); boolean testnet = WalletManager.getInstance().isTestNet(); tvWalletMnemonic.setTextIsSelectable(testnet); tvWalletSpendKey.setTextIsSelectable(testnet); - if (!testnet) { - tvWalletMnemonic.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Toast.makeText(getActivity(), getString(R.string.message_noselect_seed), Toast.LENGTH_SHORT).show(); - } - }); - tvWalletSpendKey.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Toast.makeText(getActivity(), getString(R.string.message_noselect_key), Toast.LENGTH_SHORT).show(); - } - }); - } bAccept.setOnClickListener(new View.OnClickListener() { @Override @@ -91,6 +91,36 @@ public class GenerateReviewFragment extends Fragment { acceptWallet(); } }); + view.findViewById(R.id.bCopyViewKey).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + copyViewKey(); + } + }); + bCopyAddress.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + copyAddress(); + } + }); + view.findViewById(R.id.bCopySeed).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nocopy(); + } + }); + view.findViewById(R.id.bCopySepndKey).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nocopy(); + } + }); + view.findViewById(R.id.bAdvancedInfo).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showAdvancedInfo(); + } + }); showProgress(); @@ -103,6 +133,31 @@ public class GenerateReviewFragment extends Fragment { return view; } + void copyViewKey() { + Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_viewkey), tvWalletViewKey.getText().toString()); + Toast.makeText(getActivity(), getString(R.string.message_copy_viewkey), Toast.LENGTH_SHORT).show(); + } + + void copyAddress() { + Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_address), tvWalletAddress.getText().toString()); + Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show(); + } + + void nocopy() { + Toast.makeText(getActivity(), getString(R.string.message_nocopy), Toast.LENGTH_SHORT).show(); + } + + void showAdvancedInfo() { + llAdvancedInfo.setVisibility(View.VISIBLE); + bAdvancedInfo.setVisibility(View.GONE); + scrollview.post(new Runnable() { + @Override + public void run() { + scrollview.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + } + String type; private void acceptWallet() { @@ -148,7 +203,7 @@ public class GenerateReviewFragment extends Fragment { address = wallet.getAddress(); seed = wallet.getSeed(); viewKey = wallet.getSecretViewKey(); - spendKey = isWatchOnly ? getActivity().getString(R.string.watchonly_label) : wallet.getSecretSpendKey(); + spendKey = isWatchOnly ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey(); isWatchOnly = wallet.isWatchOnly(); if (closeWallet) wallet.close(); return true; @@ -157,6 +212,7 @@ public class GenerateReviewFragment extends Fragment { @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); + if (!isAdded()) return; // never mind tvWalletName.setText(name); if (result) { if (type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT)) { @@ -168,9 +224,14 @@ public class GenerateReviewFragment extends Fragment { tvWalletMnemonic.setText(seed); tvWalletViewKey.setText(viewKey); tvWalletSpendKey.setText(spendKey); + bAdvancedInfo.setVisibility(View.VISIBLE); + bCopyAddress.setEnabled(true); + bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp); + activityCallback.setTitle(name, getString(R.string.details_title)); + activityCallback.setToolbarButton( + GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK); } else { - // TODO show proper error message - // TODO end the fragment + // TODO show proper error message and/or end the fragment? tvWalletAddress.setText(status.toString()); tvWalletMnemonic.setText(status.toString()); tvWalletViewKey.setText(status.toString()); @@ -180,38 +241,55 @@ public class GenerateReviewFragment extends Fragment { } } - GenerateReviewFragment.Listener acceptCallback = null; - GenerateReviewFragment.ListenerWithWallet walletCallback = null; + Listener activityCallback = null; + AcceptListener acceptCallback = null; + ListenerWithWallet walletCallback = null; public interface Listener { + void setTitle(String title, String subtitle); + + void setToolbarButton(int type); + } + + public interface AcceptListener { void onAccept(String name, String password); } public interface ListenerWithWallet { Wallet getWallet(); - } @Override public void onAttach(Context context) { super.onAttach(context); - if (context instanceof GenerateReviewFragment.Listener) { - this.acceptCallback = (GenerateReviewFragment.Listener) context; - } else if (context instanceof GenerateReviewFragment.ListenerWithWallet) { - this.walletCallback = (GenerateReviewFragment.ListenerWithWallet) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); + if (context instanceof Listener) { + this.activityCallback = (Listener) context; + } + if (context instanceof AcceptListener) { + this.acceptCallback = (AcceptListener) context; + } + if (context instanceof ListenerWithWallet) { + this.walletCallback = (ListenerWithWallet) context; } } + @Override + public void onResume() { + super.onResume(); + Log.d(TAG, "onResume()"); + String name = tvWalletName.getText().toString(); + if (name.isEmpty()) name = null; + activityCallback.setTitle(name, getString(R.string.details_title)); + activityCallback.setToolbarButton( + GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK); + } + public void showProgress() { - pbProgress.setIndeterminate(true); pbProgress.setVisibility(View.VISIBLE); } public void hideProgress() { - pbProgress.setVisibility(View.INVISIBLE); + pbProgress.setVisibility(View.GONE); } boolean backOk() { diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index 71156dd..28d9242 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -27,30 +27,32 @@ import android.content.pm.PackageManager; import android.media.MediaScannerConnection; import android.os.AsyncTask; import android.os.Bundle; -import android.os.StrictMode; +import android.os.Handler; import android.support.annotation.NonNull; +import android.support.design.widget.TextInputLayout; import android.support.v4.app.Fragment; - import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.EditorInfo; +import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.LicensesFragment; +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.AsyncExchangeRate; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; @@ -60,19 +62,41 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; +import java.net.SocketAddress; import java.nio.channels.FileChannel; import java.util.Date; public class LoginActivity extends AppCompatActivity implements LoginFragment.Listener, GenerateFragment.Listener, - GenerateReviewFragment.Listener, ReceiveFragment.Listener { + GenerateReviewFragment.Listener, GenerateReviewFragment.AcceptListener, ReceiveFragment.Listener { static final String TAG = "LoginActivity"; private static final String GENERATE_STACK = "gen"; static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms + static final int DAEMON_DNS_TIMEOUT = 5000; // how long to wait for DNS resolver Toolbar toolbar; + @Override + public void setToolbarButton(int type) { + toolbar.setButton(type); + } + + @Override + public void setTitle(String title) { + toolbar.setTitle(title); + } + + @Override + public void setSubtitle(String subtitle) { + toolbar.setSubtitle(subtitle); + } + + @Override + public void setTitle(String title, String subtitle) { + toolbar.setTitle(title, subtitle); + } + @Override protected void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate()"); @@ -81,9 +105,29 @@ public class LoginActivity extends AppCompatActivity // we don't store anything ourselves } - setContentView(R.layout.login_activity); + setContentView(R.layout.activity_login); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); + getSupportActionBar().setDisplayShowTitleEnabled(false); + + toolbar.setOnButtonListener(new Toolbar.OnButtonListener() { + @Override + public void onButton(int type) { + switch (type) { + case Toolbar.BUTTON_BACK: + onBackPressed(); + break; + case Toolbar.BUTTON_CLOSE: + finish(); + break; + case Toolbar.BUTTON_DONATE: + Toast.makeText(LoginActivity.this, getString(R.string.label_donate), Toast.LENGTH_SHORT).show(); + case Toolbar.BUTTON_NONE: + default: + Log.e(TAG, "Button " + type + "pressed - how can this be?"); + } + } + }); if (Helper.getWritePermission(this)) { startLoginFragment(); @@ -102,37 +146,26 @@ public class LoginActivity extends AppCompatActivity } @Override - public boolean onWalletSelected(String daemon, final String walletName, boolean testnet) { + public boolean onWalletSelected(String walletName, String daemon, boolean testnet) { if (daemon.length() == 0) { Toast.makeText(this, getString(R.string.prompt_daemon_missing), Toast.LENGTH_SHORT).show(); return false; } - - if (!checkAndSetWalletDaemon(daemon, testnet)) { - Toast.makeText(this, getString(R.string.warn_daemon_unavailable), Toast.LENGTH_SHORT).show(); + if (checkServiceRunning()) return false; + try { + WalletNode aWalletNode = new WalletNode(walletName, daemon, testnet); + new AsyncOpenWallet().execute(aWalletNode); + } catch (IllegalArgumentException ex) { + Log.e(TAG, ex.getLocalizedMessage()); + Toast.makeText(this, ex.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); return false; } - - if (checkServiceRunning()) return true; - Log.d(TAG, "selected wallet is ." + walletName + "."); - // now it's getting real, check if wallet exists - File walletFile = Helper.getWalletFile(this, walletName); - if (WalletManager.getInstance().walletExists(walletFile)) { - promptPassword(walletName, new PasswordAction() { - @Override - public void action(String walletName, String password) { - startWallet(walletName, password); - } - }); - } else { // this cannot really happen as we prefilter choices - Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show(); - } return true; } @Override public void onWalletDetails(final String walletName, boolean testnet) { - checkAndSetWalletDaemon("", testnet); // just set selected net + setNet(testnet); Log.d(TAG, "details for wallet ." + walletName + "."); if (checkServiceRunning()) return; DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { @@ -170,7 +203,7 @@ public class LoginActivity extends AppCompatActivity @Override public void onWalletReceive(String walletName, boolean testnet) { - checkAndSetWalletDaemon("", testnet); // just set selected net + setNet(testnet); Log.d(TAG, "receive for wallet ." + walletName + "."); if (checkServiceRunning()) return; final File walletFile = Helper.getWalletFile(this, walletName); @@ -406,10 +439,10 @@ public class LoginActivity extends AppCompatActivity } @Override - public void onAddWallet(boolean testnet) { - checkAndSetWalletDaemon("", testnet); + public void onAddWallet(boolean testnet, String type) { + setNet(testnet); if (checkServiceRunning()) return; - startGenerateFragment(); + startGenerateFragment(type); } AlertDialog passwordDialog = null; // for preventing multiple clicks in wallet list @@ -423,23 +456,33 @@ public class LoginActivity extends AppCompatActivity AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); alertDialogBuilder.setView(promptsView); - final EditText etPassword = (EditText) promptsView.findViewById(R.id.etPassword); - final TextView tvPasswordLabel = (TextView) promptsView.findViewById(R.id.tvPasswordLabel); + final TextInputLayout etPassword = (TextInputLayout) promptsView.findViewById(R.id.etPassword); + etPassword.setHint(LoginActivity.this.getString(R.string.prompt_password, wallet)); - tvPasswordLabel.setText(LoginActivity.this.getString(R.string.prompt_password, wallet)); + 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) { + } + }); // set dialog message alertDialogBuilder .setCancelable(false) - .setPositiveButton("OK", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - Helper.hideKeyboardAlways(LoginActivity.this); - String pass = etPassword.getText().toString(); - processPasswordEntry(wallet, pass, action); - passwordDialog = null; - } - }) + .setPositiveButton("OK", null) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { @@ -448,20 +491,43 @@ public class LoginActivity extends AppCompatActivity passwordDialog = null; } }); - passwordDialog = alertDialogBuilder.create(); + + passwordDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String pass = etPassword.getEditText().getText().toString(); + if (processPasswordEntry(wallet, pass, action)) { + passwordDialog.dismiss(); + passwordDialog = null; + Helper.hideKeyboardAlways(LoginActivity.this); + } else { + etPassword.setError(getString(R.string.bad_password)); + } + } + }); + } + }); + Helper.showKeyboard(passwordDialog); // accept keyboard "ok" - etPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() { + 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.getText().toString(); - passwordDialog.cancel(); - processPasswordEntry(wallet, pass, action); - passwordDialog = null; - return false; + String pass = etPassword.getEditText().getText().toString(); + if (processPasswordEntry(wallet, pass, action)) { + passwordDialog.cancel(); + passwordDialog = null; + } else { + etPassword.setError(getString(R.string.bad_password)); + } + return true; } return false; } @@ -481,11 +547,12 @@ public class LoginActivity extends AppCompatActivity void action(String walletName, String password); } - private void processPasswordEntry(String walletName, String pass, PasswordAction action) { + private boolean processPasswordEntry(String walletName, String pass, PasswordAction action) { if (checkWalletPassword(walletName, pass)) { action.action(walletName, pass); + return true; } else { - Toast.makeText(this, getString(R.string.bad_password), Toast.LENGTH_SHORT).show(); + return false; } } @@ -505,22 +572,14 @@ public class LoginActivity extends AppCompatActivity //////////////////////////////////////// //////////////////////////////////////// - public void setTitle(String title) { - toolbar.setTitle(title); - } - - public void setSubtitle(String subtitle) { - toolbar.setSubtitle(subtitle); - } - @Override public void showNet(boolean testnet) { if (testnet) { toolbar.setBackgroundResource(R.color.colorPrimaryDark); } else { - toolbar.setBackgroundResource(R.color.moneroOrange); + toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet); } - setSubtitle(getString(testnet ? R.string.connect_testnet : R.string.connect_mainnet)); + toolbar.setSubtitle(getString(testnet ? R.string.connect_testnet : R.string.connect_mainnet)); } @Override @@ -532,9 +591,20 @@ public class LoginActivity extends AppCompatActivity ProgressDialog progressDialog = null; private void showProgressDialog(int msgId) { + showProgressDialog(msgId, 0); + } + + private void showProgressDialog(int msgId, long delay) { dismissProgressDialog(); // just in case progressDialog = new MyProgressDialog(LoginActivity.this, msgId); - progressDialog.show(); + if (delay > 0) { + Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + public void run() { + if (progressDialog != null) progressDialog.show(); + } + }, delay); + } } private void dismissProgressDialog() { @@ -554,7 +624,6 @@ public class LoginActivity extends AppCompatActivity protected void onResume() { super.onResume(); Log.d(TAG, "onResume()"); - setTitle(getString(R.string.login_activity_name)); // wait for WalletService to finish if (WalletService.Running && (progressDialog == null)) { // and show a progress dialog, but only if there isn't one already @@ -671,8 +740,10 @@ public class LoginActivity extends AppCompatActivity Log.d(TAG, "LoginFragment added"); } - void startGenerateFragment() { - replaceFragment(new GenerateFragment(), GENERATE_STACK, null); + void startGenerateFragment(String type) { + Bundle extras = new Bundle(); + extras.putString(GenerateFragment.TYPE, type); + replaceFragment(new GenerateFragment(), GENERATE_STACK, extras); Log.d(TAG, "GenerateFragment placed"); } @@ -705,7 +776,6 @@ public class LoginActivity extends AppCompatActivity ////////////////////////////////////////// static final String MNEMONIC_LANGUAGE = "English"; // see mnemonics/electrum-words.cpp for more - private class AsyncCreateWallet extends AsyncTask { String walletName; String walletPassword; @@ -762,8 +832,6 @@ public class LoginActivity extends AppCompatActivity if (result) { startDetails(newWalletFile, walletPassword, GenerateReviewFragment.VIEW_TYPE_ACCEPT); } else { - Toast.makeText(LoginActivity.this, - getString(R.string.generate_wallet_create_failed), Toast.LENGTH_LONG).show(); walletGenerateError(); } } @@ -797,7 +865,10 @@ public class LoginActivity extends AppCompatActivity Wallet newWallet = WalletManager.getInstance() .createWallet(aFile, password, MNEMONIC_LANGUAGE); boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok); - if (!success) Log.e(TAG, newWallet.getErrorString()); + if (!success) { + Log.e(TAG, newWallet.getErrorString()); + toast(newWallet.getErrorString()); + } newWallet.close(); return success; } @@ -816,6 +887,7 @@ public class LoginActivity extends AppCompatActivity success = success && newWallet.store(); } else { Log.e(TAG, newWallet.getErrorString()); + toast(newWallet.getErrorString()); } newWallet.close(); return success; @@ -838,6 +910,7 @@ public class LoginActivity extends AppCompatActivity success = success && newWallet.store(); } else { Log.e(TAG, newWallet.getErrorString()); + toast(newWallet.getErrorString()); } newWallet.close(); return success; @@ -845,6 +918,15 @@ public class LoginActivity extends AppCompatActivity }); } + void toast(final String msg) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_LONG).show(); + } + }); + } + @Override public void onAccept(final String name, final String password) { File walletFolder = getStorageRoot(); @@ -859,17 +941,10 @@ public class LoginActivity extends AppCompatActivity getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show(); } else { Log.e(TAG, "Wallet store failed to " + walletFile.getAbsolutePath()); - Toast.makeText(LoginActivity.this, - getString(R.string.generate_wallet_create_failed), Toast.LENGTH_LONG).show(); + Toast.makeText(LoginActivity.this, getString(R.string.generate_wallet_create_failed), Toast.LENGTH_LONG).show(); } } - @Override - public void onExchange(AsyncExchangeRate.Listener listener, String currencyA, String currencyB) { - new AsyncExchangeRate(listener).execute(currencyA, currencyB); - } - - Wallet.Status testWallet(String path, String password) { Log.d(TAG, "testing wallet " + path); Wallet aWallet = WalletManager.getInstance().openWallet(path, password); @@ -896,14 +971,12 @@ public class LoginActivity extends AppCompatActivity boolean copyWallet(File srcWallet, File dstWallet, boolean backupMode) { if (walletExists(dstWallet, true) && !backupMode) return false; - Log.d(TAG, "B " + backupMode); boolean success = false; File srcDir = srcWallet.getParentFile(); String srcName = srcWallet.getName(); File dstDir = dstWallet.getParentFile(); String dstName = dstWallet.getName(); try { - Log.d(TAG, "C " + backupMode); try { copyFile(new File(srcDir, srcName), new File(dstDir, dstName)); } catch (IOException ex) { @@ -962,6 +1035,12 @@ public class LoginActivity extends AppCompatActivity if (((GenerateReviewFragment) f).backOk()) { super.onBackPressed(); } + } else if (f instanceof LoginFragment) { + if (((LoginFragment) f).isFabOpen()) { + ((LoginFragment) f).animateFAB(); + } else { + super.onBackPressed(); + } } else { super.onBackPressed(); } @@ -971,10 +1050,10 @@ public class LoginActivity extends AppCompatActivity public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_create_help: - HelpFragment.displayHelp(getSupportFragmentManager(),R.raw.help_create); + HelpFragment.displayHelp(getSupportFragmentManager(), R.raw.help_create); return true; case R.id.action_details_help: - HelpFragment.displayHelp(getSupportFragmentManager(),R.raw.help_details); + HelpFragment.displayHelp(getSupportFragmentManager(), R.raw.help_details); return true; case R.id.action_lincense_info: LicensesFragment.displayLicensesFragment(getSupportFragmentManager()); @@ -992,59 +1071,128 @@ public class LoginActivity extends AppCompatActivity } } - private boolean checkAndSetWalletDaemon(String daemon, boolean testnet) { - String daemonAddress = ""; - String username = ""; + private void setNet(boolean testnet) { + WalletManager.getInstance().setDaemon("", testnet, "", ""); + } + + static class WalletNode { + String name = null; + String host = ""; + int port = 28081; + String user = ""; String password = ""; - if (!daemon.isEmpty()) { // no actual daemon is also fine + boolean isTestnet; + + WalletNode(String walletName, String daemon, boolean isTestnet) { + if ((daemon == null) || daemon.isEmpty()) return; + this.name = walletName; + String daemonAddress; String a[] = daemon.split("@"); if (a.length == 1) { // no credentials daemonAddress = a[0]; } else if (a.length == 2) { // credentials - String up[] = a[0].split(":"); - if (up.length != 2) return false; - username = up[0]; - if (!username.isEmpty()) password = up[1]; + String userPassword[] = a[0].split(":"); + if (userPassword.length != 2) + throw new IllegalArgumentException("User:Password invalid"); + user = userPassword[0]; + if (!user.isEmpty()) password = userPassword[1]; daemonAddress = a[1]; } else { - return false; + throw new IllegalArgumentException("Too many @"); } String da[] = daemonAddress.split(":"); - if ((da.length > 2) || (da.length < 1)) return false; - String host = da[0]; - int port; + if ((da.length > 2) || (da.length < 1)) + throw new IllegalArgumentException("Too many ':' or too few"); + host = da[0]; if (da.length == 2) { try { port = Integer.parseInt(da[1]); } catch (NumberFormatException ex) { - return false; + throw new IllegalArgumentException("Port not numeric"); } } else { - port = (testnet ? 28081 : 18081); - daemonAddress = daemonAddress + ":" + port; + port = (isTestnet ? 28081 : 18081); } - //Log.d(TAG, "DAEMON " + username + "/" + password + "/" + host + "/" + port); -// if (android.os.Build.VERSION.SDK_INT > 9) { - StrictMode.ThreadPolicy prevPolicy = StrictMode.getThreadPolicy(); - StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder(prevPolicy).permitNetwork().build(); - StrictMode.setThreadPolicy(policy); + this.isTestnet = isTestnet; + } + + String getAddress() { + return host + ":" + port; + } + + boolean isValid() { + return !host.isEmpty(); + } + } + + private class AsyncOpenWallet extends AsyncTask { + + WalletNode walletNode; + + @Override + protected void onPreExecute() { + super.onPreExecute(); + showProgressDialog(R.string.open_progress, DAEMON_TIMEOUT / 4); + } + + @Override + protected Boolean doInBackground(WalletNode... params) { + if (params.length != 1) return false; + this.walletNode = params[0]; + if (!walletNode.isValid()) return false; + + Log.d(TAG, "checking " + walletNode.getAddress()); + + long timeDA = new Date().getTime(); + SocketAddress address = new InetSocketAddress(walletNode.host, walletNode.port); + long timeDB = new Date().getTime(); + Log.d(TAG, "Resolving " + walletNode.host + " took " + (timeDB - timeDA) + "ms."); Socket socket = new Socket(); long timeA = new Date().getTime(); try { - socket.connect(new InetSocketAddress(host, port), LoginActivity.DAEMON_TIMEOUT); + socket.connect(address, LoginActivity.DAEMON_TIMEOUT); socket.close(); } catch (IOException ex) { - Log.d(TAG, "Cannot reach daemon " + host + "/" + port + " because " + ex.getLocalizedMessage()); + Log.d(TAG, "Cannot reach daemon " + walletNode.host + "/" + walletNode.port + " because " + ex.getMessage()); return false; - } finally { - StrictMode.setThreadPolicy(prevPolicy); } long timeB = new Date().getTime(); - Log.d(TAG, "Daemon is " + (timeB - timeA) + "ms away."); + long time = timeB - timeA; + Log.d(TAG, "Daemon " + walletNode.host + " is " + time + "ms away."); + return time < LoginActivity.DAEMON_TIMEOUT; + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + if (isDestroyed()) { + return; + } + dismissProgressDialog(); + if (result) { + Log.d(TAG, "selected wallet is ." + walletNode.name + "."); + // now it's getting real, check if wallet exists + promptAndStart(walletNode); + } else { + Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_connect_timeout), Toast.LENGTH_LONG).show(); + } + } + } + + void promptAndStart(WalletNode walletNode) { + File walletFile = Helper.getWalletFile(this, walletNode.name); + if (WalletManager.getInstance().walletExists(walletFile)) { + WalletManager.getInstance(). + setDaemon(walletNode.getAddress(), walletNode.isTestnet, walletNode.user, walletNode.password); + promptPassword(walletNode.name, new PasswordAction() { + @Override + public void action(String walletName, String password) { + startWallet(walletName, password); + } + }); + } else { // this cannot really happen as we prefilter choices + Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show(); } - WalletManager mgr = WalletManager.getInstance(); - mgr.setDaemon(daemonAddress, testnet, username, password); - return true; } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java index f51979e..fde8cb4 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java @@ -18,13 +18,15 @@ package com.m2049r.xmrwallet; import android.content.Context; import android.content.SharedPreferences; +import android.graphics.drawable.AnimationDrawable; import android.os.Bundle; +import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.Fragment; +import android.support.v7.widget.RecyclerView; import android.util.Log; -import android.view.ContextMenu; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; @@ -32,47 +34,42 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.BaseAdapter; import android.widget.EditText; -import android.widget.ListView; +import android.widget.FrameLayout; +import android.widget.ImageView; +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 java.io.File; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; -import java.util.Set; -import java.util.TreeSet; -public class LoginFragment extends Fragment { +public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInteractionListener, + View.OnClickListener { private static final String TAG = "LoginFragment"; - public static final String WALLETNAME_PREAMBLE = "[------] "; - public static final int WALLETNAME_PREAMBLE_LENGTH = WALLETNAME_PREAMBLE.length(); + private WalletInfoAdapter adapter; - ListView listView; - Set walletList = new TreeSet<>(new Comparator() { - @Override - public int compare(String o1, String o2) { - return o1.substring(WALLETNAME_PREAMBLE_LENGTH).toLowerCase() - .compareTo(o2.substring(WALLETNAME_PREAMBLE_LENGTH).toLowerCase()); - } - }); - List displayedList = new ArrayList<>(); + List walletList = new ArrayList<>(); + List displayedList = new ArrayList<>(); + ImageView ivGuntherWallets; EditText etDummy; DropDownEditText etDaemonAddress; ArrayAdapter nodeAdapter; - FloatingActionButton fabAdd; Listener activityCallback; @@ -82,7 +79,7 @@ public class LoginFragment extends Fragment { File getStorageRoot(); - boolean onWalletSelected(String daemon, String wallet, boolean testnet); + boolean onWalletSelected(String wallet, String daemon, boolean testnet); void onWalletDetails(String wallet, boolean testnet); @@ -94,9 +91,14 @@ public class LoginFragment extends Fragment { void onWalletArchive(String walletName); - void onAddWallet(boolean testnet); + void onAddWallet(boolean testnet, String type); void showNet(boolean testnet); + + void setToolbarButton(int type); + + void setTitle(String title); + } @Override @@ -121,55 +123,52 @@ public class LoginFragment extends Fragment { public void onResume() { super.onResume(); Log.d(TAG, "onResume()"); + activityCallback.setTitle(null); + activityCallback.setToolbarButton(Toolbar.BUTTON_DONATE); + activityCallback.showNet(isTestnet()); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(TAG, "onCreateView"); - View view = inflater.inflate(R.layout.login_fragment, container, false); + View view = inflater.inflate(R.layout.fragment_login, container, false); - fabAdd = (FloatingActionButton) view.findViewById(R.id.fabAdd); - fabAdd.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - activityCallback.onAddWallet(isTestnet()); - } - }); + ivGuntherWallets = (ImageView) view.findViewById(R.id.ivGuntherWallets); - listView = (ListView) view.findViewById(R.id.list); - ArrayAdapter adapter = new ArrayAdapter<>(getActivity(), - android.R.layout.simple_list_item_1, android.R.id.text1, this.displayedList); - listView.setAdapter(adapter); - registerForContextMenu(listView); + fabScreen = (FrameLayout) view.findViewById(R.id.fabScreen); + fab = (FloatingActionButton) view.findViewById(R.id.fab); + fabNew = (FloatingActionButton) view.findViewById(R.id.fabNew); + fabView = (FloatingActionButton) view.findViewById(R.id.fabView); + fabKey = (FloatingActionButton) view.findViewById(R.id.fabKey); + fabSeed = (FloatingActionButton) view.findViewById(R.id.fabSeed); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - String itemValue = (String) listView.getItemAtPosition(position); + fabNewL = (RelativeLayout) view.findViewById(R.id.fabNewL); + fabViewL = (RelativeLayout) view.findViewById(R.id.fabViewL); + fabKeyL = (RelativeLayout) view.findViewById(R.id.fabKeyL); + fabSeedL = (RelativeLayout) view.findViewById(R.id.fabSeedL); - if (itemValue.length() <= (WALLETNAME_PREAMBLE_LENGTH)) { - Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show(); - return; - } + fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen); + fab_close_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_close_screen); + fab_open = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open); + fab_close = AnimationUtils.loadAnimation(getContext(), R.anim.fab_close); + rotate_forward = AnimationUtils.loadAnimation(getContext(), R.anim.rotate_forward); + rotate_backward = AnimationUtils.loadAnimation(getContext(), R.anim.rotate_backward); + fab.setOnClickListener(this); + fabNew.setOnClickListener(this); + fabView.setOnClickListener(this); + fabKey.setOnClickListener(this); + fabSeed.setOnClickListener(this); + fabScreen.setOnClickListener(this); - String x = isTestnet() ? "9A-" : "4-"; - if (x.indexOf(itemValue.charAt(1)) < 0) { - Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show(); - return; - } - - String wallet = itemValue.substring(WALLETNAME_PREAMBLE_LENGTH); - - if (activityCallback.onWalletSelected(getDaemon(), wallet, isTestnet())) { - savePrefs(); - } - } - }); + RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list); + registerForContextMenu(recyclerView); + this.adapter = new WalletInfoAdapter(getActivity(), this); + recyclerView.setAdapter(adapter); etDummy = (EditText) view.findViewById(R.id.etDummy); etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress); - nodeAdapter = new ArrayAdapter(getContext(), android.R.layout.simple_dropdown_item_1line); + nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line); etDaemonAddress.setAdapter(nodeAdapter); Helper.hideKeyboard(getActivity()); @@ -204,72 +203,66 @@ public class LoginFragment extends Fragment { } }); + etDaemonAddress.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View arg1, int pos, long id) { + Helper.hideKeyboard(getActivity()); + etDummy.requestFocus(); + + } + }); + loadPrefs(); return view; } - private void filterList() { - displayedList.clear(); - String x = isTestnet() ? "9A" : "4"; - for (String s : walletList) { - if (x.indexOf(s.charAt(1)) >= 0) displayedList.add(s); - } - } - - public void loadList() { - Log.d(TAG, "loadList()"); - // TODO this should probably be in LoginActivity - WalletManager mgr = WalletManager.getInstance(); - List walletInfos = - mgr.findWallets(activityCallback.getStorageRoot()); - - walletList.clear(); - for (WalletManager.WalletInfo walletInfo : walletInfos) { - // ONCE the walletInfo.address was null - because the address.txt was empty - // this was before the wallet generation was in its own therad with huge stack - // TODO: keep an eye on Wallet.getAddress() returning empty - String displayAddress = walletInfo.address; - if ((displayAddress != null) && displayAddress.length() == 95) { - displayAddress = walletInfo.address.substring(0, 6); - walletList.add("[" + displayAddress + "] " + walletInfo.name); + void showGunther() { + ivGuntherWallets.setImageResource(R.drawable.gunther_wallets); + final AnimationDrawable guntherWalletsAnim = (AnimationDrawable) ivGuntherWallets.getDrawable(); + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + guntherWalletsAnim.start(); } - } - filterList(); - ((BaseAdapter) listView.getAdapter()).notifyDataSetChanged(); + }, getResources().getInteger(R.integer.gunther_wallets_delay)); + } + void normalGunther() { + ivGuntherWallets.setImageResource(R.drawable.gunther_wallets_00); + } + + // Callbacks from WalletInfoAdapter + @Override + public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) { + String x = isTestnet() ? "9A-" : "4-"; + if (x.indexOf(infoItem.address.charAt(0)) < 0) { + Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show(); + return; + } + + if (activityCallback.onWalletSelected(infoItem.name, getDaemon(), isTestnet())) { + savePrefs(); + } } @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.list_context_menu, menu); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); - String listItem = (String) listView.getItemAtPosition(info.position); - String name = nameFromListItem(listItem, isTestnet()); - if (name == null) { - Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show(); - } + public boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo listItem) { switch (item.getItemId()) { case R.id.action_info: - showInfo(name); + showInfo(listItem.name); break; case R.id.action_receive: - showReceive(name); + showReceive(listItem.name); break; case R.id.action_rename: - activityCallback.onWalletRename(name); + activityCallback.onWalletRename(listItem.name); break; case R.id.action_backup: - activityCallback.onWalletBackup(name); + activityCallback.onWalletBackup(listItem.name); break; case R.id.action_archive: - activityCallback.onWalletArchive(name); + activityCallback.onWalletArchive(listItem.name); break; default: return super.onContextItemSelected(item); @@ -277,6 +270,31 @@ public class LoginFragment extends Fragment { return true; } + private void filterList() { + displayedList.clear(); + String x = isTestnet() ? "9A" : "4"; + for (WalletManager.WalletInfo s : walletList) { + if (x.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s); + } + } + + public void loadList() { + Log.d(TAG, "loadList()"); + WalletManager mgr = WalletManager.getInstance(); + List walletInfos = + mgr.findWallets(activityCallback.getStorageRoot()); + walletList.clear(); + walletList.addAll(walletInfos); + filterList(); + adapter.setInfos(displayedList); + adapter.notifyDataSetChanged(); + if (displayedList.isEmpty()) { + showGunther(); + } else { + normalGunther(); + } + } + private void showInfo(@NonNull String name) { activityCallback.onWalletDetails(name, isTestnet()); } @@ -286,15 +304,6 @@ public class LoginFragment extends Fragment { return true; } - private String nameFromListItem(String listItem, boolean testnet) { - String wallet = listItem.substring(WALLETNAME_PREAMBLE_LENGTH); - String x = testnet ? "9A" : "4"; - if (x.indexOf(listItem.charAt(1)) < 0) { - return null; - } - return wallet; - } - @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -392,4 +401,75 @@ public class LoginFragment extends Fragment { etDummy.requestFocus(); Helper.hideKeyboard(getActivity()); } + + private boolean isFabOpen = false; + private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed; + private FrameLayout fabScreen; + private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL; + private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen; + + public boolean isFabOpen() { + return isFabOpen; + } + + public void animateFAB() { + if (isFabOpen) { + fabScreen.setVisibility(View.INVISIBLE); + fabScreen.setClickable(false); + fabScreen.startAnimation(fab_close_screen); + fab.startAnimation(rotate_backward); + fabNewL.startAnimation(fab_close); + fabNew.setClickable(false); + fabViewL.startAnimation(fab_close); + fabView.setClickable(false); + fabKeyL.startAnimation(fab_close); + fabKey.setClickable(false); + fabSeedL.startAnimation(fab_close); + fabSeed.setClickable(false); + isFabOpen = false; + } else { + fabScreen.setClickable(true); + fabScreen.startAnimation(fab_open_screen); + fab.startAnimation(rotate_forward); + fabNewL.startAnimation(fab_open); + fabNew.setClickable(true); + fabViewL.startAnimation(fab_open); + fabView.setClickable(true); + fabKeyL.startAnimation(fab_open); + fabKey.setClickable(true); + fabSeedL.startAnimation(fab_open); + fabSeed.setClickable(true); + isFabOpen = true; + } + } + + @Override + public void onClick(View v) { + int id = v.getId(); + switch (id) { + case R.id.fab: + animateFAB(); + break; + case R.id.fabNew: + fabScreen.setVisibility(View.INVISIBLE); + isFabOpen = false; + activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_NEW); + break; + case R.id.fabView: + animateFAB(); + activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_VIEWONLY); + break; + case R.id.fabKey: + animateFAB(); + activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_KEY); + break; + case R.id.fabSeed: + animateFAB(); + activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_SEED); + break; + case R.id.fabScreen: + animateFAB(); + break; + } + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java index 38153c5..80e2412 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java @@ -17,12 +17,11 @@ package com.m2049r.xmrwallet; import android.content.Context; -import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.Canvas; import android.os.AsyncTask; import android.os.Bundle; -import android.preference.PreferenceManager; +import android.support.design.widget.TextInputLayout; import android.support.v4.app.Fragment; import android.text.Editable; import android.text.InputType; @@ -33,12 +32,11 @@ import android.view.LayoutInflater; 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.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; -import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -48,151 +46,102 @@ 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.AsyncExchangeRate; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; import java.util.HashMap; -import java.util.Locale; import java.util.Map; -public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Listener { +public class ReceiveFragment extends Fragment { static final String TAG = "ReceiveFragment"; ProgressBar pbProgress; TextView tvAddress; - EditText etPaymentId; - EditText etAmount; - TextView tvAmountB; + TextInputLayout etPaymentId; + ExchangeView evAmount; Button bPaymentId; Button bGenerate; ImageView qrCode; EditText etDummy; + ImageButton bCopyAddress; - Spinner sCurrencyA; - Spinner sCurrencyB; + //String name; public interface Listener { - void onExchange(AsyncExchangeRate.Listener listener, String currencyA, String currencyB); - } + void setToolbarButton(int type); - @Override - public void exchange(String currencyA, String currencyB, double rate) { - // first, make sure this is what we want - String enteredCurrencyA = (String) sCurrencyA.getSelectedItem(); - String enteredCurrencyB = (String) sCurrencyB.getSelectedItem(); - if (!currencyA.equals(enteredCurrencyA) || !currencyB.equals(enteredCurrencyB)) { - // something's wrong - Log.e(TAG, "Currencies don't match!"); - tvAmountB.setText(""); - return; - } - String enteredAmount = etAmount.getText().toString(); - String xmrAmount = ""; - if (!enteredAmount.isEmpty()) { - // losing precision using double here doesn't matter - double amountA = Double.parseDouble(enteredAmount); - double amountB = amountA * rate; - if (enteredCurrencyA.equals("XMR")) { - String validatedAmountA = Helper.getDisplayAmount(Wallet.getAmountFromString(enteredAmount)); - xmrAmount = validatedAmountA; // take what was entered in XMR - etAmount.setText(xmrAmount); // display what we stick into the QR code - String displayB = String.format(Locale.US, "%.2f", amountB); - tvAmountB.setText(displayB); - } else if (enteredCurrencyB.equals("XMR")) { - xmrAmount = Wallet.getDisplayAmount(Wallet.getAmountFromDouble(amountB)); - // cut off at 5 decimals - xmrAmount = xmrAmount.substring(0, xmrAmount.length() - (12 - 5)); - tvAmountB.setText(xmrAmount); - } else { // no XMR currency - tvAmountB.setText(""); - return; // and no qr code - } - } else { - tvAmountB.setText(""); - } - generateQr(xmrAmount); + void setTitle(String title); + + void setSubtitle(String subtitle); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.receive_fragment, container, false); + View view = inflater.inflate(R.layout.fragment_receive, container, false); pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); tvAddress = (TextView) view.findViewById(R.id.tvAddress); - etPaymentId = (EditText) view.findViewById(R.id.etPaymentId); - etAmount = (EditText) view.findViewById(R.id.etAmountA); - tvAmountB = (TextView) view.findViewById(R.id.tvAmountB); + etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId); + evAmount = (ExchangeView) view.findViewById(R.id.evAmount); bPaymentId = (Button) view.findViewById(R.id.bPaymentId); qrCode = (ImageView) view.findViewById(R.id.qrCode); bGenerate = (Button) view.findViewById(R.id.bGenerate); etDummy = (EditText) view.findViewById(R.id.etDummy); + bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress); - sCurrencyA = (Spinner) view.findViewById(R.id.sCurrencyA); - sCurrencyB = (Spinner) view.findViewById(R.id.sCurrencyB); - - etPaymentId.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - loadPrefs(); - - etPaymentId.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 (paymentIdOk()) { - etAmount.requestFocus(); - } // otherwise ignore - return true; - } - return false; - } - }); - etPaymentId.addTextChangedListener(new TextWatcher() { + bCopyAddress.setOnClickListener(new View.OnClickListener() { @Override - public void afterTextChanged(Editable editable) { - qrCode.setImageBitmap(getMoneroLogo()); - if (paymentIdOk() && amountOk()) { - bGenerate.setEnabled(true); - } else { - bGenerate.setEnabled(false); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + public void onClick(View v) { + copyAddress(); } }); - etAmount.setOnEditorActionListener(new TextView.OnEditorActionListener() { + evAmount.setOnNewAmountListener(new ExchangeView.OnNewAmountListener() { + @Override + public void onNewAmount(String xmr) { + Log.d(TAG, "new amount = " + xmr); + generateQr(); + } + }); +/* + evAmount.setOnAmountInvalidatedListener(new ExchangeView.OnAmountInvalidatedListener() { + @Override + public void onAmountInvalidated() { + clearQR(); + } + }); + + evAmount.setOnFailedExchangeListener(new ExchangeView.OnFailedExchangeListener() { + @Override + public void onFailedExchange() { + Toast.makeText(getActivity(), getString(R.string.message_exchange_failed), Toast.LENGTH_LONG).show(); + } + }); +*/ + 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 (paymentIdOk() && amountOk()) { - Helper.hideKeyboard(getActivity()); - startExchange(); + if (checkPaymentId()) { // && evAmount.checkXmrAmount(true)) { + generateQr(); } return true; } return false; } }); - etAmount.addTextChangedListener(new TextWatcher() { + etPaymentId.getEditText().addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable editable) { - tvAmountB.setText(""); - qrCode.setImageBitmap(getMoneroLogo()); - if (paymentIdOk() && amountOk()) { - bGenerate.setEnabled(true); - } else { - bGenerate.setEnabled(false); - } + clearQR(); } @Override @@ -207,10 +156,10 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste bPaymentId.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - etPaymentId.setText((Wallet.generatePaymentId())); - etPaymentId.setSelection(etPaymentId.getText().length()); - if (paymentIdOk() && amountOk()) { - startExchange(); + etPaymentId.getEditText().setText((Wallet.generatePaymentId())); + etPaymentId.getEditText().setSelection(etPaymentId.getEditText().getText().length()); + if (checkPaymentId()) { //&& evAmount.checkXmrAmount(true)) { + generateQr(); } } }); @@ -218,101 +167,82 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste bGenerate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (paymentIdOk() && amountOk()) { - Helper.hideKeyboard(getActivity()); - startExchange(); + if (checkPaymentId()) { + evAmount.doExchange(); } } }); - sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parentView, View selectedItemView, int position, long id) { - if (position != 0) { - sCurrencyB.setSelection(0, true); - } - startExchange(); - } - - @Override - public void onNothingSelected(AdapterView parentView) { - // nothing (yet?) - } - }); - - sCurrencyB.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parentView, View selectedItemView, int position, long id) { - if (position != 0) { - sCurrencyA.setSelection(0, true); - } - tvAmountB.setText(""); - startExchange(); - } - - @Override - public void onNothingSelected(AdapterView parentView) { - // nothing (yet?) - } - }); - showProgress(); - qrCode.setImageBitmap(getMoneroLogo()); + clearQR(); Bundle b = getArguments(); String address = b.getString("address"); + String walletName = b.getString("name"); + Log.d(TAG, "address=" + address + "/name=" + walletName); if (address == null) { String path = b.getString("path"); String password = b.getString("password"); - show(path, password); + loadAndShow(path, password); } else { - show(address); + show(walletName, address); } return view; } - void startExchange() { - if (paymentIdOk() && amountOk() && tvAddress.getText().length() > 0) { - String enteredCurrencyA = (String) sCurrencyA.getSelectedItem(); - String enteredCurrencyB = (String) sCurrencyB.getSelectedItem(); - String enteredAmount = etAmount.getText().toString(); - tvAmountB.setText(""); - if (!enteredAmount.isEmpty()) { // start conversion - listenerCallback.onExchange(ReceiveFragment.this, enteredCurrencyA, enteredCurrencyB); - } else { - generateQr(""); - } + void copyAddress() { + Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_address), tvAddress.getText().toString()); + Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show(); + } + + boolean qrValid = true; + + void clearQR() { + if (qrValid) { + qrCode.setImageBitmap(getMoneroLogo()); + qrValid = false; } } + void setQR(Bitmap qr) { + qrCode.setImageBitmap(qr); + qrValid = true; + Helper.hideKeyboard(getActivity()); + etDummy.requestFocus(); + } + @Override public void onResume() { super.onResume(); Log.d(TAG, "onResume()"); - if (paymentIdOk() && amountOk() && tvAddress.getText().length() > 0) { - startExchange(); - } + listenerCallback.setToolbarButton(Toolbar.BUTTON_BACK); + listenerCallback.setSubtitle(getString(R.string.receive_title)); + generateQr(); } - private void show(String address) { + private void show(String name, String address) { + Log.d(TAG, "name=" + name); + listenerCallback.setTitle(name); tvAddress.setText(address); etPaymentId.setEnabled(true); - etAmount.setEnabled(true); + //etAmount.setEnabled(true); bPaymentId.setEnabled(true); bGenerate.setEnabled(true); + bCopyAddress.setEnabled(true); + bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp); hideProgress(); - startExchange(); + generateQr(); } - private void show(String walletPath, String password) { + private void loadAndShow(String walletPath, String password) { new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath, password); } private class AsyncShow extends AsyncTask { String password; - String address; + String name; @Override protected Boolean doInBackground(String... params) { @@ -321,6 +251,7 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste password = params[1]; Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password); address = wallet.getAddress(); + name = wallet.getName(); wallet.close(); return true; } @@ -328,8 +259,9 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); + if (!isAdded()) return; // never mind if (result) { - show(address); + show(name, address); } else { Toast.makeText(getActivity(), getString(R.string.receive_cannot_open), Toast.LENGTH_LONG).show(); hideProgress(); @@ -337,22 +269,29 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste } } + private boolean checkPaymentId() { + String paymentId = etPaymentId.getEditText().getText().toString(); + boolean ok = paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId); - private boolean amountOk() { - String amountEntry = etAmount.getText().toString(); - if (amountEntry.isEmpty()) return true; - long amount = Wallet.getAmountFromString(amountEntry); - return (amount > 0); + if (!ok) { + etPaymentId.setError(getString(R.string.receive_paymentid_invalid)); + } else { + etPaymentId.setError(null); + } + return ok; } - private boolean paymentIdOk() { - String paymentId = etPaymentId.getText().toString(); - return paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId); - } - - private void generateQr(String xmrAmount) { + private void generateQr() { + Log.d(TAG, "GENQR"); String address = tvAddress.getText().toString(); - String paymentId = etPaymentId.getText().toString(); + String paymentId = etPaymentId.getEditText().getText().toString(); + String xmrAmount = evAmount.getAmount(); + Log.d(TAG, xmrAmount + "/" + paymentId + "/" + address); + if ((xmrAmount == null) || !Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet())) { + clearQR(); + Log.d(TAG, "CLEARQR"); + return; + } StringBuffer sb = new StringBuffer(); sb.append(ScannerFragment.QR_SCHEME).append(address); boolean first = true; @@ -374,9 +313,10 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste String text = sb.toString(); Bitmap qr = generate(text, 500, 500); if (qr != null) { - qrCode.setImageBitmap(qr); + setQR(qr); + Log.d(TAG, "SETQR"); etDummy.requestFocus(); - bGenerate.setEnabled(false); + Helper.hideKeyboard(getActivity()); } } @@ -439,7 +379,6 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste } public void showProgress() { - pbProgress.setIndeterminate(true); pbProgress.setVisibility(View.VISIBLE); } @@ -463,36 +402,9 @@ public class ReceiveFragment extends Fragment implements AsyncExchangeRate.Liste static final String PREF_CURRENCY_A = "PREF_CURRENCY_A"; static final String PREF_CURRENCY_B = "PREF_CURRENCY_B"; - void loadPrefs() { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); - int currencyA = sharedPreferences.getInt(PREF_CURRENCY_A, 0); - int currencyB = sharedPreferences.getInt(PREF_CURRENCY_B, 0); - - if (currencyA * currencyB != 0) { // make sure one of them is 0 (=XMR) - currencyA = 0; - } - // in case we change the currency lists in the future - if (currencyA >= sCurrencyA.getCount()) currencyA = 0; - if (currencyB >= sCurrencyB.getCount()) currencyB = 0; - sCurrencyA.setSelection(currencyA); - sCurrencyB.setSelection(currencyB); - } - - void savePrefs() { - int currencyA = sCurrencyA.getSelectedItemPosition(); - int currencyB = sCurrencyB.getSelectedItemPosition(); - - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putInt(PREF_CURRENCY_A, currencyA); - editor.putInt(PREF_CURRENCY_B, currencyB); - editor.apply(); - } - @Override public void onPause() { Log.d(TAG, "onPause()"); - savePrefs(); super.onPause(); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java b/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java index 668f172..0240595 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java @@ -28,7 +28,6 @@ import android.widget.Toast; import com.google.zxing.BarcodeFormat; import com.google.zxing.Result; -import com.m2049r.xmrwallet.util.BarcodeData; import me.dm7.barcodescanner.zxing.ZXingScannerView; @@ -64,7 +63,6 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result @Override public void handleResult(Result rawResult) { - //Log.d(TAG, rawResult.getBarcodeFormat().toString() + "/" + rawResult.getText()); if ((rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE) && (rawResult.getText().startsWith(QR_SCHEME))) { if (activityCallback.onAddressScanned(rawResult.getText())) { @@ -101,7 +99,6 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result @Override public void onAttach(Context context) { super.onAttach(context); - //Log.d(TAG, "attaching scan"); if (context instanceof Listener) { this.activityCallback = (Listener) context; } else { diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java index 5f135e2..bad8694 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java @@ -20,11 +20,10 @@ import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.os.Handler; +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.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -35,24 +34,36 @@ 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.Helper; +import com.m2049r.xmrwallet.util.AsyncExchangeRate; import com.m2049r.xmrwallet.util.BarcodeData; +import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.TxData; public class SendFragment extends Fragment { static final String TAG = "SendFragment"; - EditText etAddress; - EditText etPaymentId; - EditText etAmount; + EditText etDummy; + + ScrollView scrollview; + + TextInputLayout etAddress; + TextInputLayout etPaymentId; + //TextInputLayout etAmount; + ExchangeView evAmount; + TextView tvAmountB; + Spinner sCurrencyA; + Spinner sCurrencyB; + Button bScan; - Button bSweep; Spinner sMixin; Spinner sPriority; Button bPrepareSend; @@ -61,7 +72,8 @@ public class SendFragment extends Fragment { LinearLayout llConfirmSend; TextView tvTxAmount; TextView tvTxFee; - TextView tvTxDust; + //TextView tvTxDust; + TextView tvTxTotal; EditText etNotes; Button bSend; Button bReallySend; @@ -78,15 +90,23 @@ public class SendFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.send_fragment, container, false); + 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 = (EditText) view.findViewById(R.id.etAddress); - etPaymentId = (EditText) view.findViewById(R.id.etPaymentId); - etAmount = (EditText) view.findViewById(R.id.etAmount); + etAddress = (TextInputLayout) view.findViewById(R.id.etAddress); + etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId); + evAmount = (ExchangeView) view.findViewById(R.id.evAmount); + tvAmountB = (TextView) view.findViewById(R.id.tvAmountB); + sCurrencyA = (Spinner) view.findViewById(R.id.sCurrencyA); + sCurrencyB = (Spinner) view.findViewById(R.id.sCurrencyB); + bScan = (Button) view.findViewById(R.id.bScan); - bSweep = (Button) view.findViewById(R.id.bSweep); bPrepareSend = (Button) view.findViewById(R.id.bPrepareSend); bPaymentId = (Button) view.findViewById(R.id.bPaymentId); bDispose = (Button) view.findViewById(R.id.bDispose); @@ -94,101 +114,64 @@ public class SendFragment extends Fragment { 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); + //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.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - etPaymentId.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + 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.requestFocus(); - etAddress.setOnEditorActionListener(new TextView.OnEditorActionListener() { + 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 (addressOk()) { + if (checkAddress()) { + evAmount.focus(); + } // otherwise ignore + return true; + } + return false; + } + }); + + evAmount.setOnNewAmountListener(new ExchangeView.OnNewAmountListener() { + @Override + public void onNewAmount(String xmr) { + if ((xmr != null)) { + // stupid workaround to not show error on open of screen + if ((checkAddressNoError() && checkAmountWithError()) || checkAmount()) { etPaymentId.requestFocus(); - } // otherwise ignore - return true; + } } - return false; - } - }); - etAddress.addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - if (addressOk() && amountOk()) { - bPrepareSend.setEnabled(true); - } else { - bPrepareSend.setEnabled(false); - } - } - - @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.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 (paymentIdOk()) { - etAmount.requestFocus(); - } // otherwise ignore - return true; - } - return false; } }); - etAmount.setOnEditorActionListener(new TextView.OnEditorActionListener() { + 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 (amountOk()) { + if (checkPaymentId()) { + etDummy.requestFocus(); Helper.hideKeyboard(getActivity()); - disableEdit(); - prepareSend(); } return true; } return false; } }); - etAmount.addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - if (addressOk() && amountOk()) { - bPrepareSend.setEnabled(true); - } else { - bPrepareSend.setEnabled(false); - } - } - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }); - - setPrepareButtonState(); - bPrepareSend.setOnClickListener(new View.OnClickListener() - - { + bPrepareSend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Helper.hideKeyboard(getActivity()); - disableEdit(); - prepareSend(); + if (checkAddress() && checkAmountWithError() && checkPaymentId()) { + Helper.hideKeyboard(getActivity()); + prepareSend(); + } } }); @@ -210,27 +193,15 @@ public class SendFragment extends Fragment { bPaymentId.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - etPaymentId.setText((Wallet.generatePaymentId())); - etPaymentId.setSelection(etPaymentId.getText().length()); - } - }); - - bSweep.setOnClickListener(new View.OnClickListener() - - { - @Override - public void onClick(View v) { - Helper.hideKeyboard(getActivity()); - prepareSweep(); + 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)) { - if (amountOk()) { - Helper.hideKeyboard(getActivity()); - } + Helper.hideKeyboard(getActivity()); return true; } return false; @@ -242,8 +213,8 @@ public class SendFragment extends Fragment { public void onClick(View v) { bSend.setEnabled(false); boolean testnet = WalletManager.getInstance().isTestNet(); - if (testnet) { - send(); + if (!testnet) { + //send(); } else { etNotes.setEnabled(false); Handler handler = new Handler(); @@ -251,6 +222,13 @@ public class SendFragment extends Fragment { @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); } @@ -264,40 +242,64 @@ public class SendFragment extends Fragment { send(); } }); + + etDummy.requestFocus(); + Helper.hideKeyboard(getActivity()); + return view; } - private void setPrepareButtonState() { - if (addressOk() && amountOk() && (bSend.getVisibility() != View.VISIBLE)) { - bPrepareSend.setEnabled(true); - } else { - bPrepareSend.setEnabled(false); - } - } - - private boolean addressOk() { - String address = etAddress.getText().toString(); + private boolean checkAddressNoError() { + String address = etAddress.getEditText().getText().toString(); return Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet()); } - private boolean amountOk() { - long amount = Wallet.getAmountFromString(etAmount.getText().toString()); - return (amount > 0); + 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 paymentIdOk() { - String paymentId = etPaymentId.getText().toString(); - return paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId); + 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() { - String dst_addr = etAddress.getText().toString(); - String paymentId = etPaymentId.getText().toString(); - long amount = Wallet.getAmountFromString(etAmount.getText().toString()); + 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]; - //Log.d(TAG, dst_addr + "/" + paymentId + "/" + amount + "/" + mixin + "/" + priority.toString()); + Log.d(TAG, dst_addr + "/" + paymentId + "/" + amount + "/" + mixin + "/" + priority.toString()); TxData txData = new TxData( dst_addr, paymentId, @@ -308,105 +310,106 @@ public class SendFragment extends Fragment { activityCallback.onPrepareSend(txData); } - private void prepareSweep() { - etAddress.setText(activityCallback.getWalletAddress()); - etPaymentId.setText(""); - etAmount.setText(""); - disableEdit(); - showProgress(); - activityCallback.onPrepareSweep(); - } - private void disableEdit() { sMixin.setEnabled(false); sPriority.setEnabled(false); - etAddress.setEnabled(false); - etPaymentId.setEnabled(false); - etAmount.setEnabled(false); + etAddress.getEditText().setEnabled(false); + etPaymentId.getEditText().setEnabled(false); + evAmount.enable(false); bScan.setEnabled(false); bPaymentId.setEnabled(false); - bSweep.setEnabled(false); bPrepareSend.setEnabled(false); + bPrepareSend.setVisibility(View.GONE); } private void enableEdit() { sMixin.setEnabled(true); sPriority.setEnabled(true); - etAddress.setEnabled(true); - etPaymentId.setEnabled(true); - etAmount.setEnabled(true); + etAddress.getEditText().setEnabled(true); + etPaymentId.getEditText().setEnabled(true); + evAmount.enable(true); bScan.setEnabled(true); bPaymentId.setEnabled(true); - bSweep.setEnabled(true); bPrepareSend.setEnabled(true); + bPrepareSend.setVisibility(View.VISIBLE); + llConfirmSend.setVisibility(View.GONE); - bSend.setEnabled(true); 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); } - SendFragment.Listener activityCallback; + Listener activityCallback; public interface Listener { void onPrepareSend(TxData data); - void onPrepareSweep(); - void onSend(String notes); String getWalletAddress(); + String getWalletName(); + void onDisposeRequest(); void onScanAddress(); - BarcodeData getScannedData(); + BarcodeData popScannedData(); + void onExchange(AsyncExchangeRate.Listener listener, String currencyA, String currencyB); + + void setSubtitle(String subtitle); + + void setToolbarButton(int type); } @Override public void onResume() { super.onResume(); Log.d(TAG, "onResume"); - BarcodeData data = activityCallback.getScannedData(); + activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); + activityCallback.setSubtitle(getString(R.string.send_title)); + BarcodeData data = activityCallback.popScannedData(); if (data != null) { + Log.d(TAG, "GOT DATA"); String scannedAddress = data.address; if (scannedAddress != null) { - etAddress.setText(scannedAddress); + etAddress.getEditText().setText(scannedAddress); + checkAddress(); } else { - etAddress.getText().clear(); + etAddress.getEditText().getText().clear(); + etAddress.setError(null); } String scannedPaymenId = data.paymentId; if (scannedPaymenId != null) { - etPaymentId.setText(scannedPaymenId); + etPaymentId.getEditText().setText(scannedPaymenId); + checkPaymentId(); } else { - etPaymentId.getText().clear(); + etPaymentId.getEditText().getText().clear(); + etPaymentId.setError(null); } - if (data.amount >= 0) { + if (data.amount > 0) { String scannedAmount = Helper.getDisplayAmount(data.amount); - etAmount.setText(scannedAmount); + evAmount.setAmount(scannedAmount); } else { - etAmount.getText().clear(); - } - etAmount.requestFocus(); - etAmount.setSelection(etAmount.getText().length()); - } else { // no scan data - // jump to first empty field - if (etAddress.getText().toString().isEmpty()) { - etAddress.requestFocus(); - } else if (etPaymentId.getText().toString().isEmpty()) { - etPaymentId.requestFocus(); - } else { - etAmount.requestFocus(); - etAmount.setSelection(etAmount.getText().length()); + evAmount.setAmount(""); } } + if ((data != null) && (data.amount <= 0)) { + evAmount.focus(); + } else { + etDummy.requestFocus(); + Helper.hideKeyboard(getActivity()); + } } @Override @@ -427,9 +430,18 @@ public class SendFragment extends Fragment { 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())); + //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) { @@ -456,4 +468,5 @@ public class SendFragment extends Fragment { public void hideProgress() { pbProgress.setVisibility(View.GONE); } + } diff --git a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java index 5cebf19..88c1eaf 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java @@ -16,19 +16,22 @@ package com.m2049r.xmrwallet; -import android.content.ClipData; -import android.content.ClipboardManager; import android.content.Context; +import android.content.Intent; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; import android.text.InputType; 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.TextView; -import android.widget.Toast; +import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.Transfer; import com.m2049r.xmrwallet.model.Wallet; @@ -64,14 +67,13 @@ public class TxFragment extends Fragment { TextView tvTxFee; TextView tvTxTransfers; TextView etTxNotes; - Button bCopy; Button bTxNotes; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.tx_fragment, container, false); + View view = inflater.inflate(R.layout.fragment_tx_info, container, false); tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp); tvTxId = (TextView) view.findViewById(R.id.tvTxId); @@ -83,18 +85,10 @@ public class TxFragment extends Fragment { tvTxFee = (TextView) view.findViewById(R.id.tvTxFee); tvTxTransfers = (TextView) view.findViewById(R.id.tvTxTransfers); etTxNotes = (TextView) view.findViewById(R.id.etTxNotes); - bCopy = (Button) view.findViewById(R.id.bCopy); bTxNotes = (Button) view.findViewById(R.id.bTxNotes); etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); - bCopy.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - copyToClipboard(); - } - }); - bTxNotes.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -119,28 +113,35 @@ public class TxFragment extends Fragment { } } - void copyToClipboard() { + void shareTxInfo() { if (this.info == null) return; StringBuffer sb = new StringBuffer(); - sb.append(getString(R.string.tx_address)).append(": "); - sb.append(activityCallback.getWalletAddress()).append("\n"); - sb.append(getString(R.string.tx_id)).append(": "); - sb.append(info.hash).append("\n"); - sb.append(getString(R.string.tx_key)).append(": "); - sb.append(info.txKey.isEmpty() ? "-" : info.txKey).append("\n"); - sb.append(getString(R.string.tx_paymentId)).append(": "); - sb.append(info.paymentId).append("\n"); - sb.append(getString(R.string.tx_amount)).append(": "); + + sb.append(getString(R.string.tx_timestamp)).append(":\n"); + sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n\n"); + + sb.append(getString(R.string.tx_amount)).append(":\n"); sb.append((info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-")); sb.append(Wallet.getDisplayAmount(info.amount)).append("\n"); - sb.append(getString(R.string.tx_fee)).append(": "); - sb.append(Wallet.getDisplayAmount(info.fee)).append("\n"); - sb.append(getString(R.string.tx_notes)).append(": "); + sb.append(getString(R.string.tx_fee)).append(":\n"); + sb.append(Wallet.getDisplayAmount(info.fee)).append("\n\n"); + + sb.append(getString(R.string.tx_notes)).append(":\n"); String oneLineNotes = info.notes.replace("\n", " ; "); - sb.append(oneLineNotes.isEmpty() ? "-" : oneLineNotes).append("\n"); - sb.append(getString(R.string.tx_timestamp)).append(": "); - sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n"); - sb.append(getString(R.string.tx_blockheight)).append(": "); + sb.append(oneLineNotes.isEmpty() ? "-" : oneLineNotes).append("\n\n"); + + sb.append(getString(R.string.tx_destination)).append(":\n"); + sb.append(tvDestination.getText()).append("\n\n"); + + sb.append(getString(R.string.tx_paymentId)).append(":\n"); + sb.append(info.paymentId).append("\n\n"); + + sb.append(getString(R.string.tx_id)).append(":\n"); + sb.append(info.hash).append("\n"); + sb.append(getString(R.string.tx_key)).append(":\n"); + sb.append(info.txKey.isEmpty() ? "-" : info.txKey).append("\n\n"); + + sb.append(getString(R.string.tx_blockheight)).append(":\n"); if (info.isFailed) { sb.append(getString(R.string.tx_failed)).append("\n"); } else if (info.isPending) { @@ -148,7 +149,9 @@ public class TxFragment extends Fragment { } else { sb.append(info.blockheight).append("\n"); } - sb.append(getString(R.string.tx_transfers)).append(": "); + sb.append("\n"); + + sb.append(getString(R.string.tx_transfers)).append(":\n"); if (info.transfers != null) { boolean comma = false; for (Transfer transfer : info.transfers) { @@ -163,12 +166,14 @@ public class TxFragment extends Fragment { } else { sb.append("-"); } - sb.append("\n"); - ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText(getString(R.string.tx_copy_label), sb.toString()); - clipboardManager.setPrimaryClip(clip); - Toast.makeText(getActivity(), getString(R.string.tx_copy_message), Toast.LENGTH_SHORT).show(); - //Log.d(TAG, sb.toString()); + 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()); + sendIntent.setType("text/plain"); + startActivity(Intent.createChooser(sendIntent, null)); } TransactionInfo info = null; @@ -176,17 +181,24 @@ public class TxFragment extends Fragment { void loadNotes(TransactionInfo info) { if (info.notes == null) { info.notes = activityCallback.getTxNotes(info.hash); - //Log.d(TAG, "NOTES:" + info.notes + ":"); } etTxNotes.setText(info.notes); } + private void setTxColour(int clr) { + tvTxAmount.setTextColor(clr); + tvTxFee.setTextColor(clr); + } + private void show(TransactionInfo info) { if (info.txKey == null) { info.txKey = activityCallback.getTxKey(info.hash); - //Log.d(TAG, "TXKEY:" + info.txKey + ":"); } loadNotes(info); + + activityCallback.setSubtitle(getString(R.string.tx_title)); + activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); + tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000))); tvTxId.setText(info.hash); tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey); @@ -199,8 +211,30 @@ public class TxFragment extends Fragment { tvTxBlockheight.setText("" + info.blockheight); } String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-"); + tvTxAmount.setText(sign + Wallet.getDisplayAmount(info.amount)); - tvTxFee.setText(Wallet.getDisplayAmount(info.fee)); + if ((info.fee > 0)) { + String fee = Wallet.getDisplayAmount(info.fee); + if (info.isPending) { + tvTxFee.setText(getString(R.string.tx_list_fee_pending, fee)); + } else { + tvTxFee.setText(getString(R.string.tx_list_fee, fee)); + } + } else { + tvTxFee.setText(null); + tvTxFee.setVisibility(View.GONE); + } + if (info.isFailed) { + tvTxAmount.setText(getString(R.string.tx_list_amount_failed, Wallet.getDisplayAmount(info.amount))); + tvTxFee.setText(getString(R.string.tx_list_failed_text)); + setTxColour(ContextCompat.getColor(getContext(), R.color.tx_failed)); + } else if (info.isPending) { + setTxColour(ContextCompat.getColor(getContext(), R.color.tx_pending)); + } else if (info.direction == TransactionInfo.Direction.Direction_In) { + setTxColour(ContextCompat.getColor(getContext(), R.color.tx_green)); + } else { + setTxColour(ContextCompat.getColor(getContext(), R.color.tx_red)); + } Set destinations = new HashSet<>(); StringBuffer sb = new StringBuffer(); StringBuffer dstSb = new StringBuffer(); @@ -232,10 +266,21 @@ public class TxFragment extends Fragment { tvTxTransfers.setText(sb.toString()); tvDestination.setText(dstSb.toString()); this.info = info; - bCopy.setEnabled(true); } - TxFragment.Listener activityCallback; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.tx_info_menu, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + Listener activityCallback; public interface Listener { String getWalletAddress(); @@ -246,6 +291,10 @@ public class TxFragment extends Fragment { void onSetNote(String txId, String notes); + void setToolbarButton(int type); + + void setSubtitle(String subtitle); + } @Override diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java index 4dfb332..8890192 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java @@ -17,7 +17,6 @@ package com.m2049r.xmrwallet; import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; @@ -25,7 +24,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.net.Uri; -import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.os.PowerManager; @@ -34,11 +32,11 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.MenuItem; import android.widget.Toast; +import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.Wallet; @@ -49,16 +47,13 @@ import com.m2049r.xmrwallet.util.BarcodeData; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.TxData; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; import java.util.HashMap; import java.util.Map; public class WalletActivity extends AppCompatActivity implements WalletFragment.Listener, WalletService.Observer, SendFragment.Listener, TxFragment.Listener, GenerateReviewFragment.ListenerWithWallet, + GenerateReviewFragment.Listener, ScannerFragment.Listener, ReceiveFragment.Listener { private static final String TAG = "WalletActivity"; @@ -67,6 +62,27 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. Toolbar toolbar; + @Override + public void setToolbarButton(int type) { + toolbar.setButton(type); + } + + @Override + public void setTitle(String title, String subtitle) { + toolbar.setTitle(title, subtitle); + } + + @Override + public void setTitle(String title) { + Log.d(TAG, "setTitle:" + title + "."); + toolbar.setTitle(title); + } + + @Override + public void setSubtitle(String subtitle) { + toolbar.setSubtitle(subtitle); + } + private boolean synced = false; @Override @@ -137,8 +153,11 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. case R.id.action_info: onWalletDetails(); return true; - case R.id.action_receive: - onWalletReceive(); + case R.id.action_donate: + onWalletDetails(); + return true; + case R.id.action_share: + onShareTxInfo(); return true; default: return super.onOptionsItemSelected(item); @@ -157,17 +176,35 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. return; } - setContentView(R.layout.wallet_activity); + setContentView(R.layout.activity_wallet); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); + getSupportActionBar().setDisplayShowTitleEnabled(false); - toolbar.setTitle(R.string.app_name); + toolbar.setOnButtonListener(new Toolbar.OnButtonListener() { + @Override + public void onButton(int type) { + switch (type) { + case Toolbar.BUTTON_BACK: + onBackPressed(); + break; + case Toolbar.BUTTON_CLOSE: + finish(); + break; + case Toolbar.BUTTON_DONATE: + Toast.makeText(WalletActivity.this, getString(R.string.label_donate), Toast.LENGTH_SHORT).show(); + case Toolbar.BUTTON_NONE: + default: + Log.e(TAG, "Button " + type + "pressed - how can this be?"); + } + } + }); boolean testnet = WalletManager.getInstance().isTestNet(); if (testnet) { toolbar.setBackgroundResource(R.color.colorPrimaryDark); } else { - toolbar.setBackgroundResource(R.color.moneroOrange); + toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet); } Fragment walletFragment = new WalletFragment(); @@ -195,18 +232,15 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. // service that we know is running in our own process, we can // cast its IBinder to a concrete class and directly access it. mBoundService = ((WalletService.WalletServiceBinder) service).getService(); - //Log.d(TAG, "setting observer of " + mBoundService); mBoundService.setObserver(WalletActivity.this); Bundle extras = getIntent().getExtras(); if (extras != null) { String walletId = extras.getString(REQUEST_ID); if (walletId != null) { - setTitle(walletId); - setSubtitle(""); + setTitle(walletId, getString(R.string.status_wallet_connecting)); } } updateProgress(); - //TODO show current pbProgress (eg. if the service is already busy saving last wallet) Log.d(TAG, "CONNECTED"); } @@ -216,8 +250,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. // Because it is running in our same process, we should never // see this happen. mBoundService = null; - setTitle(getString(R.string.wallet_activity_name)); - setSubtitle(""); + setTitle(getString(R.string.wallet_activity_name), getString(R.string.status_wallet_disconnected)); Log.d(TAG, "DISCONNECTED"); } }; @@ -269,7 +302,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. wl.acquire(); Log.d(TAG, "WakeLock acquired"); } catch (SecurityException ex) { - Log.d(TAG, "WakeLock NOT acquired: " + ex.getLocalizedMessage()); + Log.w(TAG, "WakeLock NOT acquired: " + ex.getLocalizedMessage()); wl = null; } } @@ -292,9 +325,9 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. } } - ////////////////////////////////////////// - // WalletFragment.Listener - ////////////////////////////////////////// +////////////////////////////////////////// +// WalletFragment.Listener +////////////////////////////////////////// @Override public boolean hasBoundService() { @@ -311,16 +344,6 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. return mBoundService.getDaemonHeight(); } - @Override - public void setTitle(String title) { - toolbar.setTitle(title); - } - - @Override - public void setSubtitle(String subtitle) { - toolbar.setSubtitle(subtitle); - } - @Override public void onSendRequest() { replaceFragment(new SendFragment(), null, null); @@ -342,9 +365,9 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. } } - /////////////////////////// - // WalletService.Observer - /////////////////////////// +/////////////////////////// +// WalletService.Observer +/////////////////////////// // refresh and return if successful @Override @@ -514,9 +537,9 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. } } - /////////////////////////// - // SendFragment.Listener - /////////////////////////// +/////////////////////////// +// SendFragment.Listener +/////////////////////////// @Override public void onSend(String notes) { @@ -561,20 +584,13 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. } @Override - public void onPrepareSweep() { - if (mIsBound) { // no point in talking to unbound service - Intent intent = new Intent(getApplicationContext(), WalletService.class); - intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SWEEP); - startService(intent); - Log.d(TAG, "SWEEP TX request sent"); - } else { - Log.e(TAG, "Service not bound"); - } + public String getWalletAddress() { + return getWallet().getAddress(); } @Override - public String getWalletAddress() { - return getWallet().getAddress(); + public String getWalletName() { + return getWallet().getName(); } void popFragmentStack(String name) { @@ -619,6 +635,18 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. .show(); } + void onShareTxInfo() { + try { + TxFragment fragment = (TxFragment) + getSupportFragmentManager().findFragmentById(R.id.fragment_container); + fragment.shareTxInfo(); + } catch (ClassCastException ex) { + // not in wallet fragment + Log.e(TAG, ex.getLocalizedMessage()); + // keep calm and carry on + } + } + @Override public void onDisposeRequest() { getWallet().disposePendingTransaction(); @@ -710,7 +738,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. @Override - public BarcodeData getScannedData() { + public BarcodeData popScannedData() { BarcodeData data = scannedData; scannedData = null; return data; @@ -745,6 +773,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. Log.d(TAG, "startReceive()"); Bundle b = new Bundle(); b.putString("address", address); + b.putString("name", getWalletName()); startReceiveFragment(b); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java index b9f205f..d8acead 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java @@ -19,22 +19,25 @@ package com.m2049r.xmrwallet; import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.constraint.ConstraintLayout; import android.support.v4.app.Fragment; -import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; import android.widget.Button; -import android.widget.LinearLayout; +import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.ProgressBar; +import android.widget.Spinner; import android.widget.TextView; +import com.m2049r.xmrwallet.layout.AsyncExchangeRate; +import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.Wallet; @@ -43,19 +46,24 @@ import com.m2049r.xmrwallet.util.Helper; import java.text.NumberFormat; import java.util.List; -public class WalletFragment extends Fragment implements TransactionInfoAdapter.OnInteractionListener { +public class WalletFragment extends Fragment + implements TransactionInfoAdapter.OnInteractionListener, + AsyncExchangeRate.Listener { private static final String TAG = "WalletFragment"; private TransactionInfoAdapter adapter; private NumberFormat formatter = NumberFormat.getInstance(); + FrameLayout flExchange; TextView tvBalance; TextView tvUnconfirmedAmount; - TextView tvBlockHeightProgress; - ConstraintLayout clProgress; TextView tvProgress; + ImageView ivSynced; ProgressBar pbProgress; + Button bReceive; Button bSend; + Spinner sCurrency; + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -72,44 +80,145 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.wallet_fragment, container, false); + View view = inflater.inflate(R.layout.fragment_wallet, container, false); + + flExchange = (FrameLayout) view.findViewById(R.id.flExchange); + ((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable(). + setColorFilter(getResources().getColor(R.color.trafficGray), + android.graphics.PorterDuff.Mode.MULTIPLY); tvProgress = (TextView) view.findViewById(R.id.tvProgress); pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); - clProgress = (ConstraintLayout) view.findViewById(R.id.clProgress); tvBalance = (TextView) view.findViewById(R.id.tvBalance); - tvBalance.setText(getResources().getString(R.string.xmr_balance, Helper.getDisplayAmount(0))); + tvBalance.setText(Helper.getDisplayAmount(0)); tvUnconfirmedAmount = (TextView) view.findViewById(R.id.tvUnconfirmedAmount); tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, Helper.getDisplayAmount(0))); - tvBlockHeightProgress = (TextView) view.findViewById(R.id.tvBlockHeightProgress); + ivSynced = (ImageView) view.findViewById(R.id.ivSynced); + + sCurrency = (Spinner) view.findViewById(R.id.sCurrency); + sCurrency.setAdapter(ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner)); bSend = (Button) view.findViewById(R.id.bSend); + bReceive = (Button) view.findViewById(R.id.bReceive); RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list); this.adapter = new TransactionInfoAdapter(getActivity(), this); recyclerView.setAdapter(adapter); - bSend.setOnClickListener(new View.OnClickListener() - - { + bSend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { activityCallback.onSendRequest(); } }); + bReceive.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + activityCallback.onWalletReceive(); + } + }); + + sCurrency.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parentView, View selectedItemView, int position, long id) { + refreshBalance(); + } + + @Override + public void onNothingSelected(AdapterView parentView) { + // nothing (yet?) + } + }); + if (activityCallback.isSynced()) { onSynced(); } -// activityCallback.setTitle(getString(R.string.status_wallet_loading)); - activityCallback.forceUpdate(); return view; } + String balanceCurrency = "XMR"; + double balanceRate = 1.0; + + void refreshBalance() { + if (sCurrency.getSelectedItemPosition() == 0) { // XMR + double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail! + tvBalance.setText(Helper.getFormattedAmount(amountXmr, true)); + } else { // not XMR + String currency = (String) sCurrency.getSelectedItem(); + if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) { + showExchanging(); + new AsyncExchangeRate(this).execute("XMR", currency); + } else { + exchange("XMR", balanceCurrency, balanceRate); + } + } + } + + boolean isExchanging = false; + + void showExchanging() { + isExchanging = true; + tvBalance.setVisibility(View.GONE); + flExchange.setVisibility(View.VISIBLE); + } + + void hideExchanging() { + isExchanging = false; + tvBalance.setVisibility(View.VISIBLE); + flExchange.setVisibility(View.GONE); + } + + // Callbacks from AsyncExchangeRate + + // callback from AsyncExchangeRate when it can't get exchange rate + public void exchangeFailed() { + sCurrency.setSelection(0, true); // default to XMR + double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail! + tvBalance.setText(Helper.getFormattedAmount(amountXmr, true)); + hideExchanging(); + } + + void updateBalance() { + if (isExchanging) return; // wait for exchange to finish - it will fire this itself then. + // at this point selection is XMR in case of error + String displayB; + double amountA = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail! + if (!"XMR".equals(balanceCurrency)) { // not XMR + double amountB = amountA * balanceRate; + displayB = Helper.getFormattedAmount(amountB, false); + } else { // XMR + displayB = Helper.getFormattedAmount(amountA, true); + } + tvBalance.setText(displayB); + } + + // callback from AsyncExchangeRate when we have a rate + public void exchange(String currencyA, String currencyB, double rate) { + hideExchanging(); + if (!"XMR".equals(currencyA)) { + Log.e(TAG, "Not XMR"); + sCurrency.setSelection(0, true); + balanceCurrency = "XMR"; + balanceRate = 1.0; + } else { + int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(currencyB); + if (spinnerPosition < 0) { // requested currency not in list + Log.e(TAG, "Requested currency not in list " + currencyB); + sCurrency.setSelection(0, true); + } else { + sCurrency.setSelection(spinnerPosition, true); + } + balanceCurrency = currencyB; + balanceRate = rate; + } + updateBalance(); + } + // Callbacks from TransactionInfoAdapter @Override public void onInteraction(final View view, final TransactionInfo infoItem) { @@ -117,6 +226,7 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O } // called from activity + public void onRefreshed(final Wallet wallet, final boolean full) { Log.d(TAG, "onRefreshed()"); if (full) { @@ -134,13 +244,17 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O } } + public void setProgressText(final String text) { + tvProgress.setText(text); + } + public void onProgress(final String text) { if (text != null) { - tvProgress.setText(text); - showProgress(); + setProgressText(text); + pbProgress.setVisibility(View.VISIBLE); } else { - hideProgress(); - tvProgress.setText(getString(R.string.status_working)); + pbProgress.setVisibility(View.INVISIBLE); + setProgressText(getString(R.string.status_working)); onProgress(-1); } } @@ -154,48 +268,35 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O } } - public void showProgress() { - clProgress.setVisibility(View.VISIBLE); - tvBlockHeightProgress.setVisibility(View.GONE); - } - - public void hideProgress() { - clProgress.setVisibility(View.GONE); - tvBlockHeightProgress.setVisibility(View.VISIBLE); - } - - String setActivityTitle(Wallet wallet) { - if (wallet == null) return null; - String shortName = wallet.getName(); - if (shortName.length() > 16) { - shortName = shortName.substring(0, 14) + "..."; - } - // TODO very very rarely this craches because getAddress returns "" or so ... - // maybe because this runs in the ui thread and not in a 5MB thread - String title = "[" + wallet.getAddress().substring(0, 6) + "] " + shortName; - activityCallback.setTitle(title); - - String watchOnly = (wallet.isWatchOnly() ? " " + getString(R.string.watchonly_label) : ""); - String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet)); - activityCallback.setSubtitle(net + " " + watchOnly); - Log.d(TAG, "wallet title is " + title); - return title; + void setActivityTitle(Wallet wallet) { + if (wallet == null) return; + walletTitle = wallet.getName(); + String watchOnly = (wallet.isWatchOnly() ? getString(R.string.label_watchonly) : ""); + walletSubtitle = wallet.getAddress().substring(0, 16) + "…" + watchOnly; + activityCallback.setTitle(walletTitle, walletSubtitle); + Log.d(TAG, "wallet title is " + walletTitle); } private long firstBlock = 0; private String walletTitle = null; + private String walletSubtitle = null; + private long unlockedBalance = 0; private void updateStatus(Wallet wallet) { if (!isAdded()) return; Log.d(TAG, "updateStatus()"); if (walletTitle == null) { - walletTitle = setActivityTitle(wallet); + setActivityTitle(wallet); onProgress(100); // of loading } long balance = wallet.getBalance(); - long unlockedBalance = wallet.getUnlockedBalance(); - tvBalance.setText(getResources().getString(R.string.xmr_balance, Helper.getDisplayAmount(unlockedBalance))); - tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, Helper.getDisplayAmount(balance - unlockedBalance))); + unlockedBalance = wallet.getUnlockedBalance(); + refreshBalance(); + double amountXmr = Double.parseDouble(Helper.getDisplayAmount(balance - unlockedBalance)); // assume this cannot fail! + String unconfirmed = Helper.getFormattedAmount(amountXmr, true); + tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed)); + //tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, + // Helper.getDisplayAmount(balance - unlockedBalance, Helper.DISPLAY_DIGITS_SHORT))); String sync = ""; if (!activityCallback.hasBoundService()) throw new IllegalStateException("WalletService not bound."); @@ -212,13 +313,13 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O onProgress(getString(R.string.status_syncing) + " " + sync); if (x == 0) x = -1; onProgress(x); + ivSynced.setVisibility(View.GONE); } else { sync = getString(R.string.status_synced) + ": " + formatter.format(wallet.getBlockChainHeight()); + ivSynced.setVisibility(View.VISIBLE); } } - tvBlockHeightProgress.setText(sync); - //String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet)); - //activityCallback.setSubtitle(net + " " + daemonConnected.toString().substring(17)); + setProgressText(sync); // TODO show connected status somewhere } @@ -234,10 +335,6 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O long getDaemonHeight(); //mBoundService.getDaemonHeight(); - void setTitle(String title); - - void setSubtitle(String subtitle); - void onSendRequest(); void onTxDetailsRequest(TransactionInfo info); @@ -251,6 +348,12 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O void onWalletReceive(); boolean hasWallet(); + + void setToolbarButton(int type); + + void setTitle(String title, String subtitle); + + void setSubtitle(String subtitle); } @Override @@ -263,4 +366,12 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O + " must implement Listener"); } } + + @Override + public void onResume() { + super.onResume(); + Log.d(TAG, "onResume()"); + activityCallback.setTitle(walletTitle, walletSubtitle); + activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE); + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/HelpFragment.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/HelpFragment.java index 72751f8..75a15e6 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/dialog/HelpFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/dialog/HelpFragment.java @@ -26,6 +26,7 @@ import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.webkit.WebView; @@ -43,6 +44,7 @@ import java.io.InputStreamReader; * http://speakman.net.nz */ public class HelpFragment extends DialogFragment { + static final String TAG = "HelpFragment"; private static final String FRAGMENT_TAG = "com.m2049r.xmrwallet.dialog.HelpFragment"; private static final String HELP_ID = "HELP_ID"; @@ -67,7 +69,6 @@ public class HelpFragment extends DialogFragment { if (prev != null) { ft.remove(prev); } - ft.addToBackStack(null); // Create and show the dialog. DialogFragment newFragment = HelpFragment.newInstance(helpResourceId); @@ -100,7 +101,7 @@ public class HelpFragment extends DialogFragment { @SuppressLint("InflateParams") @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - View content = LayoutInflater.from(getActivity()).inflate(R.layout.help_fragment, null); + View content = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_help, null); webView = (WebView) content.findViewById(R.id.helpFragmentWebView); progress = (ProgressBar) content.findViewById(R.id.helpFragmentProgress); @@ -136,8 +137,8 @@ public class HelpFragment extends DialogFragment { sb.append("\n"); } bufferedReader.close(); - } catch (IOException e) { - // TODO You may want to include some logging here. + } catch (IOException ex) { + Log.e(TAG, ex.getLocalizedMessage()); } return sb.toString(); diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/LicensesFragment.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/LicensesFragment.java index f9c0794..d2fcbab 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/dialog/LicensesFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/dialog/LicensesFragment.java @@ -16,22 +16,17 @@ package com.m2049r.xmrwallet.dialog; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - import android.annotation.SuppressLint; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.AsyncTask; import android.os.Bundle; -//TODO If you don't support Android 2.x, you should use the non-support version! import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.webkit.WebView; @@ -41,11 +36,17 @@ import android.widget.TextView; import com.m2049r.xmrwallet.BuildConfig; import com.m2049r.xmrwallet.R; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + /** * Created by Adam Speakman on 24/09/13. * http://speakman.net.nz */ public class LicensesFragment extends DialogFragment { + static final String TAG = "LicensesFragment"; int versionCode = BuildConfig.VERSION_CODE; String versionName = BuildConfig.VERSION_NAME; @@ -102,7 +103,7 @@ public class LicensesFragment extends DialogFragment { @SuppressLint("InflateParams") @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - View content = LayoutInflater.from(getActivity()).inflate(R.layout.licenses_fragment, null); + View content = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_licenses, null); mWebView = (WebView) content.findViewById(R.id.licensesFragmentWebView); mIndeterminateProgress = (ProgressBar) content.findViewById(R.id.licensesFragmentIndeterminateProgress); @@ -140,8 +141,8 @@ public class LicensesFragment extends DialogFragment { sb.append("\n"); } bufferedReader.close(); - } catch (IOException e) { - // TODO You may want to include some logging here. + } catch (IOException ex) { + Log.e(TAG, ex.getLocalizedMessage()); } return sb.toString(); diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/AsyncExchangeRate.java b/app/src/main/java/com/m2049r/xmrwallet/layout/AsyncExchangeRate.java new file mode 100644 index 0000000..9d56364 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/AsyncExchangeRate.java @@ -0,0 +1,159 @@ +/* + * 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.os.AsyncTask; +import android.util.Log; + +import com.m2049r.xmrwallet.util.Helper; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.ref.WeakReference; + +public class AsyncExchangeRate extends AsyncTask { + static final String TAG = "AsyncExchangeRate"; + static final long TIME_REFRESH_INTERVAL = 60000; // refresh exchange rate max every minute + + public interface Listener { + void exchangeFailed(); + + // callback from AsyncExchangeRate when we have a rate + void exchange(String currencyA, String currencyB, double rate); + } + + static long RateTime = 0; + static double Rate = 0; + static String Fiat = null; + + private final WeakReference exchangeViewRef; + + public AsyncExchangeRate(Listener exchangeView) { + super(); + exchangeViewRef = new WeakReference<>(exchangeView); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + } + + boolean inverse = false; + String currencyA = null; + String currencyB = null; + + @Override + protected Boolean doInBackground(String... params) { + if (params.length != 2) return false; + Log.d(TAG, "Getting " + params[0]); + currencyA = params[0]; + currencyB = params[1]; + + String fiat = null; + if (currencyA.equals("XMR")) { + fiat = currencyB; + inverse = false; + } + if (currencyB.equals("XMR")) { + fiat = currencyA; + inverse = true; + } + + if (currencyA.equals(currencyB)) { + Fiat = null; + Rate = 1; + RateTime = System.currentTimeMillis(); + return true; + } + + if (fiat == null) { + Fiat = null; + Rate = 0; + RateTime = 0; + return false; + } + + if (!fiat.equals(Fiat)) { // new currency - reset all + Fiat = fiat; + Rate = 0; + RateTime = 0; + } + + if (System.currentTimeMillis() > RateTime + TIME_REFRESH_INTERVAL) { + Log.d(TAG, "Fetching " + Fiat); + String closePrice = getExchangeRate(Fiat); + if (closePrice != null) { + try { + Rate = Double.parseDouble(closePrice); + RateTime = System.currentTimeMillis(); + return true; + } catch (NumberFormatException ex) { + Rate = 0; + Log.e(TAG, ex.getLocalizedMessage()); + return false; + } + } else { + Rate = 0; + Log.e(TAG, "exchange url failed"); + return false; + } + } + return true; // no change but still valid + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + Listener exchangeView = exchangeViewRef.get(); + if (result) { + Log.d(TAG, "yay! = " + Rate); + if (exchangeView != null) { + exchangeView.exchange(currencyA, currencyB, inverse ? (1 / Rate) : Rate); + } + } else { + Log.d(TAG, "nay!"); + if (exchangeView != null) { + exchangeView.exchangeFailed(); + } + } + } + + // "https://api.kraken.com/0/public/Ticker?pair=XMREUR" + String getExchangeRate(String fiat) { + String jsonResponse = + Helper.getUrl("https://api.kraken.com/0/public/Ticker?pair=XMR" + fiat); + if (jsonResponse == null) return null; + try { + JSONObject response = new JSONObject(jsonResponse); + JSONArray errors = response.getJSONArray("error"); + Log.e(TAG, "errors=" + errors.toString()); + if (errors.length() == 0) { + JSONObject result = response.getJSONObject("result"); + JSONObject pair = result.getJSONObject("XXMRZ" + fiat); + JSONArray close = pair.getJSONArray("c"); + String closePrice = close.getString(0); + Log.d(TAG, "closePrice=" + closePrice); + return closePrice; + } + } catch (JSONException ex) { + Log.e(TAG, ex.getLocalizedMessage()); + } + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/CTextInputLayout.java b/app/src/main/java/com/m2049r/xmrwallet/layout/CTextInputLayout.java new file mode 100644 index 0000000..6be40f0 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/CTextInputLayout.java @@ -0,0 +1,44 @@ +/* + * 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 from https://stackoverflow.com/a/45325876 (which did not work for me) + +package com.m2049r.xmrwallet.layout; + +import android.content.Context; +import android.support.design.widget.TextInputLayout; +import android.util.AttributeSet; +import android.widget.EditText; + +public class CTextInputLayout extends TextInputLayout { + public CTextInputLayout(Context context) { + super(context); + } + + public CTextInputLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public int getBaseline() { + EditText editText = getEditText(); + return editText.getBaseline() - (getMeasuredHeight() - editText.getMeasuredHeight()); + } +} \ 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/layout/ExchangeView.java new file mode 100644 index 0000000..39b4c02 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/ExchangeView.java @@ -0,0 +1,399 @@ +/* + * 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.layout; + +import android.content.Context; +import android.support.design.widget.TextInputLayout; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.inputmethod.EditorInfo; +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.util.Helper; + +import java.util.Locale; + +public class ExchangeView extends LinearLayout implements AsyncExchangeRate.Listener { + static final String TAG = "ExchangeView"; + + public boolean focus() { + return etAmount.requestFocus(); + } + + public void enable(boolean enable) { + etAmount.setEnabled(enable); + sCurrencyA.setEnabled(enable); + sCurrencyB.setEnabled(enable); + } + + String xmrAmount = null; + String notXmrAmount = null; + + void setXmr(String xmr) { + xmrAmount = xmr; + if (onNewAmountListener != null) { + onNewAmountListener.onNewAmount(xmr); + } + } + + public void setAmount(String xmrAmount) { + if (xmrAmount != null) { + setCurrencyA(0); + setXmr(xmrAmount); + this.notXmrAmount = null; + doExchange(); + } else { + setXmr(null); + this.notXmrAmount = null; + tvAmountB.setText("--"); + } + } + + public String getAmount() { + return xmrAmount; + } + + public void setError(String msg) { + etAmount.setError(msg); + } + + TextInputLayout etAmount; + 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 ExchangeView(Context context) { + super(context); + initializeViews(context); + } + + public ExchangeView(Context context, AttributeSet attrs) { + super(context, attrs); + initializeViews(context); + } + + public ExchangeView(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, this); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + etAmount = (TextInputLayout) findViewById(R.id.etAmount); + 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 (yet?) + } + }); + + sCurrencyB.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 + sCurrencyA.setSelection(0, true); + } + doExchange(); + } + + @Override + public void onNothingSelected(AdapterView parentView) { + // nothing (yet?) + } + }); + + etAmount.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)) { + doExchange(); + return true; + } + return false; + } + }); + + + etAmount.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + etAmount.setError(null); + if ((xmrAmount != null) || (notXmrAmount != null)) { + tvAmountB.setText("--"); + setXmr(null); + notXmrAmount = 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) { + } + }); + + } + + final static double MAX_AMOUNT_XMR = 1000; + final static double MAX_AMOUNT_NOTXMR = 100000; + + public boolean checkEnteredAmount() { + boolean ok = true; + Log.d(TAG, "checkEnteredAmount"); + String amountEntry = etAmount.getEditText().getText().toString(); + if (!amountEntry.isEmpty()) { + try { + double a = Double.parseDouble(amountEntry); + double maxAmount = (getCurrencyA() == 0) ? MAX_AMOUNT_XMR : MAX_AMOUNT_NOTXMR; + if (a > (maxAmount)) { + etAmount.setError(getResources(). + getString(R.string.receive_amount_too_big, + String.format(Locale.US, "%,.0f", maxAmount))); + ok = false; + } else if (a < 0) { + etAmount.setError(getResources().getString(R.string.receive_amount_negative)); + ok = false; + } + } catch (NumberFormatException ex) { + etAmount.setError(getResources().getString(R.string.receive_amount_nan)); + ok = false; + } + } + if (ok) { + etAmount.setError(null); + } + return ok; + } + + int selectedNotXmrCurrency() { + return Math.max(getCurrencyA(), getCurrencyB()); + } + + public void doExchange() { + tvAmountB.setText("--"); + // TODO cache & use cached exchange rate here + startExchange(); + } + + void startExchange() { + showProgress(); + String currencyA = (String) sCurrencyA.getSelectedItem(); + String currencyB = (String) sCurrencyB.getSelectedItem(); + new AsyncExchangeRate(this).execute(currencyA, currencyB); + } + + public void exchange(double rate) { + if (getCurrencyA() == 0) { + if (!xmrAmount.isEmpty() && (rate > 0)) { + double amountB = rate * Double.parseDouble(xmrAmount); + notXmrAmount = Helper.getFormattedAmount(amountB, getCurrencyB() == 0); + } else { + notXmrAmount = ""; + } + tvAmountB.setText(notXmrAmount); + } else if (getCurrencyB() == 0) { + if (!notXmrAmount.isEmpty() && (rate > 0)) { + double amountB = rate * Double.parseDouble(notXmrAmount); + setXmr(Helper.getFormattedAmount(amountB, true)); + } else { + setXmr(""); + } + tvAmountB.setText(xmrAmount); + } else { // no XMR currency - cannot happen! + Log.e(TAG, "No XMR currency!"); + setXmr(null); + notXmrAmount = null; + return; + } + } + + boolean prepareExchange() { + Log.d(TAG, "prepareExchange()"); + if (checkEnteredAmount()) { + String enteredAmount = etAmount.getEditText().getText().toString(); + if (!enteredAmount.isEmpty()) { + String cleanAmount = ""; + if (getCurrencyA() == 0) { + // sanitize the input + cleanAmount = Helper.getDisplayAmount(Wallet.getAmountFromString(enteredAmount)); + setXmr(cleanAmount); + notXmrAmount = null; + Log.d(TAG, "cleanAmount = " + cleanAmount); + } 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! + Log.e(TAG, "No XMR currency!"); + setXmr(null); + notXmrAmount = null; + return false; + } + Log.d(TAG, "prepareExchange() " + cleanAmount); + //etAmount.getEditText().setText(cleanAmount); // display what we use + } else { + setXmr(""); + notXmrAmount = ""; + } + return true; + } else { + setXmr(null); + notXmrAmount = null; + return false; + } + } + + // callback from AsyncExchangeRate when it failed getting a rate + public void exchangeFailed() { + hideProgress(); + exchange(0); + // TODO Toast it failed - I think this happens elsewhere already + } + + // callback from AsyncExchangeRate when we have a rate + public void exchange(String currencyA, String currencyB, double rate) { + hideProgress(); + // first, make sure this is what we want + String enteredCurrencyA = (String) sCurrencyA.getSelectedItem(); + String enteredCurrencyB = (String) sCurrencyB.getSelectedItem(); + if (!currencyA.equals(enteredCurrencyA) || !currencyB.equals(enteredCurrencyB)) { + // something's wrong + Log.e(TAG, "Currencies don't match!"); + return; + } + if (prepareExchange()) { + exchange(rate); + } + } + + private void showProgress() { + pbExchange.setVisibility(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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/Toolbar.java b/app/src/main/java/com/m2049r/xmrwallet/layout/Toolbar.java new file mode 100644 index 0000000..4da1b17 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/Toolbar.java @@ -0,0 +1,155 @@ +/* + * 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.layout; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.m2049r.xmrwallet.R; + +public class Toolbar extends android.support.v7.widget.Toolbar { + static final String TAG = "Toolbar"; + + public interface OnButtonListener { + void onButton(int type); + } + + OnButtonListener onButtonListener; + + public void setOnButtonListener(OnButtonListener listener) { + onButtonListener = listener; + } + + ImageView toolbarImage; + TextView toolbarTitle; + TextView toolbarSubtitle; + Button bDonate; + + public Toolbar(Context context) { + super(context); + initializeViews(context); + } + + public Toolbar(Context context, AttributeSet attrs) { + super(context, attrs); + initializeViews(context); + } + + public Toolbar(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_toolbar, this); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + toolbarImage = (ImageView) findViewById(R.id.toolbarImage); + toolbarTitle = (TextView) findViewById(R.id.toolbarTitle); + toolbarSubtitle = (TextView) findViewById(R.id.toolbarSubtitle); + bDonate = (Button) findViewById(R.id.bDonate); + bDonate.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + if (onButtonListener != null) { + onButtonListener.onButton(buttonType); + } + } + }); + } + + public void setTitle(String title, String subtitle) { + setTitle(title); + setSubtitle(subtitle); + } + + public void setTitle(String title) { + toolbarTitle.setText(title); + if (title != null) { + toolbarImage.setVisibility(View.INVISIBLE); + toolbarTitle.setVisibility(View.VISIBLE); + } else { + toolbarImage.setVisibility(View.VISIBLE); + toolbarTitle.setVisibility(View.INVISIBLE); + } + } + + public final static int BUTTON_NONE = 0; + public final static int BUTTON_BACK = 1; + public final static int BUTTON_CLOSE = 2; + public final static int BUTTON_DONATE = 3; + + int buttonType = BUTTON_DONATE; + + public void setButton(int type) { + switch (type) { + case BUTTON_BACK: + Log.d(TAG, "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"); + 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"); + 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"); + bDonate.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + bDonate.setText(null); + bDonate.setVisibility(View.INVISIBLE); + } + buttonType = type; + } + + public void setSubtitle(String subtitle) { + toolbarSubtitle.setText(subtitle); + if (subtitle != null) { + toolbarSubtitle.setVisibility(View.VISIBLE); + } else { + toolbarSubtitle.setVisibility(View.INVISIBLE); + } + } +} \ 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 7e83300..3ea8ea6 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java @@ -17,7 +17,6 @@ package com.m2049r.xmrwallet.layout; import android.content.Context; -import android.graphics.Color; import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -28,13 +27,12 @@ import android.widget.TextView; import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.model.TransactionInfo; -import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.util.Helper; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.TimeZone; @@ -69,7 +67,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter(); + infoItems = new ArrayList<>(); this.listener = listener; Calendar cal = Calendar.getInstance(); TimeZone tz = cal.getTimeZone(); //get the local time zone. @@ -79,7 +77,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter() { - @Override - public int compare(TransactionInfo o1, TransactionInfo o2) { - long b1 = o1.timestamp; - long b2 = o2.timestamp; - if (b1 > b2) { - return -1; - } else if (b1 < b2) { - return 1; - } else { - return o1.hash.compareTo(o2.hash); - } - } - }); - this.infoItems.addAll(data); + infoItems.addAll(data); + Collections.sort(infoItems); } else { Log.d(TAG, "setInfos null"); } @@ -147,36 +131,41 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter 0)) { - String feeAmount = Wallet.getDisplayAmount(infoItem.fee); - String fee = feeAmount.substring(0, feeAmount.length() - (12 - 5)); - if (infoItem.isPending) { - this.tvFee.setText(context.getString(R.string.tx_list_fee_pending, fee)); - } else { - this.tvFee.setText(context.getString(R.string.tx_list_fee, fee)); - } + String fee = Helper.getDisplayAmount(infoItem.fee, 5); + this.tvFee.setText(context.getString(R.string.tx_list_fee, fee)); } else { this.tvFee.setText(""); } if (infoItem.isFailed) { - this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, amount)); + this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount)); + this.tvFee.setText(context.getString(R.string.tx_list_failed_text)); setTxColour(failedColour); } else if (infoItem.isPending) { setTxColour(pendingColour); - if (infoItem.direction == TransactionInfo.Direction.Direction_Out) { - this.tvAmount.setText(context.getString(R.string.tx_list_amount_negative, amount)); - } } else if (infoItem.direction == TransactionInfo.Direction.Direction_In) { setTxColour(inboundColour); } else { setTxColour(outboundColour); } - this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ? "" : infoItem.paymentId); + if ((infoItem.notes == null) || (infoItem.notes.isEmpty())) { + this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ? "" : infoItem.paymentId); + } else { + this.tvPaymentId.setText(infoItem.notes); + } this.tvDateTime.setText(getDateTime(infoItem.timestamp)); itemView.setOnClickListener(this); diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java new file mode 100644 index 0000000..1ac2c93 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java @@ -0,0 +1,157 @@ +/* + * 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.v7.widget.PopupMenu; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.TextView; + +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.model.WalletManager; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +public class WalletInfoAdapter extends RecyclerView.Adapter { + private static final String TAG = "WalletInfoAdapter"; + + private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + public interface OnInteractionListener { + void onInteraction(View view, WalletManager.WalletInfo item); + + boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo infoItem); + } + + private final List infoItems; + private final OnInteractionListener listener; + + Context context; + + public WalletInfoAdapter(Context context, OnInteractionListener listener) { + this.context = context; + this.infoItems = new ArrayList<>(); + this.listener = listener; + Calendar cal = Calendar.getInstance(); + TimeZone tz = cal.getTimeZone(); //get the local time zone. + DATETIME_FORMATTER.setTimeZone(tz); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_wallet, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int position) { + holder.bind(position); + } + + @Override + public int getItemCount() { + return infoItems.size(); + } + + public WalletManager.WalletInfo getItem(int position) { + return infoItems.get(position); + } + + public void setInfos(List data) { + // TODO do stuff with data so we can really recycle elements (i.e. add only new tx) + // as the WalletInfo items are always recreated, we cannot recycle + infoItems.clear(); + if (data != null) { + Log.d(TAG, "setInfos " + data.size()); + infoItems.addAll(data); + Collections.sort(infoItems); + } else { + Log.d(TAG, "setInfos null"); + } + notifyDataSetChanged(); + } + + class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + final TextView tvName; + final TextView tvAddress; + final ImageButton ibOptions; + WalletManager.WalletInfo infoItem; + + ViewHolder(View itemView) { + super(itemView); + tvName = (TextView) itemView.findViewById(R.id.tvName); + tvAddress = (TextView) itemView.findViewById(R.id.tvAddress); + ibOptions = (ImageButton) itemView.findViewById(R.id.ibOptions); + ibOptions.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + //creating a popup menu + PopupMenu popup = new PopupMenu(context, ibOptions); + //inflating menu from xml resource + popup.inflate(R.menu.list_context_menu); + //adding click listener + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + if (listener != null) { + return listener.onContextInteraction(item, infoItem); + } + return false; + } + }); + //displaying the popup + popup.show(); + + } + }); + itemView.setOnClickListener(this); + } + + private String getDateTime(long time) { + return DATETIME_FORMATTER.format(new Date(time * 1000)); + } + + void bind(int position) { + infoItem = infoItems.get(position); + tvName.setText(infoItem.name); + tvAddress.setText(infoItem.address.substring(0, 16) + "..."); + } + + @Override + public void onClick(View view) { + if (listener != null) { + int position = getAdapterPosition(); // gets item position + if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it + listener.onInteraction(view, infoItems.get(position)); + } + } + } + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java index e6c55f4..efee88a 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java @@ -30,6 +30,12 @@ public class TransactionHistory { this.handle = handle; } + public void loadNotes(Wallet wallet) { + for (TransactionInfo info : transactions) { + info.notes = wallet.getUserNote(info.hash); + } + } + public native int getCount(); //private native long getTransactionByIndexJ(int i); @@ -42,6 +48,11 @@ public class TransactionHistory { private List transactions = new ArrayList<>(); + public void refreshWithNotes(Wallet wallet) { + refresh(); + loadNotes(wallet); + } + public void refresh() { transactions = refreshJ(); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java index 141b09f..40a0b7d 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java @@ -25,7 +25,7 @@ import java.util.Random; // this is not the TransactionInfo from the API as that is owned by the TransactionHistory // this is a POJO for the TransactionInfoAdapter -public class TransactionInfo implements Parcelable { +public class TransactionInfo implements Parcelable, Comparable { static final String TAG = "TransactionInfo"; public enum Direction { @@ -92,7 +92,6 @@ public class TransactionInfo implements Parcelable { this.confirmations = confirmations; this.transfers = transfers; } - Random rnd = new Random(); public String toString() { return direction + "@" + blockheight + " " + amount; @@ -146,4 +145,17 @@ public class TransactionInfo implements Parcelable { return 0; } + @Override + public int compareTo(TransactionInfo another) { + long b1 = this.timestamp; + long b2 = another.timestamp; + if (b1 > b2) { + return -1; + } else if (b1 < b2) { + return 1; + } else { + return this.hash.compareTo(another.hash); + } + } + } diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java index 479922c..7c50673 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java @@ -144,10 +144,20 @@ public class WalletManager { //public native List findWallets(String path); // this does not work - some error in boost - public class WalletInfo { + public class WalletInfo implements Comparable { public File path; public String name; public String address; + + @Override + public int compareTo(WalletInfo another) { + int n = name.toLowerCase().compareTo(another.name.toLowerCase()); + if (n != 0) { + return n; + } else { // wallet names are the same + return address.compareTo(another.address); + } + } } public WalletInfo getWalletInfo(File wallet) { diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java index 791ad7e..5d48b60 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java @@ -147,7 +147,7 @@ public class WalletService extends Service { if (updated) { if (observer != null) { updateDaemonState(wallet, 0); - wallet.getHistory().refresh(); + wallet.getHistory().refreshWithNotes(wallet); if (observer != null) { updated = !observer.onRefreshed(wallet, true); } @@ -297,7 +297,7 @@ public class WalletService extends Service { boolean rc = myWallet.store(); Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc); if (!rc) { - Log.d(TAG, "Wallet store failed: " + myWallet.getErrorString()); + Log.w(TAG, "Wallet store failed: " + myWallet.getErrorString()); } if (observer != null) observer.onWalletStored(rc); } else if (cmd.equals(REQUEST_CMD_TX)) { @@ -309,7 +309,7 @@ public class WalletService extends Service { PendingTransaction.Status status = pendingTransaction.getStatus(); Log.d(TAG, "transaction status " + status); if (status != PendingTransaction.Status.Status_Ok) { - Log.d(TAG, "Create Transaction failed: " + pendingTransaction.getErrorString()); + Log.w(TAG, "Create Transaction failed: " + pendingTransaction.getErrorString()); } if (observer != null) { observer.onCreatedTransaction(pendingTransaction); @@ -323,7 +323,7 @@ public class WalletService extends Service { PendingTransaction.Status status = pendingTransaction.getStatus(); Log.d(TAG, "transaction status " + status); if (status != PendingTransaction.Status.Status_Ok) { - Log.d(TAG, "Create Transaction failed: " + pendingTransaction.getErrorString()); + Log.w(TAG, "Create Transaction failed: " + pendingTransaction.getErrorString()); } if (observer != null) { observer.onCreatedTransaction(pendingTransaction); @@ -352,7 +352,7 @@ public class WalletService extends Service { boolean rc = myWallet.store(); Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc); if (!rc) { - Log.d(TAG, "Wallet store failed: " + myWallet.getErrorString()); + Log.w(TAG, "Wallet store failed: " + myWallet.getErrorString()); } if (observer != null) observer.onWalletStored(rc); listener.updated = true; @@ -372,7 +372,7 @@ public class WalletService extends Service { boolean rc = myWallet.store(); Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc); if (!rc) { - Log.d(TAG, "Wallet store failed: " + myWallet.getErrorString()); + Log.w(TAG, "Wallet store failed: " + myWallet.getErrorString()); } if (observer != null) observer.onWalletStored(rc); } @@ -505,17 +505,12 @@ public class WalletService extends Service { } private Wallet loadWallet(String walletName, String walletPassword) { - //String path = Helper.getWalletPath(getApplicationContext(), walletName); - //Log.d(TAG, "open wallet " + path); Wallet wallet = openWallet(walletName, walletPassword); - //Log.d(TAG, "wallet opened: " + wallet); if (wallet != null) { - //Log.d(TAG, wallet.getStatus().toString()); Log.d(TAG, "Using daemon " + WalletManager.getInstance().getDaemonAddress()); showProgress(55); wallet.init(0); showProgress(90); - //Log.d(TAG, wallet.getConnectionStatus().toString()); } return wallet; } diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java index 782ea3e..59924ab 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java @@ -19,6 +19,8 @@ package com.m2049r.xmrwallet.util; import android.Manifest; import android.app.Activity; import android.app.Dialog; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Bitmap; @@ -40,7 +42,9 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; +import java.net.SocketTimeoutException; import java.net.URL; +import java.util.Locale; import javax.net.ssl.HttpsURLConnection; @@ -48,6 +52,9 @@ public class Helper { static private final String TAG = "Helper"; static private final String WALLET_DIR = "monerujo"; + static public int DISPLAY_DIGITS_INFO = 5; + static public int DISPLAY_DIGITS_SHORT = 5; + static public File getStorageRoot(Context context) { if (!isExternalStorageWritable()) { String msg = context.getString(R.string.message_strorage_not_writable); @@ -73,7 +80,7 @@ public class Helper { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - Log.d(TAG, "Permission denied to WRITE_EXTERNAL_STORAGE - requesting it"); + Log.w(TAG, "Permission denied to WRITE_EXTERNAL_STORAGE - requesting it"); String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE}; context.requestPermissions(permissions, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); return false; @@ -91,7 +98,7 @@ public class Helper { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { if (context.checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) { - Log.d(TAG, "Permission denied for CAMERA - requesting it"); + Log.w(TAG, "Permission denied for CAMERA - requesting it"); String[] permissions = {Manifest.permission.CAMERA}; context.requestPermissions(permissions, PERMISSIONS_REQUEST_CAMERA); return false; @@ -103,13 +110,8 @@ public class Helper { } } -// static public String getWalletPath(Context context, String aWalletName) { -// return getWalletFile(context, aWalletName).getAbsolutePath(); -// } - static public File getWalletFile(Context context, String aWalletName) { File walletDir = getStorageRoot(context); - //d(TAG, "walletdir=" + walletDir.getAbsolutePath()); File f = new File(walletDir, aWalletName); Log.d(TAG, "wallet = " + f.getAbsolutePath() + " size=" + f.length()); return f; @@ -127,6 +129,7 @@ public class Helper { } static public void hideKeyboard(Activity act) { + if (act == null) return; if (act.getCurrentFocus() == null) { act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); } else { @@ -145,19 +148,38 @@ public class Helper { } static public String getDisplayAmount(long amount) { - String s = Wallet.getDisplayAmount(amount); + return getDisplayAmount(amount, 20); + } + + static public String getDisplayAmount(long amount, int maxDecimals) { + return getDisplayAmount(Wallet.getDisplayAmount(amount), maxDecimals); + } + + // amountString must have '.' as decimal point + static public String getDisplayAmount(String amountString, int maxDecimals) { int lastZero = 0; int decimal = 0; - for (int i = s.length() - 1; i >= 0; i--) { - if ((lastZero == 0) && (s.charAt(i) != '0')) lastZero = i + 1; + for (int i = amountString.length() - 1; i >= 0; i--) { + if ((lastZero == 0) && (amountString.charAt(i) != '0')) lastZero = i + 1; // TODO i18n - if (s.charAt(i) == '.') { - decimal = i; + if (amountString.charAt(i) == '.') { + decimal = i + 1; break; } } - int cutoff = Math.max(lastZero, decimal + 2); - return s.substring(0, cutoff); + int cutoff = Math.min(Math.max(lastZero, decimal + 2), decimal + maxDecimals); + return amountString.substring(0, cutoff); + } + + 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 + displayB = String.format(Locale.US, "%,.2f", amount); + } + return displayB; } static public Bitmap getBitmap(Context context, int drawableId) { @@ -180,11 +202,15 @@ public class Helper { return bitmap; } + static final int HTTP_TIMEOUT = 5000; + static public String getUrl(String httpsUrl) { HttpsURLConnection urlConnection = null; try { URL url = new URL(httpsUrl); urlConnection = (HttpsURLConnection) url.openConnection(); + urlConnection.setConnectTimeout(HTTP_TIMEOUT); + urlConnection.setReadTimeout(HTTP_TIMEOUT); InputStreamReader in = new InputStreamReader(urlConnection.getInputStream()); StringBuffer sb = new StringBuffer(); final int BUFFER_SIZE = 512; @@ -195,6 +221,8 @@ public class Helper { length = in.read(buffer, 0, BUFFER_SIZE); } return sb.toString(); + } catch (SocketTimeoutException ex) { + Log.w(TAG, "C " + ex.getLocalizedMessage()); } catch (MalformedURLException ex) { Log.e(TAG, "A " + ex.getLocalizedMessage()); } catch (IOException ex) { @@ -206,4 +234,10 @@ public class Helper { } return null; } + + static public void clipBoardCopy(Context context, String label, String text) { + ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText(label, text); + clipboardManager.setPrimaryClip(clip); + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/NodeList.java b/app/src/main/java/com/m2049r/xmrwallet/util/NodeList.java index f036a6a..6989bf9 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/NodeList.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/NodeList.java @@ -16,8 +16,6 @@ package com.m2049r.xmrwallet.util; -import android.util.Log; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/app/src/main/res/anim/fab_close.xml b/app/src/main/res/anim/fab_close.xml new file mode 100644 index 0000000..9dd7e78 --- /dev/null +++ b/app/src/main/res/anim/fab_close.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/fab_close_screen.xml b/app/src/main/res/anim/fab_close_screen.xml new file mode 100644 index 0000000..ec4ee1a --- /dev/null +++ b/app/src/main/res/anim/fab_close_screen.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/fab_open.xml b/app/src/main/res/anim/fab_open.xml new file mode 100644 index 0000000..5ac74ac --- /dev/null +++ b/app/src/main/res/anim/fab_open.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/fab_open_screen.xml b/app/src/main/res/anim/fab_open_screen.xml new file mode 100644 index 0000000..ceb2831 --- /dev/null +++ b/app/src/main/res/anim/fab_open_screen.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/rotate_backward.xml b/app/src/main/res/anim/rotate_backward.xml new file mode 100644 index 0000000..fed9d93 --- /dev/null +++ b/app/src/main/res/anim/rotate_backward.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/rotate_forward.xml b/app/src/main/res/anim/rotate_forward.xml new file mode 100644 index 0000000..47877dd --- /dev/null +++ b/app/src/main/res/anim/rotate_forward.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/color/text_color.xml b/app/src/main/res/color/text_color.xml index bdb0a30..97f46e9 100644 --- a/app/src/main/res/color/text_color.xml +++ b/app/src/main/res/color/text_color.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/backgound_all.xml b/app/src/main/res/drawable/backgound_all.xml new file mode 100644 index 0000000..7e94c2e --- /dev/null +++ b/app/src/main/res/drawable/backgound_all.xml @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/backgound_amount.xml b/app/src/main/res/drawable/backgound_amount.xml new file mode 100644 index 0000000..8fe9ec7 --- /dev/null +++ b/app/src/main/res/drawable/backgound_amount.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/app/src/main/res/drawable/backgound_scan.xml b/app/src/main/res/drawable/backgound_scan.xml new file mode 100644 index 0000000..255a292 --- /dev/null +++ b/app/src/main/res/drawable/backgound_scan.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/app/src/main/res/drawable/backgound_seed.xml b/app/src/main/res/drawable/backgound_seed.xml new file mode 100644 index 0000000..bb134fc --- /dev/null +++ b/app/src/main/res/drawable/backgound_seed.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/app/src/main/res/drawable/backgound_spinner.xml b/app/src/main/res/drawable/backgound_spinner.xml new file mode 100644 index 0000000..4e22711 --- /dev/null +++ b/app/src/main/res/drawable/backgound_spinner.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/backgound_toolbar_mainnet.xml b/app/src/main/res/drawable/backgound_toolbar_mainnet.xml new file mode 100644 index 0000000..5d19b55 --- /dev/null +++ b/app/src/main/res/drawable/backgound_toolbar_mainnet.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_default.xml b/app/src/main/res/drawable/button_default.xml new file mode 100644 index 0000000..a337794 --- /dev/null +++ b/app/src/main/res/drawable/button_default.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_disabled.xml b/app/src/main/res/drawable/button_disabled.xml new file mode 100644 index 0000000..4186e72 --- /dev/null +++ b/app/src/main/res/drawable/button_disabled.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_selector.xml b/app/src/main/res/drawable/button_selector.xml new file mode 100644 index 0000000..3363a24 --- /dev/null +++ b/app/src/main/res/drawable/button_selector.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_all.xml b/app/src/main/res/drawable/gradient_all.xml new file mode 100644 index 0000000..4675e86 --- /dev/null +++ b/app/src/main/res/drawable/gradient_all.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/gradient_oval.xml b/app/src/main/res/drawable/gradient_oval.xml new file mode 100644 index 0000000..8874940 --- /dev/null +++ b/app/src/main/res/drawable/gradient_oval.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gunther_wallets.xml b/app/src/main/res/drawable/gunther_wallets.xml new file mode 100644 index 0000000..9873abd --- /dev/null +++ b/app/src/main/res/drawable/gunther_wallets.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gunther_wallets_00.png b/app/src/main/res/drawable/gunther_wallets_00.png new file mode 100644 index 0000000000000000000000000000000000000000..acb5162478d558b06a0ffd580987884c28c77022 GIT binary patch literal 2424 zcma)8c{r478-Is0Uz*O;L=2xLgM4YoSj!S&YzJeB=nw`ovWu~dv1hV}5FJsHqw^h0 zWGfmY*~%5N?~x*zk&$IA%{QGo=QQ(ue|+zCz1MR;_x;@W?|y#I?|rT}*$QI}h5Qo& z007j~1Z@KVpsp`3A1~Xp7n3r|9wgmOY%BmELKXmG698bF?TTFlfDjY_EV%-JWY`I~gFL9>^|i){|HH zsc?8`$kNi1y&j^ytHDkO5o|E$fV1yL@n0rxX2xhB2`p~Hp6nx-S{Uw|g@_#l4otG} zR{(&wKf>05V1o^l3=9gud->ud3B*90q^B2NGu$sw(gRDtvPS?2AJ4TjyM6=#n+k?x z08B*d#78wJ;n}C;&dp;(0|WIcC837)dOXZ1CZ7w1-mBXZ#ZsNfxEipP;F4m;6Mz&7 zetwH|ULfUW4gQ6iViDSBbNbZbd9_rtK?ht=j+eDC$*j^QUad_J=Tl>=OtI=pGe9>J zt>>}ig50z#TV|LeQ8$d8J1BEYypdVWQwRJcCSVoE| z_rS;RA9tP1KcfBl@Dt2_25WWLRqgWn0v$YiMp1C9rWn=w5DP}=wN%Z#BCUZA-Z422 zdK*z=nF5D7o&wfC6hxE|}Mvo9qF5?>H_O*2i=+rB>Au7A1aoGk1lph)y|aVz20Rs)7+jmeTv&wRM$mXHXO>NfiA++`$K}C)owbI z=+!&^P4pV5-H1X#f2&t#-N?_u=|lF8A-Op0+fuX1-*V62B2{WEJ*NuwKf(}XY?U>uO(&~EjLx^>`?7SBWR_Ly%(DZ^uco(ehou-(Tav)ZzMF& zWk41xHeiz)G9tKCUa~RnT~Qj}{`>jMMOD9b)Vw(HFzuBYvMwjyR?rDYK9FmB*UwSi z&!Iu6T*?bSCMT9J$!kFb9UNOsPrOusm0G2Vvag8$ zv!=Xq!ZF$e;Ahj$jO%MGC^aP z&Nl9l_ewfzcv5m+@6W~GvXaW6YggVpxkjl>l;(u!(2nVQF~9hM=@w5#mE`Z(PAK4gqWCV;J=z(_Us!lx?BZF( zSw=)@rZEFp$*u#(5&aeJk>eC)IXNzxY=%7$V)VNCW-#mad=TJ0DNeFIp6!A&dK>hw z9i>iZuB|5QdT__b;X67^6_J=zF`p@JyECi2Fh=(NKUMNgE`kFLyAZnuQIF}F)Go5-3-VUnNtoIE38&(A4L=^{AE z;PW)9_hN*0r3@zpI!P6%qlF7bT<7zhtp8%1+ek8V$@$R!IP53x-KeI4<$BjNW4wt1}Jo}ov%cPNR&)#5Zt^&{a7(T0GYm-S_YWVi) z{!JY*XpMKS0+siYyM)aDyd7AZaIF2tOpQO|96NrDVCX<#|2NLMol;Oj zDJY+|RZ`PbQ`b~gN1~K9Q7B*jIFg3)WEA?ID F{{u*T=!*aV literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_01.png b/app/src/main/res/drawable/gunther_wallets_01.png new file mode 100644 index 0000000000000000000000000000000000000000..eaaabcdc6b2c2155527424f0e3726c099243f05c GIT binary patch literal 2438 zcma)8c{rQt7XLzxh*4W@BaF~$HFmKpMQqoup*jqLq}NU?4M8!C+Nu2(MVWSHsHs|N z1R=GwHAPXe*RHOlW2^ni#BFbHlg=Oad!Fw(=RM~==ls6&{?7Y+DRwqyJdkf7008h< zm?Iqk0MvK#g4r3Cn}MOxML2xT@hlM31muVdoP$751Kd+|><<9I zJ`{y=4s$@?kP5~HVSW5DQelz77^&+%SRH&ou#`7C49$=LfS<^Bw7h)=#y1@b$pZFa zos)m6yNb?rCH3x}8yy~QQo9#na@CM+KV~27-cB1Z=!l^sCbNDF+Dmj#bz}>I3x_^0 zAvg)97T056s4JBr{dQ+G`4`pGEJvI%xI7mTi)G zk5hEoHwn+lE)#e2b=l%!)zs&Fza$$|Y723;7I=6Ko$GD)Kk<(a1o{v{<7OCxs9bGY~%o`t>$QXwq7xK0SWF`DY;*{6&V=%ZCkAMd42 zw_Ym|?#}&<<^>s~2~y64DXwFh6?@BmXQ>Gqs9rgfhdx-NwkiF zBjc(h-U?X}%4*Ma!|eUSH%FU9s%Ka0cKm^oh5_8RnKzpX`d%bsC_MCO!>${F_UJIM zl~&K=IHquCsMDvnaqPR$jM1wu;rST!QnlsO&-qRzgooNIMKMj+t`}?Ucs?n{LJ2i0 zy`nJ<_GV%ln}aa+%;X%?_0~H5Ly{vUidyY5@O^6-ZRAQ;K=UY8ytetZFPx{)eOQ(U zI_!`ZJ|?tQbMO6xZ*6r#_peSNWpzLI)W5j+IQ^9+yfH5UCFF`p;?74^2DoShI5!K| zNc&*N<)B4U?ADQ>)8jkq@FRv*)tDZ3T-4N8hu79Nj7#SSLY)}s_dKO2cKmt zuKLTBn}^MSKsuI{A_?btX_p;2yrG~q>et@vYA>_WWNAY-JjW=zb}Ld`sLI?+Yu#GJ zu=%A%)D?>Y+^e&(u={zQNHJriiu9&f&h1+dq@ihp5|1QfK^+Uiq$REUUjx9Y2n(bk z3O_-e9ts>*1}@uskSK9fI=!IK*{GnhYEb$|HVxXeWy$FAHW%ZD_&=UE-umyh zF&hn92gG8q7|c5{o8QRvqe5VZM6M*J^Qjv3fnhQql-xt=?yfXO@kT z`9H7EsMW-l!F|-WAPa|=bXZL}>vJ(9Q}y)6Bf4q=ZXpa57fV^wna0%y zlSP%jaCo&J%YfdcGXoRbIyN%9s|081O3{FLroKUh05eis-#8Vs`yj9oA*Z|;WTPTds&mN!Bv_g0F}GaZsq z*%T2_R-7`6YkoG7K++3Ts7i|Gm1f>!TGQ4U+PB-SCE3yZe(yVTLj%?`Qt9YiSaE^% ze$`4aepz>ZF`tWt3|kpX3R@yE?Y1*c{eS20P(s|CTRBW!>CWoLw{393zDN-w^Aw7O zW^of>?@Cy@`bY?Oss&Lc{z6lh1$Z>doSTi++CzLuIRuUG?=RmB){lziHD=*K5!pY6 zFG5n&V3s)dYG{if%0j!RPDONyfeco@v+VkO6h65M1M~d6rghJ;z;yklCxKMTa{e;% zU?gjey4<#fB%<1<8#_)in|QJHtww3=v*BYlBVDc3J+#gv7BxA+&w<|h_f_?bDz z%*T@v-dtQ3f9ir({POev_Odvl^8P!0YBp(?JD%Y`7iQud#`ufC=y>5U3<0Prt7s~y zC@ZLHp;Xj$)HQSv8gOM*9c5*IfeXG&0RchYKG;as02LjC>W2XCsXF2b0t*uxU literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_02.png b/app/src/main/res/drawable/gunther_wallets_02.png new file mode 100644 index 0000000000000000000000000000000000000000..860705d62011ac2ad411b104d3e677c298f695bc GIT binary patch literal 2451 zcma)8dpMNa8vkabgoz@QA%oi0Bxc-l&lq7(u2G6G#*8z`B?iMFsUabwh%OYiU7bxz zZbf4>F|(`fe#@-3vsp2O9AY2CkCVc?FI0pa} zL+(6xHWe-CIvsVj7D*o?Il8CruQ4tmHoH1*c`dQ%`;kZsOU$I3CJWK$v7Y zAVAdjeb)C>8hT0REbZ=WZ?DuXjIcgo2^GW&B)pmYW{bL5ppQgLa#wDak>2QQ=V1eu1`W4<)K9^6xD!J&$<9hN+#{C+eYPbrpv)D`bmFF3VQZtD zKjP`Pvr;cE;~eB;Z`gP>GKVL{Vlrw6q=LGkl@sscR;g~#lS>+*7M9K_N7FVmV+JFz z#pOBVm$$je_^hp3e#!Js4g+TUsaP&BrZn=`-}m`=m?$e(DmC0sq9XJXWP0*7)dl+= z2Lx^`@I3k%8ze#M6SU3=XS;m#&n=Ae#fQwaw=b9F>(oERiz6)SO5eB9<{?||+3tmm zMwhu}sPI_u-?-ZKO6X|Q=PoPm;*KRpGpXiZX>Uuqq4>8-_Pr3!HS5S-$qkB<(c%Stig6h@$D&vj0x9>M6+%b+n@*i-ns%HT;YEWM$i1Eff1%--R>V z_bLtV@97wltWbAzV0-$T>KU1JIJpmnUdVura3fgRi13@G+ex#XSG;_=I>c>Ue*VC> z<&=S%lXnyvu0Q4zBwP4$J@S-;JYvB;wc_oTnn7%pw-Q=?{O(-Mps|ByiaJsDJ zD~ZjCz;`;AJV>RoA0#p7mvhDS#)b!GJe0naPs=8tSB$nv47cRgobR(-7{rtgMakte z>(M-XV)BT(xli?I5hGMKKG0}q~WKbs_;Q~lZ1ohLADW# zUw32G>KD39{vO4iiN=BQ_xH0utj^U=`@XFCa$!Z=zyj%5CBvA8oK{S04G()&zT`#Y zKlvI|!!Lumb!**jttWFTx=)3rb)N7D&m!VSi*Wr9vyR`PJu({4iLE^8f74*WxB4at zPAf)nlw!-BZFU&UwIIZMK*_-`OQ`>OJip&Jp?Vd!sRF-Q&poc-A zdzC`5I+^rtA3R4*%svXT<=q1B-T|K?F^{?l#^3{wYzLLz3UX%w%fZR|#BfB%FV2+* zT7G97oH)Dho-Jh%0x`xFC?@o2dNuTV2vKSFAV@2 zF4|!&U8%j(1NA|DN8}H;R+De_*z<(KzO#o8&jJZ9gp{sMBa>5vjKZn3#IA`qilRH> z|M=of=F0^E^9vY%Z^Mg>t(YScI}m=Cz7J4VUOT_=rbx1A3|9i7aVhotV5YzQYF$#{ zWB=xnq0N8o{x1yK->NKZz|{Y=`%fc3teKuB(@l3qF4n4E1$mhbTppLnHM^SxhQz#| zC2)#Hn)15YTh}wt7)xL|-@|co%}SFrK#n|V1VK~?L2w53QApG7+N0mG+y_*K&JwN@kzqhu@pIP~lp39}1nwaJX zHP^EmeE1B~_J&0F;53^wG2;x@d-#~U68RMR4NhtaH|4yLt~2m&p27dE4r?il3}Svp zwoH){J?qDzEVuubQlRpKJOnBUlW=8Ps z@Y=ZGnb{YNggDvtM$o=Y*3`HSqPy{VcH*6Jxw%|$iRT;VG8P*0kRSU#KPTilawGSw zkM+pv^S0H3<|-8ngZGA0F;g;_e{5+ik=1=`>=n$TuYLsQuaJS{Rv-r4nJ5sk;X=$Q z*=n$MjL0e4#mH?o6xIuP0yAk4$T!hwMU4kHyc--;O<7!3O$4Rmocx$0Cro2~JwWk* zD)HltysA(h?R8(ez75oxk6;AL5C6~}kide?XtRq(=&OF9_$8#E>klEq@0D;s=Tn>b zh9E4cX{oA%G*d%$5Zz)m3&R<+JZ12i0Xss>^w(O($dC2T_Jf*a^Z&XxSm(cO6#wIs z_aYo;CR*Q3v3943o)ko5LMTxr0JILuPz$A_g+Anp(l^#OFxE3b>Y$Bvbk1(uduAOV gFxZbwiUI>r#(L=Q0IB}c+BF7t)(+T57@vgy0Mj7y>i_@% literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_03.png b/app/src/main/res/drawable/gunther_wallets_03.png new file mode 100644 index 0000000000000000000000000000000000000000..eaaabcdc6b2c2155527424f0e3726c099243f05c GIT binary patch literal 2438 zcma)8c{rQt7XLzxh*4W@BaF~$HFmKpMQqoup*jqLq}NU?4M8!C+Nu2(MVWSHsHs|N z1R=GwHAPXe*RHOlW2^ni#BFbHlg=Oad!Fw(=RM~==ls6&{?7Y+DRwqyJdkf7008h< zm?Iqk0MvK#g4r3Cn}MOxML2xT@hlM31muVdoP$751Kd+|><<9I zJ`{y=4s$@?kP5~HVSW5DQelz77^&+%SRH&ou#`7C49$=LfS<^Bw7h)=#y1@b$pZFa zos)m6yNb?rCH3x}8yy~QQo9#na@CM+KV~27-cB1Z=!l^sCbNDF+Dmj#bz}>I3x_^0 zAvg)97T056s4JBr{dQ+G`4`pGEJvI%xI7mTi)G zk5hEoHwn+lE)#e2b=l%!)zs&Fza$$|Y723;7I=6Ko$GD)Kk<(a1o{v{<7OCxs9bGY~%o`t>$QXwq7xK0SWF`DY;*{6&V=%ZCkAMd42 zw_Ym|?#}&<<^>s~2~y64DXwFh6?@BmXQ>Gqs9rgfhdx-NwkiF zBjc(h-U?X}%4*Ma!|eUSH%FU9s%Ka0cKm^oh5_8RnKzpX`d%bsC_MCO!>${F_UJIM zl~&K=IHquCsMDvnaqPR$jM1wu;rST!QnlsO&-qRzgooNIMKMj+t`}?Ucs?n{LJ2i0 zy`nJ<_GV%ln}aa+%;X%?_0~H5Ly{vUidyY5@O^6-ZRAQ;K=UY8ytetZFPx{)eOQ(U zI_!`ZJ|?tQbMO6xZ*6r#_peSNWpzLI)W5j+IQ^9+yfH5UCFF`p;?74^2DoShI5!K| zNc&*N<)B4U?ADQ>)8jkq@FRv*)tDZ3T-4N8hu79Nj7#SSLY)}s_dKO2cKmt zuKLTBn}^MSKsuI{A_?btX_p;2yrG~q>et@vYA>_WWNAY-JjW=zb}Ld`sLI?+Yu#GJ zu=%A%)D?>Y+^e&(u={zQNHJriiu9&f&h1+dq@ihp5|1QfK^+Uiq$REUUjx9Y2n(bk z3O_-e9ts>*1}@uskSK9fI=!IK*{GnhYEb$|HVxXeWy$FAHW%ZD_&=UE-umyh zF&hn92gG8q7|c5{o8QRvqe5VZM6M*J^Qjv3fnhQql-xt=?yfXO@kT z`9H7EsMW-l!F|-WAPa|=bXZL}>vJ(9Q}y)6Bf4q=ZXpa57fV^wna0%y zlSP%jaCo&J%YfdcGXoRbIyN%9s|081O3{FLroKUh05eis-#8Vs`yj9oA*Z|;WTPTds&mN!Bv_g0F}GaZsq z*%T2_R-7`6YkoG7K++3Ts7i|Gm1f>!TGQ4U+PB-SCE3yZe(yVTLj%?`Qt9YiSaE^% ze$`4aepz>ZF`tWt3|kpX3R@yE?Y1*c{eS20P(s|CTRBW!>CWoLw{393zDN-w^Aw7O zW^of>?@Cy@`bY?Oss&Lc{z6lh1$Z>doSTi++CzLuIRuUG?=RmB){lziHD=*K5!pY6 zFG5n&V3s)dYG{if%0j!RPDONyfeco@v+VkO6h65M1M~d6rghJ;z;yklCxKMTa{e;% zU?gjey4<#fB%<1<8#_)in|QJHtww3=v*BYlBVDc3J+#gv7BxA+&w<|h_f_?bDz z%*T@v-dtQ3f9ir({POev_Odvl^8P!0YBp(?JD%Y`7iQud#`ufC=y>5U3<0Prt7s~y zC@ZLHp;Xj$)HQSv8gOM*9c5*IfeXG&0RchYKG;as02LjC>W2XCsXF2b0t*uxU literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_04.png b/app/src/main/res/drawable/gunther_wallets_04.png new file mode 100644 index 0000000000000000000000000000000000000000..530ef69738cbc64a1910873e179c89771836aa4d GIT binary patch literal 2451 zcma)8c{E%37k{Bf#H5tk2CZ#O6-DezX^7&71dXi}LDE6(OJgsZ8cU1Xe=V)HhMBQd zt+j;=qBTYBYa7%y5-nA0$HXz6GkNp-fSZ}3 zYybd4J$#|;OwH%mm*l~b+>VS{{0PF)1 zScecB+;xe7Ab*0_HM~SfcmQ7Fsuw|r=o=v6i3`CoDF6^BvTe=po|NRB4u+)xJCf~_ z@70}!X&t2Qt#5}12kTTHhZ)-=*><9LpsuZqK7+RC1GUMtTmIWOU6XCu{N)9MU)+zh z<4?}7CcIQvDn$8g&1jyOS5Gk?a=-^=d07ibnpfDwt5cA8pK2_k)ry*GglZ1Ap2v~y zWv3==o1;%g=a{&3wa%`vM`bimbNh|5)h&(3>=SL-9Ji!{43O3-7PpV}8P0^E%4+hw zI_^=EaM>pmJ(Ebo-RW@C57Wh33rwx5+&8*dB%!lq;}-0e0;xp zC<458Pa2aL~>x32avvDYrkvaY!QDD&M@0+<+W^XnF?nE}|Nn zA-+l$`-Q61E{urKdv3Gqz=0{Z$Rg_RMfR4Dvf+A#Ehiw|ca5)~PU;kbEt3k;A)5nY^2hpJ*7_xzE$OfqG9JK+GIvs&c4wT z5>Jt=(AZMfbj_QR+_(Gd&2;yUrjK#EN*V_8n0lUcI^>Gb?SatXUu(8pA{kHj{F)fm zaNAMEdjsuW-L<2agKrPpJB4QBaqo)Fr+&(|yB}Ghy_gqW=Xf<=W7EAMpCA@lrqV4O zU1Mz`qOsmD$$lp>^nSKjJM9&vhM?g^LZ^ zq=b$Ntdu?eG~r!d9N+niU0`A5PhHh7&pu0iZ7yG%6^|8g#*?_Sv5$P6G<+TE1lD`WfFu%Rc`bqf7G>F1WN z$vrd;nSnrb%pVEGf5*ePV9Vht23eth?a8kCDlJ8pF@T1D##*&nkZ%O5&|X>Uma7e# zp0CATGRp~iEfXVIlI4yPF+!8Th<+ngZzomR=$0B|h9428rJ zC+JfH=qWAW!hoOS_xWs}589@49WFa2-(ALL_7()s06taG;Ys}KbW!FN@qawzgq^D; zYbm*%0~-V>&ztEd3@<`OB#)R?_?h`0t}oJ&n#WImsx32Dz@&0+8GPiuMmiX2KvEO_ zj_?n6ho{aa}*Hh$|QqDpXSXW-2ej z@Q4|MshxYjbthONfR{zjiy*XG@9PJ8>>$Aa=13;ey%XFS3lF%%5&x+0(C$}gm4Kh{ z97#_E6m4OXhl>cd?w!oOQ>Y~W4>*UoN^3cD(h7y_`MzL00*hIw-mVpP?Nc&+ZgH`p z_5)?$_sDNp)6e((hO5(v`t9=4&W671Mnhlcz}|WQC_Lw;&w8W#wf(Kmm&vsf8*BNL zSjCZ!Q!SX1?^yeJvFu7!TCAS9sE$Sy9Wr-XWX3YxVNT^dVz0t#+y5y*yT@ zTa-s1V^ZLV2GO|JGwjD0C{Q0RR91 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_05.png b/app/src/main/res/drawable/gunther_wallets_05.png new file mode 100644 index 0000000000000000000000000000000000000000..e0e69e1907f6209aaa034a2d73ce3d3ca065a251 GIT binary patch literal 2460 zcma)83pmqzAOFud7H6&rH8QtTNwyh64Qp-(bIDK&!{(@5Vl+m%jYO!^qFhQHse=?k znS0tQ<(|75$0f0fG!owRR_~jg^FGh}f1dyI{r$e*@8|cue7?```6WBxEumtY#Q*?+ zT3g|q006}P@E4KP3bx$Zec6N8K;NOIWnzY%Ca42JmB8=0OtYQ3zvjN^ z>9M!BUt3#ybJ^XS4+Bx0@cRMNi9Yg|jkk>@PKr?m-O1mGQmpOFMLA+H2|#>kjT{dE z;EqUVH;NPSgi3Ho5ZV72Nre&-Oj7alCmT@%f>nHp6e3>&0Cj*tusOdGE;Sq~mJTez z8wcO%9a7*l(OEo=p02J+-Rt4zu4Y0@(Mu4|I&Pauee@c7Fg-D7F~Rc;K`2OTQ|OZ% znv3k2OJ(GzdOCTyqr4FV>A&<+Y`Wb@As74|6=*iMo#OPG%}7VfoKbZS?9_cYW`yHk zM0z$OHGa_szcKovrN^VX(P?nhIp(lZP9NI0&z zBFn!io1IK#NNf8h(|cGMP^;I)vH?-0CvH66?&WU0WlN<(OJNcfj*gS;%U4%j+J5I~ zz)#l>*}lUCLdb(l9dm2%UwG*qUzp^IaQ5oREtcgY8q0|yaI^Z-mz}hEki=E1ZJ_bU zGUszjGYzmJwoao0Dss;Qr%!Go_9eC#sF-D1Ye}yVu~ort0K__Peqw8Ki@eyRO|9~U z@?@jBIogwE_kk-6W1(1h!R!;MKa=+Lm1Kt4TT``)*EYOv`Hgz_lT53&u^rQU;fUNe zh0z^--5jxdssw!VA-_Fn!@b=OZk&+VbI>t%cta*U>{6*5`F+nB4{tVtB*W(B53eky z4A&gU+0^p;9j=d9J6E=EBW2eNscO4uGfNgziEO@}z9FW6`hKXoP)6Re$;=Cb*|PR! zax*KO*yC7oGnLwWGpTNVF;@gVF*^L#UExE;tW+H8lc5Y`v^}@x)S%hI2)2|HA)8;< zh?*fLCXcCNysF2OU{P5@Br%AR4~Fq!(Oy|?a+46D|8H$&6XhVfnKiVWAHzcpfzF z-Q~adoc8DkE?qagG3xJuo=3=aLJ+FHk{0gt~}s(Nq@n+`VtvN zD@L*uqAMIN74_%Z;o!8Sa~3l-rI_UgLSN);fqUEG8Va|2e|kVw4|z*T)r%uq&`i%R zO(?9(DJ85|Zo2sT$AKdy1#vA!F30mqZ#^n|x}!X`(?;vwg*a!qLnOL5!}(f(yMBOM z)uv)qe{%nJSe6RdE&?Rczc6!kr&j^ky?!cVHSDYQ{Y#U-ik(}pt z#wF-3tG)M}_gh~K=~O=nFTCK5Q{0C`s?I5%JAeA7DlDa4>8|ogQ2p4ZhVk8nKLdbp zsx{8cnL02#WEMEM8!+v-q3fTG_J4b7Fc9Zu8k3>7Yv=NEZLVw+e5o9%@#d*K|BCoO zz8KN@vZV)0lUTpD?Bon-tOZ07?wjy+flOker~2o~s^1k~Etu*|`l(-N#5NCBmn;g) zhQQSlQl-8wEUIfISL5U3j5XF{ESNAfmV4HicG%u>9Uvi z@E;id;)HShC5h54+Ys~DQ6a+<(69WTmgPJhOGmX!pK)e_9NH!%mCc11kGMu7Z75-A6P&tvy|f z=uMln5YYZO+yBdS`nu$k1+U@1jXVrGX8H;*Sg3@ss=z^~d5;Um=uBOK$`in`g3(9U zFo@TkVBtWVK>w-PpJ(3>ofc>~GA`=a>PPC|yai@fEg>p+USzZuVaWLDN||3Wur|lzZezXT{sj$C^e_Mb literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_06.png b/app/src/main/res/drawable/gunther_wallets_06.png new file mode 100644 index 0000000000000000000000000000000000000000..a616646981c50c24b2a87c97d3b03f61dc3bc02a GIT binary patch literal 2463 zcma)8dpwl+8h>Xb6YHMaXU;#P4pnxD-K)@Sfh~-YG_zmSuc920+9{C&t+6AP5mFrUJnks zy1H&}Z!cZ-4d%iiL=Tsvz~Q-J+|D7`$sR3MR3og(JqQpTooxjef^ZQ)=*>3n902h3 zC3yM}JuqjK2vLzZ{3)ywF`j@`3d7?};=&0^p%@~DO93Emw8+cp(q4qvc(h<1z(KT+ zy*KcaWjrN!t?eJ|?{Cn*9c$}t1?41hV1ccy9?Q0*ZPZv^M&x>W-~}&eq=t0#i)*A~ zk{7Pj;9eT&Tt}Z=n=lfeG01ir@WDnE;@xFQP7gfN3_7f^Cu={gr?^!S=el%_L1COJ=PPk=&HIB z{L@l;7N$sCD>REd*p)Br@U}`aJdtwd?sN4ZU(&SohbO~# z-|{>19vuP0jdA+e+kd_eGB~|D&*Ep8>qxEFlE~}Sd_Sj>?hu^jONMW4CCaJr62#V>JbsmLuU9vr+nDtCXaNg;OLSb!| zNh_Q5qQiIeMv*Br6;ZMLLhRp}HpA7$QLc`08dckS-ah*&?$L%sx0b0hHF))e)B)M4 zgTn(1!Fpvcmkz%$GnBFUfSV5^Dz!j(njT9lM#Nm9Na6k*ybusf*TqWES>@xO*0aZ3 zj$f00_VYtlh+r>Ea(FLMn~7~y7wG7cv}izf+|JvRGP2kltqRS*zG=yPZM0m|yNT=Q zip31NSKrHt>$sQMy0TuzkD8krf9EUvp>A0$O>e_k0yfoK){;DCwK`!CLdx^^kLmqL2P?ZQ^M(HIzuH?MM3%xeF2z@pmv8bjn#H} z%jOPQv|LSQc%gH=ZnCoYk1aNJIrwSIhtv%nLrV?UM$!Cbh`&s3Urh9`b!!16)}zgc z7FLb0*N|3eUpv05e(1;O+(B>Om?A7@w!-PnFGa_$kscV&mn1bD54&Qx8r*aR2Pah_ zyJVB<-0kHI*}Vw9i%od8pCe3R&VY$M$$G?Th8aSF4-Z+TcSKavfgobc&V85C$ zyx$``W=LwW>h{Oc6V(-I&+Z>PeVy`4XU)rlwK=bxH0le}Jf-}wWT7I@Tj9Qj;XaMh zRmynWh&sGPiO)G6A~Ledyr~&f!ROnykUw$Hx7bB~v>rcri{|2H>pc}aaL2ttv-f$v z_QEOkn-0VY2*kwcmQ33BqO8MS{Go8j;_`Q)eEPrVWvj9JT!cS)y0spmr9>kczdM^& zqxv1p>phP;UXFUDdKOVx7>t&)K_ivfas`*p-&2NX_sTy~I16c;mZr@fs{9TBcI7#u ztvusKm)}@Lj2!|F_ubXUm!k0RlE+5Q(^3G$eQnLn%_n7&PZ6A20XGTbZT|9Daa zR%$qpIrG+GJ*8Rs;?{OBIYems7Y3P0MJ)xE3 z$5F2}?vI!s?fz@bsv##Oi zr8z7{D)h5y^m<^a*KqCHTh^Zcwe3G$x-ZI{7OD)f>B^u)=qjXoBfJHmZEE-PMRv!2 z)8QRc9{5sws7G`*MsKYOY*veOI>^K%3ZLX+VPoUr9b-v=Hl*6d*# zc~%3IBGTTb^<pAi*0c+i}_7XF|xlZE|UbxoR%VtJMn@S!oVYFlw7@%*{7a1Es+v zH^-V8fyVvYjri|sfEjp_+XI2633ZUSsh?y8pl5ynB}z~d1LLW#+VVOxW#YVJqXLJa z!BtG&pxtp26>Aa)y9#GFc15dzmk7pw#2K4z23v!-LyX=2o6PyOr*Qe6T#(0qyhOGp zla;&OSd_OGx4HWLL|Y#s_xA#85)y^w56KYUt{j=<1%5 lIBEU3q^^jYPW@AN`M8}kcQ#{d; zE<_R)X{_4n6o%yzGM9*12g$vib$ZTIew{y_-|O{zeLmm!=kxh~uHWzLo9cmggv0&` z0{{T-?1b|I0EpnzuLPANOUF)k$cg?zCoeYupl<+xfh0E`9z05%5z zv?DLx_uL^DU_4w24sse`rNQ#WT#5IJbo{>U7rz-UYm1@bPCNTi;(8#W3OJWnYqu5i zW8O=V$lcvtCXg?vQQVi2o0hGIC zloJ2|719abR4>v|{m7^YN^mGypBfiQ);|>ifAZedJ8R4zGf zf(+3s2$^~kuI>$iaXk`E-*Mk--doYV)HRD{yTEu}+O9x)j&$pS@XtCN{XX@vHf-4C z!P>0bso1(n#xtHz*MB(H3W=zq@n@>nQ+9WhW=FX@(+o>wtKL5TH|^HE`g0>|x7xtz z5Y0`<{w*DCV%Qx6BA(}aXos1VO`C_eI4Us{J|KwYW}{+q%QPtyqSJnX0u!>jKw8kV zG@I5_bKs)Z<8$TGgRo|)M#pOEHVL_U^GY6H!?wzlcR6F#@y^kE(d!l1`3pNGFD=I_ zninWMek@7kS$Z{{#=DwQH#M85X!fSR=baDoedV}n5_;ZBU8%o0ujbe*yXjv0GI5+n zL0vssLOPi`U|<_?cPK?SK1YEJQ_?$V%XvTRzE)MX>Ms4rJ%M;#(7Lr+eVCO>c-S_c z6<7N3{NnCkS%`uS+8rZLdMaNRXZI~m){X}@)VxoeH@4Vm=w6Lrk3)XaJ|m2YepET* z$B^Dy2(OV=z=`cf7lie}{5$RciasOS?-RozlZJ|1y03BeU1Z#}8qP_mI&di0Vmk0{ zE=8A7V#-G*RC+qDv6yT|L4Quk+#{(evt8g4JLuy@K21N>P^E2qGs3Dx6rIxQmm!Am zZ2#5`aNSm~w3v3y(UQv_xx>}?~Vn=d(0dm#|4OQCksMuc=1Q87ptGCKZU5ERpxk+wl9#KV^e9(AtV$(@-*S8)xr<&y)-q@*F60lH_dIPzaM!m@l5Gj#~# z*MzX8=cPy9nBWP=7|7)@44%2blZ4Ga+wy*q8F4PId*RJU!aZw{PTo8w&L62GOPH!G zhoNP8R*QU!=7*7l`*F(^gC31S7iJsYb~N7--_S{S>>~<9iClh=W=lO+WqCW%6k7m^ z8b(NmPx>xhPLDih$v`REWP#s52Rp0!=&t8UaMeyM{@>k-4S(jgDpSNFb?Nbk2<4)ZMuyW_WTf8dQovM6d)U=4_Gb+L2=WKs)m~t2>960b94)2Y#4Yd5iod%>`ut^3 zRIyXI$AiLgx%>yJgEv+Fz93@{Mv>(NK%1Ca8kw3Hp|=xE&9UYdSThSl6ExPuBvgG< j$TEYlh@fCf9N54VYli-8pxj+n`zZov2R!bkeL&J5v)%j5 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_08.png b/app/src/main/res/drawable/gunther_wallets_08.png new file mode 100644 index 0000000000000000000000000000000000000000..7b11c8391ab556ceb1bb5cb4520676d8c5fa535d GIT binary patch literal 2472 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_10.png b/app/src/main/res/drawable/gunther_wallets_10.png new file mode 100644 index 0000000000000000000000000000000000000000..1f013994bb86251e5584b7ff45646ec9ec044735 GIT binary patch literal 2468 zcma)8dpMNa9{y%zo7fm6?1l`bi%T;MxfB`WcHA|CHpyV7qjHI%VTdp;xz;vOqN}t| zWJjUMHO8)X2)T?)ZY7P;9+xpD&6!TSXXo4falYsIp5OY`TJQS3>-WBEJzu)3v#qq` zPm%xtNZZ@t+yMa6@%e>`iF(%J&kl%!dVrm~69B|$0YE}B0IZ6-5@rA(5(NOWegI%v z002s1mmj(96Ac)+I^wNG(a%JI?qhH();-L2YwsmrUK4${wzh>Oc9yUW2(1QwDXO>B zG5A-}tG+%L7Z;&W`2Lc&zX%4QxH}&N4!jv4fBx`yu*Ja{mC`z*4+)CBleNUSBtiy| z8Wxh106?re2Jc02C!SId3lAj+ogk@GqQgk)M}x?w)Zj4nKq7@GiU2?zVh|k8Z$iRH zA|!JFKC+qh!O%x}yoJu>ZSU*psW!L~W$kGR<;U@1M;bVt7L9R23@axkbS3%7Spqav zdvnCo%QO##vllDL&kXkz~h!cij^WTP;b)EAjdbt{s_H zxcX?fDI&d)k(spO;JhjBf~{|R!^Esu?78|8sgQnX^~~Vuk5mHm5KlAQ!qP3nF>6C7 zb~FlCcE2E~rLZHN$dJ3cA0SFyS9H*@2$!|*EMsh z_g^}uxKnvz_dxHs^tl0-+>~@ zcW|zatgU2>)E&CKx$T$VI02Gf9EE{R6g@VnW|u@OQ^C9%-FhQuW5VF0mb;iJ_}y6iPArGc6Dk^5t%`L926>*xGi>K4ze?lH2^cBzrgor4@!%Ic1c zcygcTOXJ)Vgw$~=r3wAIh270T%m@8HM`ZPRdPg!y#OV@;;a?dZmuYuR-WJ4FA3A!` zXvx3!A{jv|Lo=1*?z`El7;(FhV%e$ZY}j=b=7J`|K+If;cjw_c3a9sAPH;^hc}scC zt7F>I`A2%Rq!B&t8Ik>pvt>7yhmMt(B(~l0I9XirYkTFh-Bp<{9kd_hCE^u*NOUO% z{(7*tQLtCd<}!^S^58B+fx4JeG(=`_iG5YauSCqdaVB^4ws*d>>d=FrzUxiSuGXFt zQN1_as&%@4&()hbvFoZGWfTH2b-1pSxI>n6fFK@-fXvSQ7$|1&A}2$O)9oz1hIegn zY)XhgkH2s-E64QM?SFtjXn!I6<+jtv(ma2hiWLs6!BshTKJB&!BBM+7p4w?hR@Vg%%{g3cdaP zGbPa#@qauC5(}05NBplVwIb>|(nPkeo>^imiJ&_&)mQ zb-9s&Pk8jLQu*26gA~YaScV=p<*+07tA3OGGmD8C`gBdY)U#9Tc_n9h(GjxdmyUG4 z8N2ehxD-sU44d6g8&5%hVlVB}1uaRIiS8X-II6jRNA2}$l;zk%f9?Bg0RlYsZo9I7 zkBt2Ky39Wpym?EmFWwrvXs$BuF_~J`&%=Dh{8c@(y{Ut&4-CQ#f6ZKLp|;@m zG)H17g8L9<^y&Q@5>YYmlUc;dkwU^i74IEq`AH804runMF>jmj3?4l z0@|o`Cs*c&sBaYhJQckFX@Z%=hgtj-zLcZivNp=5Z(78H#298}=g@g{x#gF+0cu!Z zdys|&t%qZhxUp7T#3FC1zomc~E4K6<`e`OpB}x}^QV^$4G=#SAL%@8%3)LP9Ghp^ zbZh}zc)7*LtayvudIA4-IEcWv=Fc|Y*`E|Sp9jiz5|b%*<{t;b7@h|5j^^l&{A?xe z3y?l?G8;5NNz;Uarkf73@LD;LRVRYEHOvT2ka$mE+Ly$QAGb3B-4O!k|+TBDD+-kG)h_nIH8e8C u7-^&QO;M;5@;i^M0|bW#29cw|0JJGa{|i8BxT5|u1AA*{++D0+;(r0CE&7lE literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_11.png b/app/src/main/res/drawable/gunther_wallets_11.png new file mode 100644 index 0000000000000000000000000000000000000000..7135fd03968e4a1350f7a96a6b213ce22a24d1ee GIT binary patch literal 2474 zcma)8do+|=8-Hiym^kjqs4*GjB#B{=amyID<1&cokPK!zDol*)kn1FoOA+N#D(QUD zoG6z}h#03j$31tQ(S)Nh%B3OX8>{+G-qj!9yVkp&y`N|Q_WteX_w2oPs*|I&xaba1 z006{oZSXDt0BPU&VL}4WTHN^ofl>0aad7~EXf*(cPX>UG0$2PL0EB4+z_bqlVDkY$ zo>KV8*;o+Jb+UK06j&caS-O|*?HCt|^{(El-ux!|UJVU1bKJC>DiB-^TqK}UpY>tHFu7DY$^Vk7J1 zBmfZVigxv&x)39jC?Ub*fKwzTY7~W}bUc8JjSQqH`4g!`fdzobp8aMe8E)xr4qc92h8=6*b(l5Au49HX&jv3gA3N^`4c6Em z`mB(4SoZvtO7e3(tz!I1{+NL@M=#B;*Mk(26W|P|+1+zV)N3&(ovd`#X>e*!x4<)_ zoH;~#L2i1|lAYtW*vr=5Z4KkoLNVFQQL&(YX!X?Knbk-)=n=k3h?%)_n*D_>9k{V@ zeEEa?faZesRAR2Qrhh8EkCi2E^G~^KU<@PT_8;m#o<=)&R>ND%QX)~9M9F~?Rpn*% z-%keqaKp>~9X<#~9$xO6TYvx3NAL9FBu|)aq9wOfS)%=PUXHx%3u^6bx3nAD>YLu^U{ipTU$on52>m8ha_Z+zitn2%0q=+VaSo+_nr6lZPzBrwDU?v z*Ot;o>yH#}Z@u(8&rh_ICp)l>x}Qs`Q5R`p$(mN9TW)4z*Wnkl-MfGvR=8I!E20Kc& zq~VDUmv}bywX&&C?VA)tOg@w(3RCnm&0Sa`Af?u~)<&PMYiFzYt01@ktt$d^6y4uq z*u-nRylQcaE?J@`KQPrc`ruVr-ruWp%o*S2`h|p#TKZ-hgc`}L8OTxj3teHMPap8T zX}tR@LG`>!akqZWg03e4tcU%-gkI=#_YBJ=5#N;Bjr^8-xR7?wa56u(`pEGs`is7` zSI7ujIhqBJec)`Zpg-4%63R%)w&K<^OjnxR2BK$5Jv)xpQ+d6IG6QS+$U7@)UY^hp z&pXzmCXVQFNek5ZGzqM69-&>X5ZKv@tC(%{Ti$oX8b-fYj zsUPT3v%Oq7fIO&<$X60_h=NEAE^@E$^C=bbY@Es(yW^SXs5ta6pzlVLqm!lkczEwE z=jwf(e`M{SI;DQyhB^j;VC`9NaO46O>YjA z{RjYCuG-?wT_cBPM$ChT4*&5)Y-_1%MSqkh@6 zHp>4gpT0{hFXMZVe90{zphvMhZkUPUH;Zqu7@wlQsYw-k9^Vvc;-_tymY9X3$CW}+n_ zvpFxbFIFZd$;{$iF$<;&?8C29s`~kuub3~{GcrgS9DbMUHnqHFsy1KS@#o;m90fE+ zWHEHEPFw%e+X)g;&i_-P;VR?Lz1+bSDjziPT%Y`h_mX9P7Ob<3AZ=uVJIY-I+NgFn zOX7#{Zxr4T6g>}Vf*Ho0Hro-hn4;6XHpZoITfl+D7#YsUr1R<0AG)*rkT`E!kcRm! zN28PGVl3tm3w%z0b3Q9ZXz@Gr&lz$`rfO&x@s@3NpD63xWJHdq& z6h@5Fx+V799h6zW3?z&>OHrc8fK}7tz-uA|poP^s#-%Dgo4xhl%HG&Ffh;j!=Z7?C zd{uKH%Rp_jx>j6zUAVCUsPex(9RE8d;07Yr>7K~jF?A51@n7L0(1@QwiK8ebj zenl-#k@V&YQbEA*AmOCf`3bgpNF|C2NUW#BoZ!q!D{x6jbNuFOSfSw2Uk>}fG&o5G zT2ixgLu~O*5DCclE|evPS)h{vZ>V=ik0898YUx20yfjEyzYvnZ06N-e15LEHrp^IZ zv>sMZAB)k~(AL3fYoC(Yb7B)9FxWqU90dlTu^64t0I?AUbAy1br6c|x&L{D|0P6QX literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_12.png b/app/src/main/res/drawable/gunther_wallets_12.png new file mode 100644 index 0000000000000000000000000000000000000000..92bb8ac1025f44216efd8ce40f0a72f79f7f141a GIT binary patch literal 2483 zcma)8eLT}^8~=@*t*s%#JYJ`ADzBTjyo@m6U|ymW#m3fyL^d^Md5I(|6ycy3PDeeF zlGj5_lAUtAFY{WHOkx$uYwgUF(>eC^$MgGqexK{QulxRf@9*#WUDy4&Q`~U&aOl6F z006)p9k3n%0Aj7a5OHDTbL@GJ(5MGEc(?#Sv?c(=B?7>bFcdcd0Fn9tFzF8fW;p<$ z5T5^=yM=JVz|GmyPH6p2Ws`gjevR=6w^!@^`N%voX_uCkm9@>J*ES%m3b??c zQ^9jK+tt-oAP~I$*@qy6fhZoheZZb^4rz5jaI(irXO+XXg#$3f(ZvqTg(^z|upt5I z3;>9CMSFTvJn*N~!z02-LC1*dl&El``q3bg88tLqJrGa93oQUp2eQ1JE^R?d50jy3 zz#_6~@V${Qg8L|`b$&a$r>DxG=%n2NYq3T8BE+wO-(l5A7Z?tvoef(^^gHh*7N#Xn zewt6)FMB?>ob=2{?;M;xQTZ!?5FhVpS9Ur&p+6Vh889Mp#+9r#fHQ&|x!j z5?fM{6Z9yLm4eUOtP_}$#BNQ8JG?284UH)~_3Q6D{e8@pl&TOd#mQ8pVS*Ir#x{+` zo%cdQe<<{Ievds4Aq_5e%?Lg`_cuB*$Ky+IF?wD0m^fF zsG9j8#jIh5_O#h&;98csSUmFP^i%2oB-?UIvm;y`sahq1jc;21O})P))23tYQcsu* zmfL|C-NotULMt`AaLv9)EeuUDy>8yzi1-ZnE7nP7HZn4|Opf${ef|i6rB9S$@ox-& zUPv9TJD4xua`_%V0NTlyQ) zTqZTQp2V}=OK&l#&9{;pW)})1497=@-}xXuR!mDLpqET#AfufHb+Loib0aoo+$h-_ z4Nd4N{MnRO8kYVwualKya>R&Gh)RHE*2e``RcXP-n&=a?Ebi98t*Y`b4Zsi_RJYlb z$#1-}V*5*y)D2Aq&cu`9iWkM%FIQ&jrwNbhKE^NU8Cz+&R!gN%gAOZP=!zsiu9!bU zB~ne(%1t&}ueGskHieuogVq zuSXNE+~biN*(WzyQuJvcxb$X1%boovu9n??QvPh$14g%#R%K>_r<^Y_36|wq80upj z>Rl~gq7g*u->ICVF76TqlI)+Gx~}biQ{1OMQO|^W?VXVOG7!eQ{}$uX;9-UdFJcg#hUbuT_CRV)_!E&B6_H&=BWGUO*q8BwHvUDj#%85( zS|{J^dk})9GzUhRtSRc7qpVYG!#YRS({6O}^=^>;hpJV=$GgU;@4Hr_jbjB9a~3)x zA_#I3^&>D7#)t0+L6KQx6-=zL2w~gX+>2NBV4~588XZ8ikbvH33MV68*0R)mK#pj*% z{anZhk2R(tB`_K5ek(+AzUox~myS=w7>(s5rz^<(T_yi5it+H`k?R&O(k;NB4Hgd} zW^=->TSoh-UKC+LC0oornyE!5`{iBBLrHe}#&>VWCg@bE2>S7DBv1dE`S^+1 zg=Aku2n6e6^niexp}vA&A#>f1SX2duXWuw zno)YhBo+CNA(BJx9`$ca)|`bwux3GY9P`4rK3sFcE{CM?_KFJFJQhHn{xEb-gm|rm z|K?bHISzYF!?&w+h!TJIU|D$)`@>yZU8nh_)$7T2ik&w_c;z6P1w;^q20-hhOmtBC zI_TY=C?hi?V>3fzEq%0^zWy%Xothzwee5* E6F8gv@&Et; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_13.png b/app/src/main/res/drawable/gunther_wallets_13.png new file mode 100644 index 0000000000000000000000000000000000000000..9eed5c39fa97ccfae183f32fc5fd96faaed6cb79 GIT binary patch literal 2488 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 K$KACjr2iXru?bfI literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_15.png b/app/src/main/res/drawable/gunther_wallets_15.png new file mode 100644 index 0000000000000000000000000000000000000000..e878836d7505627c00257a654a3927ff50d90312 GIT binary patch literal 2493 zcma)8dpy&7AOCHRhomTngj^O9dy3?moptJ5>^y%wzt`*c`h32h+w1-Le&3((>zn9+w}wK#hX4Qo zwY9-I0RV{olX+mFB>O200>tFfSAhwuqrUcOanlu4gkz}0025202G3< zo;sQe9C{8%ovj4Y!&o-{wBFAVPC?eYdVh3VV#e=RS2wr7%((0Z0;_;q*>x5gdPlNf zaya((_Iy77-H)!Gf;14t34aVYG}%x7vhc)NW2Mr{pc;aO7{%7kN^A^**ak=p@yV9} zK%^_&nLu$OhA9OF2aVyS&C2~dFox$rcZ;0gUze>3hxP00DnhQ8kT|VS_ z7X7&FwTv=yn}K!?_T18lku=vJ3D-*?1>g2{gwb(DPH_g!7Nm1!&M1ZhJJ}LjM|0#7 z<1^EeFD>Kn+avE-yR|aLXG9`y)D26V?*ms&4@9j~UBD-oRD#Vd9FvY-|E>cwauHil zk?z}+$xbAuNo#s1#&cMyP@A^}vH=mrVL$(>=HY4rhgZQ`@)M{?{W!_~+}+A6Y9;3a z{&oNK(f8Q%;^bE=UGw}8FFgz{EKc!6$IP_lmdkQ=8q0~INQ;Kz*WL65(6)Ov`#^8P z%bag0&N2~?+1iaTRJiF=r&WTeec{pD)PrmE_QF0eu^nbN2x8r|3fq;~q5zq~)$hDr zo``15)1NoH4*ry80**!I%{`a;cYId z@%{b1V~|Q^7ku++A5(o}vt9?nSa9qO=mh&B^DZ(pqgamofpg8xldVINVe@i_KQAW@ z*PhIhZ@E>%^MZ8pWc#;Mv}Q?9)Wn)uvInbln;)cXi5Zxw3E2%!%~>;_eQ7jT*11M* zW?dw5919;MQ=1Ul79=YDdD-Krra_%$n4pt||FZMoktkUTG zHC1ccU+ta^WdsC5X3(ogElQnK2={V=wGX z3iW$z%qpFa+1?56-W`R^zwL?LVTsjMp5Jlf=G8~ah@?)%(w$MDh6#D*n*;e<0pPnr zTdajMb#QLD3lBF24s~^(8N|eH@L%)V2&Yb#srD$T))-G|dPt{eYnLXBy)qJ95&y>* zW3o`O!dRKY_;h3@rb=TDi|;^sU;e7-Ce-Oq4=j+C|1f;@jMU`ENAIN*I~YGC?A`1- zoE${owJru1H9Eo;N2**-mO^2^Dpd5@%R}(6PoZXrO?D~NIBkC7h>v?mv&sK&s^b_PqkQVHgm7j zhSx`u`xY*xxb0OL@>>u3v}m?<^G3|K{)hNUK7u3{zCjDTMH^b1oDHop5vr5DYDRO1 zN$?{VDoP-zh7QDrS2=|K2|aAOxfsfGF*b6&@hzdVbo4Pi*?QE4&AGy4dCPUTGPenZ zl1Bg-a!QhCZCMoMc(~d;vpzM>%|___=mQ_B-apl8a{E5G-IA@oOCt2S@6RoS=uHwWM~`go&-+;=aw=xx9VSXw z2;?|vEP&+`C@whJn?f|z18>Zk2h|upgvQznQMGkeQ6uF7U963sJGB&X+i|J`T!2bjO-Hv`i<{@5?^Y*5fSN7 zB=+tfk>h7!aPU6{r35MU=Ky|F>hM~M+T82XLi6D-j{V!P{vI2>#(an~l3G?4-qtp5 zIcvd6xB+Qyz{0@9ig5I|@&5}3H#<}E>QWOzu|q)!1n7TVG0P2n;Eby+FQfwcR^Z=5 zu_90ej}H>sE0`n@07^&KNK;ow6Lr8@*8pu`h}Jh$*Fm9mbo^!Z`E5!F2=w+P(}WXr Y(fX*b2@*rabzczJTH&!p7>~H$0O;TbJ^%m! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_16.png b/app/src/main/res/drawable/gunther_wallets_16.png new file mode 100644 index 0000000000000000000000000000000000000000..5a598b45b412f25d109456b72cc56d50dbf61525 GIT binary patch literal 2497 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 Y(fX*b2@*rabzczJTH&!p7>~H$0O;TbJ^%m! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_20.png b/app/src/main/res/drawable/gunther_wallets_20.png new file mode 100644 index 0000000000000000000000000000000000000000..92bb8ac1025f44216efd8ce40f0a72f79f7f141a GIT binary patch literal 2483 zcma)8eLT}^8~=@*t*s%#JYJ`ADzBTjyo@m6U|ymW#m3fyL^d^Md5I(|6ycy3PDeeF zlGj5_lAUtAFY{WHOkx$uYwgUF(>eC^$MgGqexK{QulxRf@9*#WUDy4&Q`~U&aOl6F z006)p9k3n%0Aj7a5OHDTbL@GJ(5MGEc(?#Sv?c(=B?7>bFcdcd0Fn9tFzF8fW;p<$ z5T5^=yM=JVz|GmyPH6p2Ws`gjevR=6w^!@^`N%voX_uCkm9@>J*ES%m3b??c zQ^9jK+tt-oAP~I$*@qy6fhZoheZZb^4rz5jaI(irXO+XXg#$3f(ZvqTg(^z|upt5I z3;>9CMSFTvJn*N~!z02-LC1*dl&El``q3bg88tLqJrGa93oQUp2eQ1JE^R?d50jy3 zz#_6~@V${Qg8L|`b$&a$r>DxG=%n2NYq3T8BE+wO-(l5A7Z?tvoef(^^gHh*7N#Xn zewt6)FMB?>ob=2{?;M;xQTZ!?5FhVpS9Ur&p+6Vh889Mp#+9r#fHQ&|x!j z5?fM{6Z9yLm4eUOtP_}$#BNQ8JG?284UH)~_3Q6D{e8@pl&TOd#mQ8pVS*Ir#x{+` zo%cdQe<<{Ievds4Aq_5e%?Lg`_cuB*$Ky+IF?wD0m^fF zsG9j8#jIh5_O#h&;98csSUmFP^i%2oB-?UIvm;y`sahq1jc;21O})P))23tYQcsu* zmfL|C-NotULMt`AaLv9)EeuUDy>8yzi1-ZnE7nP7HZn4|Opf${ef|i6rB9S$@ox-& zUPv9TJD4xua`_%V0NTlyQ) zTqZTQp2V}=OK&l#&9{;pW)})1497=@-}xXuR!mDLpqET#AfufHb+Loib0aoo+$h-_ z4Nd4N{MnRO8kYVwualKya>R&Gh)RHE*2e``RcXP-n&=a?Ebi98t*Y`b4Zsi_RJYlb z$#1-}V*5*y)D2Aq&cu`9iWkM%FIQ&jrwNbhKE^NU8Cz+&R!gN%gAOZP=!zsiu9!bU zB~ne(%1t&}ueGskHieuogVq zuSXNE+~biN*(WzyQuJvcxb$X1%boovu9n??QvPh$14g%#R%K>_r<^Y_36|wq80upj z>Rl~gq7g*u->ICVF76TqlI)+Gx~}biQ{1OMQO|^W?VXVOG7!eQ{}$uX;9-UdFJcg#hUbuT_CRV)_!E&B6_H&=BWGUO*q8BwHvUDj#%85( zS|{J^dk})9GzUhRtSRc7qpVYG!#YRS({6O}^=^>;hpJV=$GgU;@4Hr_jbjB9a~3)x zA_#I3^&>D7#)t0+L6KQx6-=zL2w~gX+>2NBV4~588XZ8ikbvH33MV68*0R)mK#pj*% z{anZhk2R(tB`_K5ek(+AzUox~myS=w7>(s5rz^<(T_yi5it+H`k?R&O(k;NB4Hgd} zW^=->TSoh-UKC+LC0oornyE!5`{iBBLrHe}#&>VWCg@bE2>S7DBv1dE`S^+1 zg=Aku2n6e6^niexp}vA&A#>f1SX2duXWuw zno)YhBo+CNA(BJx9`$ca)|`bwux3GY9P`4rK3sFcE{CM?_KFJFJQhHn{xEb-gm|rm z|K?bHISzYF!?&w+h!TJIU|D$)`@>yZU8nh_)$7T2ik&w_c;z6P1w;^q20-hhOmtBC zI_TY=C?hi?V>3fzEq%0^zWy%Xothzwee5* E6F8gv@&Et; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_21.png b/app/src/main/res/drawable/gunther_wallets_21.png new file mode 100644 index 0000000000000000000000000000000000000000..1f013994bb86251e5584b7ff45646ec9ec044735 GIT binary patch literal 2468 zcma)8dpMNa9{y%zo7fm6?1l`bi%T;MxfB`WcHA|CHpyV7qjHI%VTdp;xz;vOqN}t| zWJjUMHO8)X2)T?)ZY7P;9+xpD&6!TSXXo4falYsIp5OY`TJQS3>-WBEJzu)3v#qq` zPm%xtNZZ@t+yMa6@%e>`iF(%J&kl%!dVrm~69B|$0YE}B0IZ6-5@rA(5(NOWegI%v z002s1mmj(96Ac)+I^wNG(a%JI?qhH();-L2YwsmrUK4${wzh>Oc9yUW2(1QwDXO>B zG5A-}tG+%L7Z;&W`2Lc&zX%4QxH}&N4!jv4fBx`yu*Ja{mC`z*4+)CBleNUSBtiy| z8Wxh106?re2Jc02C!SId3lAj+ogk@GqQgk)M}x?w)Zj4nKq7@GiU2?zVh|k8Z$iRH zA|!JFKC+qh!O%x}yoJu>ZSU*psW!L~W$kGR<;U@1M;bVt7L9R23@axkbS3%7Spqav zdvnCo%QO##vllDL&kXkz~h!cij^WTP;b)EAjdbt{s_H zxcX?fDI&d)k(spO;JhjBf~{|R!^Esu?78|8sgQnX^~~Vuk5mHm5KlAQ!qP3nF>6C7 zb~FlCcE2E~rLZHN$dJ3cA0SFyS9H*@2$!|*EMsh z_g^}uxKnvz_dxHs^tl0-+>~@ zcW|zatgU2>)E&CKx$T$VI02Gf9EE{R6g@VnW|u@OQ^C9%-FhQuW5VF0mb;iJ_}y6iPArGc6Dk^5t%`L926>*xGi>K4ze?lH2^cBzrgor4@!%Ic1c zcygcTOXJ)Vgw$~=r3wAIh270T%m@8HM`ZPRdPg!y#OV@;;a?dZmuYuR-WJ4FA3A!` zXvx3!A{jv|Lo=1*?z`El7;(FhV%e$ZY}j=b=7J`|K+If;cjw_c3a9sAPH;^hc}scC zt7F>I`A2%Rq!B&t8Ik>pvt>7yhmMt(B(~l0I9XirYkTFh-Bp<{9kd_hCE^u*NOUO% z{(7*tQLtCd<}!^S^58B+fx4JeG(=`_iG5YauSCqdaVB^4ws*d>>d=FrzUxiSuGXFt zQN1_as&%@4&()hbvFoZGWfTH2b-1pSxI>n6fFK@-fXvSQ7$|1&A}2$O)9oz1hIegn zY)XhgkH2s-E64QM?SFtjXn!I6<+jtv(ma2hiWLs6!BshTKJB&!BBM+7p4w?hR@Vg%%{g3cdaP zGbPa#@qauC5(}05NBplVwIb>|(nPkeo>^imiJ&_&)mQ zb-9s&Pk8jLQu*26gA~YaScV=p<*+07tA3OGGmD8C`gBdY)U#9Tc_n9h(GjxdmyUG4 z8N2ehxD-sU44d6g8&5%hVlVB}1uaRIiS8X-II6jRNA2}$l;zk%f9?Bg0RlYsZo9I7 zkBt2Ky39Wpym?EmFWwrvXs$BuF_~J`&%=Dh{8c@(y{Ut&4-CQ#f6ZKLp|;@m zG)H17g8L9<^y&Q@5>YYmlUc;dkwU^i74IEq`AH804runMF>jmj3?4l z0@|o`Cs*c&sBaYhJQckFX@Z%=hgtj-zLcZivNp=5Z(78H#298}=g@g{x#gF+0cu!Z zdys|&t%qZhxUp7T#3FC1zomc~E4K6<`e`OpB}x}^QV^$4G=#SAL%@8%3)LP9Ghp^ zbZh}zc)7*LtayvudIA4-IEcWv=Fc|Y*`E|Sp9jiz5|b%*<{t;b7@h|5j^^l&{A?xe z3y?l?G8;5NNz;Uarkf73@LD;LRVRYEHOvT2ka$mE+Ly$QAGb3B-4O!k|+TBDD+-kG)h_nIH8e8C u7-^&QO;M;5@;i^M0|bW#29cw|0JJGa{|i8BxT5|u1AA*{++D0+;(r0CE&7lE literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/gunther_wallets_22.png b/app/src/main/res/drawable/gunther_wallets_22.png new file mode 100644 index 0000000000000000000000000000000000000000..eaaabcdc6b2c2155527424f0e3726c099243f05c GIT binary patch literal 2438 zcma)8c{rQt7XLzxh*4W@BaF~$HFmKpMQqoup*jqLq}NU?4M8!C+Nu2(MVWSHsHs|N z1R=GwHAPXe*RHOlW2^ni#BFbHlg=Oad!Fw(=RM~==ls6&{?7Y+DRwqyJdkf7008h< zm?Iqk0MvK#g4r3Cn}MOxML2xT@hlM31muVdoP$751Kd+|><<9I zJ`{y=4s$@?kP5~HVSW5DQelz77^&+%SRH&ou#`7C49$=LfS<^Bw7h)=#y1@b$pZFa zos)m6yNb?rCH3x}8yy~QQo9#na@CM+KV~27-cB1Z=!l^sCbNDF+Dmj#bz}>I3x_^0 zAvg)97T056s4JBr{dQ+G`4`pGEJvI%xI7mTi)G zk5hEoHwn+lE)#e2b=l%!)zs&Fza$$|Y723;7I=6Ko$GD)Kk<(a1o{v{<7OCxs9bGY~%o`t>$QXwq7xK0SWF`DY;*{6&V=%ZCkAMd42 zw_Ym|?#}&<<^>s~2~y64DXwFh6?@BmXQ>Gqs9rgfhdx-NwkiF zBjc(h-U?X}%4*Ma!|eUSH%FU9s%Ka0cKm^oh5_8RnKzpX`d%bsC_MCO!>${F_UJIM zl~&K=IHquCsMDvnaqPR$jM1wu;rST!QnlsO&-qRzgooNIMKMj+t`}?Ucs?n{LJ2i0 zy`nJ<_GV%ln}aa+%;X%?_0~H5Ly{vUidyY5@O^6-ZRAQ;K=UY8ytetZFPx{)eOQ(U zI_!`ZJ|?tQbMO6xZ*6r#_peSNWpzLI)W5j+IQ^9+yfH5UCFF`p;?74^2DoShI5!K| zNc&*N<)B4U?ADQ>)8jkq@FRv*)tDZ3T-4N8hu79Nj7#SSLY)}s_dKO2cKmt zuKLTBn}^MSKsuI{A_?btX_p;2yrG~q>et@vYA>_WWNAY-JjW=zb}Ld`sLI?+Yu#GJ zu=%A%)D?>Y+^e&(u={zQNHJriiu9&f&h1+dq@ihp5|1QfK^+Uiq$REUUjx9Y2n(bk z3O_-e9ts>*1}@uskSK9fI=!IK*{GnhYEb$|HVxXeWy$FAHW%ZD_&=UE-umyh zF&hn92gG8q7|c5{o8QRvqe5VZM6M*J^Qjv3fnhQql-xt=?yfXO@kT z`9H7EsMW-l!F|-WAPa|=bXZL}>vJ(9Q}y)6Bf4q=ZXpa57fV^wna0%y zlSP%jaCo&J%YfdcGXoRbIyN%9s|081O3{FLroKUh05eis-#8Vs`yj9oA*Z|;WTPTds&mN!Bv_g0F}GaZsq z*%T2_R-7`6YkoG7K++3Ts7i|Gm1f>!TGQ4U+PB-SCE3yZe(yVTLj%?`Qt9YiSaE^% ze$`4aepz>ZF`tWt3|kpX3R@yE?Y1*c{eS20P(s|CTRBW!>CWoLw{393zDN-w^Aw7O zW^of>?@Cy@`bY?Oss&Lc{z6lh1$Z>doSTi++CzLuIRuUG?=RmB){lziHD=*K5!pY6 zFG5n&V3s)dYG{if%0j!RPDONyfeco@v+VkO6h65M1M~d6rghJ;z;yklCxKMTa{e;% zU?gjey4<#fB%<1<8#_)in|QJHtww3=v*BYlBVDc3J+#gv7BxA+&w<|h_f_?bDz z%*T@v-dtQ3f9ir({POev_Odvl^8P!0YBp(?JD%Y`7iQud#`ufC=y>5U3<0Prt7s~y zC@ZLHp;Xj$)HQSv8gOM*9c5*IfeXG&0RchYKG;as02LjC>W2XCsXF2b0t*uxU literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_add_circle_outline.xml b/app/src/main/res/drawable/ic_add_circle_outline.xml new file mode 100644 index 0000000..13c94bf --- /dev/null +++ b/app/src/main/res/drawable/ic_add_circle_outline.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_add_white_24dp.xml b/app/src/main/res/drawable/ic_add_white_24dp.xml new file mode 100644 index 0000000..ba7fc6e --- /dev/null +++ b/app/src/main/res/drawable/ic_add_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_back_white_24dp.xml b/app/src/main/res/drawable/ic_arrow_back_white_24dp.xml new file mode 100644 index 0000000..bd27631 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close_white_24dp.xml b/app/src/main/res/drawable/ic_close_white_24dp.xml new file mode 100644 index 0000000..f107a22 --- /dev/null +++ b/app/src/main/res/drawable/ic_close_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_content_copy_black_24dp.xml b/app/src/main/res/drawable/ic_content_copy_black_24dp.xml new file mode 100644 index 0000000..53a5858 --- /dev/null +++ b/app/src/main/res/drawable/ic_content_copy_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_content_nocopy_black_24dp.xml b/app/src/main/res/drawable/ic_content_nocopy_black_24dp.xml new file mode 100644 index 0000000..73932a1 --- /dev/null +++ b/app/src/main/res/drawable/ic_content_nocopy_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_done_all.xml b/app/src/main/res/drawable/ic_done_all.xml new file mode 100644 index 0000000..d2b3c56 --- /dev/null +++ b/app/src/main/res/drawable/ic_done_all.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_eye.xml b/app/src/main/res/drawable/ic_eye.xml new file mode 100644 index 0000000..1668f11 --- /dev/null +++ b/app/src/main/res/drawable/ic_eye.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_favorite_white_14dp.xml b/app/src/main/res/drawable/ic_favorite_white_14dp.xml new file mode 100644 index 0000000..736aacd --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite_white_14dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_favorite_white_24dp.xml b/app/src/main/res/drawable/ic_favorite_white_24dp.xml new file mode 100644 index 0000000..df182e1 --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_hand.xml b/app/src/main/res/drawable/ic_hand.xml new file mode 100644 index 0000000..c8f20cf --- /dev/null +++ b/app/src/main/res/drawable/ic_hand.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_help_black_24dp.xml b/app/src/main/res/drawable/ic_help_white_24dp.xml similarity index 67% rename from app/src/main/res/drawable/ic_help_black_24dp.xml rename to app/src/main/res/drawable/ic_help_white_24dp.xml index 1517747..d5f3520 100644 --- a/app/src/main/res/drawable/ic_help_black_24dp.xml +++ b/app/src/main/res/drawable/ic_help_white_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + android:fillColor="#FFffffff" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z" /> diff --git a/app/src/main/res/drawable/ic_info_outline_gray_24dp.xml b/app/src/main/res/drawable/ic_info_outline_gray_24dp.xml new file mode 100644 index 0000000..979fc48 --- /dev/null +++ b/app/src/main/res/drawable/ic_info_outline_gray_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_info_black_24dp.xml b/app/src/main/res/drawable/ic_info_white_24dp.xml similarity index 89% rename from app/src/main/res/drawable/ic_info_black_24dp.xml rename to app/src/main/res/drawable/ic_info_white_24dp.xml index 34b8202..449ec46 100644 --- a/app/src/main/res/drawable/ic_info_black_24dp.xml +++ b/app/src/main/res/drawable/ic_info_white_24dp.xml @@ -4,6 +4,6 @@ android:viewportHeight="24.0" android:viewportWidth="24.0"> diff --git a/app/src/main/res/drawable/ic_key.xml b/app/src/main/res/drawable/ic_key.xml new file mode 100644 index 0000000..ac50e39 --- /dev/null +++ b/app/src/main/res/drawable/ic_key.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml new file mode 100644 index 0000000..a3d1622 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_logo_horizontol_xmrujo.xml b/app/src/main/res/drawable/ic_logo_horizontol_xmrujo.xml new file mode 100644 index 0000000..cf1dde3 --- /dev/null +++ b/app/src/main/res/drawable/ic_logo_horizontol_xmrujo.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + 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 new file mode 100644 index 0000000..2483512 --- /dev/null +++ b/app/src/main/res/drawable/ic_navigate_next_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_new.xml b/app/src/main/res/drawable/ic_new.xml new file mode 100644 index 0000000..eb0b47b --- /dev/null +++ b/app/src/main/res/drawable/ic_new.xml @@ -0,0 +1,41 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml new file mode 100644 index 0000000..dfe72fe --- /dev/null +++ b/app/src/main/res/drawable/ic_scan.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_seed.xml b/app/src/main/res/drawable/ic_seed.xml new file mode 100644 index 0000000..ba29504 --- /dev/null +++ b/app/src/main/res/drawable/ic_seed.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_send.xml b/app/src/main/res/drawable/ic_send.xml new file mode 100644 index 0000000..d42e154 --- /dev/null +++ b/app/src/main/res/drawable/ic_send.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings_orange_24dp.xml b/app/src/main/res/drawable/ic_settings_orange_24dp.xml new file mode 100644 index 0000000..090a85e --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_orange_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_share_white_24dp.xml b/app/src/main/res/drawable/ic_share_white_24dp.xml new file mode 100644 index 0000000..874169d --- /dev/null +++ b/app/src/main/res/drawable/ic_share_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_traffic.xml b/app/src/main/res/drawable/ic_traffic.xml new file mode 100644 index 0000000..e7c45cd --- /dev/null +++ b/app/src/main/res/drawable/ic_traffic.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/selector_login.xml b/app/src/main/res/drawable/selector_login.xml new file mode 100644 index 0000000..25d6816 --- /dev/null +++ b/app/src/main/res/drawable/selector_login.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/texture_cash.png b/app/src/main/res/drawable/texture_cash.png new file mode 100644 index 0000000000000000000000000000000000000000..bcd550b09b860c1e4014c126a47dc5b71c44d51c GIT binary patch literal 49327 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/login_activity.xml b/app/src/main/res/layout/activity_login.xml similarity index 57% rename from app/src/main/res/layout/login_activity.xml rename to app/src/main/res/layout/activity_login.xml index 49d3f66..7b15569 100644 --- a/app/src/main/res/layout/login_activity.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -2,11 +2,17 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +