diff --git a/app/build.gradle b/app/build.gradle index 999189e9..8ab7b8ab 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 8d7c6431..16387bf5 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 4891fdeb..571c26d7 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 71156dde..28d9242b 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 f51979e5..fde8cb4c 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 38153c5c..80e2412e 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 668f1721..02405957 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 5f135e27..bad8694e 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 5cebf19d..88c1eafc 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 4dfb3329..8890192e 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 b9f205f1..d8acead1 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 72751f89..75a15e66 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 f9c0794a..d2fcbab5 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 00000000..9d563644 --- /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 00000000..6be40f04 --- /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 00000000..39b4c02e --- /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 00000000..4da1b17b --- /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 7e833005..3ea8ea6b 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 00000000..1ac2c932 --- /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 e6c55f4a..efee88a5 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 141b09fa..40a0b7dc 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 479922ca..7c506735 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 791ad7eb..5d48b603 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 782ea3e2..59924ab0 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 f036a6aa..6989bf95 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 00000000..9dd7e780 --- /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 00000000..ec4ee1ac --- /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 00000000..5ac74ac0 --- /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 00000000..ceb2831a --- /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 00000000..fed9d931 --- /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 00000000..47877dd4 --- /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 bdb0a30f..97f46e94 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 00000000..7e94c2e7 --- /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 00000000..8fe9ec70 --- /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 00000000..255a292d --- /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 00000000..bb134fc4 --- /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 00000000..4e227118 --- /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 00000000..5d19b551 --- /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 00000000..a337794a --- /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 00000000..4186e72b --- /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 00000000..3363a24a --- /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 00000000..4675e861 --- /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 00000000..88749402 --- /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 00000000..9873abda --- /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 00000000..acb51624 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_00.png differ 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 00000000..eaaabcdc Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_01.png differ 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 00000000..860705d6 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_02.png differ 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 00000000..eaaabcdc Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_03.png differ 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 00000000..530ef697 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_04.png differ 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 00000000..e0e69e19 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_05.png differ 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 00000000..a6166469 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_06.png differ diff --git a/app/src/main/res/drawable/gunther_wallets_07.png b/app/src/main/res/drawable/gunther_wallets_07.png new file mode 100644 index 00000000..534d353a Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_07.png differ 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 00000000..7b11c839 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_08.png differ diff --git a/app/src/main/res/drawable/gunther_wallets_09.png b/app/src/main/res/drawable/gunther_wallets_09.png new file mode 100644 index 00000000..1d3ce00e Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_09.png differ 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 00000000..1f013994 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_10.png differ 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 00000000..7135fd03 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_11.png differ 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 00000000..92bb8ac1 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_12.png differ 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 00000000..9eed5c39 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_13.png differ diff --git a/app/src/main/res/drawable/gunther_wallets_14.png b/app/src/main/res/drawable/gunther_wallets_14.png new file mode 100644 index 00000000..c6b4f890 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_14.png differ 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 00000000..e878836d Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_15.png differ 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 00000000..5a598b45 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_16.png differ diff --git a/app/src/main/res/drawable/gunther_wallets_17.png b/app/src/main/res/drawable/gunther_wallets_17.png new file mode 100644 index 00000000..5a598b45 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_17.png differ diff --git a/app/src/main/res/drawable/gunther_wallets_18.png b/app/src/main/res/drawable/gunther_wallets_18.png new file mode 100644 index 00000000..5a598b45 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_18.png differ diff --git a/app/src/main/res/drawable/gunther_wallets_19.png b/app/src/main/res/drawable/gunther_wallets_19.png new file mode 100644 index 00000000..e878836d Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_19.png differ 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 00000000..92bb8ac1 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_20.png differ 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 00000000..1f013994 Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_21.png differ 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 00000000..eaaabcdc Binary files /dev/null and b/app/src/main/res/drawable/gunther_wallets_22.png differ 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 00000000..13c94bf1 --- /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 00000000..ba7fc6e4 --- /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 00000000..bd276311 --- /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 00000000..f107a22f --- /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 00000000..53a58588 --- /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 00000000..73932a18 --- /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 00000000..d2b3c562 --- /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 00000000..1668f114 --- /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 00000000..736aacd7 --- /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 00000000..df182e10 --- /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 00000000..c8f20cf5 --- /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 1517747d..d5f35201 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 00000000..979fc481 --- /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 34b8202e..449ec46b 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 00000000..ac50e393 --- /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 00000000..a3d16222 --- /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 00000000..cf1dde3c --- /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 00000000..24835127 --- /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 00000000..eb0b47bb --- /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 00000000..dfe72fe9 --- /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 00000000..ba295044 --- /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 00000000..d42e1543 --- /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 00000000..090a85e8 --- /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 00000000..874169d9 --- /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 00000000..e7c45cd4 --- /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 00000000..25d68162 --- /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 00000000..bcd550b0 Binary files /dev/null and b/app/src/main/res/drawable/texture_cash.png differ 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 49d3f66c..7b155697 100644 --- a/app/src/main/res/layout/login_activity.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -2,11 +2,17 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +