mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-02 15:53:04 +02:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
aa768596a4 | ||
![]() |
c4958f6c54 | ||
![]() |
2c2a5314d4 | ||
![]() |
669516c60b | ||
![]() |
a56a29a6c4 | ||
![]() |
4e31f47482 | ||
![]() |
c1f14f9653 | ||
![]() |
2746c52d7b | ||
![]() |
5df323bacb | ||
![]() |
776cc26377 | ||
![]() |
bdfb6a90b6 | ||
![]() |
6db44dfab1 | ||
![]() |
c68ac7db6d | ||
![]() |
e09862e940 | ||
![]() |
c7bd7469a1 | ||
![]() |
b39857fd2e | ||
![]() |
8170f823ab | ||
![]() |
38c0ead45c | ||
![]() |
1d027c1694 | ||
![]() |
45dc21fbf7 | ||
![]() |
d4f4de234a | ||
![]() |
394d5471e3 | ||
![]() |
beb098adb3 | ||
![]() |
fda370d35b | ||
![]() |
99681e1bbb | ||
![]() |
21f44380b1 | ||
![]() |
f4c1af1bb8 | ||
![]() |
c002b81ebd | ||
![]() |
d2f07ba3b6 |
@@ -1,14 +1,15 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.3'
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.3'
|
||||
ndkVersion '17.2.4988734'
|
||||
defaultConfig {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 801
|
||||
versionName "1.18.1 'ChAdOx1'"
|
||||
targetSdkVersion 30
|
||||
versionCode 1008
|
||||
versionName "2.0.8 'Puginarug'"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
@@ -123,14 +124,14 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
implementation "com.squareup.okhttp3:okhttp:4.9.0"
|
||||
implementation "com.burgstaller:okhttp-digest:2.1"
|
||||
implementation "io.github.rburgst:okhttp-digest:2.5"
|
||||
implementation "com.jakewharton.timber:timber:4.7.1"
|
||||
|
||||
implementation 'com.nulab-inc:zxcvbn:1.3.0'
|
||||
|
@@ -4,20 +4,19 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:name=".XmrWalletApplication"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:preserveLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/MyMaterialTheme"
|
||||
android:usesCleartextTraffic="true">
|
||||
@@ -36,7 +35,7 @@
|
||||
android:configChanges="orientation|keyboardHidden|uiMode"
|
||||
android:label="@string/wallet_activity_name"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="behind"/>
|
||||
android:screenOrientation="behind" />
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:configChanges="orientation|keyboardHidden|uiMode"
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,6 @@ import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -62,8 +61,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
|
||||
private WalletInfoAdapter adapter;
|
||||
|
||||
private List<WalletManager.WalletInfo> walletList = new ArrayList<>();
|
||||
private List<WalletManager.WalletInfo> displayedList = new ArrayList<>();
|
||||
private final List<WalletManager.WalletInfo> walletList = new ArrayList<>();
|
||||
|
||||
private View tvGuntherSays;
|
||||
private ImageView ivGunther;
|
||||
@@ -86,7 +84,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
|
||||
void onWalletBackup(String name);
|
||||
|
||||
void onWalletArchive(String walletName);
|
||||
void onWalletRestore();
|
||||
|
||||
void onWalletDelete(String walletName);
|
||||
|
||||
void onWalletDeleteCache(String walletName);
|
||||
|
||||
void onAddWallet(String type);
|
||||
|
||||
@@ -110,7 +112,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof Listener) {
|
||||
this.activityCallback = (Listener) context;
|
||||
@@ -200,12 +202,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
// Wallet touched
|
||||
@Override
|
||||
public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) {
|
||||
String addressPrefix = WalletManager.getInstance().addressPrefix();
|
||||
if (addressPrefix.indexOf(infoItem.address.charAt(0)) < 0) {
|
||||
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
openWallet(infoItem.name, false);
|
||||
openWallet(infoItem.getName(), false);
|
||||
}
|
||||
|
||||
private void openWallet(String name, boolean streetmode) {
|
||||
@@ -214,48 +211,34 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
|
||||
@Override
|
||||
public boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo listItem) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_streetmode:
|
||||
openWallet(listItem.name, true);
|
||||
break;
|
||||
case R.id.action_info:
|
||||
showInfo(listItem.name);
|
||||
break;
|
||||
case R.id.action_rename:
|
||||
activityCallback.onWalletRename(listItem.name);
|
||||
break;
|
||||
case R.id.action_backup:
|
||||
activityCallback.onWalletBackup(listItem.name);
|
||||
break;
|
||||
case R.id.action_archive:
|
||||
activityCallback.onWalletArchive(listItem.name);
|
||||
break;
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
final int id = item.getItemId();
|
||||
if (id == R.id.action_streetmode) {
|
||||
openWallet(listItem.getName(), true);
|
||||
} else if (id == R.id.action_info) {
|
||||
showInfo(listItem.getName());
|
||||
} else if (id == R.id.action_rename) {
|
||||
activityCallback.onWalletRename(listItem.getName());
|
||||
} else if (id == R.id.action_backup) {
|
||||
activityCallback.onWalletBackup(listItem.getName());
|
||||
} else if (id == R.id.action_archive) {
|
||||
activityCallback.onWalletDelete(listItem.getName());
|
||||
} else if (id == R.id.action_deletecache) {
|
||||
activityCallback.onWalletDeleteCache(listItem.getName());
|
||||
} else {
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void filterList() {
|
||||
displayedList.clear();
|
||||
String addressPrefix = WalletManager.getInstance().addressPrefix();
|
||||
for (WalletManager.WalletInfo s : walletList) {
|
||||
if (addressPrefix.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadList() {
|
||||
Timber.d("loadList()");
|
||||
WalletManager mgr = WalletManager.getInstance();
|
||||
List<WalletManager.WalletInfo> walletInfos =
|
||||
mgr.findWallets(activityCallback.getStorageRoot());
|
||||
walletList.clear();
|
||||
walletList.addAll(walletInfos);
|
||||
filterList();
|
||||
adapter.setInfos(displayedList);
|
||||
walletList.addAll(mgr.findWallets(activityCallback.getStorageRoot()));
|
||||
adapter.setInfos(walletList);
|
||||
|
||||
// deal with Gunther & FAB animation
|
||||
if (displayedList.isEmpty()) {
|
||||
if (walletList.isEmpty()) {
|
||||
fab.startAnimation(fab_pulse);
|
||||
if (ivGunther.getDrawable() == null) {
|
||||
ivGunther.setImageResource(R.drawable.ic_emptygunther);
|
||||
@@ -274,7 +257,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
.getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.getAll().keySet();
|
||||
for (WalletManager.WalletInfo s : walletList) {
|
||||
removedWallets.remove(s.name);
|
||||
removedWallets.remove(s.getName());
|
||||
}
|
||||
for (String name : removedWallets) {
|
||||
KeyStoreHelper.removeWalletUserPass(getActivity(), name);
|
||||
@@ -292,7 +275,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.list_menu, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
@@ -362,37 +345,29 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int id = v.getId();
|
||||
final int id = v.getId();
|
||||
Timber.d("onClick %d/%d", id, R.id.fabLedger);
|
||||
switch (id) {
|
||||
case R.id.fab:
|
||||
animateFAB();
|
||||
break;
|
||||
case R.id.fabNew:
|
||||
fabScreen.setVisibility(View.INVISIBLE);
|
||||
isFabOpen = false;
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
|
||||
break;
|
||||
case R.id.fabView:
|
||||
animateFAB();
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY);
|
||||
break;
|
||||
case R.id.fabKey:
|
||||
animateFAB();
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_KEY);
|
||||
break;
|
||||
case R.id.fabSeed:
|
||||
animateFAB();
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
|
||||
break;
|
||||
case R.id.fabLedger:
|
||||
Timber.d("FAB_LEDGER");
|
||||
animateFAB();
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
|
||||
break;
|
||||
case R.id.fabScreen:
|
||||
animateFAB();
|
||||
break;
|
||||
if (id == R.id.fab) {
|
||||
animateFAB();
|
||||
} else if (id == R.id.fabNew) {
|
||||
fabScreen.setVisibility(View.INVISIBLE);
|
||||
isFabOpen = false;
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
|
||||
} else if (id == R.id.fabView) {
|
||||
animateFAB();
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY);
|
||||
} else if (id == R.id.fabKey) {
|
||||
animateFAB();
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_KEY);
|
||||
} else if (id == R.id.fabSeed) {
|
||||
animateFAB();
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
|
||||
} else if (id == R.id.fabLedger) {
|
||||
Timber.d("FAB_LEDGER");
|
||||
animateFAB();
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
|
||||
} else if (id == R.id.fabScreen) {
|
||||
animateFAB();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -385,24 +385,18 @@ public class ReceiveFragment extends Fragment {
|
||||
}
|
||||
|
||||
private Bitmap addLogo(Bitmap qrBitmap) {
|
||||
// addume logo & qrcode are both square
|
||||
Bitmap logo = getMoneroLogo();
|
||||
int qrWidth = qrBitmap.getWidth();
|
||||
int qrHeight = qrBitmap.getHeight();
|
||||
int logoWidth = logo.getWidth();
|
||||
int logoHeight = logo.getHeight();
|
||||
final int qrSize = qrBitmap.getWidth();
|
||||
final int logoSize = logo.getWidth();
|
||||
|
||||
Bitmap logoBitmap = Bitmap.createBitmap(qrWidth, qrHeight, Bitmap.Config.ARGB_8888);
|
||||
Bitmap logoBitmap = Bitmap.createBitmap(qrSize, qrSize, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(logoBitmap);
|
||||
canvas.drawBitmap(qrBitmap, 0, 0, null);
|
||||
canvas.save();
|
||||
// figure out how to scale the logo
|
||||
float scaleSize = 1.0f;
|
||||
while ((logoWidth / scaleSize) > (qrWidth / 5.) || (logoHeight / scaleSize) > (qrHeight / 5.)) {
|
||||
scaleSize *= 2;
|
||||
}
|
||||
float sx = 1.0f / scaleSize;
|
||||
canvas.scale(sx, sx, qrWidth / 2f, qrHeight / 2f);
|
||||
canvas.drawBitmap(logo, (qrWidth - logoWidth) / 2f, (qrHeight - logoHeight) / 2f, null);
|
||||
final float sx = 0.2f * qrSize / logoSize;
|
||||
canvas.scale(sx, sx, qrSize / 2f, qrSize / 2f);
|
||||
canvas.drawBitmap(logo, (qrSize - logoSize) / 2f, (qrSize - logoSize) / 2f, null);
|
||||
canvas.restore();
|
||||
return logoBitmap;
|
||||
}
|
||||
|
@@ -48,7 +48,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class SubaddressFragment extends Fragment implements SubaddressInfoAdapter.OnInteractionListener,
|
||||
View.OnClickListener {
|
||||
View.OnClickListener, OnBlockUpdateListener {
|
||||
static public final String KEY_MODE = "mode";
|
||||
static public final String MODE_MANAGER = "manager";
|
||||
|
||||
@@ -169,6 +169,11 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
|
||||
adapter.setInfos(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockUpdate(Wallet wallet) {
|
||||
loadList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int id = v.getId();
|
||||
|
@@ -126,7 +126,8 @@ public class WalletFragment extends Fragment
|
||||
sCurrency = view.findViewById(R.id.sCurrency);
|
||||
List<String> currencies = new ArrayList<>();
|
||||
currencies.add(Helper.BASE_CRYPTO);
|
||||
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
||||
if (Helper.SHOW_EXCHANGERATES)
|
||||
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
||||
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()), R.layout.item_spinner_balance, currencies);
|
||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
sCurrency.setAdapter(spinnerAdapter);
|
||||
|
@@ -18,12 +18,13 @@ package com.m2049r.xmrwallet.data;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
public class Subaddress implements Comparable<Subaddress> {
|
||||
@@ -35,6 +36,9 @@ public class Subaddress implements Comparable<Subaddress> {
|
||||
final private String address;
|
||||
@Getter
|
||||
private final String label;
|
||||
@Getter
|
||||
@Setter
|
||||
private long amount;
|
||||
|
||||
@Override
|
||||
public int compareTo(Subaddress another) { // newer is <
|
||||
|
@@ -21,6 +21,7 @@ import android.os.Parcelable;
|
||||
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
|
||||
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
|
||||
public class TxData implements Parcelable {
|
||||
@@ -54,7 +55,7 @@ public class TxData implements Parcelable {
|
||||
}
|
||||
|
||||
public double getAmountAsDouble() {
|
||||
return 1.0 * amount / 1000000000000L;
|
||||
return 1.0 * amount / Helper.ONE_XMR;
|
||||
}
|
||||
|
||||
public int getMixin() {
|
||||
|
@@ -117,32 +117,34 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
|
||||
View view = inflater.inflate(R.layout.fragment_send_address, container, false);
|
||||
|
||||
tvXmrTo = view.findViewById(R.id.tvXmrTo);
|
||||
|
||||
ibCrypto = new HashMap<>();
|
||||
for (Crypto crypto : Crypto.values()) {
|
||||
final ImageButton button = view.findViewById(crypto.getButtonId());
|
||||
ibCrypto.put(crypto, button);
|
||||
button.setOnClickListener(v -> {
|
||||
if (possibleCryptos.contains(crypto)) {
|
||||
selectedCrypto = crypto;
|
||||
updateCryptoButtons(false);
|
||||
} else {
|
||||
// show help what to do:
|
||||
if (button.getId() != R.id.ibXMR) {
|
||||
final String name = getResources().getStringArray(R.array.cryptos)[crypto.ordinal()];
|
||||
final String symbol = getCryptoForButton(button).getSymbol();
|
||||
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help, name, symbol)));
|
||||
tvXmrTo.setVisibility(View.VISIBLE);
|
||||
if (Helper.ALLOW_SHIFT) {
|
||||
tvXmrTo = view.findViewById(R.id.tvXmrTo);
|
||||
ibCrypto = new HashMap<>();
|
||||
for (Crypto crypto : Crypto.values()) {
|
||||
final ImageButton button = view.findViewById(crypto.getButtonId());
|
||||
ibCrypto.put(crypto, button);
|
||||
button.setOnClickListener(v -> {
|
||||
if (possibleCryptos.contains(crypto)) {
|
||||
selectedCrypto = crypto;
|
||||
updateCryptoButtons(false);
|
||||
} else {
|
||||
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help_xmr)));
|
||||
tvXmrTo.setVisibility(View.VISIBLE);
|
||||
// show help what to do:
|
||||
if (button.getId() != R.id.ibXMR) {
|
||||
final String name = getResources().getStringArray(R.array.cryptos)[crypto.ordinal()];
|
||||
final String symbol = getCryptoForButton(button).getSymbol();
|
||||
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help, name, symbol)));
|
||||
tvXmrTo.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help_xmr)));
|
||||
tvXmrTo.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
updateCryptoButtons(true);
|
||||
} else {
|
||||
view.findViewById(R.id.llExchange).setVisibility(View.GONE);
|
||||
}
|
||||
updateCryptoButtons(true);
|
||||
|
||||
etAddress = view.findViewById(R.id.etAddress);
|
||||
etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@@ -181,6 +183,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
selectedCrypto = Crypto.XMR;
|
||||
sendListener.setMode(SendFragment.Mode.XMR);
|
||||
}
|
||||
if (!Helper.ALLOW_SHIFT) return;
|
||||
if ((selectedCrypto == null) && isEthAddress(address)) {
|
||||
Timber.d("isEthAddress");
|
||||
possibleCryptos.add(Crypto.ETH);
|
||||
@@ -285,6 +288,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
|
||||
private void updateCryptoButtons(boolean noAddress) {
|
||||
if (!Helper.ALLOW_SHIFT) return;
|
||||
for (Crypto crypto : Crypto.values()) {
|
||||
if (crypto == selectedCrypto) {
|
||||
selectedCrypto(crypto);
|
||||
@@ -345,7 +349,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
|
||||
private boolean checkAddress() {
|
||||
boolean ok = checkAddressNoError();
|
||||
if (!ok) {
|
||||
if (possibleCryptos.isEmpty()) {
|
||||
etAddress.setError(getString(R.string.send_address_invalid));
|
||||
} else {
|
||||
etAddress.setError(null);
|
||||
@@ -447,7 +451,11 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
BarcodeData barcodeData = sendListener.getBarcodeData();
|
||||
if (barcodeData != null) {
|
||||
Timber.d("GOT DATA");
|
||||
|
||||
if (!Helper.ALLOW_SHIFT && (barcodeData.asset != Crypto.XMR)) {
|
||||
Timber.d("BUT ONLY XMR SUPPORTED");
|
||||
barcodeData = null;
|
||||
sendListener.setBarcodeData(barcodeData);
|
||||
}
|
||||
if (barcodeData.address != null) {
|
||||
etAddress.getEditText().setText(barcodeData.address);
|
||||
possibleCryptos.clear();
|
||||
@@ -458,7 +466,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
possibleCryptos.add(barcodeData.asset);
|
||||
selectedCrypto = barcodeData.asset;
|
||||
}
|
||||
updateCryptoButtons(false);
|
||||
if (Helper.ALLOW_SHIFT)
|
||||
updateCryptoButtons(false);
|
||||
if (checkAddress()) {
|
||||
if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC)
|
||||
etAddress.setError(getString(R.string.send_address_no_dnssec));
|
||||
|
@@ -139,7 +139,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
Timber.d("onResumeFragment()");
|
||||
Helper.showKeyboard(getActivity());
|
||||
final long funds = getTotalFunds();
|
||||
maxFunds = 1.0 * funds / 1000000000000L;
|
||||
maxFunds = 1.0 * funds / Helper.ONE_XMR;
|
||||
if (!sendListener.getActivityCallback().isStreetMode()) {
|
||||
tvFunds.setText(getString(R.string.send_available,
|
||||
Wallet.getDisplayAmount(funds)));
|
||||
|
@@ -34,6 +34,7 @@ import com.m2049r.xmrwallet.service.shift.ShiftException;
|
||||
import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters;
|
||||
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
|
||||
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.OkHttpHelper;
|
||||
import com.m2049r.xmrwallet.util.ServiceHelper;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
|
||||
@@ -89,7 +90,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onValidateFields() {
|
||||
Timber.i(maxBtc + "/" + minBtc);
|
||||
@@ -179,7 +179,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
tvXmrToParms.setText(xmrParmText);
|
||||
|
||||
final long funds = getTotalFunds();
|
||||
double availableXmr = 1.0 * funds / 1000000000000L;
|
||||
double availableXmr = 1.0 * funds / Helper.ONE_XMR;
|
||||
|
||||
String availBtcString;
|
||||
String availXmrString;
|
||||
|
@@ -359,40 +359,22 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
|
||||
private RequestQuote xmrtoQuote = null;
|
||||
private int stageARetries = 0;
|
||||
private final int RETRIES = 3;
|
||||
private double stageAPrice = 0;
|
||||
|
||||
private void processStageA(final RequestQuote requestQuote) {
|
||||
Timber.d("processCreateOrder %s", requestQuote.getId());
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
// verify the BTC amount is correct (price can change and we can only specify XMR amount)
|
||||
// verify the BTC amount is correct
|
||||
if (requestQuote.getBtcAmount() != txDataBtc.getBtcAmount()) {
|
||||
if (--stageARetries <= 0) {
|
||||
Timber.d("Failed to get quote");
|
||||
getView().post(() ->
|
||||
showStageError(ShiftError.Error.SERVICE.toString(),
|
||||
getString(R.string.shift_noquote),
|
||||
getString(R.string.shift_checkamount)));
|
||||
return; // just stop for now
|
||||
}
|
||||
if (stageAPrice == requestQuote.getPrice()) {
|
||||
// same price but different BTC amount - something else is wrong (e.g. too many decimals)
|
||||
Timber.d("Price unchanged");
|
||||
getView().post(() ->
|
||||
showStageError(ShiftError.Error.SERVICE.toString(),
|
||||
getString(R.string.shift_noquote),
|
||||
getString(R.string.shift_checkamount)));
|
||||
return; // just stop for now
|
||||
}
|
||||
stageAPrice = requestQuote.getPrice();
|
||||
// recalc XMR and try again
|
||||
txDataBtc.setAmount(txDataBtc.getBtcAmount() / requestQuote.getPrice());
|
||||
getView().post(this::stageAOneShot);
|
||||
return; // stageA will run in the main thread
|
||||
Timber.d("Failed to get quote");
|
||||
getView().post(() -> showStageError(ShiftError.Error.SERVICE.toString(),
|
||||
getString(R.string.shift_noquote),
|
||||
getString(R.string.shift_checkamount)));
|
||||
return; // just stop for now
|
||||
}
|
||||
xmrtoQuote = requestQuote;
|
||||
txDataBtc.setAmount(xmrtoQuote.getXmrAmount());
|
||||
getView().post(() -> {
|
||||
// show data from the actual quote as that is what is used to
|
||||
NumberFormat df = NumberFormat.getInstance(Locale.US);
|
||||
df.setMaximumFractionDigits(12);
|
||||
final String btcAmount = df.format(xmrtoQuote.getBtcAmount());
|
||||
@@ -438,18 +420,12 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
|
||||
private void stageA() {
|
||||
stageARetries = RETRIES;
|
||||
stageAOneShot();
|
||||
}
|
||||
|
||||
private void stageAOneShot() {
|
||||
if (!isResumed) return;
|
||||
Timber.d("Request Quote");
|
||||
xmrtoQuote = null;
|
||||
xmrtoOrder = null;
|
||||
showProgress(1, getString(R.string.label_send_progress_xmrto_create));
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
stageAPrice = 0;
|
||||
|
||||
ShiftCallback<RequestQuote> callback = new ShiftCallback<RequestQuote>() {
|
||||
@Override
|
||||
@@ -473,7 +449,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
};
|
||||
|
||||
getXmrToApi().requestQuote(txDataBtc.getAmountAsDouble(), callback);
|
||||
getXmrToApi().requestQuote(txDataBtc.getBtcAmount(), callback);
|
||||
}
|
||||
|
||||
private CreateOrder xmrtoOrder = null;
|
||||
|
@@ -66,7 +66,7 @@ public class SendFragment extends Fragment
|
||||
SendSuccessWizardFragment.Listener,
|
||||
OnBackPressedListener, OnUriScannedListener {
|
||||
|
||||
final static public int MIXIN = 10;
|
||||
final static public int MIXIN = 0;
|
||||
|
||||
private Listener activityCallback;
|
||||
|
||||
|
@@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.data.Subaddress;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -110,12 +111,14 @@ public class SubaddressInfoAdapter extends RecyclerView.Adapter<SubaddressInfoAd
|
||||
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
||||
final TextView tvName;
|
||||
final TextView tvAddress;
|
||||
final TextView tvAmount;
|
||||
Subaddress item;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
tvAddress = itemView.findViewById(R.id.tvAddress);
|
||||
tvAmount = itemView.findViewById(R.id.tx_amount);
|
||||
itemView.setOnClickListener(this);
|
||||
itemView.setOnLongClickListener(this);
|
||||
}
|
||||
@@ -129,6 +132,12 @@ public class SubaddressInfoAdapter extends RecyclerView.Adapter<SubaddressInfoAd
|
||||
item.getAddressIndex(), item.getSquashedAddress());
|
||||
tvName.setText(label.isEmpty() ? address : label);
|
||||
tvAddress.setText(address);
|
||||
final long amount = item.getAmount();
|
||||
if (amount > 0)
|
||||
tvAmount.setText(context.getString(R.string.tx_list_amount_positive,
|
||||
Helper.getDisplayAmount(amount, Helper.DISPLAY_DIGITS_INFO)));
|
||||
else
|
||||
tvAmount.setText("");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -74,7 +74,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
return mOldList.get(oldItemPosition).name.equals(mNewList.get(newItemPosition).name);
|
||||
return mOldList.get(oldItemPosition).getName().equals(mNewList.get(newItemPosition).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,9 +86,9 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_wallet, parent, false);
|
||||
return new ViewHolder(view);
|
||||
return new ViewHolder(
|
||||
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_wallet, parent, false)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,7 +122,6 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
final TextView tvName;
|
||||
final TextView tvAddress;
|
||||
final ImageButton ibOptions;
|
||||
WalletManager.WalletInfo infoItem;
|
||||
boolean popupOpen = false;
|
||||
@@ -130,7 +129,6 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
tvAddress = itemView.findViewById(R.id.tvAddress);
|
||||
ibOptions = itemView.findViewById(R.id.ibOptions);
|
||||
ibOptions.setOnClickListener(view -> {
|
||||
if (popupOpen) return;
|
||||
@@ -160,8 +158,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
||||
|
||||
void bind(int position) {
|
||||
infoItem = infoItems.get(position);
|
||||
tvName.setText(infoItem.name);
|
||||
tvAddress.setText(infoItem.address.substring(0, 16) + "...");
|
||||
tvName.setText(infoItem.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -173,7 +173,16 @@ public class Wallet {
|
||||
}
|
||||
|
||||
public Subaddress getSubaddressObject(int subAddressIndex) {
|
||||
return getSubaddressObject(accountIndex, subAddressIndex);
|
||||
Subaddress subaddress = getSubaddressObject(accountIndex, subAddressIndex);
|
||||
long amount = 0;
|
||||
for (TransactionInfo info : getHistory().getAll()) {
|
||||
if ((info.addressIndex == subAddressIndex)
|
||||
&& (info.direction == TransactionInfo.Direction.Direction_In)) {
|
||||
amount += info.amount;
|
||||
}
|
||||
}
|
||||
subaddress.setAmount(amount);
|
||||
return subaddress;
|
||||
}
|
||||
|
||||
public native String getPath();
|
||||
|
@@ -21,15 +21,13 @@ import com.m2049r.xmrwallet.data.Node;
|
||||
import com.m2049r.xmrwallet.ledger.Ledger;
|
||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class WalletManager {
|
||||
@@ -103,7 +101,8 @@ public class WalletManager {
|
||||
wallet.setRestoreHeight(restoreHeight);
|
||||
Timber.d("Changed Restore Height from %d to %d", oldHeight, wallet.getRestoreHeight());
|
||||
wallet.setPassword(password); // this rewrites the keys file (which contains the restore height)
|
||||
}
|
||||
} else
|
||||
Timber.e(wallet.getStatus().toString());
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@@ -210,46 +209,22 @@ public class WalletManager {
|
||||
//public native List<String> findWallets(String path); // this does not work - some error in boost
|
||||
|
||||
public class WalletInfo implements Comparable<WalletInfo> {
|
||||
public File path;
|
||||
public String name;
|
||||
public String address;
|
||||
@Getter
|
||||
final private File path;
|
||||
@Getter
|
||||
final private String name;
|
||||
|
||||
public WalletInfo(File wallet) {
|
||||
path = wallet.getParentFile();
|
||||
name = wallet.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(WalletInfo another) {
|
||||
int n = name.toLowerCase().compareTo(another.name.toLowerCase());
|
||||
if (n != 0) {
|
||||
return n;
|
||||
} else { // wallet names are the same
|
||||
return address.compareTo(another.address);
|
||||
}
|
||||
return name.toLowerCase().compareTo(another.name.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
public WalletInfo getWalletInfo(File wallet) {
|
||||
WalletInfo info = new WalletInfo();
|
||||
info.path = wallet.getParentFile();
|
||||
info.name = wallet.getName();
|
||||
File addressFile = new File(info.path, info.name + ".address.txt");
|
||||
//Timber.d(addressFile.getAbsolutePath());
|
||||
info.address = "??????";
|
||||
BufferedReader addressReader = null;
|
||||
try {
|
||||
addressReader = new BufferedReader(new FileReader(addressFile));
|
||||
info.address = addressReader.readLine();
|
||||
} catch (IOException ex) {
|
||||
Timber.d(ex.getLocalizedMessage());
|
||||
} finally {
|
||||
if (addressReader != null) {
|
||||
try {
|
||||
addressReader.close();
|
||||
} catch (IOException ex) {
|
||||
// that's just too bad
|
||||
}
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
public List<WalletInfo> findWallets(File path) {
|
||||
List<WalletInfo> wallets = new ArrayList<>();
|
||||
Timber.d("Scanning: %s", path.getAbsolutePath());
|
||||
@@ -261,7 +236,7 @@ public class WalletManager {
|
||||
for (int i = 0; i < found.length; i++) {
|
||||
String filename = found[i].getName();
|
||||
File f = new File(found[i].getParent(), filename.substring(0, filename.length() - 5)); // 5 is length of ".keys"+1
|
||||
wallets.add(getWalletInfo(f));
|
||||
wallets.add(new WalletInfo(f));
|
||||
}
|
||||
return wallets;
|
||||
}
|
||||
|
@@ -167,7 +167,7 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
private Calendar fetchDate = null;
|
||||
|
||||
synchronized private ExchangeRate getRate(String currency) throws ExchangeException {
|
||||
Timber.e("Getting %s", currency);
|
||||
Timber.d("Getting %s", currency);
|
||||
final Double rate = fxEntries.get(currency);
|
||||
if (rate == null) throw new ExchangeException(404, "Currency not supported: " + currency);
|
||||
return new ExchangeRateImpl(currency, rate, fxDate.getTime());
|
||||
|
@@ -74,10 +74,10 @@ class RequestQuoteImpl implements RequestQuote {
|
||||
price = jsonObject.getDouble("rate");
|
||||
}
|
||||
|
||||
public static void call(@NonNull final ShiftApiCall api, final double xmrAmount,
|
||||
public static void call(@NonNull final ShiftApiCall api, final double btcAmount,
|
||||
@NonNull final ShiftCallback<RequestQuote> callback) {
|
||||
try {
|
||||
final JSONObject request = createRequest(xmrAmount);
|
||||
final JSONObject request = createRequest(btcAmount);
|
||||
api.call("quotes", request, new NetworkCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject jsonObject) {
|
||||
@@ -104,13 +104,13 @@ class RequestQuoteImpl implements RequestQuote {
|
||||
* @param xmrAmount how much XMR to shift to BTC
|
||||
*/
|
||||
|
||||
static JSONObject createRequest(final double xmrAmount) throws JSONException {
|
||||
static JSONObject createRequest(final double btcAmount) throws JSONException {
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("depositMethod", "xmr");
|
||||
jsonObject.put("settleMethod", ServiceHelper.ASSET);
|
||||
// #sideshift is silly and likes numbers as strings
|
||||
String amount = AmountFormatter.format(xmrAmount);
|
||||
jsonObject.put("depositAmount", amount);
|
||||
String amount = AmountFormatter.format(btcAmount);
|
||||
jsonObject.put("settleAmount", amount);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
@@ -64,8 +64,8 @@ public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestQuote(final double xmrAmount, @NonNull final ShiftCallback<RequestQuote> callback) {
|
||||
RequestQuoteImpl.call(this, xmrAmount, callback);
|
||||
public void requestQuote(final double btcAmount, @NonNull final ShiftCallback<RequestQuote> callback) {
|
||||
RequestQuoteImpl.call(this, btcAmount, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -34,7 +34,6 @@ import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Environment;
|
||||
import android.os.StrictMode;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
@@ -58,6 +57,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.data.Crypto;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
|
||||
import java.io.File;
|
||||
@@ -77,16 +77,17 @@ import javax.net.ssl.HttpsURLConnection;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Helper {
|
||||
static private final String FLAVOR_SUFFIX =
|
||||
(BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
|
||||
+ (BuildConfig.DEBUG ? "-debug" : "");
|
||||
|
||||
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
|
||||
|
||||
static public final String BASE_CRYPTO = "XMR";
|
||||
static public final String BASE_CRYPTO = Crypto.XMR.getSymbol();
|
||||
static public final int XMR_DECIMALS = 12;
|
||||
static public final long ONE_XMR = Math.round(Math.pow(10, Helper.XMR_DECIMALS));
|
||||
|
||||
static private final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
|
||||
static private final String HOME_DIR = "monero" + FLAVOR_SUFFIX;
|
||||
static public final boolean SHOW_EXCHANGERATES = true;
|
||||
static public final boolean ALLOW_SHIFT = true;
|
||||
|
||||
static private final String WALLET_DIR = "wallets";
|
||||
static private final String MONERO_DIR = "monero";
|
||||
|
||||
static public int DISPLAY_DIGITS_INFO = 5;
|
||||
|
||||
@@ -95,12 +96,7 @@ public class Helper {
|
||||
}
|
||||
|
||||
static public File getStorage(Context context, String folderName) {
|
||||
if (!isExternalStorageWritable()) {
|
||||
String msg = context.getString(R.string.message_strorage_not_writable);
|
||||
Timber.e(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
File dir = new File(Environment.getExternalStorageDirectory(), folderName);
|
||||
File dir = new File(context.getFilesDir(), folderName);
|
||||
if (!dir.exists()) {
|
||||
Timber.i("Creating %s", dir.getAbsolutePath());
|
||||
dir.mkdirs(); // try to make it
|
||||
@@ -113,24 +109,6 @@ public class Helper {
|
||||
return dir;
|
||||
}
|
||||
|
||||
static public final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
|
||||
|
||||
static public boolean getWritePermission(Activity context) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
== PackageManager.PERMISSION_DENIED) {
|
||||
Timber.w("Permission denied to WRITE_EXTERNAL_STORAGE - requesting it");
|
||||
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
||||
context.requestPermissions(permissions, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static public final int PERMISSIONS_REQUEST_CAMERA = 7;
|
||||
|
||||
static public boolean getCameraPermission(Activity context) {
|
||||
@@ -156,12 +134,6 @@ public class Helper {
|
||||
return f;
|
||||
}
|
||||
|
||||
/* Checks if external storage is available for read and write */
|
||||
private static boolean isExternalStorageWritable() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
return Environment.MEDIA_MOUNTED.equals(state);
|
||||
}
|
||||
|
||||
static public void showKeyboard(Activity act) {
|
||||
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
final View focus = act.getCurrentFocus();
|
||||
@@ -189,11 +161,11 @@ public class Helper {
|
||||
}
|
||||
|
||||
static public BigDecimal getDecimalAmount(long amount) {
|
||||
return new BigDecimal(amount).scaleByPowerOfTen(-12);
|
||||
return new BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS);
|
||||
}
|
||||
|
||||
static public String getDisplayAmount(long amount) {
|
||||
return getDisplayAmount(amount, 12);
|
||||
return getDisplayAmount(amount, XMR_DECIMALS);
|
||||
}
|
||||
|
||||
static public String getDisplayAmount(long amount, int maxDecimals) {
|
||||
@@ -225,7 +197,7 @@ public class Helper {
|
||||
static public String getDisplayAmount(double amount) {
|
||||
// a Java bug does not strip zeros properly if the value is 0
|
||||
BigDecimal d = new BigDecimal(amount)
|
||||
.setScale(12, BigDecimal.ROUND_HALF_UP)
|
||||
.setScale(XMR_DECIMALS, BigDecimal.ROUND_HALF_UP)
|
||||
.stripTrailingZeros();
|
||||
if (d.scale() < 1)
|
||||
d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY);
|
||||
@@ -339,7 +311,7 @@ public class Helper {
|
||||
|
||||
static public void setMoneroHome(Context context) {
|
||||
try {
|
||||
String home = getStorage(context, HOME_DIR).getAbsolutePath();
|
||||
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
|
||||
Os.setenv("HOME", home, true);
|
||||
} catch (ErrnoException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
@@ -355,7 +327,7 @@ public class Helper {
|
||||
|
||||
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
|
||||
static public void initLogger(Context context, int level) {
|
||||
String home = getStorage(context, HOME_DIR).getAbsolutePath();
|
||||
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
|
||||
WalletManager.initLogger(home + "/monerujo", "monerujo.log");
|
||||
if (level >= WalletManager.LOGLEVEL_SILENT)
|
||||
WalletManager.setLogLevel(level);
|
||||
|
@@ -24,9 +24,10 @@ import android.os.Build;
|
||||
import android.security.KeyPairGeneratorSpec;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -167,6 +168,11 @@ public class KeyStoreHelper {
|
||||
.remove(wallet).apply();
|
||||
}
|
||||
|
||||
public static void copyWalletUserPass(Context context, String srcWallet, String dstWallet) throws BrokenPasswordStoreException {
|
||||
final String pass = loadWalletUserPass(context, srcWallet);
|
||||
saveWalletUserPass(context, dstWallet, pass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a public and private key and stores it using the Android Key
|
||||
* Store, so that only this application will be able to access the keys.
|
||||
|
@@ -0,0 +1,170 @@
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Environment;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import timber.log.Timber;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class LegacyStorageHelper {
|
||||
final private File srcDir;
|
||||
final private File dstDir;
|
||||
|
||||
static public void migrateWallets(Context context) {
|
||||
try {
|
||||
if (isStorageMigrated(context)) return;
|
||||
if (!hasReadPermission(context)) {
|
||||
// can't migrate - don't remember this, as the user may turn on permissions later
|
||||
return;
|
||||
}
|
||||
final File oldRoot = getWalletRoot();
|
||||
if (!oldRoot.exists()) {
|
||||
// nothing to migrate, so don't try again
|
||||
setStorageMigrated(context);
|
||||
return;
|
||||
}
|
||||
final File newRoot = Helper.getWalletRoot(context);
|
||||
(new LegacyStorageHelper(oldRoot, newRoot)).migrate();
|
||||
setStorageMigrated(context); // done it once - don't try again
|
||||
} catch (IllegalStateException ex) {
|
||||
Timber.d(ex);
|
||||
// nothing we can do here
|
||||
}
|
||||
}
|
||||
|
||||
public void migrate() {
|
||||
String addressPrefix = WalletManager.getInstance().addressPrefix();
|
||||
File[] wallets = srcDir.listFiles((dir, filename) -> filename.endsWith(".keys"));
|
||||
if (wallets == null) return;
|
||||
for (File wallet : wallets) {
|
||||
final String walletName = wallet.getName().substring(0, wallet.getName().length() - ".keys".length());
|
||||
if (addressPrefix.indexOf(getAddress(walletName).charAt(0)) < 0) {
|
||||
Timber.d("skipping %s", walletName);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
copy(walletName);
|
||||
} catch (IOException ex) { // something failed - try to clean up
|
||||
deleteDst(walletName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return "@" by default so we don't need to deal with null stuff
|
||||
private String getAddress(String walletName) {
|
||||
File addressFile = new File(srcDir, walletName + ".address.txt");
|
||||
if (!addressFile.exists()) return "@";
|
||||
try (BufferedReader addressReader = new BufferedReader(new FileReader(addressFile))) {
|
||||
return addressReader.readLine();
|
||||
} catch (IOException ex) {
|
||||
Timber.d(ex.getLocalizedMessage());
|
||||
}
|
||||
return "@";
|
||||
}
|
||||
|
||||
private void copy(String walletName) throws IOException {
|
||||
final String dstName = getUniqueName(dstDir, walletName);
|
||||
copyFile(new File(srcDir, walletName), new File(dstDir, dstName));
|
||||
copyFile(new File(srcDir, walletName + ".keys"), new File(dstDir, dstName + ".keys"));
|
||||
}
|
||||
|
||||
private void deleteDst(String walletName) {
|
||||
// do our best, but if it fails, it fails
|
||||
(new File(dstDir, walletName)).delete();
|
||||
(new File(dstDir, walletName + ".keys")).delete();
|
||||
}
|
||||
|
||||
private void copyFile(File src, File dst) throws IOException {
|
||||
if (!src.exists()) return;
|
||||
Timber.d("%s => %s", src.getAbsolutePath(), dst.getAbsolutePath());
|
||||
try (FileChannel inChannel = new FileInputStream(src).getChannel();
|
||||
FileChannel outChannel = new FileOutputStream(dst).getChannel()) {
|
||||
inChannel.transferTo(0, inChannel.size(), outChannel);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isExternalStorageWritable() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
return Environment.MEDIA_MOUNTED.equals(state);
|
||||
}
|
||||
|
||||
private static File getWalletRoot() {
|
||||
if (!isExternalStorageWritable())
|
||||
throw new IllegalStateException();
|
||||
|
||||
// wallet folder for legacy (pre-Q) installations
|
||||
final String FLAVOR_SUFFIX =
|
||||
(BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
|
||||
+ (BuildConfig.DEBUG ? "-debug" : "");
|
||||
final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
|
||||
|
||||
File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR);
|
||||
if (!dir.exists() || !dir.isDirectory())
|
||||
throw new IllegalStateException();
|
||||
return dir;
|
||||
}
|
||||
|
||||
private static boolean hasReadPermission(Context context) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
return context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
|
||||
|
||||
private static String getUniqueName(File root, String name) {
|
||||
if (!(new File(root, name + ".keys")).exists()) // <name> does not exist => it's ok to use
|
||||
return name;
|
||||
|
||||
File[] wallets = root.listFiles(
|
||||
(dir, filename) -> {
|
||||
Matcher m = WALLET_PATTERN.matcher(filename);
|
||||
if (m.find())
|
||||
return m.group(1).equals(name);
|
||||
else return false;
|
||||
});
|
||||
if (wallets.length == 0) return name + " (1)";
|
||||
int maxIndex = 0;
|
||||
for (File wallet : wallets) {
|
||||
try {
|
||||
final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
|
||||
if (!m.find())
|
||||
throw new IllegalStateException("this must match as it did before");
|
||||
final int index = Integer.parseInt(m.group(2));
|
||||
if (index > maxIndex) maxIndex = index;
|
||||
} catch (NumberFormatException ex) {
|
||||
// this cannot happen & we can ignore it if it does
|
||||
}
|
||||
}
|
||||
return name + " (" + (maxIndex + 1) + ")";
|
||||
}
|
||||
|
||||
private static final String MIGRATED_KEY = "migrated_legacy_storage";
|
||||
|
||||
public static boolean isStorageMigrated(Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MIGRATED_KEY, false);
|
||||
}
|
||||
|
||||
public static void setStorageMigrated(Context context) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(MIGRATED_KEY, true).apply();
|
||||
}
|
||||
}
|
@@ -18,8 +18,6 @@ package com.m2049r.xmrwallet.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -27,8 +25,12 @@ import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.dialog.HelpFragment;
|
||||
import com.m2049r.xmrwallet.ledger.Ledger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -44,18 +46,20 @@ public class Notice {
|
||||
synchronized (Notice.class) {
|
||||
if (notices != null) return;
|
||||
notices = new ArrayList<>();
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_XMRTO_ENABLED_SEND,
|
||||
R.string.info_xmrto_enabled,
|
||||
R.string.help_xmrto,
|
||||
1)
|
||||
);
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_LEDGER,
|
||||
R.string.info_ledger_enabled,
|
||||
R.string.help_create_ledger,
|
||||
1)
|
||||
);
|
||||
if (Helper.ALLOW_SHIFT)
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_XMRTO_ENABLED_SEND,
|
||||
R.string.info_xmrto_enabled,
|
||||
R.string.help_xmrto,
|
||||
1)
|
||||
);
|
||||
if (Ledger.ENABLED)
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_LEDGER,
|
||||
R.string.info_ledger_enabled,
|
||||
R.string.help_create_ledger,
|
||||
1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -26,6 +26,8 @@ import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RestoreHeight {
|
||||
static final int DIFFICULTY_TARGET = 120; // seconds
|
||||
|
||||
static private RestoreHeight Singleton = null;
|
||||
|
||||
static public RestoreHeight getInstance() {
|
||||
@@ -190,7 +192,7 @@ public class RestoreHeight {
|
||||
} else {
|
||||
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime,
|
||||
TimeUnit.MILLISECONDS);
|
||||
height = Math.round(prevBc + 1.0 * days * (24 * 60 / 2));
|
||||
height = Math.round(prevBc + 1.0 * days * (24f * 60 * 60 / DIFFICULTY_TARGET));
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
64
app/src/main/java/com/m2049r/xmrwallet/util/ZipBackup.java
Normal file
64
app/src/main/java/com/m2049r/xmrwallet/util/ZipBackup.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ZipBackup {
|
||||
final private Context context;
|
||||
final private String walletName;
|
||||
|
||||
private ZipOutputStream zip;
|
||||
|
||||
public void writeTo(Uri zipUri) throws IOException {
|
||||
if (zip != null)
|
||||
throw new IllegalStateException("zip already initialized");
|
||||
try {
|
||||
zip = new ZipOutputStream(context.getContentResolver().openOutputStream(zipUri));
|
||||
|
||||
final File walletRoot = Helper.getWalletRoot(context);
|
||||
addFile(new File(walletRoot, walletName + ".keys"));
|
||||
addFile(new File(walletRoot, walletName));
|
||||
|
||||
zip.close();
|
||||
} finally {
|
||||
if (zip != null) zip.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void addFile(File file) throws IOException {
|
||||
if (!file.exists()) return; // ignore missing files (e.g. the cache file might not exist)
|
||||
ZipEntry entry = new ZipEntry(file.getName());
|
||||
zip.putNextEntry(entry);
|
||||
writeFile(file);
|
||||
zip.closeEntry();
|
||||
}
|
||||
|
||||
private void writeFile(File source) throws IOException {
|
||||
try (InputStream is = new FileInputStream(source)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
while ((length = is.read(buffer)) > 0) {
|
||||
zip.write(buffer, 0, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat DATETIME_FORMATTER =
|
||||
new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss");
|
||||
|
||||
public String getBackupName() {
|
||||
return walletName + " " + DATETIME_FORMATTER.format(new Date());
|
||||
}
|
||||
}
|
139
app/src/main/java/com/m2049r/xmrwallet/util/ZipRestore.java
Normal file
139
app/src/main/java/com/m2049r/xmrwallet/util/ZipRestore.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import timber.log.Timber;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ZipRestore {
|
||||
final private Context context;
|
||||
final private Uri zipUri;
|
||||
|
||||
private File walletRoot;
|
||||
|
||||
private ZipInputStream zip;
|
||||
|
||||
public boolean restore() throws IOException {
|
||||
walletRoot = Helper.getWalletRoot(context);
|
||||
String walletName = testArchive();
|
||||
if (walletName == null) return false;
|
||||
|
||||
walletName = getUniqueName(walletName);
|
||||
|
||||
if (zip != null)
|
||||
throw new IllegalStateException("zip already initialized");
|
||||
try {
|
||||
zip = new ZipInputStream(context.getContentResolver().openInputStream(zipUri));
|
||||
for (ZipEntry entry = zip.getNextEntry(); entry != null; zip.closeEntry(), entry = zip.getNextEntry()) {
|
||||
File destination;
|
||||
final String name = entry.getName();
|
||||
if (name.endsWith(".keys")) {
|
||||
destination = new File(walletRoot, walletName + ".keys");
|
||||
} else if (name.endsWith(".address.txt")) {
|
||||
destination = new File(walletRoot, walletName + ".address.txt");
|
||||
} else {
|
||||
destination = new File(walletRoot, walletName);
|
||||
}
|
||||
writeFile(destination);
|
||||
}
|
||||
} finally {
|
||||
if (zip != null) zip.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void writeFile(File destination) throws IOException {
|
||||
try (OutputStream os = new FileOutputStream(destination)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
while ((length = zip.read(buffer)) > 0) {
|
||||
os.write(buffer, 0, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test the archive to contain files we expect & return the name of the contained wallet or null
|
||||
private String testArchive() {
|
||||
String walletName = null;
|
||||
boolean keys = false;
|
||||
ZipInputStream zipStream = null;
|
||||
try {
|
||||
zipStream = new ZipInputStream(context.getContentResolver().openInputStream(zipUri));
|
||||
for (ZipEntry entry = zipStream.getNextEntry(); entry != null;
|
||||
zipStream.closeEntry(), entry = zipStream.getNextEntry()) {
|
||||
if (entry.isDirectory())
|
||||
return null;
|
||||
final String name = entry.getName();
|
||||
if ((new File(name)).getParentFile() != null)
|
||||
return null;
|
||||
if (walletName == null) {
|
||||
if (name.endsWith(".keys")) {
|
||||
walletName = name.substring(0, name.length() - ".keys".length());
|
||||
keys = true; // we have they keys
|
||||
} else if (name.endsWith(".address.txt")) {
|
||||
walletName = name.substring(0, name.length() - ".address.txt".length());
|
||||
} else {
|
||||
walletName = name;
|
||||
}
|
||||
} else { // we have a wallet name
|
||||
if (name.endsWith(".keys")) {
|
||||
if (!name.equals(walletName + ".keys")) return null;
|
||||
keys = true; // we have they keys
|
||||
} else if (name.endsWith(".address.txt")) {
|
||||
if (!name.equals(walletName + ".address.txt")) return null;
|
||||
} else if (!name.equals(walletName)) return null;
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
return null;
|
||||
} finally {
|
||||
try {
|
||||
if (zipStream != null) zipStream.close();
|
||||
} catch (IOException ex) {
|
||||
Timber.w(ex);
|
||||
}
|
||||
}
|
||||
// we need the keys at least
|
||||
if (keys) return walletName;
|
||||
else return null;
|
||||
}
|
||||
|
||||
final static Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
|
||||
|
||||
private String getUniqueName(String name) {
|
||||
if (!(new File(walletRoot, name + ".keys")).exists()) // <name> does not exist => it's ok to use
|
||||
return name;
|
||||
|
||||
File[] wallets = walletRoot.listFiles(
|
||||
(dir, filename) -> {
|
||||
Matcher m = WALLET_PATTERN.matcher(filename);
|
||||
if (m.find())
|
||||
return m.group(1).equals(name);
|
||||
else return false;
|
||||
});
|
||||
if (wallets.length == 0) return name + " (1)";
|
||||
int maxIndex = 0;
|
||||
for (File wallet : wallets) {
|
||||
try {
|
||||
final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
|
||||
m.find();
|
||||
final int index = Integer.parseInt(m.group(2));
|
||||
if (index > maxIndex) maxIndex = index;
|
||||
} catch (NumberFormatException ex) {
|
||||
// this cannot happen & we can ignore it if it does
|
||||
}
|
||||
}
|
||||
return name + " (" + (maxIndex + 1) + ")";
|
||||
}
|
||||
}
|
@@ -172,7 +172,8 @@ public class ExchangeEditText extends LinearLayout {
|
||||
}
|
||||
|
||||
protected void setCurrencyAdapter(Spinner spinner, List<String> currencies) {
|
||||
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
||||
if (Helper.SHOW_EXCHANGERATES)
|
||||
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
||||
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, currencies);
|
||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(spinnerAdapter);
|
||||
@@ -348,11 +349,13 @@ public class ExchangeEditText extends LinearLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String CLEAN_FORMAT = "%." + Helper.XMR_DECIMALS + "f";
|
||||
|
||||
private String getCleanAmountString(String enteredAmount) {
|
||||
try {
|
||||
double amount = Double.parseDouble(enteredAmount);
|
||||
if (amount >= 0) {
|
||||
return String.format(Locale.US, "%,.12f", amount);
|
||||
return String.format(Locale.US, CLEAN_FORMAT, amount);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@@ -155,7 +155,8 @@ public class ExchangeView extends LinearLayout {
|
||||
void setCurrencyAdapter(Spinner spinner) {
|
||||
List<String> currencies = new ArrayList<>();
|
||||
currencies.add(Helper.BASE_CRYPTO);
|
||||
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
||||
if (Helper.SHOW_EXCHANGERATES)
|
||||
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
||||
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, currencies);
|
||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(spinnerAdapter);
|
||||
@@ -167,11 +168,7 @@ public class ExchangeView extends LinearLayout {
|
||||
etAmount = findViewById(R.id.etAmount);
|
||||
tvAmountB = findViewById(R.id.tvAmountB);
|
||||
sCurrencyA = findViewById(R.id.sCurrencyA);
|
||||
ArrayAdapter adapter = ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner);
|
||||
adapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item);
|
||||
sCurrencyA.setAdapter(adapter);
|
||||
sCurrencyB = findViewById(R.id.sCurrencyB);
|
||||
sCurrencyB.setAdapter(adapter);
|
||||
evExchange = findViewById(R.id.evExchange);
|
||||
pbExchange = findViewById(R.id.pbExchange);
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user