1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-07 11:35:59 +02:00

Compare commits

...

20 Commits

Author SHA1 Message Date
m2049r
c1d2db3d7d onboarding tweaks & version bump (#676) 2020-09-14 00:24:57 +02:00
m2049r
0c9a2f5e01 fixups 2020-09-13 18:52:26 +02:00
m2049r
64616e3921 onboarding (#675)
Co-authored-by: Stephan <stephan.hagios@gmail.com>
2020-09-13 18:34:22 +02:00
m2049r
82b4d66987 allow spend with fingerprint only (#674) 2020-09-13 18:26:36 +02:00
m2049r
10f2bc6561 upgrade monero core (#673) 2020-09-05 11:01:34 +02:00
m2049r
2ed9a78d9e upgrade gradle 2020-07-16 14:14:26 +02:00
m2049r
ca19f32f8f bump version 2020-07-06 15:00:10 +02:00
m2049r
4431d74051 Fix for Ledger protocol v3 (#670)
* update protocol to V3 for Monero App v1.6.0
2020-07-06 14:59:38 +02:00
TheFuzzStone
f00da6ecda Renamed folder to '*-uk' and files to *-uk. (#666)
* Renamed folder to '*-uk' and files to `*-uk`. 
Changed '&apos;' to '\''

* Renamed file names
2020-06-20 18:07:43 +02:00
m2049r
1cecd0b718 xmr.to v3 API (#667) 2020-06-19 19:17:38 +02:00
m2049r
a0d6117bbb don't keepTimestampsInApk 2020-06-06 09:46:28 +02:00
m2049r
0b0648a172 upgrade dev env 2020-06-05 08:56:02 +02:00
m2049r
775dcf01ae bump version 2020-06-01 20:14:03 +02:00
m2049r
aed4051d44 Merge branch 'TheFuzzStone-master' 2020-06-01 20:09:51 +02:00
m2049r
a586c0781a renamed directory to match locale 2020-06-01 20:08:22 +02:00
m2049r
616d93cb18 replace &apos; 2020-06-01 20:04:55 +02:00
v1docq47
73d9cb6d58 Update for Russian translation (#649)
Update:
 - strings.xml

Co-authored-by: m2049r <m2049r@monerujo.io>
2020-06-01 19:57:33 +02:00
m2049r
9846e8b5cf fix progress bar (#665) 2020-06-01 19:56:24 +02:00
m2049r
aa66a12dac Merge branch 'master' into master 2020-06-01 13:43:16 +02:00
TheFuzzStone
835a35c6a8 Updating Ukranian language 2020-02-13 13:49:52 +02:00
48 changed files with 2138 additions and 797 deletions

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@
/app/prodMainnet /app/prodMainnet
/app/alphaStagenet /app/alphaStagenet
/app/prodStagenet /app/prodStagenet
/app/.cxx

View File

@@ -2,14 +2,13 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion 28 compileSdkVersion 28
buildToolsVersion '28.0.3' buildToolsVersion '29.0.2'
defaultConfig { defaultConfig {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 28 targetSdkVersion 28
versionCode 300 versionCode 401
versionName "1.13.0 'ReStart'" versionName "1.14.1 'On Board'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@@ -93,6 +92,11 @@ android {
outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk" outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk"
} }
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
} }
dependencies { dependencies {
@@ -102,6 +106,7 @@ dependencies {
implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion" implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion"
implementation "com.android.support:cardview-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:swiperefreshlayout:$rootProject.ext.supportVersion"
implementation "com.android.support.constraint:constraint-layout:$rootProject.ext.constraintVersion"
implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion" implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
@@ -124,5 +129,4 @@ dependencies {
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
testImplementation 'org.json:json:20180813' testImplementation 'org.json:json:20180813'
testImplementation 'net.jodah:concurrentunit:0.4.4' testImplementation 'net.jodah:concurrentunit:0.4.4'
} }

View File

@@ -20,24 +20,27 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/MyMaterialTheme" android:theme="@style/MyMaterialTheme"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
<activity android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTask"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity <activity
android:name=".WalletActivity" android:name=".WalletActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:label="@string/wallet_activity_name" android:label="@string/wallet_activity_name"
android:launchMode="singleTask" android:launchMode="singleTask"
android:screenOrientation="behind" /> android:screenOrientation="behind" />
<activity <activity
android:name=".LoginActivity" android:name=".LoginActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="locked"> android:screenOrientation="locked">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter> </intent-filter>
@@ -62,6 +65,10 @@
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter" /> android:resource="@xml/usb_device_filter" />
</activity> </activity>
<activity android:name=".onboarding.OnBoardingActivity"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTask"
android:screenOrientation="portrait"/>
<service <service
android:name=".service.WalletService" android:name=".service.WalletService"

View File

@@ -315,10 +315,14 @@ public class LoginActivity extends BaseActivity
if (WalletManager.getInstance().walletExists(walletFile)) { if (WalletManager.getInstance().walletExists(walletFile)) {
Helper.promptPassword(LoginActivity.this, walletName, true, new Helper.PasswordAction() { Helper.promptPassword(LoginActivity.this, walletName, true, new Helper.PasswordAction() {
@Override @Override
public void action(String walletName, String password, boolean fingerprintUsed) { public void act(String walletName, String password, boolean fingerprintUsed) {
if (checkDevice(walletName, password)) if (checkDevice(walletName, password))
startDetails(walletFile, password, GenerateReviewFragment.VIEW_TYPE_DETAILS); 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 } else { // this cannot really happen as we prefilter choices
Timber.e("Wallet missing: %s", walletName); Timber.e("Wallet missing: %s", walletName);
@@ -348,10 +352,14 @@ public class LoginActivity extends BaseActivity
if (WalletManager.getInstance().walletExists(walletFile)) { if (WalletManager.getInstance().walletExists(walletFile)) {
Helper.promptPassword(LoginActivity.this, walletName, false, new Helper.PasswordAction() { Helper.promptPassword(LoginActivity.this, walletName, false, new Helper.PasswordAction() {
@Override @Override
public void action(String walletName, String password, boolean fingerprintUsed) { public void act(String walletName, String password, boolean fingerprintUsed) {
if (checkDevice(walletName, password)) if (checkDevice(walletName, password))
startReceive(walletFile, password); startReceive(walletFile, password);
} }
@Override
public void fail(String walletName, String password, boolean fingerprintUsed) {
}
}); });
} else { // this cannot really happen as we prefilter choices } else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show(); 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, Helper.promptPassword(LoginActivity.this, walletName, false,
new Helper.PasswordAction() { new Helper.PasswordAction() {
@Override @Override
public void action(String walletName, String password, boolean fingerprintUsed) { public void act(String walletName, String password, boolean fingerprintUsed) {
if (checkDevice(walletName, password)) if (checkDevice(walletName, password))
startWallet(walletName, password, fingerprintUsed, streetmode); startWallet(walletName, password, fingerprintUsed, streetmode);
} }
@Override
public void fail(String walletName, String password, boolean fingerprintUsed) {
}
}); });
} else { // this cannot really happen as we prefilter choices } else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show(); Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();

View File

@@ -0,0 +1,40 @@
/*
* 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;
import timber.log.Timber;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (OnBoardingManager.shouldShowOnBoarding(getApplicationContext()) || BuildConfig.DEBUG) {
startActivity(new Intent(this, OnBoardingActivity.class));
} else {
startActivity(new Intent(this, LoginActivity.class));
}
finish();
}
}

View File

@@ -88,7 +88,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
private ActionBarDrawerToggle drawerToggle; private ActionBarDrawerToggle drawerToggle;
private Toolbar toolbar; private Toolbar toolbar;
private boolean needVerifyIdentity;
private boolean requestStreetMode = false; private boolean requestStreetMode = false;
private String password; private String password;
@@ -142,7 +141,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
private void enableStreetMode(boolean enable) { private void enableStreetMode(boolean enable) {
if (enable) { if (enable) {
needVerifyIdentity = true;
streetMode = getWallet().getDaemonBlockChainHeight(); streetMode = getWallet().getDaemonBlockChainHeight();
} else { } else {
streetMode = 0; streetMode = 0;
@@ -200,7 +198,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
if (extras != null) { if (extras != null) {
acquireWakeLock(); acquireWakeLock();
String walletId = extras.getString(REQUEST_ID); String walletId = extras.getString(REQUEST_ID);
needVerifyIdentity = extras.getBoolean(REQUEST_FINGERPRINT_USED);
// we can set the streetmode height AFTER opening the wallet // we can set the streetmode height AFTER opening the wallet
requestStreetMode = extras.getBoolean(REQUEST_STREETMODE); requestStreetMode = extras.getBoolean(REQUEST_STREETMODE);
password = extras.getString(REQUEST_PW); password = extras.getString(REQUEST_PW);
@@ -333,7 +330,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
private void onDisableStreetMode() { private void onDisableStreetMode() {
Helper.promptPassword(WalletActivity.this, getWallet().getName(), false, new Helper.PasswordAction() { Helper.promptPassword(WalletActivity.this, getWallet().getName(), false, new Helper.PasswordAction() {
@Override @Override
public void action(String walletName, String password, boolean fingerprintUsed) { public void act(String walletName, String password, boolean fingerprintUsed) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -342,6 +339,10 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
}); });
} }
@Override
public void fail(String walletName, String password, boolean fingerprintUsed) {
}
}); });
} }
@@ -855,17 +856,16 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
final Bundle extras = new Bundle(); final Bundle extras = new Bundle();
extras.putString(GenerateReviewFragment.REQUEST_TYPE, GenerateReviewFragment.VIEW_TYPE_WALLET); extras.putString(GenerateReviewFragment.REQUEST_TYPE, GenerateReviewFragment.VIEW_TYPE_WALLET);
if (needVerifyIdentity) {
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() { Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
@Override @Override
public void action(String walletName, String password, boolean fingerprintUsed) { public void act(String walletName, String password, boolean fingerprintUsed) {
replaceFragment(new GenerateReviewFragment(), null, extras); replaceFragment(new GenerateReviewFragment(), null, extras);
needVerifyIdentity = false; }
@Override
public void fail(String walletName, String password, boolean fingerprintUsed) {
} }
}); });
} else {
replaceFragment(new GenerateReviewFragment(), null, extras);
}
break; break;
case DialogInterface.BUTTON_NEGATIVE: case DialogInterface.BUTTON_NEGATIVE:
@@ -1003,12 +1003,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
return getWallet().getUnlockedBalance(); return getWallet().getUnlockedBalance();
} }
@Override
public boolean verifyWalletPassword(String password) {
String walletPassword = Helper.getWalletPassword(getApplicationContext(), getWalletName(), password);
return walletPassword != null;
}
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) { if (drawer.isDrawerOpen(GravityCompat.START)) {

View File

@@ -362,6 +362,7 @@ public class WalletFragment extends Fragment
bSend.setEnabled(false); bSend.setEnabled(false);
} }
if (isVisible()) enableAccountsList(false); //otherwise it is enabled in onResume() if (isVisible()) enableAccountsList(false); //otherwise it is enabled in onResume()
firstBlock = 0;
} }
boolean walletLoaded = false; boolean walletLoaded = false;

View File

@@ -346,103 +346,16 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
} }
public void preSend() { public void preSend() {
final Activity activity = getActivity(); Helper.promptPassword(getContext(), getActivityCallback().getWalletName(), false, new Helper.PasswordAction() {
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() {
@Override @Override
public void afterTextChanged(Editable s) { public void act(String walletName, String password, boolean fingerprintUsed) {
if (etPassword.getError() != null) {
etPassword.setError(null);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
}
});
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(); send();
} else {
etPassword.setError(getString(R.string.bad_password));
} }
}
}) public void fail(String walletName, String password, boolean fingerprintUsed) {
.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 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));
}
}
});
}
});
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() // creates a pending transaction and calls us back with transactionCreated()
@@ -457,8 +370,8 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
} }
showProgress(3, getString(R.string.label_send_progress_create_tx)); showProgress(3, getString(R.string.label_send_progress_create_tx));
TxData txData = sendListener.getTxData(); TxData txData = sendListener.getTxData();
txData.setDestinationAddress(xmrtoStatus.getXmrReceivingSubaddress()); txData.setDestinationAddress(xmrtoStatus.getReceivingSubaddress());
txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getXmrAmountTotal())); txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getIncomingAmountTotal()));
getActivityCallback().onPrepareSend(xmrtoStatus.getUuid(), txData); getActivityCallback().onPrepareSend(xmrtoStatus.getUuid(), txData);
} }
@@ -572,22 +485,22 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
NumberFormat df = NumberFormat.getInstance(Locale.US); NumberFormat df = NumberFormat.getInstance(Locale.US);
df.setMaximumFractionDigits(12); df.setMaximumFractionDigits(12);
String btcAmount = df.format(status.getBtcAmount()); 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)); 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)); tvTxBtcRate.setText(getString(R.string.text_send_btc_rate, xmrPriceBtc));
double calcRate = status.getBtcAmount() / status.getXmrPriceBtc(); double calcRate = status.getBtcAmount() / status.getIncomingPriceBtc();
Timber.i("Rates: %f / %f", calcRate, status.getXmrPriceBtc()); Timber.d("Rates: %f / %f", calcRate, status.getIncomingPriceBtc());
tvTxBtcAddress.setText(status.getBtcDestAddress()); // TODO test if this is different? 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()); 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(); hideProgress();
startSendTimer(); startSendTimer();
prepareSend(); prepareSend();

View File

@@ -141,8 +141,13 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
void send() { void send() {
sendListener.commitTransaction(); sendListener.commitTransaction();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
pbProgressSend.setVisibility(View.VISIBLE); pbProgressSend.setVisibility(View.VISIBLE);
} }
});
}
@Override @Override
public void sendFailed(String errorText) { public void sendFailed(String errorText) {
@@ -225,103 +230,16 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
} }
public void preSend() { public void preSend() {
final Activity activity = getActivity(); Helper.promptPassword(getContext(), getActivityCallback().getWalletName(), false, new Helper.PasswordAction() {
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() {
@Override @Override
public void afterTextChanged(Editable s) { public void act(String walletName, String password, boolean fingerprintUsed) {
if (etPassword.getError() != null) {
etPassword.setError(null);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
}
});
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(); send();
} else {
etPassword.setError(getString(R.string.bad_password));
} }
}
}) public void fail(String walletName, String password, boolean fingerprintUsed) {
.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 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));
}
}
});
}
});
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() // creates a pending transaction and calls us back with transactionCreated()

View File

@@ -75,7 +75,7 @@ public class SendFragment extends Fragment
void onPrepareSend(String tag, TxData data); void onPrepareSend(String tag, TxData data);
boolean verifyWalletPassword(String password); String getWalletName();
void onSend(UserNotes notes); void onSend(UserNotes notes);

View File

@@ -17,12 +17,15 @@
package com.m2049r.xmrwallet.ledger; package com.m2049r.xmrwallet.ledger;
public enum Instruction { public enum Instruction {
INS_NONE(0x00), INS_NONE(0x00),
INS_RESET(0x02), INS_RESET(0x02),
INS_GET_KEY(0x20), INS_GET_KEY(0x20),
INS_DISPLAY_ADDRESS(0x21),
INS_PUT_KEY(0x22), INS_PUT_KEY(0x22),
INS_GET_CHACHA8_PREKEY(0x24), INS_GET_CHACHA8_PREKEY(0x24),
INS_VERIFY_KEY(0x26), INS_VERIFY_KEY(0x26),
INS_MANAGE_SEEDWORDS(0x28),
INS_SECRET_KEY_TO_PUBLIC_KEY(0x30), INS_SECRET_KEY_TO_PUBLIC_KEY(0x30),
INS_GEN_KEY_DERIVATION(0x32), INS_GEN_KEY_DERIVATION(0x32),
@@ -30,6 +33,7 @@ public enum Instruction {
INS_DERIVE_PUBLIC_KEY(0x36), INS_DERIVE_PUBLIC_KEY(0x36),
INS_DERIVE_SECRET_KEY(0x38), INS_DERIVE_SECRET_KEY(0x38),
INS_GEN_KEY_IMAGE(0x3A), INS_GEN_KEY_IMAGE(0x3A),
INS_SECRET_KEY_ADD(0x3C), INS_SECRET_KEY_ADD(0x3C),
INS_SECRET_KEY_SUB(0x3E), INS_SECRET_KEY_SUB(0x3E),
INS_GENERATE_KEYPAIR(0x40), INS_GENERATE_KEYPAIR(0x40),
@@ -45,15 +49,20 @@ public enum Instruction {
INS_SET_SIGNATURE_MODE(0x72), INS_SET_SIGNATURE_MODE(0x72),
INS_GET_ADDITIONAL_KEY(0x74), INS_GET_ADDITIONAL_KEY(0x74),
INS_STEALTH(0x76), INS_STEALTH(0x76),
INS_GEN_COMMITMENT_MASK(0x77),
INS_BLIND(0x78), INS_BLIND(0x78),
INS_UNBLIND(0x7A), INS_UNBLIND(0x7A),
INS_GEN_TXOUT_KEYS(0x7B),
INS_VALIDATE(0x7C), INS_VALIDATE(0x7C),
INS_PREFIX_HASH(0x7D),
INS_MLSAG(0x7E), INS_MLSAG(0x7E),
INS_CLOSE_TX(0x80), 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) { public static Instruction fromByte(byte n) {
switch (n & 0xFF) { switch (n & 0xFF) {

View File

@@ -42,11 +42,11 @@ public class Ledger {
static public final int LOOKAHEAD_SUBADDRESSES = 20; static public final int LOOKAHEAD_SUBADDRESSES = 20;
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES; 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_OK = 0x9000;
public static final int SW_INS_NOT_SUPPORTED = 0x6D00; public static final int SW_INS_NOT_SUPPORTED = 0x6D00;
public static final int OK[] = {SW_OK}; 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) { public static UsbDevice findDevice(UsbManager usbManager) {
if (!ENABLED) return null; if (!ENABLED) return null;

View File

@@ -51,6 +51,7 @@ public class LedgerProgressDialog extends ProgressDialog implements Ledger.Liste
switch (ins) { switch (ins) {
case INS_RESET: // ledger may ask for confirmation - maybe a bug? case INS_RESET: // ledger may ask for confirmation - maybe a bug?
case INS_GET_KEY: // ledger asks for confirmation to send keys case INS_GET_KEY: // ledger asks for confirmation to send keys
case INS_DISPLAY_ADDRESS:
setIndeterminate(true); setIndeterminate(true);
setMessage(getContext().getString(R.string.progress_ledger_confirm)); setMessage(getContext().getString(R.string.progress_ledger_confirm));
break; break;
@@ -102,6 +103,11 @@ public class LedgerProgressDialog extends ProgressDialog implements Ledger.Liste
setMessage(getContext().getString(R.string.progress_ledger_mlsag)); setMessage(getContext().getString(R.string.progress_ledger_mlsag));
} }
break; 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: case INS_VALIDATE:
if ((apdu[2] != 1) || (apdu[3] != 1)) break; if ((apdu[2] != 1) || (apdu[3] != 1)) break;
validate = true; validate = true;

View File

@@ -0,0 +1,117 @@
/*
* 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;
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);
}
});
for (int i = 0; i < OnBoardingScreen.values().length; i++) {
agreed[i] = !OnBoardingScreen.values()[i].isMustAgree();
}
setButtonState(0);
}
private void finishOnboarding() {
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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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) && KeyStoreHelper.hasStoredPasswords(context);
}
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);
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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 final 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;
}
}

View File

@@ -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;
}
}

View File

@@ -323,9 +323,9 @@ public class Helper {
static public HttpUrl getXmrToBaseUrl() { static public HttpUrl getXmrToBaseUrl() {
if ((WalletManager.getInstance() == null) if ((WalletManager.getInstance() == null)
|| (WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet)) { || (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 { } 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 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) { 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 if (openDialog != null) return; // we are already asking for password
@@ -442,11 +442,11 @@ public class Helper {
final AtomicBoolean incorrectSavedPass = new AtomicBoolean(false); final AtomicBoolean incorrectSavedPass = new AtomicBoolean(false);
class LoginWalletTask extends AsyncTask<Void, Void, Boolean> { class PasswordTask extends AsyncTask<Void, Void, Boolean> {
private String pass; private String pass;
private boolean fingerprintUsed; private boolean fingerprintUsed;
LoginWalletTask(String pass, boolean fingerprintUsed) { PasswordTask(String pass, boolean fingerprintUsed) {
this.pass = pass; this.pass = pass;
this.fingerprintUsed = fingerprintUsed; this.fingerprintUsed = fingerprintUsed;
} }
@@ -488,7 +488,7 @@ public class Helper {
etPassword.setError(context.getString(R.string.bad_password)); 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) { public void onClick(DialogInterface dialog, int id) {
Helper.hideKeyboardAlways((Activity) context); Helper.hideKeyboardAlways((Activity) context);
cancelSignal.cancel(); cancelSignal.cancel();
if (loginTask != null) { if (passwordTask != null) {
loginTask.cancel(true); passwordTask.cancel(true);
loginTask = null; passwordTask = null;
} }
dialog.cancel(); dialog.cancel();
openDialog = null; openDialog = null;
@@ -552,9 +552,9 @@ public class Helper {
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
try { try {
String userPass = KeyStoreHelper.loadWalletUserPass(context, wallet); String userPass = KeyStoreHelper.loadWalletUserPass(context, wallet);
if (loginTask == null) { if (passwordTask == null) {
loginTask = new LoginWalletTask(userPass, true); passwordTask = new PasswordTask(userPass, true);
loginTask.execute(); passwordTask.execute();
} }
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) { } catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
etPassword.setError(context.getString(R.string.bad_password)); etPassword.setError(context.getString(R.string.bad_password));
@@ -586,9 +586,9 @@ public class Helper {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
String pass = etPassword.getEditText().getText().toString(); String pass = etPassword.getEditText().getText().toString();
if (loginTask == null) { if (passwordTask == null) {
loginTask = new LoginWalletTask(pass, false); passwordTask = new PasswordTask(pass, false);
loginTask.execute(); passwordTask.execute();
} }
} }
}); });
@@ -601,9 +601,9 @@ public class Helper {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) { || (actionId == EditorInfo.IME_ACTION_DONE)) {
String pass = etPassword.getEditText().getText().toString(); String pass = etPassword.getEditText().getText().toString();
if (loginTask == null) { if (passwordTask == null) {
loginTask = new LoginWalletTask(pass, false); passwordTask = new PasswordTask(pass, false);
loginTask.execute(); passwordTask.execute();
} }
return true; return true;
} }
@@ -620,15 +620,18 @@ public class Helper {
} }
public interface PasswordAction { 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) { static private boolean processPasswordEntry(Context context, String walletName, String pass, boolean fingerprintUsed, PasswordAction action) {
String walletPassword = Helper.getWalletPassword(context, walletName, pass); String walletPassword = Helper.getWalletPassword(context, walletName, pass);
if (walletPassword != null) { if (walletPassword != null) {
action.action(walletName, walletPassword, fingerprintUsed); action.act(walletName, walletPassword, fingerprintUsed);
return true; return true;
} else { } else {
action.fail(walletName, walletPassword, fingerprintUsed);
return false; return false;
} }
} }

View File

@@ -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 { public static String loadWalletUserPass(@NonNull Context context, String wallet) throws BrokenPasswordStoreException {
String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet; String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet;
String encoded = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE) String encoded = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)

View File

@@ -43,47 +43,31 @@ public interface QueryOrderStatus {
boolean isError(); 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>, String getReceivingSubaddress();
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"
int getRecommendedMixin();
} }

View File

@@ -113,7 +113,8 @@ class CreateOrderImpl implements CreateOrder {
static JSONObject createRequest(final double amount, final String address) throws JSONException { static JSONObject createRequest(final double amount, final String address) throws JSONException {
final JSONObject jsonObject = new JSONObject(); 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); jsonObject.put("btc_dest_address", address);
return jsonObject; return jsonObject;
} }

View File

@@ -42,23 +42,17 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
private String btcDestAddress; // "btc_dest_address": "<requested_destination_address_as_string>", private String btcDestAddress; // "btc_dest_address": "<requested_destination_address_as_string>",
private String uuid; // "uuid": "<unique_order_identifier_as_12_character_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 // 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 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 int btcNumConfirmationsThreshold; // "btc_num_confirmations_threshold": <btc_num_confirmations_threshold_as_integer>,
private String btcTransactionId; // "btc_transaction_id": "<btc_transaction_id_as_string>",
private Date createdAt; // "created_at": "<timestamp_as_string>", private Date createdAt; // "created_at": "<timestamp_as_string>",
private Date expiresAt; // "expires_at": "<timestamp_as_string>", private Date expiresAt; // "expires_at": "<timestamp_as_string>",
private int secondsTillTimeout; // "seconds_till_timeout": <seconds_till_timeout_as_integer>, 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 incomingAmountTotal; // "incoming_amount_total": <amount_in_incoming_currency_for_this_order_as_float>,
private double xmrAmountRemaining; // "xmr_amount_remaining": <amount_in_xmr_that_the_user_must_still_send_as_float>, private double remainingAmountIncoming; // "remaining_amount_incoming": <amount_in_incoming_currency_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 int incomingNumConfirmationsRemaining; // "incoming_num_confirmations_remaining": <num_incoming_currency_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 double incomingPriceBtc; // "incoming_price_btc": <price_of_1_incoming_in_btc_currency_as_offered_by_service_as_float>,
private String xmrReceivingSubaddress; // <xmr_subaddress_user_needs_to_send_funds_to_as_string>, private String receivingSubaddress; // "receiving_subaddress": <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 int recommendedMixin; // "recommended_mixin": <recommended_mixin_as_integer>,
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"
public QueryOrderStatus.State getState() { public QueryOrderStatus.State getState() {
return state; return state;
@@ -76,16 +70,8 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
return uuid; return uuid;
} }
public int getBtcNumConfirmations() { public int getBtcNumConfirmationsThreshold() {
return btcNumConfirmations; return btcNumConfirmationsThreshold;
}
public int getBtcNumConfirmationsBeforePurge() {
return btcNumConfirmationsBeforePurge;
}
public String getBtcTransactionId() {
return btcTransactionId;
} }
public Date getCreatedAt() { public Date getCreatedAt() {
@@ -100,48 +86,28 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
return secondsTillTimeout; return secondsTillTimeout;
} }
public double getXmrAmountTotal() { public double getIncomingAmountTotal() {
return xmrAmountTotal; return incomingAmountTotal;
} }
public double getXmrAmountRemaining() { public double getRemainingAmountIncoming() {
return xmrAmountRemaining; return remainingAmountIncoming;
} }
public int getXmrNumConfirmationsRemaining() { public int getIncomingNumConfirmationsRemaining() {
return xmrNumConfirmationsRemaining; return incomingNumConfirmationsRemaining;
} }
public double getXmrPriceBtc() { public double getIncomingPriceBtc() {
return xmrPriceBtc; return incomingPriceBtc;
} }
public String getXmrReceivingSubaddress() { public String getReceivingSubaddress() {
return xmrReceivingSubaddress; return receivingSubaddress;
} }
public String getXmrReceivingAddress() { public int getRecommendedMixin() {
return xmrReceivingAddress; return recommendedMixin;
}
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 boolean isCreated() { public boolean isCreated() {
@@ -197,29 +163,22 @@ class QueryOrderStatusImpl implements QueryOrderStatus {
uuid = jsonObject.getString("uuid"); // "uuid": "<unique_order_identifier_as_12_character_string>" uuid = jsonObject.getString("uuid"); // "uuid": "<unique_order_identifier_as_12_character_string>"
if (isCreated()) { if (isCreated()) {
btcNumConfirmations = jsonObject.getInt("btc_num_confirmations"); // "btc_num_confirmations": <btc_num_confirmations_as_integer>, btcNumConfirmationsThreshold = jsonObject.getInt("btc_num_confirmations_threshold");
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>",
try { try {
String created = jsonObject.getString("created_at"); // "created_at": "<timestamp_as_string>", String created = jsonObject.getString("created_at");
createdAt = parseDate(created); createdAt = parseDate(created);
String expires = jsonObject.getString("expires_at"); // "expires_at": "<timestamp_as_string>", String expires = jsonObject.getString("expires_at");
expiresAt = parseDate(expires); expiresAt = parseDate(expires);
} catch (ParseException ex) { } catch (ParseException ex) {
throw new JSONException(ex.getLocalizedMessage()); throw new JSONException(ex.getLocalizedMessage());
} }
secondsTillTimeout = jsonObject.getInt("seconds_till_timeout"); // "seconds_till_timeout": <seconds_till_timeout_as_integer>, secondsTillTimeout = jsonObject.getInt("seconds_till_timeout");
xmrAmountTotal = jsonObject.getDouble("xmr_amount_total"); // "xmr_amount_total": <amount_in_xmr_for_this_order_as_float>, incomingAmountTotal = jsonObject.getDouble("incoming_amount_total");
xmrAmountRemaining = jsonObject.getDouble("xmr_amount_remaining"); // "xmr_amount_remaining": <amount_in_xmr_that_the_user_must_still_send_as_float>, remainingAmountIncoming = jsonObject.getDouble("remaining_amount_incoming");
xmrNumConfirmationsRemaining = jsonObject.getInt("xmr_num_confirmations_remaining"); // "xmr_num_confirmations_remaining": <num_xmr_confirmations_remaining_before_bitcoins_will_be_sent_as_integer>, incomingNumConfirmationsRemaining = jsonObject.getInt("incoming_num_confirmations_remaining");
xmrPriceBtc = jsonObject.getDouble("xmr_price_btc"); // "xmr_price_btc": <price_of_1_btc_in_xmr_as_offered_by_service_as_float>, incomingPriceBtc = jsonObject.getDouble("incoming_price_btc");
xmrReceivingSubaddress = jsonObject.getString("xmr_receiving_subaddress"); // <xmr_subaddress_user_needs_to_send_funds_to_as_string>, receivingSubaddress = jsonObject.getString("receiving_subaddress");
xmrReceivingAddress = jsonObject.getString("xmr_receiving_address"); // "xmr_receiving_address": "xmr_old_style_address_user_can_send_funds_to_as_string", recommendedMixin = jsonObject.getInt("recommended_mixin");
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"
} }
} }

View File

@@ -53,11 +53,6 @@ public class XmrToApiImpl implements XmrToApi, XmrToApiCall {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
} }
public XmrToApiImpl(@NonNull final OkHttpClient okHttpClient) {
this(okHttpClient, HttpUrl.parse("https://xmr.to/api/v2/xmr2btc/"));
}
@Override @Override
public void createOrder(final double amount, @NonNull final String address, public void createOrder(final double amount, @NonNull final String address,
@NonNull final XmrToCallback<CreateOrder> callback) { @NonNull final XmrToCallback<CreateOrder> callback) {

View 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>

View 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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/dot_dark" android:state_selected="true" />
<item android:drawable="@drawable/dot_light" />
</selector>

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