1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-04 00:53:36 +02:00

Compare commits

...

34 Commits

Author SHA1 Message Date
m2049r
091538752b add our stagenet server to defaults (#321) 2018-06-17 13:53:01 +02:00
m2049r
d5b95dd976 new version v1.5.7 2018-06-17 13:31:33 +02:00
m2049r
008f06959c remove mixin settings (#320) 2018-06-17 13:22:29 +02:00
m2049r
817816cd34 Layout tweaks (#319)
* sweep layout tweak

* reduce label to fit

* change sweep message
2018-06-17 13:21:59 +02:00
m2049r
9d41d5da52 new version v1.5.6 2018-06-16 15:27:28 +02:00
m2049r
ad76a7ffc1 Fixes to v1.5.5 (#318)
* fix disappearing hamburger

* german fixes to fit space
2018-06-16 15:20:13 +02:00
el00ruobuob
475542c4f3 French Strings updated to #297 (#316)
+ Missing update to #312
+ enhancement on "wrapping things up"
2018-06-16 15:19:40 +02:00
m2049r
b5d0659ca9 sweep all (#317) 2018-06-16 15:17:48 +02:00
m2049r
781bfbc78b Update FAQ.md 2018-06-14 23:40:41 +02:00
m2049r
8985511209 fix length test (#314) 2018-06-14 22:28:33 +02:00
m2049r
3c8a4ce967 Merge branch 'master' of https://github.com/m2049r/xmrwallet 2018-06-14 21:59:48 +02:00
m2049r
fcfedbcfae Fix balance (#313)
* new version

* fix balance
2018-06-14 22:00:15 +02:00
m2049r
74279b135a new version 2018-06-14 21:37:19 +02:00
0140454
d6d2de8312 Inform user the progress or result of opening wallet (#297) 2018-06-14 21:33:23 +02:00
m2049r
af0ecb2894 accounts (#312) 2018-06-14 21:32:52 +02:00
m2049r
975cc4f43c show correct amount for pending tx (#309) 2018-06-11 10:21:22 +02:00
0140454
74ba36de26 Use FingerprintManager instead of FingerprintManagerCompat (Fix #300) (#302) 2018-06-10 10:59:20 +02:00
m2049r
7627e15a48 Fix keystore null (#308)
* avoid crash if input to large

* avoid NPE if wallet key not found
2018-06-10 10:57:24 +02:00
m2049r
37244cb9e0 coinmarketcap for exchange rates (#304) 2018-06-10 10:56:46 +02:00
m2049r
843566b820 spinner tweaks (#306) 2018-06-09 12:42:09 +02:00
m2049r
0bcf156929 update gradle version (#305) 2018-06-09 10:21:44 +02:00
m2049r
b1d91e2671 new version 2018-05-27 10:59:10 +02:00
m2049r
271cd2d4a8 deal with all broken variants (#292)
* remove variant code for arm32

* deal with all broken variants
2018-05-25 23:44:37 +02:00
m2049r
22c5a543db upgrade to v0.12.1.0 (#291) 2018-05-25 22:38:05 +02:00
m2049r
cd986860c5 remove variant code for arm32 (#290) 2018-05-25 22:37:50 +02:00
m2049r
0cf5981eae Fixes "Invalid Password" although password correct (#289)
* don't log warning

* fix cn_slow_hash variant&prehash
cn_slow_hash signature was changed in monero-core but the linker didn't
notice - also added code to support wallets created with variant &
prehash enabled
2018-05-25 18:20:48 +02:00
m2049r
e109df34f0 remove if save fails (#281) 2018-05-25 18:20:27 +02:00
m2049r
5a7aa6cc77 testnet => stagenet (#288) 2018-05-25 18:19:29 +02:00
m2049r
f50629ff81 check for encoded pw (#280) 2018-05-10 15:26:44 +02:00
m2049r
cb12d64e5f Various Fixes (#279)
* load password only if it's passed

* cancel fingerprint if password entered

* rework fingerprint code

* cleanup unused params

* new version code
2018-05-10 13:48:11 +02:00
m2049r
a8f08fb9b9 new version 2018-05-06 17:46:48 +02:00
m2049r
3e9be418a8 removed removed strings (#276) 2018-05-06 12:30:01 +02:00
KillASIC.com
fa5dc9988d Monero translation to simplified chinese. (#263) 2018-05-06 12:17:09 +02:00
m2049r
857cf8d6d8 Random fixes (#275)
* reduce label length

* api fix

* show correct password after change
2018-05-06 12:12:53 +02:00
68 changed files with 2123 additions and 526 deletions

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 25
versionCode 91
versionName "1.5.1 'CrAzY Nacho'"
versionCode 97
versionName "1.5.7 'Maximum Nacho'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {

View File

@@ -551,9 +551,10 @@ Java_com_m2049r_xmrwallet_model_Wallet_setPassword(JNIEnv *env, jobject instance
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getAddressJ(JNIEnv *env, jobject instance) {
Java_com_m2049r_xmrwallet_model_Wallet_getAddressJ(JNIEnv *env, jobject instance,
jint accountIndex) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return env->NewStringUTF(wallet->address().c_str());
return env->NewStringUTF(wallet->address((uint32_t) accountIndex).c_str());
}
JNIEXPORT jstring JNICALL
@@ -646,15 +647,29 @@ Java_com_m2049r_xmrwallet_model_Wallet_getConnectionStatusJ(JNIEnv *env, jobject
//TODO virtual bool trustedDaemon() const = 0;
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance) {
Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance,
jint accountIndex) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->balance();
return wallet->balance((uint32_t) accountIndex);
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getUnlockedBalance(JNIEnv *env, jobject instance) {
Java_com_m2049r_xmrwallet_model_Wallet_getBalanceAll(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->unlockedBalance();
return wallet->balanceAll();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getUnlockedBalance(JNIEnv *env, jobject instance,
jint accountIndex) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->unlockedBalance((uint32_t) accountIndex);
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getUnlockedBalanceAll(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->unlockedBalanceAll();
}
JNIEXPORT jboolean JNICALL
@@ -697,16 +712,26 @@ Java_com_m2049r_xmrwallet_model_Wallet_isSynchronized(JNIEnv *env, jobject insta
//void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h
JNIEXPORT jbyteArray JNICALL
Java_com_m2049r_xmrwallet_util_KeyStoreHelper_cnSlowHash(JNIEnv *env, jobject clazz,
jbyteArray data) {
Java_com_m2049r_xmrwallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jobject clazz,
jbyteArray data, jint brokenVariant) {
char hash[HASH_SIZE];
jsize size = env->GetArrayLength(data);
if ((brokenVariant > 0) && (size < 200 /*sizeof(union hash_state)*/)) {
return nullptr;
}
jbyte *buffer = env->GetByteArrayElements(data, NULL);
jsize size = env->GetArrayLength(data);
char hash[HASH_SIZE];
cn_slow_hash(buffer, (size_t) size, hash);
switch (brokenVariant) {
case 1:
slow_hash_broken(buffer, hash, 1);
break;
case 2:
slow_hash_broken(buffer, hash, 0);
break;
default: // not broken
slow_hash(buffer, (size_t) size, hash);
}
env->ReleaseByteArrayElements(data, buffer, JNI_ABORT); // do not update java byte[]
jbyteArray result = env->NewByteArray(HASH_SIZE);
env->SetByteArrayRegion(result, 0, HASH_SIZE, (jbyte *) hash);
return result;
@@ -757,8 +782,6 @@ Java_com_m2049r_xmrwallet_model_Wallet_isAddressValid(JNIEnv *env, jobject clazz
return static_cast<jboolean>(isValid);
}
//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getPaymentIdFromAddress(JNIEnv *env, jobject clazz,
jstring address,
@@ -806,7 +829,8 @@ JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_createTransactionJ(JNIEnv *env, jobject instance,
jstring dst_addr, jstring payment_id,
jlong amount, jint mixin_count,
jint priority) {
jint priority,
jint accountIndex) {
const char *_dst_addr = env->GetStringUTFChars(dst_addr, NULL);
const char *_payment_id = env->GetStringUTFChars(payment_id, NULL);
@@ -815,8 +839,34 @@ Java_com_m2049r_xmrwallet_model_Wallet_createTransactionJ(JNIEnv *env, jobject i
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
Bitmonero::PendingTransaction *tx = wallet->createTransaction(_dst_addr, _payment_id,
amount, mixin_count,
_priority);
amount, (uint32_t) mixin_count,
_priority,
(uint32_t) accountIndex);
env->ReleaseStringUTFChars(dst_addr, _dst_addr);
env->ReleaseStringUTFChars(payment_id, _payment_id);
return reinterpret_cast<jlong>(tx);
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_createSweepTransaction(JNIEnv *env, jobject instance,
jstring dst_addr, jstring payment_id,
jint mixin_count,
jint priority,
jint accountIndex) {
const char *_dst_addr = env->GetStringUTFChars(dst_addr, NULL);
const char *_payment_id = env->GetStringUTFChars(payment_id, NULL);
Bitmonero::PendingTransaction::Priority _priority =
static_cast<Bitmonero::PendingTransaction::Priority>(priority);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
Monero::optional<uint64_t> empty;
Bitmonero::PendingTransaction *tx = wallet->createTransaction(_dst_addr, _payment_id,
empty, (uint32_t) mixin_count,
_priority,
(uint32_t) accountIndex);
env->ReleaseStringUTFChars(dst_addr, _dst_addr);
env->ReleaseStringUTFChars(payment_id, _payment_id);
@@ -935,6 +985,54 @@ Java_com_m2049r_xmrwallet_model_Wallet_getTxKey(JNIEnv *env, jobject instance,
return env->NewStringUTF(txKey.c_str());
}
//virtual void addSubaddressAccount(const std::string& label) = 0;
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_addAccount(JNIEnv *env, jobject instance,
jstring label) {
const char *_label = env->GetStringUTFChars(label, NULL);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->addSubaddressAccount(_label);
env->ReleaseStringUTFChars(label, _label);
}
//virtual std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const = 0;
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getSubaddressLabel(JNIEnv *env, jobject instance,
jint accountIndex, jint addressIndex) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
std::string label = wallet->getSubaddressLabel((uint32_t) accountIndex,
(uint32_t) addressIndex);
return env->NewStringUTF(label.c_str());
}
//virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0;
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_setSubaddressLabel(JNIEnv *env, jobject instance,
jint accountIndex, jint addressIndex,
jstring label) {
const char *_label = env->GetStringUTFChars(label, NULL);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->setSubaddressLabel(accountIndex, addressIndex, _label);
env->ReleaseStringUTFChars(label, _label);
}
// virtual size_t numSubaddressAccounts() const = 0;
JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_numSubaddressAccounts(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->numSubaddressAccounts();
}
//virtual std::string signMessage(const std::string &message) = 0;
//virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0;
@@ -981,7 +1079,7 @@ jobject newTransferList(JNIEnv *env, Bitmonero::TransactionInfo *info) {
jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) {
jmethodID c = env->GetMethodID(class_TransactionInfo, "<init>",
"(IZZJJJLjava/lang/String;JLjava/lang/String;JLjava/util/List;)V");
"(IZZJJJLjava/lang/String;JLjava/lang/String;IJLjava/util/List;)V");
jobject transfers = newTransferList(env, info);
jstring _hash = env->NewStringUTF(info->hash().c_str());
jstring _paymentId = env->NewStringUTF(info->paymentId().c_str());
@@ -995,6 +1093,7 @@ jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) {
_hash,
static_cast<jlong> (info->timestamp()),
_paymentId,
info->subaddrAccount(),
info->confirmations(),
transfers);
env->DeleteLocalRef(transfers);
@@ -1170,7 +1269,6 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jobject i
Bitmonero::WalletManagerFactory::setLogLevel(level);
}
#ifdef __cplusplus
}
#endif

View File

@@ -60,7 +60,15 @@ enum {
HASH_DATA_AREA = 136
};
void cn_slow_hash(const void *data, size_t length, char *hash);
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed);
inline void slow_hash(const void *data, const size_t length, char *hash) {
cn_slow_hash(data, length, hash, 0 /* variant */, 0/*prehashed*/);
}
inline void slow_hash_broken(const void *data, char *hash, int variant) {
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/);
}
#ifdef __cplusplus
}

View File

@@ -53,7 +53,6 @@ import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.io.File;
import java.security.KeyStoreException;
import timber.log.Timber;
@@ -62,27 +61,26 @@ public class GenerateReviewFragment extends Fragment {
static final public String VIEW_TYPE_ACCEPT = "accept";
static final public String VIEW_TYPE_WALLET = "wallet";
ScrollView scrollview;
public static final String REQUEST_TYPE = "type";
public static final String REQUEST_PATH = "path";
public static final String REQUEST_PASSWORD = "password";
ProgressBar pbProgress;
TextView tvWalletPassword;
TextView tvWalletAddress;
TextView tvWalletMnemonic;
TextView tvWalletViewKey;
TextView tvWalletSpendKey;
ImageButton bCopyAddress;
LinearLayout llAdvancedInfo;
LinearLayout llPassword;
Button bAdvancedInfo;
Button bAccept;
private ScrollView scrollview;
// TODO fix visibility of variables
String walletPath;
String walletName;
// we need to keep the password so the user is not asked again if they want to change it
// note they can only enter this fragment immediately after entering the password
// so asking them to enter it a couple of seconds later seems silly
String walletPassword = null;
private ProgressBar pbProgress;
private TextView tvWalletPassword;
private TextView tvWalletAddress;
private TextView tvWalletMnemonic;
private TextView tvWalletViewKey;
private TextView tvWalletSpendKey;
private ImageButton bCopyAddress;
private LinearLayout llAdvancedInfo;
private LinearLayout llPassword;
private Button bAdvancedInfo;
private Button bAccept;
private String walletPath;
private String walletName;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -104,10 +102,10 @@ public class GenerateReviewFragment extends Fragment {
bAccept = (Button) view.findViewById(R.id.bAccept);
boolean testnet = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
tvWalletMnemonic.setTextIsSelectable(testnet);
tvWalletSpendKey.setTextIsSelectable(testnet);
tvWalletPassword.setTextIsSelectable(testnet);
boolean allowCopy = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
tvWalletMnemonic.setTextIsSelectable(allowCopy);
tvWalletSpendKey.setTextIsSelectable(allowCopy);
tvWalletPassword.setTextIsSelectable(allowCopy);
bAccept.setOnClickListener(new View.OnClickListener() {
@Override
@@ -136,14 +134,14 @@ public class GenerateReviewFragment extends Fragment {
});
Bundle args = getArguments();
type = args.getString("type");
walletPath = args.getString("path");
showDetails(args.getString("password"));
type = args.getString(REQUEST_TYPE);
walletPath = args.getString(REQUEST_PATH);
localPassword = args.getString(REQUEST_PASSWORD);
showDetails();
return view;
}
void showDetails(String password) {
walletPassword = password;
void showDetails() {
showProgress();
tvWalletPassword.setText(null);
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath);
@@ -178,7 +176,7 @@ public class GenerateReviewFragment extends Fragment {
private void acceptWallet() {
bAccept.setEnabled(false);
acceptCallback.onAccept(walletName, walletPassword);
acceptCallback.onAccept(walletName, getPassword());
}
private class AsyncShow extends AsyncTask<String, Void, Boolean> {
@@ -201,7 +199,7 @@ public class GenerateReviewFragment extends Fragment {
wallet = GenerateReviewFragment.this.walletCallback.getWallet();
closeWallet = false;
} else {
wallet = WalletManager.getInstance().openWallet(walletPath, walletPassword);
wallet = WalletManager.getInstance().openWallet(walletPath, getPassword());
closeWallet = true;
}
name = wallet.getName();
@@ -231,10 +229,8 @@ public class GenerateReviewFragment extends Fragment {
bAccept.setVisibility(View.VISIBLE);
bAccept.setEnabled(true);
}
if (walletPassword != null) {
llPassword.setVisibility(View.VISIBLE);
tvWalletPassword.setText(walletPassword);
}
llPassword.setVisibility(View.VISIBLE);
tvWalletPassword.setText(getPassword());
tvWalletAddress.setText(address);
tvWalletMnemonic.setText(seed);
tvWalletViewKey.setText(viewKey);
@@ -260,6 +256,7 @@ public class GenerateReviewFragment extends Fragment {
ProgressListener progressCallback = null;
AcceptListener acceptCallback = null;
ListenerWithWallet walletCallback = null;
PasswordChangedListener passwordCallback = null;
public interface Listener {
void setTitle(String title, String subtitle);
@@ -282,6 +279,24 @@ public class GenerateReviewFragment extends Fragment {
Wallet getWallet();
}
public interface PasswordChangedListener {
void onPasswordChanged(String newPassword);
String getPassword();
}
private String localPassword = null;
private String getPassword() {
if (passwordCallback != null) return passwordCallback.getPassword();
return localPassword;
}
private void setPassword(String password) {
if (passwordCallback != null) passwordCallback.onPasswordChanged(password);
else localPassword = password;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -297,6 +312,9 @@ public class GenerateReviewFragment extends Fragment {
if (context instanceof ListenerWithWallet) {
this.walletCallback = (ListenerWithWallet) context;
}
if (context instanceof PasswordChangedListener) {
this.passwordCallback = (PasswordChangedListener) context;
}
}
@Override
@@ -328,7 +346,7 @@ public class GenerateReviewFragment extends Fragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
String type = getArguments().getString("type");
String type = getArguments().getString(REQUEST_TYPE); // intance variable <type> not set yet
if (GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type)) {
inflater.inflate(R.menu.wallet_details_help_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
@@ -345,7 +363,7 @@ public class GenerateReviewFragment extends Fragment {
wallet = GenerateReviewFragment.this.walletCallback.getWallet();
closeWallet = false;
} else {
wallet = WalletManager.getInstance().openWallet(walletPath, walletPassword);
wallet = WalletManager.getInstance().openWallet(walletPath, getPassword());
closeWallet = true;
}
@@ -373,19 +391,19 @@ public class GenerateReviewFragment extends Fragment {
@Override
protected Boolean doInBackground(String... params) {
if (params.length != 4) return false;
File walletFile = Helper.getWalletFile(getActivity(), params[0]);
String oldPassword = params[1];
String userPassword = params[2];
boolean fingerprintAuthAllowed = Boolean.valueOf(params[3]);
if (params.length != 2) return false;
final String userPassword = params[0];
final boolean fingerPassValid = Boolean.valueOf(params[1]);
newPassword = KeyStoreHelper.getCrazyPass(getActivity(), userPassword);
boolean success = changeWalletPassword(newPassword);
final boolean success = changeWalletPassword(newPassword);
if (success) {
if (fingerprintAuthAllowed) {
KeyStoreHelper.saveWalletUserPass(getActivity(), walletName, userPassword);
} else {
KeyStoreHelper.removeWalletUserPass(getActivity(), walletName);
}
Context ctx = getActivity();
if (ctx != null)
if (fingerPassValid) {
KeyStoreHelper.saveWalletUserPass(ctx, walletName, userPassword);
} else {
KeyStoreHelper.removeWalletUserPass(ctx, walletName);
}
}
return success;
}
@@ -393,14 +411,15 @@ public class GenerateReviewFragment extends Fragment {
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (getActivity().isDestroyed()) {
if ((getActivity() == null) || getActivity().isDestroyed()) {
return;
}
if (progressCallback != null)
progressCallback.dismissProgressDialog();
if (result) {
Toast.makeText(getActivity(), getString(R.string.changepw_success), Toast.LENGTH_SHORT).show();
showDetails(newPassword);
setPassword(newPassword);
showDetails();
} else {
Toast.makeText(getActivity(), getString(R.string.changepw_failed), Toast.LENGTH_LONG).show();
}
@@ -447,11 +466,7 @@ public class GenerateReviewFragment extends Fragment {
}
});
try {
swFingerprintAllowed.setChecked(FingerprintHelper.isFingerprintAuthAllowed(walletName));
} catch (KeyStoreException ex) {
ex.printStackTrace();
}
swFingerprintAllowed.setChecked(FingerprintHelper.isFingerPassValid(getActivity(), walletName));
}
etPasswordA.getEditText().addTextChangedListener(new TextWatcher() {
@@ -527,7 +542,7 @@ public class GenerateReviewFragment extends Fragment {
} else if (!newPasswordA.equals(newPasswordB)) {
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
} else if (newPasswordA.equals(newPasswordB)) {
new AsyncChangePassword().execute(walletName, walletPassword, newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
Helper.hideKeyboardAlways(getActivity());
openDialog.dismiss();
openDialog = null;
@@ -549,7 +564,7 @@ public class GenerateReviewFragment extends Fragment {
} else if (!newPasswordA.equals(newPasswordB)) {
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
} else if (newPasswordA.equals(newPasswordB)) {
new AsyncChangePassword().execute(walletName, walletPassword, newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
Helper.hideKeyboardAlways(getActivity());
openDialog.dismiss();
openDialog = null;

View File

@@ -50,7 +50,6 @@ import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.FingerprintHelper;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
@@ -63,7 +62,6 @@ import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.channels.FileChannel;
import java.security.KeyStoreException;
import java.util.Date;
import timber.log.Timber;
@@ -229,17 +227,20 @@ public class LoginActivity extends SecureActivity
@Override
protected Boolean doInBackground(String... params) {
if (params.length != 2) return false;
File walletFile = Helper.getWalletFile(LoginActivity.this, params[0]);
String oldName = params[0];
String newName = params[1];
File walletFile = Helper.getWalletFile(LoginActivity.this, oldName);
boolean success = renameWallet(walletFile, newName);
try {
if (success && FingerprintHelper.isFingerprintAuthAllowed(params[0])) {
String savedPass = KeyStoreHelper.loadWalletUserPass(LoginActivity.this, params[0]);
if (success) {
String savedPass = KeyStoreHelper.loadWalletUserPass(LoginActivity.this, oldName);
KeyStoreHelper.saveWalletUserPass(LoginActivity.this, newName, savedPass);
KeyStoreHelper.removeWalletUserPass(LoginActivity.this, params[0]);
}
} catch (KeyStoreException ex) {
ex.printStackTrace();
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
Timber.w(ex);
} finally {
// we have either set a new password or it is broken - kill the old one either way
KeyStoreHelper.removeWalletUserPass(LoginActivity.this, oldName);
}
return success;
}
@@ -1006,11 +1007,11 @@ public class LoginActivity extends SecureActivity
case R.id.action_privacy_policy:
PrivacyFragment.display(getSupportFragmentManager());
return true;
case R.id.action_testnet:
case R.id.action_stagenet:
try {
LoginFragment loginFragment = (LoginFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
item.setChecked(loginFragment.onTestnetMenuItem());
item.setChecked(loginFragment.onStagenetMenuItem());
} catch (ClassCastException ex) {
// never mind then
}

View File

@@ -18,15 +18,12 @@ package com.m2049r.xmrwallet;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.os.Bundle;
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.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -41,14 +38,11 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.WalletManager;
@@ -343,69 +337,65 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.list_menu, menu);
menu.findItem(R.id.action_testnet).setChecked(testnetCheckMenu);
menu.findItem(R.id.action_stagenet).setChecked(stagenetCheckMenu);
super.onCreateOptionsMenu(menu, inflater);
}
private boolean testnetCheckMenu = BuildConfig.DEBUG;
private boolean stagenetCheckMenu = BuildConfig.DEBUG;
//boolean isTestnet() {
// return testnet;
//}
public boolean onTestnetMenuItem() {
boolean lastState = testnetCheckMenu;
public boolean onStagenetMenuItem() {
boolean lastState = stagenetCheckMenu;
setNet(!lastState, true); // set and save
return !lastState;
}
public void setNet(boolean testnetChecked, boolean save) {
this.testnetCheckMenu = testnetChecked;
NetworkType net = testnetChecked ? NetworkType.NetworkType_Testnet : NetworkType.NetworkType_Mainnet;
public void setNet(boolean stagenetChecked, boolean save) {
this.stagenetCheckMenu = stagenetChecked;
NetworkType net = stagenetChecked ? NetworkType.NetworkType_Stagenet : NetworkType.NetworkType_Mainnet;
activityCallback.setNetworkType(net);
activityCallback.showNet();
if (save) {
savePrefs(true); // use previous state as we just clicked it
}
if (testnetChecked) {
setDaemon(daemonTestNet);
if (stagenetChecked) {
setDaemon(daemonStageNet);
} else {
setDaemon(daemonMainNet);
}
loadList();
}
private static final String PREF_DAEMON_TESTNET = "daemon_testnet";
private static final String PREF_DAEMON_STAGENET = "daemon_stagenet";
private static final String PREF_DAEMON_MAINNET = "daemon_mainnet";
private static final String PREF_DAEMONLIST_MAINNET =
"node.moneroworld.com:18089;node.xmrbackb.one;node.xmr.be";
private static final String PREF_DAEMONLIST_TESTNET =
"testnet.xmrchain.net";
private static final String PREF_DAEMONLIST_STAGENET =
"stagenet.monerujo.io;stagenet.xmr-tw.org";
private NodeList daemonTestNet;
private NodeList daemonStageNet;
private NodeList daemonMainNet;
void loadPrefs() {
SharedPreferences sharedPref = activityCallback.getPrefs();
daemonMainNet = new NodeList(sharedPref.getString(PREF_DAEMON_MAINNET, PREF_DAEMONLIST_MAINNET));
daemonTestNet = new NodeList(sharedPref.getString(PREF_DAEMON_TESTNET, PREF_DAEMONLIST_TESTNET));
setNet(testnetCheckMenu, false);
daemonStageNet = new NodeList(sharedPref.getString(PREF_DAEMON_STAGENET, PREF_DAEMONLIST_STAGENET));
setNet(stagenetCheckMenu, false);
}
void savePrefs() {
savePrefs(false);
}
void savePrefs(boolean usePreviousTestnetState) {
Timber.d("SAVE / %s", usePreviousTestnetState);
void savePrefs(boolean usePreviousNetState) {
Timber.d("SAVE / %s", usePreviousNetState);
// save the daemon address for the net
boolean testnet = testnetCheckMenu ^ usePreviousTestnetState;
boolean stagenet = stagenetCheckMenu ^ usePreviousNetState;
String daemon = getDaemon();
if (testnet) {
daemonTestNet.setRecent(daemon);
if (stagenet) {
daemonStageNet.setRecent(daemon);
} else {
daemonMainNet.setRecent(daemon);
}
@@ -413,7 +403,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
SharedPreferences sharedPref = activityCallback.getPrefs();
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(PREF_DAEMON_MAINNET, daemonMainNet.toString());
editor.putString(PREF_DAEMON_TESTNET, daemonTestNet.toString());
editor.putString(PREF_DAEMON_STAGENET, daemonStageNet.toString());
editor.apply();
}

View File

@@ -59,6 +59,7 @@ public class TxFragment extends Fragment {
TS_FORMATTER.setTimeZone(tz);
}
private TextView tvAccount;
private TextView tvTxTimestamp;
private TextView tvTxId;
private TextView tvTxKey;
@@ -88,6 +89,7 @@ public class TxFragment extends Fragment {
tvDestinationBtc = (TextView) view.findViewById(R.id.tvDestinationBtc);
tvTxAmountBtc = (TextView) view.findViewById(R.id.tvTxAmountBtc);
tvAccount = (TextView) view.findViewById(R.id.tvAccount);
tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
tvTxId = (TextView) view.findViewById(R.id.tvTxId);
tvTxKey = (TextView) view.findViewById(R.id.tvTxKey);
@@ -222,6 +224,8 @@ public class TxFragment extends Fragment {
activityCallback.setSubtitle(getString(R.string.tx_title));
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
tvAccount.setText("" + info.subaddrAccount);
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
tvTxId.setText(info.hash);
tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey);
@@ -236,9 +240,6 @@ public class TxFragment extends Fragment {
String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-");
long realAmount = info.amount;
if (info.isPending) {
realAmount = realAmount - info.fee;
}
tvTxAmount.setText(sign + Wallet.getDisplayAmount(realAmount));
if ((info.fee > 0)) {

File diff suppressed because it is too large Load Diff

View File

@@ -43,9 +43,7 @@ import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.NumberFormat;
@@ -101,7 +99,9 @@ public class WalletFragment extends Fragment
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_balance));
ArrayAdapter currencyAdapter = ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner_balance);
currencyAdapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item);
sCurrency.setAdapter(currencyAdapter);
bSend = (Button) view.findViewById(R.id.bSend);
bReceive = (Button) view.findViewById(R.id.bReceive);
@@ -150,7 +150,7 @@ public class WalletFragment extends Fragment
// at this point selection is XMR in case of error
String displayB;
double amountA = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // crash if this fails!
if (!"XMR".equals(balanceCurrency)) { // not XMR
if (!Helper.CRYPTO.equals(balanceCurrency)) { // not XMR
double amountB = amountA * balanceRate;
displayB = Helper.getFormattedAmount(amountB, false);
} else { // XMR
@@ -159,10 +159,10 @@ public class WalletFragment extends Fragment
tvBalance.setText(displayB);
}
String balanceCurrency = "XMR";
String balanceCurrency = Helper.CRYPTO;
double balanceRate = 1.0;
private final ExchangeApi exchangeApi = new ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient());
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
void refreshBalance() {
if (sCurrency.getSelectedItemPosition() == 0) { // XMR
@@ -170,9 +170,10 @@ public class WalletFragment extends Fragment
tvBalance.setText(Helper.getFormattedAmount(amountXmr, true));
} else { // not XMR
String currency = (String) sCurrency.getSelectedItem();
Timber.d(currency);
if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) {
showExchanging();
exchangeApi.queryExchangeRate("XMR", currency,
exchangeApi.queryExchangeRate(Helper.CRYPTO, currency,
new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
@@ -228,10 +229,10 @@ public class WalletFragment extends Fragment
public void exchange(final ExchangeRate exchangeRate) {
hideExchanging();
if (!"XMR".equals(exchangeRate.getBaseCurrency())) {
if (!Helper.CRYPTO.equals(exchangeRate.getBaseCurrency())) {
Timber.e("Not XMR");
sCurrency.setSelection(0, true);
balanceCurrency = "XMR";
balanceCurrency = Helper.CRYPTO;
balanceRate = 1.0;
} else {
int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency());
@@ -256,7 +257,7 @@ public class WalletFragment extends Fragment
// called from activity
public void onRefreshed(final Wallet wallet, final boolean full) {
Timber.d("onRefreshed()");
Timber.d("onRefreshed(%b)", full);
if (full) {
List<TransactionInfo> list = wallet.getHistory().getAll();
adapter.setInfos(list);
@@ -270,6 +271,7 @@ public class WalletFragment extends Fragment
bSend.setVisibility(View.VISIBLE);
bSend.setEnabled(true);
}
enableAccountsList(true);
}
boolean walletLoaded = false;
@@ -313,7 +315,7 @@ public class WalletFragment extends Fragment
if (wallet == null) return;
walletTitle = wallet.getName();
String watchOnly = (wallet.isWatchOnly() ? getString(R.string.label_watchonly) : "");
walletSubtitle = wallet.getAddress().substring(0, 10) + "…" + watchOnly;
walletSubtitle = wallet.getAccountLabel();
activityCallback.setTitle(walletTitle, walletSubtitle);
Timber.d("wallet title is %s", walletTitle);
}
@@ -323,10 +325,13 @@ public class WalletFragment extends Fragment
private String walletSubtitle = null;
private long unlockedBalance = 0;
private int accountIdx = -1;
private void updateStatus(Wallet wallet) {
if (!isAdded()) return;
Timber.d("updateStatus()");
if (walletTitle == null) {
if ((walletTitle == null) || (accountIdx != wallet.getAccountIndex())) {
accountIdx = wallet.getAccountIndex();
setActivityTitle(wallet);
}
long balance = wallet.getBalance();
@@ -412,9 +417,28 @@ public class WalletFragment extends Fragment
super.onResume();
Timber.d("onResume()");
activityCallback.setTitle(walletTitle, walletSubtitle);
activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE);
//activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE); // TODO: Close button somewhere else
activityCallback.setToolbarButton(Toolbar.BUTTON_NONE);
setProgress(syncProgress);
setProgress(syncText);
showReceive();
if (activityCallback.isSynced()) enableAccountsList(true);
}
@Override
public void onPause() {
enableAccountsList(false);
super.onPause();
}
public interface DrawerLocker {
void setDrawerEnabled(boolean enabled);
}
private void enableAccountsList(boolean enable) {
if (activityCallback instanceof DrawerLocker) {
((DrawerLocker) activityCallback).setDrawerEnabled(enable);
}
}
}

View File

@@ -233,7 +233,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private boolean isBitcoinAddress() {
String address = etAddress.getEditText().getText().toString();
if ((address.length() >= 27) && (address.length() <= 34))
if ((address.length() >= 27) && (address.length() <= 35))
return BitcoinAddressValidator.validate(address);
else
return false;

View File

@@ -20,6 +20,9 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
@@ -57,8 +60,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
private TextView tvFunds;
private ExchangeTextView evAmount;
//private Button bSendAll;
private NumberPadView numberPad;
private View llAmount;
private View rlSweep;
private ImageButton ibSweep;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -73,37 +77,64 @@ public class SendAmountWizardFragment extends SendWizardFragment {
tvFunds = (TextView) view.findViewById(R.id.tvFunds);
evAmount = (ExchangeTextView) view.findViewById(R.id.evAmount);
numberPad = (NumberPadView) view.findViewById(R.id.numberPad);
numberPad.setListener(evAmount);
((NumberPadView) view.findViewById(R.id.numberPad)).setListener(evAmount);
/*
bSendAll = (Button) view.findViewById(R.id.bSendAll);
bSendAll.setOnClickListener(new View.OnClickListener() {
rlSweep = view.findViewById(R.id.rlSweep);
llAmount = view.findViewById(R.id.llAmount);
view.findViewById(R.id.ivSweep).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO: send all - figure out how to display this
sweepAll(false);
}
});
ibSweep = (ImageButton) view.findViewById(R.id.ibSweep);
ibSweep.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sweepAll(true);
}
});
*/
Helper.hideKeyboard(getActivity());
return view;
}
private boolean spendAllMode = false;
private void sweepAll(boolean spendAllMode) {
if (spendAllMode) {
ibSweep.setVisibility(View.INVISIBLE);
llAmount.setVisibility(View.GONE);
rlSweep.setVisibility(View.VISIBLE);
} else {
ibSweep.setVisibility(View.VISIBLE);
llAmount.setVisibility(View.VISIBLE);
rlSweep.setVisibility(View.GONE);
}
this.spendAllMode = spendAllMode;
}
@Override
public boolean onValidateFields() {
if (!evAmount.validate(maxFunds)) {
return false;
}
if (spendAllMode) {
if (sendListener != null) {
sendListener.getTxData().setAmount(Wallet.SWEEP_ALL);
}
} else {
if (!evAmount.validate(maxFunds)) {
return false;
}
if (sendListener != null) {
String xmr = evAmount.getAmount();
if (xmr != null) {
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
} else {
sendListener.getTxData().setAmount(0L);
if (sendListener != null) {
String xmr = evAmount.getAmount();
if (xmr != null) {
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
} else {
sendListener.getTxData().setAmount(0L);
}
}
}
return true;

View File

@@ -247,7 +247,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
private XmrToApi xmrToApi = null;
private final XmrToApi getXmrToApi() {
private XmrToApi getXmrToApi() {
if (xmrToApi == null) {
synchronized (this) {
if (xmrToApi == null) {

View File

@@ -36,6 +36,7 @@ import com.m2049r.xmrwallet.util.UserNotes;
import timber.log.Timber;
public class SendSettingsWizardFragment extends SendWizardFragment {
final static public int MIXIN = 6;
public static SendSettingsWizardFragment newInstance(Listener listener) {
SendSettingsWizardFragment instance = new SendSettingsWizardFragment();
@@ -54,15 +55,12 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
TxData getTxData();
}
// Mixin = Ringsize - 1
final static int Mixins[] = {6, 9, 12, 25}; // must match the layout XML / "@array/mixin"
final static PendingTransaction.Priority Priorities[] =
{PendingTransaction.Priority.Priority_Default,
PendingTransaction.Priority.Priority_Low,
PendingTransaction.Priority.Priority_Medium,
PendingTransaction.Priority.Priority_High}; // must match the layout XML
private Spinner sMixin;
private Spinner sPriority;
private EditText etNotes;
private EditText etDummy;
@@ -77,7 +75,6 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
View view = inflater.inflate(
R.layout.fragment_send_settings, container, false);
sMixin = (Spinner) view.findViewById(R.id.sMixin);
sPriority = (Spinner) view.findViewById(R.id.sPriority);
etNotes = (EditText) view.findViewById(R.id.etNotes);
@@ -104,7 +101,7 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
if (sendListener != null) {
TxData txData = sendListener.getTxData();
txData.setPriority(Priorities[sPriority.getSelectedItemPosition()]);
txData.setMixin(Mixins[sMixin.getSelectedItemPosition()]);
txData.setMixin(MIXIN);
txData.setUserNotes(new UserNotes(etNotes.getText().toString()));
}
return true;

View File

@@ -17,8 +17,11 @@
package com.m2049r.xmrwallet.model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import timber.log.Timber;
public class TransactionHistory {
static {
System.loadLibrary("monerujo");
@@ -26,8 +29,18 @@ public class TransactionHistory {
private long handle;
public TransactionHistory(long handle) {
int accountIndex;
public void setAccountFor(Wallet wallet) {
if (accountIndex != wallet.getAccountIndex()) {
this.accountIndex = wallet.getAccountIndex();
refreshWithNotes(wallet);
}
}
public TransactionHistory(long handle, int accountIndex) {
this.handle = handle;
this.accountIndex = accountIndex;
}
public void loadNotes(Wallet wallet) {
@@ -36,7 +49,7 @@ public class TransactionHistory {
}
}
public native int getCount();
public native int getCount(); // over all accounts/subaddresses
//private native long getTransactionByIndexJ(int i);
@@ -53,8 +66,23 @@ public class TransactionHistory {
loadNotes(wallet);
}
// public void refresh() {
// transactions = refreshJ();
// }
public void refresh() {
transactions = refreshJ();
List<TransactionInfo> t = refreshJ();
Timber.d("refreshed %d", t.size());
for (Iterator<TransactionInfo> iterator = t.iterator(); iterator.hasNext(); ) {
TransactionInfo info = iterator.next();
if (info.subaddrAccount != accountIndex) {
iterator.remove();
Timber.d("removed %s", info.hash);
} else {
Timber.d("kept %s", info.hash);
}
}
transactions = t;
}
private native List<TransactionInfo> refreshJ();

View File

@@ -53,6 +53,11 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
}
}
// virtual std::set<uint32_t> subaddrIndex() const = 0;
// virtual uint32_t subaddrAccount() const = 0;
// virtual std::string label() const = 0;
// virtual uint64_t confirmations() const = 0;
public Direction direction;
public boolean isPending;
public boolean isFailed;
@@ -62,6 +67,7 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
public String hash;
public long timestamp;
public String paymentId;
public int subaddrAccount;
public long confirmations;
public List<Transfer> transfers;
@@ -78,6 +84,7 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
String hash,
long timestamp,
String paymentId,
int subaddrAccount,
long confirmations,
List<Transfer> transfers) {
this.direction = Direction.values()[direction];
@@ -89,6 +96,7 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
this.hash = hash;
this.timestamp = timestamp;
this.paymentId = paymentId;
this.subaddrAccount = subaddrAccount;
this.confirmations = confirmations;
this.transfers = transfers;
}
@@ -108,6 +116,7 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
out.writeString(hash);
out.writeLong(timestamp);
out.writeString(paymentId);
out.writeInt(subaddrAccount);
out.writeLong(confirmations);
out.writeList(transfers);
out.writeString(txKey);
@@ -134,6 +143,7 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
hash = in.readString();
timestamp = in.readLong();
paymentId = in.readString();
subaddrAccount = in.readInt();
confirmations = in.readLong();
transfers = in.readArrayList(Transfer.class.getClassLoader());
txKey = in.readString();

View File

@@ -20,12 +20,26 @@ import com.m2049r.xmrwallet.data.TxData;
import java.io.File;
import timber.log.Timber;
public class Wallet {
final static public long SWEEP_ALL = Long.MAX_VALUE;
static {
System.loadLibrary("monerujo");
}
static final String TAG = "Wallet";
private int accountIndex = 0;
public int getAccountIndex() {
return accountIndex;
}
public void setAccountIndex(int accountIndex) {
Timber.d("setAccountIndex(%d)", accountIndex);
this.accountIndex = accountIndex;
getHistory().setAccountFor(this);
}
public String getName() {
return new File(getPath()).getName();
@@ -38,6 +52,11 @@ public class Wallet {
this.handle = handle;
}
Wallet(long handle, int accountIndex) {
this.handle = handle;
this.accountIndex = accountIndex;
}
public enum Status {
Status_Ok,
Status_Error,
@@ -66,16 +85,11 @@ public class Wallet {
public native boolean setPassword(String password);
private String address = null;
public String getAddress() {
if (address == null) {
address = getAddressJ();
}
return address;
return getAddressJ(accountIndex);
}
private native String getAddressJ();
private native String getAddressJ(int accountIndex);
public native String getPath();
@@ -132,9 +146,21 @@ public class Wallet {
//TODO virtual void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0;
public native long getBalance();
public long getBalance() {
return getBalance(accountIndex);
}
public native long getUnlockedBalance();
public native long getBalance(int accountIndex);
public native long getBalanceAll();
public long getUnlockedBalance() {
return getUnlockedBalance(accountIndex);
}
public native long getUnlockedBalanceAll();
public native long getUnlockedBalance(int accountIndex);
public native boolean isWatchOnly();
@@ -164,9 +190,7 @@ public class Wallet {
public static native boolean isAddressValid(String address, int networkType);
//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);
public static native String getPaymentIdFromAddress(String address, boolean isTestNet);
public static native String getPaymentIdFromAddress(String address, int networkType);
public static native long getMaximumAllowedAmount();
@@ -209,14 +233,23 @@ public class Wallet {
PendingTransaction.Priority priority) {
disposePendingTransaction();
int _priority = priority.getValue();
long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority);
long txHandle =
(amount == SWEEP_ALL ?
createSweepTransaction(dst_addr, payment_id, mixin_count, _priority,
accountIndex) :
createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority,
accountIndex));
pendingTransaction = new PendingTransaction(txHandle);
return pendingTransaction;
}
private native long createTransactionJ(String dst_addr, String payment_id,
long amount, int mixin_count,
int priority);
int priority, int accountIndex);
private native long createSweepTransaction(String dst_addr, String payment_id,
int mixin_count,
int priority, int accountIndex);
public PendingTransaction createSweepUnmixableTransaction() {
@@ -243,7 +276,7 @@ public class Wallet {
public TransactionHistory getHistory() {
if (history == null) {
history = new TransactionHistory(getHistoryJ());
history = new TransactionHistory(getHistoryJ(), accountIndex);
}
return history;
}
@@ -275,4 +308,43 @@ public class Wallet {
//virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0;
//virtual bool rescanSpent() = 0;
private static final String NEW_ACCOUNT_NAME = "Untitled account"; // src/wallet/wallet2.cpp:941
public void addAccount() {
addAccount(NEW_ACCOUNT_NAME);
}
public native void addAccount(String label);
public String getAccountLabel() {
return getAccountLabel(accountIndex);
}
public String getAccountLabel(int accountIndex) {
String label = getSubaddressLabel(accountIndex, 0);
if (label.equals(NEW_ACCOUNT_NAME)) {
String address = getAddressJ(accountIndex);
int len = address.length();
return address.substring(0, 6) +
"\u2026" + address.substring(len - 6, len);
} else return label;
}
public native String getSubaddressLabel(int accountIndex, int addressIndex);
public void setAccountLabel(String label) {
setSubaddressLabel(accountIndex, 0, label);
}
public void setAccountLabel(int accountIndex, String label) {
setSubaddressLabel(accountIndex, 0, label);
}
public native void setSubaddressLabel(int accountIndex, int addressIndex, String label);
public int numAccounts() {
return numSubaddressAccounts();
}
public native int numSubaddressAccounts();
}

View File

@@ -80,6 +80,13 @@ public class WalletManager {
private native long createWalletJ(String path, String password, String language, int networkType);
public Wallet openAccount(String path, int accountIndex, String password) {
long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
Wallet wallet = new Wallet(walletHandle, accountIndex);
manageWallet(wallet);
return wallet;
}
public Wallet openWallet(String path, String password) {
long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
Wallet wallet = new Wallet(walletHandle);
@@ -228,7 +235,6 @@ public class WalletManager {
public String getDaemonAddress() {
if (daemonAddress == null) {
// assume testnet not explicitly initialised
throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
}
return this.daemonAddress;
@@ -236,13 +242,13 @@ public class WalletManager {
private native void setDaemonAddressJ(String address);
String daemonUsername = "";
private String daemonUsername = "";
public String getDaemonUsername() {
return daemonUsername;
}
String daemonPassword = "";
private String daemonPassword = "";
public String getDaemonPassword() {
return daemonPassword;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017 m2049r et al.
* Copyright (c) 2017-2018 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.exchange.kraken;
package com.m2049r.xmrwallet.service.exchange.coinmarketcap;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
@@ -23,6 +23,7 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeException;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper;
import org.json.JSONArray;
import org.json.JSONException;
@@ -37,6 +38,7 @@ import okhttp3.Request;
import okhttp3.Response;
public class ExchangeApiImpl implements ExchangeApi {
static final String CRYPTO_ID = "328";
@NonNull
private final OkHttpClient okHttpClient;
@@ -52,7 +54,7 @@ public class ExchangeApiImpl implements ExchangeApi {
}
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
this(okHttpClient, HttpUrl.parse("https://api.kraken.com/0/public/Ticker"));
this(okHttpClient, HttpUrl.parse("https://api.coinmarketcap.com/v2/ticker/"));
}
@Override
@@ -67,12 +69,12 @@ public class ExchangeApiImpl implements ExchangeApi {
boolean inverse = false;
String fiat = null;
if (baseCurrency.equals("XMR")) {
if (baseCurrency.equals(Helper.CRYPTO)) {
fiat = quoteCurrency;
inverse = false;
}
if (quoteCurrency.equals("XMR")) {
if (quoteCurrency.equals(Helper.CRYPTO)) {
fiat = baseCurrency;
inverse = true;
}
@@ -85,7 +87,8 @@ public class ExchangeApiImpl implements ExchangeApi {
final boolean swapAssets = inverse;
final HttpUrl url = baseUrl.newBuilder()
.addQueryParameter("pair", "XMR" + fiat)
.addEncodedPathSegments(CRYPTO_ID + "/")
.addQueryParameter("convert", fiat)
.build();
final Request httpRequest = createHttpRequest(url);
@@ -101,12 +104,12 @@ public class ExchangeApiImpl implements ExchangeApi {
if (response.isSuccessful()) {
try {
final JSONObject json = new JSONObject(response.body().string());
final JSONArray jsonError = json.getJSONArray("error");
if (jsonError.length() > 0) {
final String errorMsg = jsonError.getString(0);
callback.onError(new ExchangeException(response.code(), errorMsg));
final JSONObject metadata = json.getJSONObject("metadata");
if (!metadata.isNull("error")) {
final String errorMsg = metadata.getString("error");
callback.onError(new ExchangeException(response.code(), (String) errorMsg));
} else {
final JSONObject jsonResult = json.getJSONObject("result");
final JSONObject jsonResult = json.getJSONObject("data");
reportSuccess(jsonResult, swapAssets, callback);
}
} catch (JSONException ex) {
@@ -130,7 +133,6 @@ public class ExchangeApiImpl implements ExchangeApi {
}
}
private Request createHttpRequest(final HttpUrl url) {
return new Request.Builder()
.url(url)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017 m2049r et al.
* Copyright (c) 2017-2018 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.exchange.kraken;
package com.m2049r.xmrwallet.service.exchange.coinmarketcap;
import android.support.annotation.NonNull;
@@ -25,6 +25,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -37,7 +38,7 @@ class ExchangeRateImpl implements ExchangeRate {
@Override
public String getServiceName() {
return "kraken.com";
return "coinmarketcap.com";
}
@Override
@@ -64,29 +65,21 @@ class ExchangeRateImpl implements ExchangeRate {
ExchangeRateImpl(final JSONObject jsonObject, final boolean swapAssets) throws JSONException, ExchangeException {
try {
final String key = jsonObject.keys().next(); // we expect only one
Pattern pattern = Pattern.compile("^X(.*?)Z(.*?)$");
Matcher matcher = pattern.matcher(key);
if (matcher.find()) {
this.baseCurrency = swapAssets ? matcher.group(2) : matcher.group(1);
this.quoteCurrency = swapAssets ? matcher.group(1) : matcher.group(2);
} else {
throw new ExchangeException("no pair returned!");
}
JSONObject pair = jsonObject.getJSONObject(key);
JSONArray close = pair.getJSONArray("c");
String closePrice = close.getString(0);
if (closePrice != null) {
try {
double rate = Double.parseDouble(closePrice);
this.rate = swapAssets ? (1 / rate) : rate;
} catch (NumberFormatException ex) {
throw new ExchangeException(ex.getLocalizedMessage());
}
} else {
throw new ExchangeException("no close price returned!");
final String baseC = jsonObject.getString("symbol");
final JSONObject quotes = jsonObject.getJSONObject("quotes");
final Iterator<String> keys = quotes.keys();
String key = null;
// get key which is not USD unless it is the only one
while (keys.hasNext()) {
key = keys.next();
if (!key.equals("USD")) break;
}
final String quoteC = key;
baseCurrency = swapAssets ? quoteC : baseC;
quoteCurrency = swapAssets ? baseC : quoteC;
JSONObject quote = quotes.getJSONObject(key);
double price = quote.getDouble("price");
this.rate = swapAssets ? (1d / price) : price;
} catch (NoSuchElementException ex) {
throw new ExchangeException(ex.getLocalizedMessage());
}

View File

@@ -66,6 +66,8 @@ public class BitcoinAddressValidator {
byte[] result = new byte[25];
byte[] numBytes = num.toByteArray();
if (num.bitLength() > 200) return null;
if (num.bitLength() == 200) {
System.arraycopy(numBytes, 1, result, 0, 25);
} else {

View File

@@ -2,39 +2,44 @@ package com.m2049r.xmrwallet.util;
import android.app.KeyguardManager;
import android.content.Context;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.os.CancellationSignal;
import java.security.KeyStore;
import java.security.KeyStoreException;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.CancellationSignal;
public class FingerprintHelper {
public static boolean isDeviceSupported(Context context) {
FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(context);
KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
return keyguardManager != null &&
FingerprintManager fingerprintManager = context.getSystemService(FingerprintManager.class);
KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
return (keyguardManager != null) && (fingerprintManager != null) &&
keyguardManager.isKeyguardSecure() &&
fingerprintManager.isHardwareDetected() &&
fingerprintManager.hasEnrolledFingerprints();
}
public static boolean isFingerprintAuthAllowed(String wallet) throws KeyStoreException {
KeyStore keyStore = KeyStore.getInstance(KeyStoreHelper.SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
public static boolean isFingerPassValid(Context context, String wallet) {
try {
keyStore.load(null);
} catch (Exception ex) {
throw new IllegalStateException("Could not load KeyStore", ex);
KeyStoreHelper.loadWalletUserPass(context, wallet);
return true;
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
return false;
}
return keyStore.containsAlias(KeyStoreHelper.SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet);
}
public static void authenticate(Context context, CancellationSignal cancelSignal,
FingerprintManagerCompat.AuthenticationCallback callback) {
FingerprintManagerCompat manager = FingerprintManagerCompat.from(context);
manager.authenticate(null, 0, cancelSignal, callback, null);
}
FingerprintManager.AuthenticationCallback callback) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return;
}
FingerprintManager manager = context.getSystemService(FingerprintManager.class);
if (manager != null) {
manager.authenticate(null, cancelSignal, 0, callback, null);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,7 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
@@ -36,9 +37,7 @@ import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
import java.util.Locale;
@@ -47,6 +46,8 @@ import timber.log.Timber;
public class ExchangeTextView extends LinearLayout
implements NumberPadView.NumberPadListener {
private static String MAX = "\u221E";
String xmrAmount = null;
String notXmrAmount = null;
@@ -70,7 +71,7 @@ public class ExchangeTextView extends LinearLayout
if (amount > max) {
ok = false;
}
if (amount <= 0) {
if (amount <= 0) { /////////////////////////////
ok = false;
}
} catch (NumberFormatException ex) {
@@ -250,7 +251,7 @@ public class ExchangeTextView extends LinearLayout
}
}
private final ExchangeApi exchangeApi = new ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient());
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
void startExchange() {
showProgress();
@@ -458,4 +459,4 @@ public class ExchangeTextView extends LinearLayout
tvAmountA.setText(null);
doExchange();
}
}
}

View File

@@ -30,6 +30,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
@@ -41,9 +42,7 @@ import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
import java.util.Locale;
@@ -166,7 +165,11 @@ public class ExchangeView extends LinearLayout
etAmount = (TextInputLayout) findViewById(R.id.etAmount);
tvAmountB = (TextView) findViewById(R.id.tvAmountB);
sCurrencyA = (Spinner) findViewById(R.id.sCurrencyA);
ArrayAdapter adapter = ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner);
adapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item);
sCurrencyA.setAdapter(adapter);
sCurrencyB = (Spinner) findViewById(R.id.sCurrencyB);
sCurrencyB.setAdapter(adapter);
evExchange = (ImageView) findViewById(R.id.evExchange);
pbExchange = (ProgressBar) findViewById(R.id.pbExchange);
@@ -310,12 +313,13 @@ public class ExchangeView extends LinearLayout
}
}
private final ExchangeApi exchangeApi = new ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient());
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
void startExchange() {
showProgress();
String currencyA = (String) sCurrencyA.getSelectedItem();
String currencyB = (String) sCurrencyB.getSelectedItem();
exchangeApi.queryExchangeRate(currencyA, currencyB,
new ExchangeCallback() {
@Override

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M21,18v1c0,1.1 -0.9,2 -2,2L5,21c-1.11,0 -2,-0.9 -2,-2L3,5c0,-1.1 0.89,-2 2,-2h14c1.1,0 2,0.9 2,2v1h-9c-1.11,0 -2,0.9 -2,2v8c0,1.1 0.89,2 2,2h9zM12,16h10L22,8L12,8v8zM16,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="20.0"
android:viewportWidth="20.0">
<path
android:fillColor="@color/moneroWhite"
android:pathData="M11,5L9,5L9,9L5,9L5,11L9,11L9,15L11,15L11,11L15,11L15,9L11,9L11,5L11,5ZM10,0C4.5,0 0,4.5 0,10C0,15.5 4.5,20 10,20C15.5,20 20,15.5 20,10C20,4.5 15.5,0 10,0L10,0ZM10,18C5.6,18 2,14.4 2,10C2,5.6 5.6,2 10,2C14.4,2 18,5.6 18,10C18,14.4 14.4,18 10,18L10,18Z" />
</vector>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@@ -0,0 +1,27 @@
<!--
~ Copyright (C) 2015 The Android Open Source Project
~
~ 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
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36" >
<path
android:fillColor="#f4511e"
android:pathData="M18,18m -18, 0a 18, 18 0 1, 0 36, 0a 18, 18 0 1, 0 -36, 0" />
<path
android:fillColor="#ffffffff"
android:pathData="m16.665,24.915001l2.67,0l0,2.67l-2.67,0l0,-2.67zm0,-16.5l2.67,0l0,12.67l-2.67,0l0,-12.67z" />
</vector>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,27 @@
<!--
~ Copyright (C) 2015 The Android Open Source Project
~
~ 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
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36" >
<path
android:fillColor="#009688"
android:pathData="M18,18m -18, 0a 18, 18 0 1, 0 36, 0a 18, 18 0 1, 0 -36, 0" />
<path
android:fillColor="#ffffff"
android:pathData="m16.665,8.415001l2.67,0l0,2.67l-2.67,0l0,-2.67zm0,6.5l2.67,0l0,12.67l-2.67,0l0,-12.67z" />
</vector>

Some files were not shown because too many files have changed in this diff Show More