mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-02 15:53:04 +02:00
Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8985511209 | ||
![]() |
3c8a4ce967 | ||
![]() |
fcfedbcfae | ||
![]() |
74279b135a | ||
![]() |
d6d2de8312 | ||
![]() |
af0ecb2894 | ||
![]() |
975cc4f43c | ||
![]() |
74ba36de26 | ||
![]() |
7627e15a48 | ||
![]() |
37244cb9e0 | ||
![]() |
843566b820 | ||
![]() |
0bcf156929 | ||
![]() |
b1d91e2671 | ||
![]() |
271cd2d4a8 | ||
![]() |
22c5a543db | ||
![]() |
cd986860c5 | ||
![]() |
0cf5981eae | ||
![]() |
e109df34f0 | ||
![]() |
5a7aa6cc77 | ||
![]() |
f50629ff81 | ||
![]() |
cb12d64e5f | ||
![]() |
a8f08fb9b9 | ||
![]() |
3e9be418a8 | ||
![]() |
fa5dc9988d | ||
![]() |
857cf8d6d8 | ||
![]() |
63e0c265cb | ||
![]() |
39d2c0a25f | ||
![]() |
ff6e00e1a1 | ||
![]() |
3a839a04d5 | ||
![]() |
42e4db5cc1 | ||
![]() |
d18999e731 | ||
![]() |
425246beb1 | ||
![]() |
e1a2572236 | ||
![]() |
7fdae76f51 | ||
![]() |
b4e1767a7b | ||
![]() |
9b9995437d | ||
![]() |
94abc00422 | ||
![]() |
d7d6601b60 | ||
![]() |
bf95994c9e | ||
![]() |
a2d6ca0740 | ||
![]() |
1115bbb706 | ||
![]() |
0b17ed4322 | ||
![]() |
bcc85a5b3f | ||
![]() |
5b26f1a30b | ||
![]() |
63677d5027 | ||
![]() |
96579c1be4 | ||
![]() |
af58b76f0c | ||
![]() |
7879f31f63 | ||
![]() |
2ca7b41982 | ||
![]() |
8bdc0f8fde | ||
![]() |
0bb9e9cb6c | ||
![]() |
bdc2a72790 | ||
![]() |
073bd96b17 | ||
![]() |
37f22a9dc2 | ||
![]() |
bc3c5b3f66 | ||
![]() |
c61a62cc85 | ||
![]() |
66a6583ec4 | ||
![]() |
d24b37e2f2 | ||
![]() |
161c155de6 | ||
![]() |
dd59233dd4 | ||
![]() |
e9c74d4d9c | ||
![]() |
b37adb4546 | ||
![]() |
a4e99209be | ||
![]() |
45aa8f30e5 | ||
![]() |
60a7b15700 | ||
![]() |
df2ff8b3b7 | ||
![]() |
da8c8f80f1 | ||
![]() |
eda3de11fe | ||
![]() |
7d7de14827 | ||
![]() |
1c709da92c |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,8 +1,7 @@
|
||||
.gradle
|
||||
/build
|
||||
*.iml
|
||||
/.idea/libraries
|
||||
/.idea/workspace.xml
|
||||
/.idea
|
||||
/local.properties
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
19
README.md
19
README.md
@@ -1,12 +1,23 @@
|
||||
# Monerujo
|
||||
Another Android Monero Wallet
|
||||
Another Android Monero Wallet for Monero
|
||||
**(not
|
||||
Monero Classic,
|
||||
Monero-Classic,
|
||||
Monero Zero,
|
||||
Monero Original,
|
||||
Monero C,
|
||||
Monero V)**
|
||||
|
||||
### QUICKSTART
|
||||
- Download the APK for the most current release [here](https://github.com/m2049r/xmrwallet/releases) and install it
|
||||
- Alternatively add our F-Droid repo https://f-droid.monerujo.io/fdroid/repo with fingerpint ```A8 2C 68 E1 4A F0 AA 6A 2E C2 0E 6B 27 2E FF 25 E5 A0 38 F3 F6 58 84 31 6E 0F 5E 0D 91 E7 B7 13``` to your F-Droid client
|
||||
- Run the App and select "Generate Wallet" to create a new wallet or recover a wallet
|
||||
- Advanced users can copy over synced wallet files (all files) onto sdcard in directory Monerujo (created first time App is started)
|
||||
- See the [FAQ](doc/FAQ.md)
|
||||
|
||||
## Translations
|
||||
Help us translate Monerujo! You can find instructions [On Taiga](https://taiga.getmonero.org/project/erciccione-monero-localization/wiki/monerujo), and if you need help/support, open an issue or contact the Localization Workgroup. You can find us on the freenode channel `#monero-translations`, also relayed on [MatterMost](https://mattermost.getmonero.org/monero/channels/monero-translations), and matrix/riot.
|
||||
|
||||
### Disclaimer
|
||||
You may lose all your Moneroj if you use this App. Be cautious when spending on the mainnet.
|
||||
|
||||
@@ -23,6 +34,8 @@ You may lose all your Moneroj if you use this App. Be cautious when spending on
|
||||
- see taiga.getmonero.org & issues on github
|
||||
|
||||
### Issues / Pitfalls
|
||||
- Users of Zenfone MAX & Zenfone 2 Laser (possibly others) **MUST** use the armeabi-v7a APK as the arm64-v8a build uses hardware AES
|
||||
functionality these models don't have.
|
||||
- You should backup your wallet files in the "monerujo" folder periodically.
|
||||
- Also note, that on some devices the backups will only be visible on a PC over USB after a reboot of the device (it's an Android bug/feature)
|
||||
- Created wallets on a private testnet are unusable because the restore height is set to that
|
||||
@@ -33,8 +46,8 @@ The official monero client shows the same behaviour.
|
||||
No need to build. Binaries are included:
|
||||
|
||||
- openssl-1.0.2l
|
||||
- monero-v0.11.1.0
|
||||
- boost_1_64_0
|
||||
- monero-v0.12
|
||||
- boost_1_58_0
|
||||
|
||||
If you want to build them yourself (recommended) check out [the instructions](doc/BUILDING-external-libs.md)
|
||||
|
||||
|
@@ -1,15 +1,14 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'witness'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion '26.0.2'
|
||||
buildToolsVersion '27.0.3'
|
||||
defaultConfig {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 25
|
||||
versionCode 82
|
||||
versionName "1.4.2 'Monero Spedner'"
|
||||
versionCode 95
|
||||
versionName "1.5.5 'Accounts Nacho'"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
@@ -17,9 +16,6 @@ android {
|
||||
arguments '-DANDROID_STL=c++_shared'
|
||||
}
|
||||
}
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -52,59 +48,34 @@ android {
|
||||
// APKs for the same app that all have the same version information.
|
||||
android.applicationVariants.all { variant ->
|
||||
// Assigns a different version code for each output APK.
|
||||
variant.outputs.each {
|
||||
variant.outputs.all {
|
||||
output ->
|
||||
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
|
||||
output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode
|
||||
|
||||
if (abiName == null) abiName = "universal"
|
||||
def v = "${variant.versionName}".replaceFirst(" .*\$", "").replace(".", "x")
|
||||
outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:appcompat-v7:25.4.0'
|
||||
compile 'com.android.support:design:25.4.0'
|
||||
compile 'com.android.support:support-v4:25.4.0'
|
||||
compile 'com.android.support:recyclerview-v7:25.4.0'
|
||||
compile 'com.android.support:cardview-v7:25.4.0'
|
||||
compile 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
compile 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
implementation 'com.android.support:appcompat-v7:25.4.0'
|
||||
implementation 'com.android.support:design:25.4.0'
|
||||
implementation 'com.android.support:support-v4:25.4.0'
|
||||
implementation 'com.android.support:recyclerview-v7:25.4.0'
|
||||
implementation 'com.android.support:cardview-v7:25.4.0'
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
|
||||
compile "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
||||
compile "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
|
||||
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
||||
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
|
||||
|
||||
compile 'com.nulab-inc:zxcvbn:1.2.3'
|
||||
implementation 'com.nulab-inc:zxcvbn:1.2.3'
|
||||
|
||||
testCompile "junit:junit:$rootProject.ext.junitVersion"
|
||||
testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
||||
testCompile "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
||||
testCompile 'org.json:json:20140107'
|
||||
testCompile 'net.jodah:concurrentunit:0.4.2'
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.android.support:design:3f409bf2019967ffc344cfaf11e52131fac982468a1707aaeb25bf3c52838966',
|
||||
'com.android.support:appcompat-v7:70551e62660db15b790c5275f56b9de4dd9407d1494d07c8f3dd5698f3638677',
|
||||
'com.android.support:transition:848270144fb180efd2bf928a00ed176dbbc5290badfd638390ffba90088df8b3',
|
||||
'me.dm7.barcodescanner:zxing:d43973c9527c23fa8e6d338c6a2c458e373ce1ac6bcaa3bc41d11ae49116000d',
|
||||
'me.dm7.barcodescanner:core:a5c8a704089b58029db166172ed8e55d756877d010a85a0b1c94fdc96ffb8f9a',
|
||||
'com.android.support:support-v4:ee44c481a1f4d6978568e223e8125379b52b2ececdd53450e09ebae144bd377d',
|
||||
'com.android.support:recyclerview-v7:a2fe121f9d01ed8980e97095b4a3fe9700a0aa0a7d4b0f8c594f765ad8455a0d',
|
||||
'com.android.support:cardview-v7:f3fbbe1fcfdbec7333c6a2c516c5fd511a909d1975271818e268d6fe297d8c70',
|
||||
'com.android.support.constraint:constraint-layout:b0c688cc2b7172608f8153a689d746da40f71e52d7e2fe2bfd9df2f92db77085',
|
||||
'com.android.support:animated-vector-drawable:628ab1d56a6ee4cbedf32617af8b2a1fe02964ed0628e8f898cc09ddba6e1835',
|
||||
'com.android.support:support-vector-drawable:077009d13882ee96f061e4bc2dbe7cce7ae1762d8297592a787ff741afbfb1f2',
|
||||
'com.android.support:support-fragment:316d35d4d2d2902057efad104a73e4bdb50bee260a7075678185b8cd71170945',
|
||||
'com.android.support:support-core-ui:e72ae29b823889686cff6fcb948d6745c2baf6d4c2af4fdffa1ec1e42e3833a3',
|
||||
'com.android.support:support-media-compat:566a161d9cb0083ef62a53e46b71ce5b3d455b8635b1a0a4ae28d96d4b583de8',
|
||||
'com.android.support:support-core-utils:34b8437dfa95ff28d29cf57ffa3b1354a9fa9bfe4059f0fd5ce2f5e4326a1748',
|
||||
'com.android.support:support-compat:54019c63614ce08b02d7b9605490cd2b29ba5b2505f394a9517450b5f72b30ca',
|
||||
'com.android.support:support-annotations:a774272036941b4e912eb426d70c848bde7f06a3bf5fb491f75a427dc6595270',
|
||||
'com.android.support.constraint:constraint-layout-solver:8c62525a9bc5cff5633a96cb9b32fffeccaf41b8841aa87fc22607070dea9b8d',
|
||||
'com.google.zxing:core:bba7724e02a997cec38213af77133ee8e24b0d5cf5fa7ecbc16a4fa93f11ee0d',
|
||||
'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850',
|
||||
'com.squareup.okhttp3:okhttp:7265adbd6f028aade307f58569d814835cd02bc9beffb70c25f72c9de50d61c4',
|
||||
'com.jakewharton.timber:timber:35c22867f2673132e97e17857d36bb2fc25f5790f0425406833ed0254d62fc66',
|
||||
'com.nulab-inc:zxcvbn:18d7862a6abd2705defec478d77dedadf8f3bb7cf811df22995494f05485785f',
|
||||
]
|
||||
testImplementation "junit:junit:$rootProject.ext.junitVersion"
|
||||
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
||||
testImplementation 'org.json:json:20140107'
|
||||
testImplementation 'net.jodah:concurrentunit:0.4.2'
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@
|
||||
#define XMRWALLET_WALLET_LIB_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
/*
|
||||
#include <android/log.h>
|
||||
|
||||
@@ -27,13 +28,13 @@
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
|
||||
*/
|
||||
|
||||
jfieldID getHandleField(JNIEnv *env, jobject obj, const char* fieldName = "handle") {
|
||||
jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
||||
jclass c = env->GetObjectClass(obj);
|
||||
return env->GetFieldID(c, fieldName, "J"); // of type long
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *getHandle(JNIEnv *env, jobject obj, const char* fieldName = "handle") {
|
||||
template<typename T>
|
||||
T *getHandle(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
||||
jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName));
|
||||
return reinterpret_cast<T *>(handle);
|
||||
}
|
||||
@@ -42,10 +43,35 @@ void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) {
|
||||
env->SetLongField(obj, getHandleField(env, obj), handle);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
void setHandle(JNIEnv *env, jobject obj, T *t) {
|
||||
jlong handle = reinterpret_cast<jlong>(t);
|
||||
setHandleFromLong(env, obj, handle);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// from monero-core crypto/hash-ops.h - avoid #including monero code here
|
||||
enum {
|
||||
HASH_SIZE = 32,
|
||||
HASH_DATA_AREA = 136
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //XMRWALLET_WALLET_LIB_H
|
||||
|
@@ -16,12 +16,15 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
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.Html;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
@@ -32,20 +35,23 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.FingerprintHelper;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
import com.nulabinc.zxcvbn.Strength;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -59,6 +65,7 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
private TextInputLayout etWalletName;
|
||||
private TextInputLayout etWalletPassword;
|
||||
private LinearLayout llFingerprintAuth;
|
||||
private TextInputLayout etWalletAddress;
|
||||
private TextInputLayout etWalletMnemonic;
|
||||
private TextInputLayout etWalletViewKey;
|
||||
@@ -79,6 +86,7 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
etWalletName = (TextInputLayout) view.findViewById(R.id.etWalletName);
|
||||
etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword);
|
||||
llFingerprintAuth = (LinearLayout) view.findViewById(R.id.llFingerprintAuth);
|
||||
etWalletMnemonic = (TextInputLayout) view.findViewById(R.id.etWalletMnemonic);
|
||||
etWalletAddress = (TextInputLayout) view.findViewById(R.id.etWalletAddress);
|
||||
etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey);
|
||||
@@ -146,6 +154,30 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
if (FingerprintHelper.isDeviceSupported(getContext())) {
|
||||
llFingerprintAuth.setVisibility(View.VISIBLE);
|
||||
|
||||
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0);
|
||||
swFingerprintAllowed.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (!swFingerprintAllowed.isChecked()) return;
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setMessage(Html.fromHtml(getString(R.string.generate_fingerprint_warn)))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(R.string.label_ok), null)
|
||||
.setNegativeButton(getString(R.string.label_cancel), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
swFingerprintAllowed.setChecked(false);
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (type.equals(TYPE_NEW)) {
|
||||
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@@ -423,18 +455,28 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
String name = etWalletName.getEditText().getText().toString();
|
||||
String password = etWalletPassword.getEditText().getText().toString();
|
||||
boolean fingerprintAuthAllowed = ((Switch) llFingerprintAuth.getChildAt(0)).isChecked();
|
||||
|
||||
// create the real wallet password
|
||||
String crazyPass = KeyStoreHelper.getCrazyPass(getActivity(), password);
|
||||
|
||||
long height = getHeight();
|
||||
if (height < 0) height = 0;
|
||||
|
||||
if (type.equals(TYPE_NEW)) {
|
||||
bGenerate.setEnabled(false);
|
||||
activityCallback.onGenerate(name, password);
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||
}
|
||||
activityCallback.onGenerate(name, crazyPass);
|
||||
} else if (type.equals(TYPE_SEED)) {
|
||||
if (!checkMnemonic()) return;
|
||||
String seed = etWalletMnemonic.getEditText().getText().toString();
|
||||
bGenerate.setEnabled(false);
|
||||
activityCallback.onGenerate(name, password, seed, height);
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||
}
|
||||
activityCallback.onGenerate(name, crazyPass, seed, height);
|
||||
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
|
||||
if (checkAddress() && checkViewKey() && checkSpendKey()) {
|
||||
bGenerate.setEnabled(false);
|
||||
@@ -444,7 +486,10 @@ public class GenerateFragment extends Fragment {
|
||||
if (type.equals(TYPE_KEY)) {
|
||||
spendKey = etWalletSpendKey.getEditText().getText().toString();
|
||||
}
|
||||
activityCallback.onGenerate(name, password, address, viewKey, spendKey, height);
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||
}
|
||||
activityCallback.onGenerate(name, crazyPass, address, viewKey, spendKey, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -43,18 +43,20 @@ 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;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.NodeList;
|
||||
import com.m2049r.xmrwallet.util.Notice;
|
||||
import com.m2049r.xmrwallet.widget.DropDownEditText;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -71,9 +73,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
private DropDownEditText etDaemonAddress;
|
||||
private ArrayAdapter<String> nodeAdapter;
|
||||
|
||||
private View llXmrToEnabled;
|
||||
private View ibXmrToInfoClose;
|
||||
|
||||
private Listener activityCallback;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
@@ -173,23 +172,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
|
||||
etDummy = (EditText) view.findViewById(R.id.etDummy);
|
||||
|
||||
llXmrToEnabled = view.findViewById(R.id.llXmrToEnabled);
|
||||
llXmrToEnabled.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HelpFragment.display(getChildFragmentManager(), R.string.help_xmrto);
|
||||
|
||||
}
|
||||
});
|
||||
ibXmrToInfoClose = view.findViewById(R.id.ibXmrToInfoClose);
|
||||
ibXmrToInfoClose.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
showXmrtoEnabled = false;
|
||||
saveXmrToPrefs();
|
||||
}
|
||||
});
|
||||
ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
|
||||
Notice.showAll(llNotice,".*_login");
|
||||
|
||||
etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress);
|
||||
nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line);
|
||||
@@ -237,9 +221,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
});
|
||||
|
||||
loadPrefs();
|
||||
if (!showXmrtoEnabled) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -326,6 +307,17 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
ivGunther.setImageDrawable(null);
|
||||
}
|
||||
}
|
||||
|
||||
// remove information of non-existent wallet
|
||||
Set<String> removedWallets = getActivity()
|
||||
.getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.getAll().keySet();
|
||||
for (WalletManager.WalletInfo s : walletList) {
|
||||
removedWallets.remove(s.name);
|
||||
}
|
||||
for (String name : removedWallets) {
|
||||
KeyStoreHelper.removeWalletUserPass(getActivity(), name);
|
||||
}
|
||||
}
|
||||
|
||||
private void showInfo(@NonNull String name) {
|
||||
@@ -345,81 +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_SHOW_XMRTO_ENABLED = "info_xmrto_enabled_login";
|
||||
|
||||
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.xmr-tw.org";
|
||||
|
||||
private NodeList daemonTestNet;
|
||||
private NodeList daemonStageNet;
|
||||
private NodeList daemonMainNet;
|
||||
|
||||
boolean showXmrtoEnabled = true;
|
||||
|
||||
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);
|
||||
|
||||
showXmrtoEnabled = sharedPref.getBoolean(PREF_SHOW_XMRTO_ENABLED, true);
|
||||
}
|
||||
|
||||
void saveXmrToPrefs() {
|
||||
SharedPreferences sharedPref = activityCallback.getPrefs();
|
||||
SharedPreferences.Editor editor = sharedPref.edit();
|
||||
editor.putBoolean(PREF_SHOW_XMRTO_ENABLED, showXmrtoEnabled);
|
||||
editor.apply();
|
||||
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);
|
||||
}
|
||||
@@ -427,8 +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.putBoolean(PREF_SHOW_XMRTO_ENABLED, showXmrtoEnabled);
|
||||
editor.putString(PREF_DAEMON_STAGENET, daemonStageNet.toString());
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
|
@@ -335,6 +335,7 @@ public class ReceiveFragment extends Fragment {
|
||||
}
|
||||
|
||||
public Bitmap generate(String text, int width, int height) {
|
||||
if ((width <= 0) || (height <= 0)) return null;
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
|
||||
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
|
||||
|
@@ -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
@@ -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();
|
||||
@@ -352,7 +357,7 @@ public class WalletFragment extends Fragment
|
||||
setProgress(x);
|
||||
ivSynced.setVisibility(View.GONE);
|
||||
} else {
|
||||
sync = getString(R.string.status_synced) + formatter.format(wallet.getBlockChainHeight());
|
||||
sync = getString(R.string.status_synced) + " " + formatter.format(wallet.getBlockChainHeight());
|
||||
ivSynced.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
@@ -412,9 +417,27 @@ 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();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -86,6 +86,10 @@ public class WalletNode {
|
||||
return name;
|
||||
}
|
||||
|
||||
public NetworkType getNetworkType() {
|
||||
return networkType;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return host + ":" + port;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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) {
|
||||
|
@@ -47,6 +47,7 @@ import com.m2049r.xmrwallet.layout.SpendViewPager;
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.NodeList;
|
||||
import com.m2049r.xmrwallet.util.Notice;
|
||||
import com.m2049r.xmrwallet.util.UserNotes;
|
||||
import com.m2049r.xmrwallet.widget.DotBar;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
@@ -119,27 +120,8 @@ public class SendFragment extends Fragment
|
||||
arrowPrev = getResources().getDrawable(R.drawable.ic_navigate_prev_white_24dp);
|
||||
arrowNext = getResources().getDrawable(R.drawable.ic_navigate_next_white_24dp);
|
||||
|
||||
llXmrToEnabled = view.findViewById(R.id.llXmrToEnabled);
|
||||
llXmrToEnabled.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HelpFragment.display(getChildFragmentManager(), R.string.help_xmrto);
|
||||
|
||||
}
|
||||
});
|
||||
ibXmrToInfoClose = view.findViewById(R.id.ibXmrToInfoClose);
|
||||
ibXmrToInfoClose.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
showXmrtoEnabled = false;
|
||||
saveXmrToPrefs();
|
||||
}
|
||||
});
|
||||
loadPrefs();
|
||||
if (!showXmrtoEnabled) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
}
|
||||
ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
|
||||
Notice.showAll(llNotice,".*_send");
|
||||
|
||||
spendViewPager = (SpendViewPager) view.findViewById(R.id.pager);
|
||||
pagerAdapter = new SpendPagerAdapter(getChildFragmentManager());
|
||||
@@ -291,7 +273,7 @@ public class SendFragment extends Fragment
|
||||
pagerAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
Timber.d("New Mode = " + mode.toString());
|
||||
Timber.d("New Mode = %s", mode.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +332,7 @@ public class SendFragment extends Fragment
|
||||
@Override
|
||||
public SendWizardFragment getItem(int position) {
|
||||
Timber.d("getItem(%d) CREATE", position);
|
||||
Timber.d("Mode=" + mode.toString());
|
||||
Timber.d("Mode=%s", mode.toString());
|
||||
if (mode == Mode.XMR) {
|
||||
switch (position) {
|
||||
case POS_ADDRESS:
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
|
@@ -20,12 +20,24 @@ import com.m2049r.xmrwallet.data.TxData;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Wallet {
|
||||
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 +50,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 +83,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 +144,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 +188,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 +231,15 @@ public class Wallet {
|
||||
PendingTransaction.Priority priority) {
|
||||
disposePendingTransaction();
|
||||
int _priority = priority.getValue();
|
||||
long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority);
|
||||
long txHandle = 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);
|
||||
|
||||
|
||||
public PendingTransaction createSweepUnmixableTransaction() {
|
||||
@@ -243,7 +266,7 @@ public class Wallet {
|
||||
|
||||
public TransactionHistory getHistory() {
|
||||
if (history == null) {
|
||||
history = new TransactionHistory(getHistoryJ());
|
||||
history = new TransactionHistory(getHistoryJ(), accountIndex);
|
||||
}
|
||||
return history;
|
||||
}
|
||||
@@ -275,5 +298,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();
|
||||
}
|
||||
|
@@ -41,6 +41,7 @@ public class WalletManager {
|
||||
if (WalletManager.Instance == null) {
|
||||
WalletManager.Instance = new WalletManager();
|
||||
}
|
||||
|
||||
return WalletManager.Instance;
|
||||
}
|
||||
|
||||
@@ -79,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);
|
||||
@@ -215,7 +223,7 @@ public class WalletManager {
|
||||
//public void setDaemon(String address, NetworkType networkType, String username, String password) {
|
||||
public void setDaemon(WalletNode walletNode) {
|
||||
this.daemonAddress = walletNode.getAddress();
|
||||
this.networkType = networkType;
|
||||
this.networkType = walletNode.getNetworkType();
|
||||
this.daemonUsername = walletNode.getUsername();
|
||||
this.daemonPassword = walletNode.getPassword();
|
||||
setDaemonAddressJ(daemonAddress);
|
||||
@@ -227,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;
|
||||
@@ -235,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;
|
||||
@@ -269,5 +276,23 @@ public class WalletManager {
|
||||
|
||||
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
|
||||
|
||||
static public native void initLogger(String argv0, String defaultLogBaseName);
|
||||
|
||||
//TODO: maybe put these in an enum like in monero core - but why?
|
||||
static public int LOGLEVEL_SILENT = -1;
|
||||
static public int LOGLEVEL_WARN = 0;
|
||||
static public int LOGLEVEL_INFO = 1;
|
||||
static public int LOGLEVEL_DEBUG = 2;
|
||||
static public int LOGLEVEL_TRACE = 3;
|
||||
static public int LOGLEVEL_MAX = 4;
|
||||
|
||||
static public native void setLogLevel(int level);
|
||||
|
||||
static public native void logDebug(String category, String message);
|
||||
|
||||
static public native void logInfo(String category, String message);
|
||||
|
||||
static public native void logWarning(String category, String message);
|
||||
|
||||
static public native void logError(String category, String message);
|
||||
}
|
@@ -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)
|
@@ -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());
|
||||
}
|
@@ -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 {
|
||||
|
@@ -0,0 +1,54 @@
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class CrazyPassEncoder {
|
||||
static final String BASE = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
||||
static final int PW_CHARS = 52;
|
||||
|
||||
// this takes a 32 byte buffer and converts it to 52 alphnumeric characters
|
||||
// separated by blanks every 4 characters = 13 groups of 4
|
||||
// always (padding by Xs if need be
|
||||
static public String encode(byte[] data) {
|
||||
if (data.length != 32) throw new IllegalArgumentException("data[] is not 32 bytes long");
|
||||
BigInteger rest = new BigInteger(1, data);
|
||||
BigInteger remainder;
|
||||
final StringBuilder result = new StringBuilder();
|
||||
final BigInteger base = BigInteger.valueOf(BASE.length());
|
||||
int i = 0;
|
||||
do {
|
||||
if ((i > 0) && (i % 4 == 0)) result.append(' ');
|
||||
i++;
|
||||
remainder = rest.remainder(base);
|
||||
rest = rest.divide(base);
|
||||
result.append(BASE.charAt(remainder.intValue()));
|
||||
} while (!BigInteger.ZERO.equals(rest));
|
||||
// pad it
|
||||
while (i < PW_CHARS) {
|
||||
if ((i > 0) && (i % 4 == 0)) result.append(' ');
|
||||
result.append('2');
|
||||
i++;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
static public String reformat(String password) {
|
||||
// maybe this is a CrAzYpass without blanks? or lowercase letters
|
||||
String noBlanks = password.toUpperCase().replaceAll(" ", "");
|
||||
if (noBlanks.length() == PW_CHARS) { // looks like a CrAzYpass
|
||||
// insert blanks every 4 characters
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < PW_CHARS; i++) {
|
||||
if ((i > 0) && (i % 4 == 0)) sb.append(' ');
|
||||
char c = noBlanks.charAt(i);
|
||||
if (BASE.indexOf(c) < 0) return null; // invalid character found
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
} else {
|
||||
return null; // not a CrAzYpass
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
|
||||
public class FingerprintHelper {
|
||||
|
||||
public static boolean isDeviceSupported(Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 isFingerPassValid(Context context, String wallet) {
|
||||
try {
|
||||
KeyStoreHelper.loadWalletUserPass(context, wallet);
|
||||
return true;
|
||||
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void authenticate(Context context, CancellationSignal cancelSignal,
|
||||
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
353
app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
Normal file
353
app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
Normal file
File diff suppressed because it is too large
Load Diff
135
app/src/main/java/com/m2049r/xmrwallet/util/Notice.java
Normal file
135
app/src/main/java/com/m2049r/xmrwallet/util/Notice.java
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.dialog.HelpFragment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Notice {
|
||||
private static final String PREFS_NAME = "notice";
|
||||
private static List<Notice> notices = null;
|
||||
|
||||
private static final String NOTICE_SHOW_XMRTO_ENABLED_LOGIN = "notice_xmrto_enabled_login";
|
||||
private static final String NOTICE_SHOW_XMRTO_ENABLED_SEND = "notice_xmrto_enabled_send";
|
||||
private static final String NOTICE_SHOW_CRAZYPASS = "notice_crazypass_enabled_login";
|
||||
|
||||
private static void init() {
|
||||
synchronized (Notice.class) {
|
||||
if (notices != null) return;
|
||||
notices = new ArrayList<>();
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_XMRTO_ENABLED_SEND,
|
||||
R.string.info_xmrto_enabled,
|
||||
R.string.help_xmrto,
|
||||
1)
|
||||
);
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_XMRTO_ENABLED_LOGIN,
|
||||
R.string.info_xmrto_enabled,
|
||||
R.string.help_xmrto,
|
||||
1)
|
||||
);
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_CRAZYPASS,
|
||||
R.string.info_crazypass_enabled,
|
||||
R.string.help_details,
|
||||
2)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void showAll(ViewGroup parent, String selector) {
|
||||
if (notices == null) init();
|
||||
for (Notice notice : notices) {
|
||||
if (notice.id.matches(selector))
|
||||
notice.show(parent);
|
||||
}
|
||||
}
|
||||
|
||||
private final String id;
|
||||
private final int textResId;
|
||||
private final int helpResId;
|
||||
private final int defaultCount;
|
||||
private transient int count = -1;
|
||||
|
||||
private Notice(final String id, final int textResId, final int helpResId, final int defaultCount) {
|
||||
this.id = id;
|
||||
this.textResId = textResId;
|
||||
this.helpResId = helpResId;
|
||||
this.defaultCount = defaultCount;
|
||||
}
|
||||
|
||||
// show this notice as a child of the given parent view
|
||||
// NB: it assumes the parent is in a Fragment
|
||||
private void show(final ViewGroup parent) {
|
||||
final Context context = parent.getContext();
|
||||
if (getCount(context) <= 0) return; // don't add it
|
||||
|
||||
final LinearLayout ll =
|
||||
(LinearLayout) LayoutInflater.from(context)
|
||||
.inflate(R.layout.template_notice, parent, false);
|
||||
|
||||
((TextView) ll.findViewById(R.id.tvNotice)).setText(textResId);
|
||||
|
||||
final FragmentManager fragmentManager =
|
||||
((FragmentActivity) context).getSupportFragmentManager();
|
||||
ll.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HelpFragment.display(fragmentManager, helpResId);
|
||||
}
|
||||
});
|
||||
|
||||
ImageButton ib = (ImageButton) ll.findViewById(R.id.ibClose);
|
||||
ib.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
ll.setVisibility(View.GONE);
|
||||
decCount(context);
|
||||
}
|
||||
});
|
||||
parent.addView(ll);
|
||||
}
|
||||
|
||||
private int getCount(final Context context) {
|
||||
count = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.getInt(id, defaultCount);
|
||||
return count;
|
||||
}
|
||||
|
||||
private void decCount(final Context context) {
|
||||
final SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
if (count < 0) // not initialized yet
|
||||
count = prefs.getInt(id, defaultCount);
|
||||
if (count > 0)
|
||||
prefs.edit().putInt(id, count - 1).apply();
|
||||
}
|
||||
}
|
@@ -87,6 +87,8 @@ public class RestoreHeight {
|
||||
blockheight.put("2017-12-01", 1454639L);
|
||||
blockheight.put("2018-01-01", 1477201L);
|
||||
blockheight.put("2018-02-01", 1499599L);
|
||||
blockheight.put("2018-03-01", 1519796L);
|
||||
blockheight.put("2018-04-01", 1542067L);
|
||||
}
|
||||
|
||||
public long getHeight(String date) {
|
||||
|
@@ -36,9 +36,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;
|
||||
|
||||
@@ -250,7 +248,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 +456,4 @@ public class ExchangeTextView extends LinearLayout
|
||||
tvAmountA.setText(null);
|
||||
doExchange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user