mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-03 08:23:04 +02:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7206857a5b | ||
![]() |
85d84d09ee | ||
![]() |
c6a1b503bc | ||
![]() |
8bda7aa0cf | ||
![]() |
97fb0a5483 | ||
![]() |
fc950c6772 | ||
![]() |
46add5e927 | ||
![]() |
fa0692ceab | ||
![]() |
ff4f4a1c2c | ||
![]() |
79abb89725 | ||
![]() |
ef8301fd6f | ||
![]() |
3a15c842ff | ||
![]() |
1697da55b5 | ||
![]() |
454f3e412a | ||
![]() |
d803a1e220 | ||
![]() |
f2fe781cb5 | ||
![]() |
dcf60ae193 | ||
![]() |
ffdf54c2e1 | ||
![]() |
c060a2ab88 | ||
![]() |
05fc654f3a | ||
![]() |
c32d157150 | ||
![]() |
74e9278baa | ||
![]() |
e41e344d63 | ||
![]() |
e66875437d | ||
![]() |
c1d2db3d7d | ||
![]() |
0c9a2f5e01 | ||
![]() |
64616e3921 | ||
![]() |
82b4d66987 | ||
![]() |
10f2bc6561 | ||
![]() |
2ed9a78d9e | ||
![]() |
ca19f32f8f | ||
![]() |
4431d74051 | ||
![]() |
f00da6ecda | ||
![]() |
1cecd0b718 | ||
![]() |
a0d6117bbb | ||
![]() |
0b0648a172 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@
|
||||
/app/prodMainnet
|
||||
/app/alphaStagenet
|
||||
/app/prodStagenet
|
||||
/app/.cxx
|
||||
|
@@ -2,14 +2,13 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
buildToolsVersion '29.0.2'
|
||||
defaultConfig {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 301
|
||||
versionName "1.13.1 'ReStart'"
|
||||
|
||||
versionCode 408
|
||||
versionName "1.14.8 'On Board'"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
@@ -93,6 +92,11 @@ android {
|
||||
outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -102,6 +106,7 @@ dependencies {
|
||||
implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion"
|
||||
implementation "com.android.support:cardview-v7:$rootProject.ext.supportVersion"
|
||||
implementation "com.android.support:swiperefreshlayout:$rootProject.ext.supportVersion"
|
||||
implementation "com.android.support.constraint:constraint-layout:$rootProject.ext.constraintVersion"
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
|
||||
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
||||
@@ -124,5 +129,4 @@ dependencies {
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
||||
testImplementation 'org.json:json:20180813'
|
||||
testImplementation 'net.jodah:concurrentunit:0.4.4'
|
||||
|
||||
}
|
||||
|
@@ -20,24 +20,27 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/MyMaterialTheme"
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
<activity android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".WalletActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/wallet_activity_name"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="behind" />
|
||||
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="locked">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
@@ -62,6 +65,10 @@
|
||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/usb_device_filter" />
|
||||
</activity>
|
||||
<activity android:name=".onboarding.OnBoardingActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="portrait"/>
|
||||
|
||||
<service
|
||||
android:name=".service.WalletService"
|
||||
@@ -79,4 +86,4 @@
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -315,10 +315,14 @@ public class LoginActivity extends BaseActivity
|
||||
if (WalletManager.getInstance().walletExists(walletFile)) {
|
||||
Helper.promptPassword(LoginActivity.this, walletName, true, new Helper.PasswordAction() {
|
||||
@Override
|
||||
public void action(String walletName, String password, boolean fingerprintUsed) {
|
||||
public void act(String walletName, String password, boolean fingerprintUsed) {
|
||||
if (checkDevice(walletName, password))
|
||||
startDetails(walletFile, password, GenerateReviewFragment.VIEW_TYPE_DETAILS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
}
|
||||
});
|
||||
} else { // this cannot really happen as we prefilter choices
|
||||
Timber.e("Wallet missing: %s", walletName);
|
||||
@@ -348,10 +352,14 @@ public class LoginActivity extends BaseActivity
|
||||
if (WalletManager.getInstance().walletExists(walletFile)) {
|
||||
Helper.promptPassword(LoginActivity.this, walletName, false, new Helper.PasswordAction() {
|
||||
@Override
|
||||
public void action(String walletName, String password, boolean fingerprintUsed) {
|
||||
public void act(String walletName, String password, boolean fingerprintUsed) {
|
||||
if (checkDevice(walletName, password))
|
||||
startReceive(walletFile, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
}
|
||||
});
|
||||
} else { // this cannot really happen as we prefilter choices
|
||||
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
|
||||
@@ -1306,10 +1314,15 @@ public class LoginActivity extends BaseActivity
|
||||
Helper.promptPassword(LoginActivity.this, walletName, false,
|
||||
new Helper.PasswordAction() {
|
||||
@Override
|
||||
public void action(String walletName, String password, boolean fingerprintUsed) {
|
||||
public void act(String walletName, String password, boolean fingerprintUsed) {
|
||||
if (checkDevice(walletName, password))
|
||||
startWallet(walletName, password, fingerprintUsed, streetmode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
}
|
||||
|
||||
});
|
||||
} else { // this cannot really happen as we prefilter choices
|
||||
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
|
||||
|
38
app/src/main/java/com/m2049r/xmrwallet/MainActivity.java
Normal file
38
app/src/main/java/com/m2049r/xmrwallet/MainActivity.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 EarlOfEgo, m2049r
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import com.m2049r.xmrwallet.onboarding.OnBoardingActivity;
|
||||
import com.m2049r.xmrwallet.onboarding.OnBoardingManager;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (OnBoardingManager.shouldShowOnBoarding(getApplicationContext())) {
|
||||
startActivity(new Intent(this, OnBoardingActivity.class));
|
||||
} else {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
@@ -88,7 +88,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
private ActionBarDrawerToggle drawerToggle;
|
||||
|
||||
private Toolbar toolbar;
|
||||
private boolean needVerifyIdentity;
|
||||
private boolean requestStreetMode = false;
|
||||
|
||||
private String password;
|
||||
@@ -142,7 +141,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
|
||||
private void enableStreetMode(boolean enable) {
|
||||
if (enable) {
|
||||
needVerifyIdentity = true;
|
||||
streetMode = getWallet().getDaemonBlockChainHeight();
|
||||
} else {
|
||||
streetMode = 0;
|
||||
@@ -151,11 +149,9 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||
if (walletFragment != null) walletFragment.resetDismissedTransactions();
|
||||
forceUpdate();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runOnUiThread(() -> {
|
||||
if (getWallet() != null)
|
||||
updateAccountsBalance();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -200,7 +196,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
if (extras != null) {
|
||||
acquireWakeLock();
|
||||
String walletId = extras.getString(REQUEST_ID);
|
||||
needVerifyIdentity = extras.getBoolean(REQUEST_FINGERPRINT_USED);
|
||||
// we can set the streetmode height AFTER opening the wallet
|
||||
requestStreetMode = extras.getBoolean(REQUEST_STREETMODE);
|
||||
password = extras.getString(REQUEST_PW);
|
||||
@@ -333,7 +328,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
private void onDisableStreetMode() {
|
||||
Helper.promptPassword(WalletActivity.this, getWallet().getName(), false, new Helper.PasswordAction() {
|
||||
@Override
|
||||
public void action(String walletName, String password, boolean fingerprintUsed) {
|
||||
public void act(String walletName, String password, boolean fingerprintUsed) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -342,6 +337,10 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -576,10 +575,9 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
@Override
|
||||
public boolean onRefreshed(final Wallet wallet, final boolean full) {
|
||||
Timber.d("onRefreshed()");
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
runOnUiThread(() -> {
|
||||
if (getWallet() != null)
|
||||
updateAccountsBalance();
|
||||
}
|
||||
});
|
||||
if (numAccounts != wallet.getNumAccounts()) {
|
||||
numAccounts = wallet.getNumAccounts();
|
||||
@@ -855,17 +853,16 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
final Bundle extras = new Bundle();
|
||||
extras.putString(GenerateReviewFragment.REQUEST_TYPE, GenerateReviewFragment.VIEW_TYPE_WALLET);
|
||||
|
||||
if (needVerifyIdentity) {
|
||||
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
|
||||
@Override
|
||||
public void action(String walletName, String password, boolean fingerprintUsed) {
|
||||
replaceFragment(new GenerateReviewFragment(), null, extras);
|
||||
needVerifyIdentity = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
replaceFragment(new GenerateReviewFragment(), null, extras);
|
||||
}
|
||||
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
|
||||
@Override
|
||||
public void act(String walletName, String password, boolean fingerprintUsed) {
|
||||
replaceFragment(new GenerateReviewFragment(), null, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
@@ -1003,12 +1000,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
return getWallet().getUnlockedBalance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyWalletPassword(String password) {
|
||||
String walletPassword = Helper.getWalletPassword(getApplicationContext(), getWalletName(), password);
|
||||
return walletPassword != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (drawer.isDrawerOpen(GravityCompat.START)) {
|
||||
|
@@ -16,24 +16,15 @@
|
||||
|
||||
package com.m2049r.xmrwallet.fragment.send;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.data.TxData;
|
||||
import com.m2049r.xmrwallet.data.TxDataBtc;
|
||||
@@ -213,7 +204,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
((TxDataBtc) sendListener.getTxData()).setXmrtoUuid(xmrtoStatus.getUuid());
|
||||
// TODO make method in TxDataBtc to set both of the above in one go
|
||||
sendListener.commitTransaction();
|
||||
pbProgressSend.setVisibility(View.VISIBLE);
|
||||
getActivity().runOnUiThread(() -> pbProgressSend.setVisibility(View.VISIBLE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -346,103 +337,18 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
|
||||
public void preSend() {
|
||||
final Activity activity = getActivity();
|
||||
View promptsView = getLayoutInflater().inflate(R.layout.prompt_password, null);
|
||||
android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity);
|
||||
alertDialogBuilder.setView(promptsView);
|
||||
|
||||
final TextInputLayout etPassword = promptsView.findViewById(R.id.etPassword);
|
||||
etPassword.setHint(getString(R.string.prompt_send_password));
|
||||
|
||||
etPassword.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
Helper.promptPassword(getContext(), getActivityCallback().getWalletName(), false, new Helper.PasswordAction() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (etPassword.getError() != null) {
|
||||
etPassword.setError(null);
|
||||
}
|
||||
public void act(String walletName, String password, boolean fingerprintUsed) {
|
||||
send();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start,
|
||||
int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start,
|
||||
int before, int count) {
|
||||
}
|
||||
});
|
||||
|
||||
alertDialogBuilder
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (getActivityCallback().verifyWalletPassword(pass)) {
|
||||
dialog.dismiss();
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
send();
|
||||
} else {
|
||||
etPassword.setError(getString(R.string.bad_password));
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(getString(R.string.label_cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
dialog.cancel();
|
||||
bSend.setEnabled(sendCountdown > 0); // allow to try again
|
||||
}
|
||||
});
|
||||
|
||||
final android.app.AlertDialog passwordDialog = alertDialogBuilder.create();
|
||||
passwordDialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
||||
@Override
|
||||
public void onShow(DialogInterface dialog) {
|
||||
Button button = ((android.app.AlertDialog) dialog).getButton(android.app.AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (getActivityCallback().verifyWalletPassword(pass)) {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
passwordDialog.dismiss();
|
||||
send();
|
||||
} else {
|
||||
etPassword.setError(getString(R.string.bad_password));
|
||||
}
|
||||
}
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
bSend.setEnabled(sendCountdown > 0); // allow to try again
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Helper.showKeyboard(passwordDialog);
|
||||
|
||||
// accept keyboard "ok"
|
||||
etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (getActivityCallback().verifyWalletPassword(pass)) {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
passwordDialog.dismiss();
|
||||
send();
|
||||
} else {
|
||||
etPassword.setError(getString(R.string.bad_password));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (Helper.preventScreenshot()) {
|
||||
passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
|
||||
passwordDialog.show();
|
||||
}
|
||||
|
||||
// creates a pending transaction and calls us back with transactionCreated()
|
||||
@@ -457,8 +363,8 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
showProgress(3, getString(R.string.label_send_progress_create_tx));
|
||||
TxData txData = sendListener.getTxData();
|
||||
txData.setDestinationAddress(xmrtoStatus.getXmrReceivingSubaddress());
|
||||
txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getXmrAmountTotal()));
|
||||
txData.setDestinationAddress(xmrtoStatus.getReceivingSubaddress());
|
||||
txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getIncomingAmountTotal()));
|
||||
getActivityCallback().onPrepareSend(xmrtoStatus.getUuid(), txData);
|
||||
}
|
||||
|
||||
@@ -572,22 +478,22 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
NumberFormat df = NumberFormat.getInstance(Locale.US);
|
||||
df.setMaximumFractionDigits(12);
|
||||
String btcAmount = df.format(status.getBtcAmount());
|
||||
String xmrAmountTotal = df.format(status.getXmrAmountTotal());
|
||||
String xmrAmountTotal = df.format(status.getIncomingAmountTotal());
|
||||
tvTxBtcAmount.setText(getString(R.string.text_send_btc_amount, btcAmount, xmrAmountTotal));
|
||||
String xmrPriceBtc = df.format(status.getXmrPriceBtc());
|
||||
String xmrPriceBtc = df.format(status.getIncomingPriceBtc());
|
||||
tvTxBtcRate.setText(getString(R.string.text_send_btc_rate, xmrPriceBtc));
|
||||
|
||||
double calcRate = status.getBtcAmount() / status.getXmrPriceBtc();
|
||||
Timber.i("Rates: %f / %f", calcRate, status.getXmrPriceBtc());
|
||||
double calcRate = status.getBtcAmount() / status.getIncomingPriceBtc();
|
||||
Timber.d("Rates: %f / %f", calcRate, status.getIncomingPriceBtc());
|
||||
|
||||
tvTxBtcAddress.setText(status.getBtcDestAddress()); // TODO test if this is different?
|
||||
|
||||
Timber.i("Expires @ %s, in %s seconds", status.getExpiresAt().toString(), status.getSecondsTillTimeout());
|
||||
Timber.d("Expires @ %s, in %s seconds", status.getExpiresAt().toString(), status.getSecondsTillTimeout());
|
||||
|
||||
Timber.i("Status = %s", status.getState().toString());
|
||||
Timber.d("Status = %s", status.getState().toString());
|
||||
tvTxXmrToKey.setText(status.getUuid());
|
||||
|
||||
Timber.d("AmountRemaining=%f, XmrAmountTotal=%f", status.getXmrAmountRemaining(), status.getXmrAmountTotal());
|
||||
Timber.d("AmountRemaining=%f, XmrAmountTotal=%f", status.getRemainingAmountIncoming(), status.getIncomingAmountTotal());
|
||||
hideProgress();
|
||||
startSendTimer();
|
||||
prepareSend();
|
||||
|
@@ -16,29 +16,19 @@
|
||||
|
||||
package com.m2049r.xmrwallet.fragment.send;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.data.TxData;
|
||||
import com.m2049r.xmrwallet.data.UserNotes;
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.data.UserNotes;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -141,7 +131,7 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
||||
|
||||
void send() {
|
||||
sendListener.commitTransaction();
|
||||
pbProgressSend.setVisibility(View.VISIBLE);
|
||||
getActivity().runOnUiThread(() -> pbProgressSend.setVisibility(View.VISIBLE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -225,103 +215,18 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
||||
}
|
||||
|
||||
public void preSend() {
|
||||
final Activity activity = getActivity();
|
||||
View promptsView = getLayoutInflater().inflate(R.layout.prompt_password, null);
|
||||
android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity);
|
||||
alertDialogBuilder.setView(promptsView);
|
||||
|
||||
final TextInputLayout etPassword = promptsView.findViewById(R.id.etPassword);
|
||||
etPassword.setHint(getString(R.string.prompt_send_password));
|
||||
|
||||
etPassword.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
Helper.promptPassword(getContext(), getActivityCallback().getWalletName(), false, new Helper.PasswordAction() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (etPassword.getError() != null) {
|
||||
etPassword.setError(null);
|
||||
}
|
||||
public void act(String walletName, String password, boolean fingerprintUsed) {
|
||||
send();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start,
|
||||
int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start,
|
||||
int before, int count) {
|
||||
}
|
||||
});
|
||||
|
||||
alertDialogBuilder
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (getActivityCallback().verifyWalletPassword(pass)) {
|
||||
dialog.dismiss();
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
send();
|
||||
} else {
|
||||
etPassword.setError(getString(R.string.bad_password));
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(getString(R.string.label_cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
dialog.cancel();
|
||||
bSend.setEnabled(true); // allow to try again
|
||||
}
|
||||
});
|
||||
|
||||
final android.app.AlertDialog passwordDialog = alertDialogBuilder.create();
|
||||
passwordDialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
||||
@Override
|
||||
public void onShow(DialogInterface dialog) {
|
||||
Button button = ((android.app.AlertDialog) dialog).getButton(android.app.AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (getActivityCallback().verifyWalletPassword(pass)) {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
passwordDialog.dismiss();
|
||||
send();
|
||||
} else {
|
||||
etPassword.setError(getString(R.string.bad_password));
|
||||
}
|
||||
}
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
bSend.setEnabled(true); // allow to try again
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Helper.showKeyboard(passwordDialog);
|
||||
|
||||
// accept keyboard "ok"
|
||||
etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (getActivityCallback().verifyWalletPassword(pass)) {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
passwordDialog.dismiss();
|
||||
send();
|
||||
} else {
|
||||
etPassword.setError(getString(R.string.bad_password));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (Helper.preventScreenshot()) {
|
||||
passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
|
||||
passwordDialog.show();
|
||||
}
|
||||
|
||||
// creates a pending transaction and calls us back with transactionCreated()
|
||||
|
@@ -75,7 +75,7 @@ public class SendFragment extends Fragment
|
||||
|
||||
void onPrepareSend(String tag, TxData data);
|
||||
|
||||
boolean verifyWalletPassword(String password);
|
||||
String getWalletName();
|
||||
|
||||
void onSend(UserNotes notes);
|
||||
|
||||
|
@@ -17,12 +17,15 @@
|
||||
package com.m2049r.xmrwallet.ledger;
|
||||
|
||||
public enum Instruction {
|
||||
|
||||
INS_NONE(0x00),
|
||||
INS_RESET(0x02),
|
||||
INS_GET_KEY(0x20),
|
||||
INS_DISPLAY_ADDRESS(0x21),
|
||||
INS_PUT_KEY(0x22),
|
||||
INS_GET_CHACHA8_PREKEY(0x24),
|
||||
INS_VERIFY_KEY(0x26),
|
||||
INS_MANAGE_SEEDWORDS(0x28),
|
||||
|
||||
INS_SECRET_KEY_TO_PUBLIC_KEY(0x30),
|
||||
INS_GEN_KEY_DERIVATION(0x32),
|
||||
@@ -30,6 +33,7 @@ public enum Instruction {
|
||||
INS_DERIVE_PUBLIC_KEY(0x36),
|
||||
INS_DERIVE_SECRET_KEY(0x38),
|
||||
INS_GEN_KEY_IMAGE(0x3A),
|
||||
|
||||
INS_SECRET_KEY_ADD(0x3C),
|
||||
INS_SECRET_KEY_SUB(0x3E),
|
||||
INS_GENERATE_KEYPAIR(0x40),
|
||||
@@ -45,15 +49,20 @@ public enum Instruction {
|
||||
INS_SET_SIGNATURE_MODE(0x72),
|
||||
INS_GET_ADDITIONAL_KEY(0x74),
|
||||
INS_STEALTH(0x76),
|
||||
INS_GEN_COMMITMENT_MASK(0x77),
|
||||
INS_BLIND(0x78),
|
||||
INS_UNBLIND(0x7A),
|
||||
INS_GEN_TXOUT_KEYS(0x7B),
|
||||
INS_VALIDATE(0x7C),
|
||||
INS_PREFIX_HASH(0x7D),
|
||||
INS_MLSAG(0x7E),
|
||||
INS_CLOSE_TX(0x80),
|
||||
|
||||
INS_GET_RESPONSE(0xc0),
|
||||
INS_GET_TX_PROOF(0xA0),
|
||||
|
||||
INS_UNDEFINED(0xff);
|
||||
INS_GET_RESPONSE(0xC0),
|
||||
|
||||
INS_UNDEFINED(0xFF);
|
||||
|
||||
public static Instruction fromByte(byte n) {
|
||||
switch (n & 0xFF) {
|
||||
|
@@ -42,11 +42,11 @@ public class Ledger {
|
||||
static public final int LOOKAHEAD_SUBADDRESSES = 20;
|
||||
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
|
||||
|
||||
private static final byte PROTOCOL_VERSION = 0x02;
|
||||
private static final byte PROTOCOL_VERSION = 0x03;
|
||||
public static final int SW_OK = 0x9000;
|
||||
public static final int SW_INS_NOT_SUPPORTED = 0x6D00;
|
||||
public static final int OK[] = {SW_OK};
|
||||
public static final int MINIMUM_LEDGER_VERSION = (1 << 16) + (3 << 8) + (1); // 1.3.1
|
||||
public static final int MINIMUM_LEDGER_VERSION = (1 << 16) + (6 << 8) + (0); // 1.6.0
|
||||
|
||||
public static UsbDevice findDevice(UsbManager usbManager) {
|
||||
if (!ENABLED) return null;
|
||||
|
@@ -51,6 +51,7 @@ public class LedgerProgressDialog extends ProgressDialog implements Ledger.Liste
|
||||
switch (ins) {
|
||||
case INS_RESET: // ledger may ask for confirmation - maybe a bug?
|
||||
case INS_GET_KEY: // ledger asks for confirmation to send keys
|
||||
case INS_DISPLAY_ADDRESS:
|
||||
setIndeterminate(true);
|
||||
setMessage(getContext().getString(R.string.progress_ledger_confirm));
|
||||
break;
|
||||
@@ -102,6 +103,11 @@ public class LedgerProgressDialog extends ProgressDialog implements Ledger.Liste
|
||||
setMessage(getContext().getString(R.string.progress_ledger_mlsag));
|
||||
}
|
||||
break;
|
||||
case INS_PREFIX_HASH:
|
||||
if ((apdu[2] != 1) || (apdu[3] != 0)) break;
|
||||
setIndeterminate(true);
|
||||
setMessage(getContext().getString(R.string.progress_ledger_confirm));
|
||||
break;
|
||||
case INS_VALIDATE:
|
||||
if ((apdu[2] != 1) || (apdu[3] != 1)) break;
|
||||
validate = true;
|
||||
|
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 EarlOfEgo, 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.onboarding;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.m2049r.xmrwallet.LoginActivity;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
|
||||
public class OnBoardingActivity extends AppCompatActivity implements OnBoardingAdapter.Listener {
|
||||
|
||||
private OnBoardingViewPager pager;
|
||||
private OnBoardingAdapter pagerAdapter;
|
||||
private Button nextButton;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_on_boarding);
|
||||
|
||||
nextButton = findViewById(R.id.buttonNext);
|
||||
|
||||
pager = findViewById(R.id.pager);
|
||||
pagerAdapter = new OnBoardingAdapter(getApplicationContext(), this);
|
||||
pager.setAdapter(pagerAdapter);
|
||||
int pixels = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics());
|
||||
pager.setPageMargin(pixels);
|
||||
pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
setButtonState(position);
|
||||
}
|
||||
});
|
||||
|
||||
final TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout);
|
||||
if (pagerAdapter.getCount() > 1) {
|
||||
tabLayout.setupWithViewPager(pager, true);
|
||||
LinearLayout tabStrip = ((LinearLayout) tabLayout.getChildAt(0));
|
||||
for (int i = 0; i < tabStrip.getChildCount(); i++) {
|
||||
tabStrip.getChildAt(i).setClickable(false);
|
||||
}
|
||||
} else {
|
||||
tabLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
nextButton.setOnClickListener(v -> {
|
||||
final int item = pager.getCurrentItem();
|
||||
if (item + 1 >= pagerAdapter.getCount()) {
|
||||
finishOnboarding();
|
||||
} else {
|
||||
pager.setCurrentItem(item + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// let old users who have fingerprint wallets already agree for fingerprint sending
|
||||
OnBoardingScreen.FPSEND.setMustAgree(KeyStoreHelper.hasStoredPasswords(this));
|
||||
|
||||
for (int i = 0; i < OnBoardingScreen.values().length; i++) {
|
||||
agreed[i] = !OnBoardingScreen.values()[i].isMustAgree();
|
||||
}
|
||||
|
||||
setButtonState(0);
|
||||
}
|
||||
|
||||
private void finishOnboarding() {
|
||||
nextButton.setEnabled(false);
|
||||
OnBoardingManager.setOnBoardingShown(getApplicationContext());
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
}
|
||||
|
||||
boolean[] agreed = new boolean[OnBoardingScreen.values().length];
|
||||
|
||||
@Override
|
||||
public void setAgreeClicked(int position, boolean isChecked) {
|
||||
agreed[position] = isChecked;
|
||||
setButtonState(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAgreeClicked(int position) {
|
||||
return agreed[position];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setButtonState(int position) {
|
||||
nextButton.setEnabled(agreed[position]);
|
||||
if (nextButton.isEnabled())
|
||||
pager.setAllowedSwipeDirection(OnBoardingViewPager.SwipeDirection.ALL);
|
||||
else
|
||||
pager.setAllowedSwipeDirection(OnBoardingViewPager.SwipeDirection.LEFT);
|
||||
if (pager.getCurrentItem() + 1 == pagerAdapter.getCount()) { // last page
|
||||
nextButton.setText(R.string.onboarding_button_ready);
|
||||
} else {
|
||||
nextButton.setText(R.string.onboarding_button_next);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 EarlOfEgo, 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.onboarding;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.PagerAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class OnBoardingAdapter extends PagerAdapter {
|
||||
|
||||
interface Listener {
|
||||
void setAgreeClicked(int position, boolean isChecked);
|
||||
|
||||
boolean isAgreeClicked(int position);
|
||||
|
||||
void setButtonState(int position);
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private Listener listener;
|
||||
|
||||
OnBoardingAdapter(final Context context, final Listener listener) {
|
||||
this.context = context;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Object instantiateItem(@NonNull ViewGroup collection, int position) {
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
final View view = inflater.inflate(R.layout.view_onboarding, collection, false);
|
||||
final OnBoardingScreen onBoardingScreen = OnBoardingScreen.values()[position];
|
||||
|
||||
final Drawable drawable = ContextCompat.getDrawable(context, onBoardingScreen.getDrawable());
|
||||
((ImageView) view.findViewById(R.id.onboardingImage)).setImageDrawable(drawable);
|
||||
((TextView) view.findViewById(R.id.onboardingTitle)).setText(onBoardingScreen.getTitle());
|
||||
((TextView) view.findViewById(R.id.onboardingInformation)).setText(onBoardingScreen.getInformation());
|
||||
if (onBoardingScreen.isMustAgree()) {
|
||||
final CheckBox agree = ((CheckBox) view.findViewById(R.id.onboardingAgree));
|
||||
agree.setVisibility(View.VISIBLE);
|
||||
agree.setChecked(listener.isAgreeClicked(position));
|
||||
agree.setOnClickListener(v -> {
|
||||
listener.setAgreeClicked(position, ((CheckBox) v).isChecked());
|
||||
});
|
||||
}
|
||||
collection.addView(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return OnBoardingScreen.values().length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) {
|
||||
Timber.d("destroy " + position);
|
||||
collection.removeView((View) view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) {
|
||||
return view == object;
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 EarlOfEgo, 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.onboarding;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class OnBoardingManager {
|
||||
|
||||
private static final String PREFS_ONBOARDING = "PREFS_ONBOARDING";
|
||||
private static final String ONBOARDING_SHOWN = "ONBOARDING_SHOWN";
|
||||
|
||||
public static boolean shouldShowOnBoarding(final Context context) {
|
||||
return !getSharedPreferences(context).contains(ONBOARDING_SHOWN);
|
||||
}
|
||||
|
||||
public static void setOnBoardingShown(final Context context) {
|
||||
Timber.d("Set onboarding shown.");
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
sharedPreferences.edit().putLong(ONBOARDING_SHOWN, new Date().getTime()).apply();
|
||||
}
|
||||
|
||||
public static void clearOnBoardingShown(final Context context) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
sharedPreferences.edit().remove(ONBOARDING_SHOWN).apply();
|
||||
}
|
||||
|
||||
private static SharedPreferences getSharedPreferences(final Context context) {
|
||||
return context.getSharedPreferences(PREFS_ONBOARDING, Context.MODE_PRIVATE);
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 EarlOfEgo, 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.onboarding;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
|
||||
enum OnBoardingScreen {
|
||||
WELCOME(R.string.onboarding_welcome_title, R.string.onboarding_welcome_information, R.drawable.ic_onboarding_welcome, false),
|
||||
SEED(R.string.onboarding_seed_title, R.string.onboarding_seed_information, R.drawable.ic_onboarding_seed, true),
|
||||
FPSEND(R.string.onboarding_fpsend_title, R.string.onboarding_fpsend_information, R.drawable.ic_onboarding_fingerprint, false),
|
||||
XMRTO(R.string.onboarding_xmrto_title, R.string.onboarding_xmrto_information, R.drawable.ic_onboarding_xmrto, false),
|
||||
NODES(R.string.onboarding_nodes_title, R.string.onboarding_nodes_information, R.drawable.ic_onboarding_nodes, false);
|
||||
|
||||
private final int title;
|
||||
private final int information;
|
||||
private final int drawable;
|
||||
private boolean mustAgree;
|
||||
|
||||
OnBoardingScreen(final int title, final int information, final int drawable, final boolean mustAgree) {
|
||||
this.title = title;
|
||||
this.information = information;
|
||||
this.drawable = drawable;
|
||||
this.mustAgree = mustAgree;
|
||||
}
|
||||
|
||||
public int getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public int getInformation() {
|
||||
return information;
|
||||
}
|
||||
|
||||
public int getDrawable() {
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public boolean isMustAgree() {
|
||||
return mustAgree;
|
||||
}
|
||||
|
||||
public boolean setMustAgree(boolean mustAgree) {
|
||||
return this.mustAgree = mustAgree;
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2020 m2049r
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// based on https://stackoverflow.com/a/34076649
|
||||
|
||||
package com.m2049r.xmrwallet.onboarding;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
public class OnBoardingViewPager extends ViewPager {
|
||||
|
||||
public enum SwipeDirection {
|
||||
ALL, LEFT, RIGHT, NONE;
|
||||
}
|
||||
|
||||
private float initialXValue;
|
||||
private SwipeDirection direction;
|
||||
|
||||
public OnBoardingViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.direction = SwipeDirection.ALL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (this.IsSwipeAllowed(event)) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
if (this.IsSwipeAllowed(event)) {
|
||||
return super.onInterceptTouchEvent(event);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean IsSwipeAllowed(MotionEvent event) {
|
||||
if (this.direction == SwipeDirection.ALL) return true;
|
||||
|
||||
if (direction == SwipeDirection.NONE)//disable any swipe
|
||||
return false;
|
||||
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
initialXValue = event.getX();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
float diffX = event.getX() - initialXValue;
|
||||
if (diffX > 0 && direction == SwipeDirection.RIGHT) {
|
||||
// swipe from left to right detected
|
||||
return false;
|
||||
} else if (diffX < 0 && direction == SwipeDirection.LEFT) {
|
||||
// swipe from right to left detected
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setAllowedSwipeDirection(SwipeDirection direction) {
|
||||
this.direction = direction;
|
||||
}
|
||||
}
|
@@ -281,97 +281,114 @@ public class WalletService extends Service {
|
||||
case START_SERVICE: {
|
||||
Bundle extras = msg.getData();
|
||||
String cmd = extras.getString(REQUEST, null);
|
||||
if (cmd.equals(REQUEST_CMD_LOAD)) {
|
||||
String walletId = extras.getString(REQUEST_WALLET, null);
|
||||
String walletPw = extras.getString(REQUEST_CMD_LOAD_PW, null);
|
||||
Timber.d("LOAD wallet %s", walletId);
|
||||
if (walletId != null) {
|
||||
showProgress(getString(R.string.status_wallet_loading));
|
||||
showProgress(10);
|
||||
Wallet.Status walletStatus = start(walletId, walletPw);
|
||||
if (observer != null) observer.onWalletStarted(walletStatus);
|
||||
if ((walletStatus == null) || !walletStatus.isOk()) {
|
||||
errorState = true;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
} else if (cmd.equals(REQUEST_CMD_STORE)) {
|
||||
Wallet myWallet = getWallet();
|
||||
Timber.d("STORE wallet: %s", myWallet.getName());
|
||||
boolean rc = myWallet.store();
|
||||
Timber.d("wallet stored: %s with rc=%b", myWallet.getName(), rc);
|
||||
if (!rc) {
|
||||
Timber.w("Wallet store failed: %s", myWallet.getStatus().getErrorString());
|
||||
}
|
||||
if (observer != null) observer.onWalletStored(rc);
|
||||
} else if (cmd.equals(REQUEST_CMD_TX)) {
|
||||
Wallet myWallet = getWallet();
|
||||
Timber.d("CREATE TX for wallet: %s", myWallet.getName());
|
||||
myWallet.disposePendingTransaction(); // remove any old pending tx
|
||||
TxData txData = extras.getParcelable(REQUEST_CMD_TX_DATA);
|
||||
String txTag = extras.getString(REQUEST_CMD_TX_TAG);
|
||||
PendingTransaction pendingTransaction = myWallet.createTransaction(txData);
|
||||
PendingTransaction.Status status = pendingTransaction.getStatus();
|
||||
Timber.d("transaction status %s", status);
|
||||
if (status != PendingTransaction.Status.Status_Ok) {
|
||||
Timber.w("Create Transaction failed: %s", pendingTransaction.getErrorString());
|
||||
}
|
||||
if (observer != null) {
|
||||
observer.onTransactionCreated(txTag, pendingTransaction);
|
||||
} else {
|
||||
myWallet.disposePendingTransaction();
|
||||
}
|
||||
} else if (cmd.equals(REQUEST_CMD_SWEEP)) {
|
||||
Wallet myWallet = getWallet();
|
||||
Timber.d("SWEEP TX for wallet: %s", myWallet.getName());
|
||||
myWallet.disposePendingTransaction(); // remove any old pending tx
|
||||
String txTag = extras.getString(REQUEST_CMD_TX_TAG);
|
||||
PendingTransaction pendingTransaction = myWallet.createSweepUnmixableTransaction();
|
||||
PendingTransaction.Status status = pendingTransaction.getStatus();
|
||||
Timber.d("transaction status %s", status);
|
||||
if (status != PendingTransaction.Status.Status_Ok) {
|
||||
Timber.w("Create Transaction failed: %s", pendingTransaction.getErrorString());
|
||||
}
|
||||
if (observer != null) {
|
||||
observer.onTransactionCreated(txTag, pendingTransaction);
|
||||
} else {
|
||||
myWallet.disposePendingTransaction();
|
||||
}
|
||||
} else if (cmd.equals(REQUEST_CMD_SEND)) {
|
||||
Wallet myWallet = getWallet();
|
||||
Timber.d("SEND TX for wallet: %s", myWallet.getName());
|
||||
PendingTransaction pendingTransaction = myWallet.getPendingTransaction();
|
||||
if (pendingTransaction == null) {
|
||||
throw new IllegalArgumentException("PendingTransaction is null"); // die
|
||||
}
|
||||
if (pendingTransaction.getStatus() != PendingTransaction.Status.Status_Ok) {
|
||||
Timber.e("PendingTransaction is %s", pendingTransaction.getStatus());
|
||||
final String error = pendingTransaction.getErrorString();
|
||||
myWallet.disposePendingTransaction(); // it's broken anyway
|
||||
if (observer != null) observer.onSendTransactionFailed(error);
|
||||
return;
|
||||
}
|
||||
final String txid = pendingTransaction.getFirstTxId(); // tx ids vanish after commit()!
|
||||
boolean success = pendingTransaction.commit("", true);
|
||||
if (success) {
|
||||
myWallet.disposePendingTransaction();
|
||||
if (observer != null) observer.onTransactionSent(txid);
|
||||
String notes = extras.getString(REQUEST_CMD_SEND_NOTES);
|
||||
if ((notes != null) && (!notes.isEmpty())) {
|
||||
myWallet.setUserNote(txid, notes);
|
||||
switch (cmd) {
|
||||
case REQUEST_CMD_LOAD:
|
||||
String walletId = extras.getString(REQUEST_WALLET, null);
|
||||
String walletPw = extras.getString(REQUEST_CMD_LOAD_PW, null);
|
||||
Timber.d("LOAD wallet %s", walletId);
|
||||
if (walletId != null) {
|
||||
showProgress(getString(R.string.status_wallet_loading));
|
||||
showProgress(10);
|
||||
Wallet.Status walletStatus = start(walletId, walletPw);
|
||||
if (observer != null) observer.onWalletStarted(walletStatus);
|
||||
if ((walletStatus == null) || !walletStatus.isOk()) {
|
||||
errorState = true;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case REQUEST_CMD_STORE: {
|
||||
Wallet myWallet = getWallet();
|
||||
if (myWallet == null) break;
|
||||
Timber.d("STORE wallet: %s", myWallet.getName());
|
||||
boolean rc = myWallet.store();
|
||||
Timber.d("wallet stored: %s with rc=%b", myWallet.getName(), rc);
|
||||
if (!rc) {
|
||||
Timber.w("Wallet store failed: %s", myWallet.getStatus().getErrorString());
|
||||
}
|
||||
if (observer != null) observer.onWalletStored(rc);
|
||||
listener.updated = true;
|
||||
} else {
|
||||
final String error = pendingTransaction.getErrorString();
|
||||
myWallet.disposePendingTransaction();
|
||||
if (observer != null) observer.onSendTransactionFailed(error);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case REQUEST_CMD_TX: {
|
||||
Wallet myWallet = getWallet();
|
||||
if (myWallet == null) break;
|
||||
Timber.d("CREATE TX for wallet: %s", myWallet.getName());
|
||||
myWallet.disposePendingTransaction(); // remove any old pending tx
|
||||
|
||||
TxData txData = extras.getParcelable(REQUEST_CMD_TX_DATA);
|
||||
String txTag = extras.getString(REQUEST_CMD_TX_TAG);
|
||||
PendingTransaction pendingTransaction = myWallet.createTransaction(txData);
|
||||
PendingTransaction.Status status = pendingTransaction.getStatus();
|
||||
Timber.d("transaction status %s", status);
|
||||
if (status != PendingTransaction.Status.Status_Ok) {
|
||||
Timber.w("Create Transaction failed: %s", pendingTransaction.getErrorString());
|
||||
}
|
||||
if (observer != null) {
|
||||
observer.onTransactionCreated(txTag, pendingTransaction);
|
||||
} else {
|
||||
myWallet.disposePendingTransaction();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case REQUEST_CMD_SWEEP: {
|
||||
Wallet myWallet = getWallet();
|
||||
if (myWallet == null) break;
|
||||
Timber.d("SWEEP TX for wallet: %s", myWallet.getName());
|
||||
myWallet.disposePendingTransaction(); // remove any old pending tx
|
||||
|
||||
String txTag = extras.getString(REQUEST_CMD_TX_TAG);
|
||||
PendingTransaction pendingTransaction = myWallet.createSweepUnmixableTransaction();
|
||||
PendingTransaction.Status status = pendingTransaction.getStatus();
|
||||
Timber.d("transaction status %s", status);
|
||||
if (status != PendingTransaction.Status.Status_Ok) {
|
||||
Timber.w("Create Transaction failed: %s", pendingTransaction.getErrorString());
|
||||
}
|
||||
if (observer != null) {
|
||||
observer.onTransactionCreated(txTag, pendingTransaction);
|
||||
} else {
|
||||
myWallet.disposePendingTransaction();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case REQUEST_CMD_SEND: {
|
||||
Wallet myWallet = getWallet();
|
||||
if (myWallet == null) break;
|
||||
Timber.d("SEND TX for wallet: %s", myWallet.getName());
|
||||
PendingTransaction pendingTransaction = myWallet.getPendingTransaction();
|
||||
if (pendingTransaction == null) {
|
||||
throw new IllegalArgumentException("PendingTransaction is null"); // die
|
||||
}
|
||||
if (pendingTransaction.getStatus() != PendingTransaction.Status.Status_Ok) {
|
||||
Timber.e("PendingTransaction is %s", pendingTransaction.getStatus());
|
||||
final String error = pendingTransaction.getErrorString();
|
||||
myWallet.disposePendingTransaction(); // it's broken anyway
|
||||
if (observer != null) observer.onSendTransactionFailed(error);
|
||||
return;
|
||||
}
|
||||
final String txid = pendingTransaction.getFirstTxId(); // tx ids vanish after commit()!
|
||||
|
||||
boolean success = pendingTransaction.commit("", true);
|
||||
if (success) {
|
||||
myWallet.disposePendingTransaction();
|
||||
if (observer != null) observer.onTransactionSent(txid);
|
||||
String notes = extras.getString(REQUEST_CMD_SEND_NOTES);
|
||||
if ((notes != null) && (!notes.isEmpty())) {
|
||||
myWallet.setUserNote(txid, notes);
|
||||
}
|
||||
boolean rc = myWallet.store();
|
||||
Timber.d("wallet stored: %s with rc=%b", myWallet.getName(), rc);
|
||||
if (!rc) {
|
||||
Timber.w("Wallet store failed: %s", myWallet.getStatus().getErrorString());
|
||||
}
|
||||
if (observer != null) observer.onWalletStored(rc);
|
||||
listener.updated = true;
|
||||
} else {
|
||||
final String error = pendingTransaction.getErrorString();
|
||||
myWallet.disposePendingTransaction();
|
||||
if (observer != null) observer.onSendTransactionFailed(error);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -323,9 +323,9 @@ public class Helper {
|
||||
static public HttpUrl getXmrToBaseUrl() {
|
||||
if ((WalletManager.getInstance() == null)
|
||||
|| (WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet)) {
|
||||
return HttpUrl.parse("https://test.xmr.to/api/v2/xmr2btc/");
|
||||
return HttpUrl.parse("https://test.xmr.to/api/v3/xmr2btc/");
|
||||
} else {
|
||||
return HttpUrl.parse("https://xmr.to/api/v2/xmr2btc/");
|
||||
return HttpUrl.parse("https://xmr.to/api/v3/xmr2btc/");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,7 +417,7 @@ public class Helper {
|
||||
}
|
||||
|
||||
static AlertDialog openDialog = null; // for preventing opening of multiple dialogs
|
||||
static AsyncTask<Void, Void, Boolean> loginTask = null;
|
||||
static AsyncTask<Void, Void, Boolean> passwordTask = null;
|
||||
|
||||
static public void promptPassword(final Context context, final String wallet, boolean fingerprintDisabled, final PasswordAction action) {
|
||||
if (openDialog != null) return; // we are already asking for password
|
||||
@@ -442,11 +442,11 @@ public class Helper {
|
||||
|
||||
final AtomicBoolean incorrectSavedPass = new AtomicBoolean(false);
|
||||
|
||||
class LoginWalletTask extends AsyncTask<Void, Void, Boolean> {
|
||||
class PasswordTask extends AsyncTask<Void, Void, Boolean> {
|
||||
private String pass;
|
||||
private boolean fingerprintUsed;
|
||||
|
||||
LoginWalletTask(String pass, boolean fingerprintUsed) {
|
||||
PasswordTask(String pass, boolean fingerprintUsed) {
|
||||
this.pass = pass;
|
||||
this.fingerprintUsed = fingerprintUsed;
|
||||
}
|
||||
@@ -488,7 +488,7 @@ public class Helper {
|
||||
etPassword.setError(context.getString(R.string.bad_password));
|
||||
}
|
||||
}
|
||||
loginTask = null;
|
||||
passwordTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,9 +521,9 @@ public class Helper {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Helper.hideKeyboardAlways((Activity) context);
|
||||
cancelSignal.cancel();
|
||||
if (loginTask != null) {
|
||||
loginTask.cancel(true);
|
||||
loginTask = null;
|
||||
if (passwordTask != null) {
|
||||
passwordTask.cancel(true);
|
||||
passwordTask = null;
|
||||
}
|
||||
dialog.cancel();
|
||||
openDialog = null;
|
||||
@@ -552,9 +552,9 @@ public class Helper {
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
try {
|
||||
String userPass = KeyStoreHelper.loadWalletUserPass(context, wallet);
|
||||
if (loginTask == null) {
|
||||
loginTask = new LoginWalletTask(userPass, true);
|
||||
loginTask.execute();
|
||||
if (passwordTask == null) {
|
||||
passwordTask = new PasswordTask(userPass, true);
|
||||
passwordTask.execute();
|
||||
}
|
||||
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
|
||||
etPassword.setError(context.getString(R.string.bad_password));
|
||||
@@ -586,9 +586,9 @@ public class Helper {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (loginTask == null) {
|
||||
loginTask = new LoginWalletTask(pass, false);
|
||||
loginTask.execute();
|
||||
if (passwordTask == null) {
|
||||
passwordTask = new PasswordTask(pass, false);
|
||||
passwordTask.execute();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -601,9 +601,9 @@ public class Helper {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (loginTask == null) {
|
||||
loginTask = new LoginWalletTask(pass, false);
|
||||
loginTask.execute();
|
||||
if (passwordTask == null) {
|
||||
passwordTask = new PasswordTask(pass, false);
|
||||
passwordTask.execute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -620,15 +620,18 @@ public class Helper {
|
||||
}
|
||||
|
||||
public interface PasswordAction {
|
||||
void action(String walletName, String password, boolean fingerprintUsed);
|
||||
void act(String walletName, String password, boolean fingerprintUsed);
|
||||
|
||||
void fail(String walletName, String password, boolean fingerprintUsed);
|
||||
}
|
||||
|
||||
static private boolean processPasswordEntry(Context context, String walletName, String pass, boolean fingerprintUsed, PasswordAction action) {
|
||||
String walletPassword = Helper.getWalletPassword(context, walletName, pass);
|
||||
if (walletPassword != null) {
|
||||
action.action(walletName, walletPassword, fingerprintUsed);
|
||||
action.act(walletName, walletPassword, fingerprintUsed);
|
||||
return true;
|
||||
} else {
|
||||
action.fail(walletName, walletPassword, fingerprintUsed);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -140,6 +140,11 @@ public class KeyStoreHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasStoredPasswords(@NonNull Context context) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE);
|
||||
return prefs.getAll().size() > 0;
|
||||
}
|
||||
|
||||
public static String loadWalletUserPass(@NonNull Context context, String wallet) throws BrokenPasswordStoreException {
|
||||
String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet;
|
||||
String encoded = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
|
@@ -37,33 +37,19 @@ 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_LEDGER = "notice_ledger_enabled_login";
|
||||
private static final String NOTICE_SHOW_NODES = "notice_nodes";
|
||||
|
||||
private static void init() {
|
||||
synchronized (Notice.class) {
|
||||
if (notices != null) return;
|
||||
notices = new ArrayList<>();
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_NODES,
|
||||
R.string.info_nodes_enabled,
|
||||
R.string.help_node,
|
||||
1)
|
||||
);
|
||||
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_LEDGER,
|
||||
R.string.info_ledger_enabled,
|
||||
|
@@ -116,6 +116,9 @@ public class RestoreHeight {
|
||||
blockheight.put("2020-04-01", 2066806L);
|
||||
blockheight.put("2020-05-01", 2088411L);
|
||||
blockheight.put("2020-06-01", 2110702L);
|
||||
blockheight.put("2020-07-01", 2132318L);
|
||||
blockheight.put("2020-08-01", 2154590L);
|
||||
blockheight.put("2020-09-01", 2176790L);
|
||||
}
|
||||
|
||||
public long getHeight(String date) {
|
||||
|
@@ -43,47 +43,31 @@ public interface QueryOrderStatus {
|
||||
|
||||
boolean isError();
|
||||
|
||||
State getState(); // "state": "<order_state_as_string>",
|
||||
QueryOrderStatus.State getState();
|
||||
|
||||
double getBtcAmount(); // "btc_amount": <requested_amount_in_btc_as_float>,
|
||||
double getBtcAmount();
|
||||
|
||||
String getBtcDestAddress(); // "btc_dest_address": "<requested_destination_address_as_string>",
|
||||
String getBtcDestAddress();
|
||||
|
||||
String getUuid(); // "uuid": "<unique_order_identifier_as_12_character_string>"
|
||||
String getUuid();
|
||||
|
||||
int getBtcNumConfirmations(); // "btc_num_confirmations": <btc_num_confirmations_as_integer>,
|
||||
int getBtcNumConfirmationsThreshold();
|
||||
|
||||
int getBtcNumConfirmationsBeforePurge(); // "btc_num_confirmations_before_purge": <btc_num_confirmations_before_purge_as_integer>,
|
||||
Date getCreatedAt();
|
||||
|
||||
String getBtcTransactionId(); // "btc_transaction_id": "<btc_transaction_id_as_string>",
|
||||
Date getExpiresAt();
|
||||
|
||||
Date getCreatedAt(); // "created_at": "<timestamp_as_string>",
|
||||
int getSecondsTillTimeout();
|
||||
|
||||
Date getExpiresAt(); // "expires_at": "<timestamp_as_string>",
|
||||
double getIncomingAmountTotal();
|
||||
|
||||
int getSecondsTillTimeout(); // "seconds_till_timeout": <seconds_till_timeout_as_integer>,
|
||||
double getRemainingAmountIncoming();
|
||||
|
||||
double getXmrAmountTotal(); // "xmr_amount_total": <amount_in_xmr_for_this_order_as_float>,
|
||||
int getIncomingNumConfirmationsRemaining();
|
||||
|
||||
double getXmrAmountRemaining(); // "xmr_amount_remaining": <amount_in_xmr_that_the_user_must_still_send_as_float>,
|
||||
double getIncomingPriceBtc();
|
||||
|
||||
int getXmrNumConfirmationsRemaining(); // "xmr_num_confirmations_remaining": <num_xmr_confirmations_remaining_before_bitcoins_will_be_sent_as_integer>,
|
||||
|
||||
double getXmrPriceBtc(); // "xmr_price_btc": <price_of_1_btc_in_xmr_as_offered_by_service_as_float>,
|
||||
|
||||
String getXmrReceivingAddress(); // "xmr_receiving_address": "xmr_old_style_address_user_can_send_funds_to_as_string",
|
||||
|
||||
String getXmrReceivingSubaddress(); // <xmr_subaddress_user_needs_to_send_funds_to_as_string>,
|
||||
|
||||
String getXmrReceivingIntegratedAddress(); // "xmr_receiving_integrated_address": "xmr_integrated_address_user_needs_to_send_funds_to_as_string",
|
||||
|
||||
int getXmrRecommendedMixin(); // "xmr_recommended_mixin": <xmr_recommended_mixin_as_integer>,
|
||||
|
||||
@Deprecated
|
||||
double getXmrRequiredAmount(); // "xmr_required_amount": <xmr_amount_user_needs_to_send_as_float>,
|
||||
|
||||
String getXmrRequiredPaymentIdLong(); // "xmr_required_payment_id_long": "xmr_payment_id_user_needs_to_include_when_using_old_stlye_address_as_string"
|
||||
|
||||
String getXmrRequiredPaymentIdShort(); // "xmr_required_payment_id_short": "xmr_payment_id_included_in_integrated_address_as_string"
|
||||
String getReceivingSubaddress();
|
||||
|
||||
int getRecommendedMixin();
|
||||
}
|
||||
|
@@ -113,7 +113,8 @@ class CreateOrderImpl implements CreateOrder {
|
||||
|
||||
static JSONObject createRequest(final double amount, final String address) throws JSONException {
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("btc_amount", amount);
|
||||
jsonObject.put("amount", amount);
|
||||
jsonObject.put("amount_currency", "BTC");
|
||||
jsonObject.put("btc_dest_address", address);
|
||||
return jsonObject;
|
||||
}
|
||||
|
@@ -42,23 +42,17 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
|
||||
private String btcDestAddress; // "btc_dest_address": "<requested_destination_address_as_string>",
|
||||
private String uuid; // "uuid": "<unique_order_identifier_as_12_character_string>"
|
||||
// the following are only returned if the state is "after" TO_BE_CREATED
|
||||
private int btcNumConfirmations; // "btc_num_confirmations": <btc_num_confirmations_as_integer>,
|
||||
private int btcNumConfirmationsBeforePurge; // "btc_num_confirmations_before_purge": <btc_num_confirmations_before_purge_as_integer>,
|
||||
private String btcTransactionId; // "btc_transaction_id": "<btc_transaction_id_as_string>",
|
||||
//private int btcNumConfirmations; // "btc_num_confirmations": <btc_num_confirmations_as_integer>,
|
||||
private int btcNumConfirmationsThreshold; // "btc_num_confirmations_threshold": <btc_num_confirmations_threshold_as_integer>,
|
||||
private Date createdAt; // "created_at": "<timestamp_as_string>",
|
||||
private Date expiresAt; // "expires_at": "<timestamp_as_string>",
|
||||
private int secondsTillTimeout; // "seconds_till_timeout": <seconds_till_timeout_as_integer>,
|
||||
private double xmrAmountTotal; // "xmr_amount_total": <amount_in_xmr_for_this_order_as_float>,
|
||||
private double xmrAmountRemaining; // "xmr_amount_remaining": <amount_in_xmr_that_the_user_must_still_send_as_float>,
|
||||
private int xmrNumConfirmationsRemaining; // "xmr_num_confirmations_remaining": <num_xmr_confirmations_remaining_before_bitcoins_will_be_sent_as_integer>,
|
||||
private double xmrPriceBtc; // "xmr_price_btc": <price_of_1_btc_in_xmr_as_offered_by_service_as_float>,
|
||||
private String xmrReceivingSubaddress; // <xmr_subaddress_user_needs_to_send_funds_to_as_string>,
|
||||
private String xmrReceivingAddress; // "xmr_receiving_address": "xmr_old_style_address_user_can_send_funds_to_as_string",
|
||||
private String xmrReceivingIntegratedAddress; // "xmr_receiving_integrated_address": "xmr_integrated_address_user_needs_to_send_funds_to_as_string",
|
||||
private int xmrRecommendedMixin; // "xmr_recommended_mixin": <xmr_recommended_mixin_as_integer>,
|
||||
private double xmrRequiredAmount; // "xmr_required_amount": <xmr_amount_user_needs_to_send_as_float>,
|
||||
private String xmrRequiredPaymentIdLong; // "xmr_required_payment_id_long": "xmr_payment_id_user_needs_to_include_when_using_old_stlye_address_as_string"
|
||||
private String xmrRequiredPaymentIdShort; // "xmr_required_payment_id_short": "xmr_payment_id_included_in_integrated_address_as_string"
|
||||
private double incomingAmountTotal; // "incoming_amount_total": <amount_in_incoming_currency_for_this_order_as_float>,
|
||||
private double remainingAmountIncoming; // "remaining_amount_incoming": <amount_in_incoming_currency_that_the_user_must_still_send_as_float>,
|
||||
private int incomingNumConfirmationsRemaining; // "incoming_num_confirmations_remaining": <num_incoming_currency_confirmations_remaining_before_bitcoins_will_be_sent_as_integer>,
|
||||
private double incomingPriceBtc; // "incoming_price_btc": <price_of_1_incoming_in_btc_currency_as_offered_by_service_as_float>,
|
||||
private String receivingSubaddress; // "receiving_subaddress": <xmr_subaddress_user_needs_to_send_funds_to_as_string>,
|
||||
private int recommendedMixin; // "recommended_mixin": <recommended_mixin_as_integer>,
|
||||
|
||||
public QueryOrderStatus.State getState() {
|
||||
return state;
|
||||
@@ -76,16 +70,8 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public int getBtcNumConfirmations() {
|
||||
return btcNumConfirmations;
|
||||
}
|
||||
|
||||
public int getBtcNumConfirmationsBeforePurge() {
|
||||
return btcNumConfirmationsBeforePurge;
|
||||
}
|
||||
|
||||
public String getBtcTransactionId() {
|
||||
return btcTransactionId;
|
||||
public int getBtcNumConfirmationsThreshold() {
|
||||
return btcNumConfirmationsThreshold;
|
||||
}
|
||||
|
||||
public Date getCreatedAt() {
|
||||
@@ -100,48 +86,28 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
|
||||
return secondsTillTimeout;
|
||||
}
|
||||
|
||||
public double getXmrAmountTotal() {
|
||||
return xmrAmountTotal;
|
||||
public double getIncomingAmountTotal() {
|
||||
return incomingAmountTotal;
|
||||
}
|
||||
|
||||
public double getXmrAmountRemaining() {
|
||||
return xmrAmountRemaining;
|
||||
public double getRemainingAmountIncoming() {
|
||||
return remainingAmountIncoming;
|
||||
}
|
||||
|
||||
public int getXmrNumConfirmationsRemaining() {
|
||||
return xmrNumConfirmationsRemaining;
|
||||
public int getIncomingNumConfirmationsRemaining() {
|
||||
return incomingNumConfirmationsRemaining;
|
||||
}
|
||||
|
||||
public double getXmrPriceBtc() {
|
||||
return xmrPriceBtc;
|
||||
public double getIncomingPriceBtc() {
|
||||
return incomingPriceBtc;
|
||||
}
|
||||
|
||||
public String getXmrReceivingSubaddress() {
|
||||
return xmrReceivingSubaddress;
|
||||
public String getReceivingSubaddress() {
|
||||
return receivingSubaddress;
|
||||
}
|
||||
|
||||
public String getXmrReceivingAddress() {
|
||||
return xmrReceivingAddress;
|
||||
}
|
||||
|
||||
public String getXmrReceivingIntegratedAddress() {
|
||||
return xmrReceivingIntegratedAddress;
|
||||
}
|
||||
|
||||
public int getXmrRecommendedMixin() {
|
||||
return xmrRecommendedMixin;
|
||||
}
|
||||
|
||||
public double getXmrRequiredAmount() {
|
||||
return xmrRequiredAmount;
|
||||
}
|
||||
|
||||
public String getXmrRequiredPaymentIdLong() {
|
||||
return xmrRequiredPaymentIdLong;
|
||||
}
|
||||
|
||||
public String getXmrRequiredPaymentIdShort() {
|
||||
return xmrRequiredPaymentIdShort;
|
||||
public int getRecommendedMixin() {
|
||||
return recommendedMixin;
|
||||
}
|
||||
|
||||
public boolean isCreated() {
|
||||
@@ -197,29 +163,22 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
|
||||
uuid = jsonObject.getString("uuid"); // "uuid": "<unique_order_identifier_as_12_character_string>"
|
||||
|
||||
if (isCreated()) {
|
||||
btcNumConfirmations = jsonObject.getInt("btc_num_confirmations"); // "btc_num_confirmations": <btc_num_confirmations_as_integer>,
|
||||
btcNumConfirmationsBeforePurge = jsonObject.getInt("btc_num_confirmations_before_purge"); // "btc_num_confirmations_before_purge": <btc_num_confirmations_before_purge_as_integer>,
|
||||
btcTransactionId = jsonObject.getString("btc_transaction_id"); // "btc_transaction_id": "<btc_transaction_id_as_string>",
|
||||
btcNumConfirmationsThreshold = jsonObject.getInt("btc_num_confirmations_threshold");
|
||||
try {
|
||||
String created = jsonObject.getString("created_at"); // "created_at": "<timestamp_as_string>",
|
||||
String created = jsonObject.getString("created_at");
|
||||
createdAt = parseDate(created);
|
||||
String expires = jsonObject.getString("expires_at"); // "expires_at": "<timestamp_as_string>",
|
||||
String expires = jsonObject.getString("expires_at");
|
||||
expiresAt = parseDate(expires);
|
||||
} catch (ParseException ex) {
|
||||
throw new JSONException(ex.getLocalizedMessage());
|
||||
}
|
||||
secondsTillTimeout = jsonObject.getInt("seconds_till_timeout"); // "seconds_till_timeout": <seconds_till_timeout_as_integer>,
|
||||
xmrAmountTotal = jsonObject.getDouble("xmr_amount_total"); // "xmr_amount_total": <amount_in_xmr_for_this_order_as_float>,
|
||||
xmrAmountRemaining = jsonObject.getDouble("xmr_amount_remaining"); // "xmr_amount_remaining": <amount_in_xmr_that_the_user_must_still_send_as_float>,
|
||||
xmrNumConfirmationsRemaining = jsonObject.getInt("xmr_num_confirmations_remaining"); // "xmr_num_confirmations_remaining": <num_xmr_confirmations_remaining_before_bitcoins_will_be_sent_as_integer>,
|
||||
xmrPriceBtc = jsonObject.getDouble("xmr_price_btc"); // "xmr_price_btc": <price_of_1_btc_in_xmr_as_offered_by_service_as_float>,
|
||||
xmrReceivingSubaddress = jsonObject.getString("xmr_receiving_subaddress"); // <xmr_subaddress_user_needs_to_send_funds_to_as_string>,
|
||||
xmrReceivingAddress = jsonObject.getString("xmr_receiving_address"); // "xmr_receiving_address": "xmr_old_style_address_user_can_send_funds_to_as_string",
|
||||
xmrReceivingIntegratedAddress = jsonObject.getString("xmr_receiving_integrated_address"); // "xmr_receiving_integrated_address": "xmr_integrated_address_user_needs_to_send_funds_to_as_string",
|
||||
xmrRecommendedMixin = jsonObject.getInt("xmr_recommended_mixin"); // "xmr_recommended_mixin": <xmr_recommended_mixin_as_integer>,
|
||||
xmrRequiredAmount = jsonObject.getDouble("xmr_required_amount"); // "xmr_required_amount": <xmr_amount_user_needs_to_send_as_float>,
|
||||
xmrRequiredPaymentIdLong = jsonObject.getString("xmr_required_payment_id_long"); // "xmr_required_payment_id_long": "xmr_payment_id_user_needs_to_include_when_using_old_stlye_address_as_string"
|
||||
xmrRequiredPaymentIdShort = jsonObject.getString("xmr_required_payment_id_short"); // "xmr_required_payment_id_short": "xmr_payment_id_included_in_integrated_address_as_string"
|
||||
secondsTillTimeout = jsonObject.getInt("seconds_till_timeout");
|
||||
incomingAmountTotal = jsonObject.getDouble("incoming_amount_total");
|
||||
remainingAmountIncoming = jsonObject.getDouble("remaining_amount_incoming");
|
||||
incomingNumConfirmationsRemaining = jsonObject.getInt("incoming_num_confirmations_remaining");
|
||||
incomingPriceBtc = jsonObject.getDouble("incoming_price_btc");
|
||||
receivingSubaddress = jsonObject.getString("receiving_subaddress");
|
||||
recommendedMixin = jsonObject.getInt("recommended_mixin");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -53,11 +53,6 @@ public class XmrToApiImpl implements XmrToApi, XmrToApiCall {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public XmrToApiImpl(@NonNull final OkHttpClient okHttpClient) {
|
||||
this(okHttpClient, HttpUrl.parse("https://xmr.to/api/v2/xmr2btc/"));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void createOrder(final double amount, @NonNull final String address,
|
||||
@NonNull final XmrToCallback<CreateOrder> callback) {
|
||||
|
12
app/src/main/res/drawable/dot_dark.xml
Normal file
12
app/src/main/res/drawable/dot_dark.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape
|
||||
android:innerRadius="0dp"
|
||||
android:shape="ring"
|
||||
android:thickness="8dp"
|
||||
android:useLevel="false">
|
||||
<solid android:color="@color/gradientOrange" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
12
app/src/main/res/drawable/dot_light.xml
Normal file
12
app/src/main/res/drawable/dot_light.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape
|
||||
android:innerRadius="0dp"
|
||||
android:shape="ring"
|
||||
android:thickness="8dp"
|
||||
android:useLevel="false">
|
||||
<solid android:color="#CDD1D9" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="36dp"
|
||||
android:height="36dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM19,15.59L17.59,17 14,13.41 10.41,17 9,15.59 12.59,12 9,8.41 10.41,7 14,10.59 17.59,7 19,8.41 15.41,12 19,15.59z" />
|
||||
</vector>
|
72
app/src/main/res/drawable/ic_onboarding_fingerprint.xml
Normal file
72
app/src/main/res/drawable/ic_onboarding_fingerprint.xml
Normal file
File diff suppressed because one or more lines are too long
279
app/src/main/res/drawable/ic_onboarding_nodes.xml
Normal file
279
app/src/main/res/drawable/ic_onboarding_nodes.xml
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user