mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-04 17:28:42 +02:00
Compare commits
49 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 | ||
![]() |
775dcf01ae | ||
![]() |
aed4051d44 | ||
![]() |
a586c0781a | ||
![]() |
616d93cb18 | ||
![]() |
73d9cb6d58 | ||
![]() |
9846e8b5cf | ||
![]() |
aa66a12dac | ||
![]() |
65ce9b0889 | ||
![]() |
291e311b8a | ||
![]() |
41290f51fd | ||
![]() |
a11c898e2c | ||
![]() |
9c921183ab | ||
![]() |
835a35c6a8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@
|
|||||||
/app/prodMainnet
|
/app/prodMainnet
|
||||||
/app/alphaStagenet
|
/app/alphaStagenet
|
||||||
/app/prodStagenet
|
/app/prodStagenet
|
||||||
|
/app/.cxx
|
||||||
|
@@ -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 202
|
versionCode 408
|
||||||
versionName "1.12.12 'Caerbannog'"
|
versionName "1.14.8 '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'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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="singleTop"
|
||||||
|
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"
|
||||||
@@ -79,4 +86,4 @@
|
|||||||
android:resource="@xml/filepaths" />
|
android:resource="@xml/filepaths" />
|
||||||
</provider>
|
</provider>
|
||||||
</application>
|
</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)) {
|
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();
|
||||||
|
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 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;
|
||||||
@@ -151,11 +149,9 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||||
if (walletFragment != null) walletFragment.resetDismissedTransactions();
|
if (walletFragment != null) walletFragment.resetDismissedTransactions();
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
if (getWallet() != null)
|
||||||
public void run() {
|
|
||||||
updateAccountsBalance();
|
updateAccountsBalance();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +196,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);
|
||||||
@@ -217,6 +212,20 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
releaseWakeLock();
|
releaseWakeLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onWalletRescan() {
|
||||||
|
try {
|
||||||
|
final WalletFragment walletFragment = (WalletFragment)
|
||||||
|
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||||
|
getWallet().rescanBlockchainAsync();
|
||||||
|
synced = false;
|
||||||
|
walletFragment.unsync();
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
} catch (ClassCastException ex) {
|
||||||
|
Timber.d(ex.getLocalizedMessage());
|
||||||
|
// keep calm and carry on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
Timber.d("onStop()");
|
Timber.d("onStop()");
|
||||||
@@ -243,7 +252,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
MenuItem renameItem = menu.findItem(R.id.action_rename);
|
MenuItem renameItem = menu.findItem(R.id.action_rename);
|
||||||
if (renameItem != null)
|
if (renameItem != null)
|
||||||
renameItem.setVisible(hasWallet() && getWallet().isSynchronized());
|
renameItem.setEnabled(hasWallet() && getWallet().isSynchronized());
|
||||||
MenuItem streetmodeItem = menu.findItem(R.id.action_streetmode);
|
MenuItem streetmodeItem = menu.findItem(R.id.action_streetmode);
|
||||||
if (streetmodeItem != null)
|
if (streetmodeItem != null)
|
||||||
if (isStreetMode()) {
|
if (isStreetMode()) {
|
||||||
@@ -251,12 +260,18 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
} else {
|
} else {
|
||||||
streetmodeItem.setIcon(R.drawable.gunther_24dp);
|
streetmodeItem.setIcon(R.drawable.gunther_24dp);
|
||||||
}
|
}
|
||||||
|
final MenuItem rescanItem = menu.findItem(R.id.action_rescan);
|
||||||
|
if (rescanItem != null)
|
||||||
|
rescanItem.setEnabled(isSynced());
|
||||||
return super.onPrepareOptionsMenu(menu);
|
return super.onPrepareOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_rescan:
|
||||||
|
onWalletRescan();
|
||||||
|
return true;
|
||||||
case R.id.action_info:
|
case R.id.action_info:
|
||||||
onWalletDetails();
|
onWalletDetails();
|
||||||
return true;
|
return true;
|
||||||
@@ -313,7 +328,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() {
|
||||||
@@ -322,6 +337,10 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,10 +575,9 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
@Override
|
@Override
|
||||||
public boolean onRefreshed(final Wallet wallet, final boolean full) {
|
public boolean onRefreshed(final Wallet wallet, final boolean full) {
|
||||||
Timber.d("onRefreshed()");
|
Timber.d("onRefreshed()");
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
public void run() {
|
if (getWallet() != null)
|
||||||
updateAccountsBalance();
|
updateAccountsBalance();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if (numAccounts != wallet.getNumAccounts()) {
|
if (numAccounts != wallet.getNumAccounts()) {
|
||||||
numAccounts = wallet.getNumAccounts();
|
numAccounts = wallet.getNumAccounts();
|
||||||
@@ -835,17 +853,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 act(String walletName, String password, boolean fingerprintUsed) {
|
||||||
public void action(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:
|
||||||
@@ -983,12 +1000,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)) {
|
||||||
|
@@ -356,6 +356,15 @@ public class WalletFragment extends Fragment
|
|||||||
if (isVisible()) enableAccountsList(true); //otherwise it is enabled in onResume()
|
if (isVisible()) enableAccountsList(true); //otherwise it is enabled in onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void unsync() {
|
||||||
|
if (!activityCallback.isWatchOnly()) {
|
||||||
|
bSend.setVisibility(View.INVISIBLE);
|
||||||
|
bSend.setEnabled(false);
|
||||||
|
}
|
||||||
|
if (isVisible()) enableAccountsList(false); //otherwise it is enabled in onResume()
|
||||||
|
firstBlock = 0;
|
||||||
|
}
|
||||||
|
|
||||||
boolean walletLoaded = false;
|
boolean walletLoaded = false;
|
||||||
|
|
||||||
public void onLoaded() {
|
public void onLoaded() {
|
||||||
|
@@ -16,24 +16,15 @@
|
|||||||
|
|
||||||
package com.m2049r.xmrwallet.fragment.send;
|
package com.m2049r.xmrwallet.fragment.send;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
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.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.view.inputmethod.EditorInfo;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.BuildConfig;
|
|
||||||
import com.m2049r.xmrwallet.R;
|
import com.m2049r.xmrwallet.R;
|
||||||
import com.m2049r.xmrwallet.data.TxData;
|
import com.m2049r.xmrwallet.data.TxData;
|
||||||
import com.m2049r.xmrwallet.data.TxDataBtc;
|
import com.m2049r.xmrwallet.data.TxDataBtc;
|
||||||
@@ -213,7 +204,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
|||||||
((TxDataBtc) sendListener.getTxData()).setXmrtoUuid(xmrtoStatus.getUuid());
|
((TxDataBtc) sendListener.getTxData()).setXmrtoUuid(xmrtoStatus.getUuid());
|
||||||
// TODO make method in TxDataBtc to set both of the above in one go
|
// TODO make method in TxDataBtc to set both of the above in one go
|
||||||
sendListener.commitTransaction();
|
sendListener.commitTransaction();
|
||||||
pbProgressSend.setVisibility(View.VISIBLE);
|
getActivity().runOnUiThread(() -> pbProgressSend.setVisibility(View.VISIBLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -346,103 +337,18 @@ 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) {
|
send();
|
||||||
etPassword.setError(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||||
public void beforeTextChanged(CharSequence s, int start,
|
getActivity().runOnUiThread(() -> {
|
||||||
int count, int after) {
|
bSend.setEnabled(sendCountdown > 0); // allow to try again
|
||||||
}
|
|
||||||
|
|
||||||
@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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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 +363,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 +478,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();
|
||||||
|
@@ -16,29 +16,19 @@
|
|||||||
|
|
||||||
package com.m2049r.xmrwallet.fragment.send;
|
package com.m2049r.xmrwallet.fragment.send;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.design.widget.TextInputLayout;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
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.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.view.inputmethod.EditorInfo;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.BuildConfig;
|
|
||||||
import com.m2049r.xmrwallet.R;
|
import com.m2049r.xmrwallet.R;
|
||||||
import com.m2049r.xmrwallet.data.TxData;
|
import com.m2049r.xmrwallet.data.TxData;
|
||||||
|
import com.m2049r.xmrwallet.data.UserNotes;
|
||||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||||
import com.m2049r.xmrwallet.model.Wallet;
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
import com.m2049r.xmrwallet.data.UserNotes;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@@ -141,7 +131,7 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
|||||||
|
|
||||||
void send() {
|
void send() {
|
||||||
sendListener.commitTransaction();
|
sendListener.commitTransaction();
|
||||||
pbProgressSend.setVisibility(View.VISIBLE);
|
getActivity().runOnUiThread(() -> pbProgressSend.setVisibility(View.VISIBLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -225,103 +215,18 @@ 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) {
|
send();
|
||||||
etPassword.setError(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||||
public void beforeTextChanged(CharSequence s, int start,
|
getActivity().runOnUiThread(() -> {
|
||||||
int count, int after) {
|
bSend.setEnabled(true); // allow to try again
|
||||||
}
|
|
||||||
|
|
||||||
@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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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()
|
||||||
|
@@ -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);
|
||||||
|
|
||||||
|
@@ -130,9 +130,9 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
|
|||||||
|
|
||||||
private void showStar() {
|
private void showStar() {
|
||||||
if (nodeItem.isFavourite()) {
|
if (nodeItem.isFavourite()) {
|
||||||
ibBookmark.setImageResource(R.drawable.ic_bookmark_24dp);
|
ibBookmark.setImageResource(R.drawable.ic_favorite_24dp);
|
||||||
} else {
|
} else {
|
||||||
ibBookmark.setImageResource(R.drawable.ic_bookmark_border_24dp);
|
ibBookmark.setImageResource(R.drawable.ic_favorite_border_24dp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
|
@@ -248,7 +248,13 @@ public class Wallet {
|
|||||||
|
|
||||||
public native long getDaemonBlockChainTargetHeight();
|
public native long getDaemonBlockChainTargetHeight();
|
||||||
|
|
||||||
public native boolean isSynchronized();
|
public native boolean isSynchronizedJ();
|
||||||
|
|
||||||
|
public boolean isSynchronized() {
|
||||||
|
final long daemonHeight = getDaemonBlockChainHeight();
|
||||||
|
if (daemonHeight == 0) return false;
|
||||||
|
return isSynchronizedJ() && (getBlockChainHeight() == daemonHeight);
|
||||||
|
}
|
||||||
|
|
||||||
public static native String getDisplayAmount(long amount);
|
public static native String getDisplayAmount(long amount);
|
||||||
|
|
||||||
@@ -278,6 +284,8 @@ public class Wallet {
|
|||||||
|
|
||||||
public native void refreshAsync();
|
public native void refreshAsync();
|
||||||
|
|
||||||
|
public native void rescanBlockchainAsync();
|
||||||
|
|
||||||
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
|
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
|
||||||
//TODO virtual int autoRefreshInterval() const = 0;
|
//TODO virtual int autoRefreshInterval() const = 0;
|
||||||
|
|
||||||
|
@@ -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: {
|
case START_SERVICE: {
|
||||||
Bundle extras = msg.getData();
|
Bundle extras = msg.getData();
|
||||||
String cmd = extras.getString(REQUEST, null);
|
String cmd = extras.getString(REQUEST, null);
|
||||||
if (cmd.equals(REQUEST_CMD_LOAD)) {
|
switch (cmd) {
|
||||||
String walletId = extras.getString(REQUEST_WALLET, null);
|
case REQUEST_CMD_LOAD:
|
||||||
String walletPw = extras.getString(REQUEST_CMD_LOAD_PW, null);
|
String walletId = extras.getString(REQUEST_WALLET, null);
|
||||||
Timber.d("LOAD wallet %s", walletId);
|
String walletPw = extras.getString(REQUEST_CMD_LOAD_PW, null);
|
||||||
if (walletId != null) {
|
Timber.d("LOAD wallet %s", walletId);
|
||||||
showProgress(getString(R.string.status_wallet_loading));
|
if (walletId != null) {
|
||||||
showProgress(10);
|
showProgress(getString(R.string.status_wallet_loading));
|
||||||
Wallet.Status walletStatus = start(walletId, walletPw);
|
showProgress(10);
|
||||||
if (observer != null) observer.onWalletStarted(walletStatus);
|
Wallet.Status walletStatus = start(walletId, walletPw);
|
||||||
if ((walletStatus == null) || !walletStatus.isOk()) {
|
if (observer != null) observer.onWalletStarted(walletStatus);
|
||||||
errorState = true;
|
if ((walletStatus == null) || !walletStatus.isOk()) {
|
||||||
stop();
|
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);
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case REQUEST_CMD_STORE: {
|
||||||
|
Wallet myWallet = getWallet();
|
||||||
|
if (myWallet == null) break;
|
||||||
|
Timber.d("STORE wallet: %s", myWallet.getName());
|
||||||
boolean rc = myWallet.store();
|
boolean rc = myWallet.store();
|
||||||
Timber.d("wallet stored: %s with rc=%b", myWallet.getName(), rc);
|
Timber.d("wallet stored: %s with rc=%b", myWallet.getName(), rc);
|
||||||
if (!rc) {
|
if (!rc) {
|
||||||
Timber.w("Wallet store failed: %s", myWallet.getStatus().getErrorString());
|
Timber.w("Wallet store failed: %s", myWallet.getStatus().getErrorString());
|
||||||
}
|
}
|
||||||
if (observer != null) observer.onWalletStored(rc);
|
if (observer != null) observer.onWalletStored(rc);
|
||||||
listener.updated = true;
|
break;
|
||||||
} else {
|
}
|
||||||
final String error = pendingTransaction.getErrorString();
|
case REQUEST_CMD_TX: {
|
||||||
myWallet.disposePendingTransaction();
|
Wallet myWallet = getWallet();
|
||||||
if (observer != null) observer.onSendTransactionFailed(error);
|
if (myWallet == null) break;
|
||||||
return;
|
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() {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
@@ -37,33 +37,19 @@ public class Notice {
|
|||||||
private static final String PREFS_NAME = "notice";
|
private static final String PREFS_NAME = "notice";
|
||||||
private static List<Notice> notices = null;
|
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_XMRTO_ENABLED_SEND = "notice_xmrto_enabled_send";
|
||||||
private static final String NOTICE_SHOW_LEDGER = "notice_ledger_enabled_login";
|
private static final String NOTICE_SHOW_LEDGER = "notice_ledger_enabled_login";
|
||||||
private static final String NOTICE_SHOW_NODES = "notice_nodes";
|
|
||||||
|
|
||||||
private static void init() {
|
private static void init() {
|
||||||
synchronized (Notice.class) {
|
synchronized (Notice.class) {
|
||||||
if (notices != null) return;
|
if (notices != null) return;
|
||||||
notices = new ArrayList<>();
|
notices = new ArrayList<>();
|
||||||
notices.add(
|
|
||||||
new Notice(NOTICE_SHOW_NODES,
|
|
||||||
R.string.info_nodes_enabled,
|
|
||||||
R.string.help_node,
|
|
||||||
1)
|
|
||||||
);
|
|
||||||
notices.add(
|
notices.add(
|
||||||
new Notice(NOTICE_SHOW_XMRTO_ENABLED_SEND,
|
new Notice(NOTICE_SHOW_XMRTO_ENABLED_SEND,
|
||||||
R.string.info_xmrto_enabled,
|
R.string.info_xmrto_enabled,
|
||||||
R.string.help_xmrto,
|
R.string.help_xmrto,
|
||||||
1)
|
1)
|
||||||
);
|
);
|
||||||
notices.add(
|
|
||||||
new Notice(NOTICE_SHOW_XMRTO_ENABLED_LOGIN,
|
|
||||||
R.string.info_xmrto_enabled,
|
|
||||||
R.string.help_xmrto,
|
|
||||||
1)
|
|
||||||
);
|
|
||||||
notices.add(
|
notices.add(
|
||||||
new Notice(NOTICE_SHOW_LEDGER,
|
new Notice(NOTICE_SHOW_LEDGER,
|
||||||
R.string.info_ledger_enabled,
|
R.string.info_ledger_enabled,
|
||||||
|
@@ -113,6 +113,12 @@ public class RestoreHeight {
|
|||||||
blockheight.put("2020-01-01", 2001315L);
|
blockheight.put("2020-01-01", 2001315L);
|
||||||
blockheight.put("2020-02-01", 2023656L);
|
blockheight.put("2020-02-01", 2023656L);
|
||||||
blockheight.put("2020-03-01", 2044552L);
|
blockheight.put("2020-03-01", 2044552L);
|
||||||
|
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) {
|
public long getHeight(String date) {
|
||||||
|
@@ -68,15 +68,19 @@ public class ExchangeEditText extends LinearLayout {
|
|||||||
}
|
}
|
||||||
boolean ok = true;
|
boolean ok = true;
|
||||||
String nativeAmount = getNativeAmount();
|
String nativeAmount = getNativeAmount();
|
||||||
try {
|
if (nativeAmount == null) {
|
||||||
double amount = Double.parseDouble(nativeAmount);
|
ok = false;
|
||||||
if ((amount < min) || (amount > max)) {
|
} else {
|
||||||
|
try {
|
||||||
|
double amount = Double.parseDouble(nativeAmount);
|
||||||
|
if ((amount < min) || (amount > max)) {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
// this cannot be
|
||||||
|
Timber.e(ex.getLocalizedMessage());
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
// this cannot be
|
|
||||||
Timber.e(ex.getLocalizedMessage());
|
|
||||||
ok = false;
|
|
||||||
}
|
}
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
shakeAmountField();
|
shakeAmountField();
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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) {
|
||||||
|
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>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user