mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-04 17:28:42 +02:00
Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
24fc27b09e |
@@ -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 800
|
||||
versionName "1.18.0 'ChAdOx1'"
|
||||
targetSdkVersion 30
|
||||
versionCode 1006
|
||||
versionName "2.0.6 '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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 com.m2049r.xmrwallet.model.Wallet;
|
||||
|
||||
public interface OnBlockUpdateListener {
|
||||
void onBlockUpdate(final Wallet wallet);
|
||||
}
|
@@ -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();
|
||||
|
@@ -45,8 +45,8 @@ import java.util.List;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
// TODO: live update - i.e. use onRefreshed() somehow
|
||||
public class SubaddressInfoFragment extends Fragment implements TransactionInfoAdapter.OnInteractionListener {
|
||||
public class SubaddressInfoFragment extends Fragment
|
||||
implements TransactionInfoAdapter.OnInteractionListener, OnBlockUpdateListener {
|
||||
private TransactionInfoAdapter adapter;
|
||||
|
||||
private Subaddress subaddress;
|
||||
@@ -123,6 +123,11 @@ public class SubaddressInfoFragment extends Fragment implements TransactionInfoA
|
||||
tvTxLabel.setText(R.string.subaddress_tx_label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockUpdate(Wallet wallet) {
|
||||
onRefreshed(wallet);
|
||||
}
|
||||
|
||||
// Callbacks from TransactionInfoAdapter
|
||||
@Override
|
||||
public void onInteraction(final View view, final TransactionInfo infoItem) {
|
||||
|
@@ -141,6 +141,14 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
return synced;
|
||||
}
|
||||
|
||||
private WalletFragment getWalletFragment() {
|
||||
return (WalletFragment) getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||
}
|
||||
|
||||
private Fragment getCurrentFragment() {
|
||||
return getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStreetMode() {
|
||||
return streetMode > 0;
|
||||
@@ -152,8 +160,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
} else {
|
||||
streetMode = 0;
|
||||
}
|
||||
final WalletFragment walletFragment = (WalletFragment)
|
||||
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||
final WalletFragment walletFragment = getWalletFragment();
|
||||
if (walletFragment != null) walletFragment.resetDismissedTransactions();
|
||||
forceUpdate();
|
||||
runOnUiThread(() -> {
|
||||
@@ -220,8 +227,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
|
||||
private void onWalletRescan() {
|
||||
try {
|
||||
final WalletFragment walletFragment = (WalletFragment)
|
||||
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||
final WalletFragment walletFragment = getWalletFragment();
|
||||
getWallet().rescanBlockchainAsync();
|
||||
synced = false;
|
||||
walletFragment.unsync();
|
||||
@@ -336,8 +342,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
|
||||
public void onWalletChangePassword() {
|
||||
try {
|
||||
GenerateReviewFragment detailsFragment = (GenerateReviewFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
GenerateReviewFragment detailsFragment = (GenerateReviewFragment) getCurrentFragment();
|
||||
AlertDialog dialog = detailsFragment.createChangePasswordDialog();
|
||||
if (dialog != null) {
|
||||
Helper.showKeyboard(dialog);
|
||||
@@ -571,8 +576,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
runOnUiThread(this::updateAccountsList);
|
||||
}
|
||||
try {
|
||||
final WalletFragment walletFragment = (WalletFragment)
|
||||
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||
final WalletFragment walletFragment = getWalletFragment();
|
||||
if (wallet.isSynchronized()) {
|
||||
Timber.d("onRefreshed() synced");
|
||||
releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced
|
||||
@@ -583,7 +587,10 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
runOnUiThread(walletFragment::onSynced);
|
||||
}
|
||||
}
|
||||
runOnUiThread(() -> walletFragment.onRefreshed(wallet, full));
|
||||
runOnUiThread(() -> {
|
||||
walletFragment.onRefreshed(wallet, full);
|
||||
updateCurrentFragment(wallet);
|
||||
});
|
||||
return true;
|
||||
} catch (ClassCastException ex) {
|
||||
// not in wallet fragment (probably send monero)
|
||||
@@ -593,6 +600,13 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateCurrentFragment(final Wallet wallet) {
|
||||
final Fragment fragment = getCurrentFragment();
|
||||
if (fragment instanceof OnBlockUpdateListener) {
|
||||
((OnBlockUpdateListener) fragment).onBlockUpdate(wallet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalletStored(final boolean success) {
|
||||
runOnUiThread(() -> {
|
||||
@@ -635,8 +649,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
|
||||
if (requestStreetMode) onEnableStreetMode();
|
||||
|
||||
final WalletFragment walletFragment = (WalletFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
final WalletFragment walletFragment = getWalletFragment();
|
||||
runOnUiThread(() -> {
|
||||
updateAccountsHeader();
|
||||
if (walletFragment != null) {
|
||||
@@ -649,8 +662,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
@Override
|
||||
public void onTransactionCreated(final String txTag, final PendingTransaction pendingTransaction) {
|
||||
try {
|
||||
final SendFragment sendFragment = (SendFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
final SendFragment sendFragment = (SendFragment) getCurrentFragment();
|
||||
runOnUiThread(() -> {
|
||||
dismissProgressDialog();
|
||||
PendingTransaction.Status status = pendingTransaction.getStatus();
|
||||
@@ -673,8 +685,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
@Override
|
||||
public void onSendTransactionFailed(final String error) {
|
||||
try {
|
||||
final SendFragment sendFragment = (SendFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
final SendFragment sendFragment = (SendFragment) getCurrentFragment();
|
||||
runOnUiThread(() -> sendFragment.onSendTransactionFailed(error));
|
||||
} catch (ClassCastException ex) {
|
||||
// not in spend fragment
|
||||
@@ -685,8 +696,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
@Override
|
||||
public void onTransactionSent(final String txId) {
|
||||
try {
|
||||
final SendFragment sendFragment = (SendFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
final SendFragment sendFragment = (SendFragment) getCurrentFragment();
|
||||
runOnUiThread(() -> sendFragment.onTransactionSent(txId));
|
||||
} catch (ClassCastException ex) {
|
||||
// not in spend fragment
|
||||
@@ -697,8 +707,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
@Override
|
||||
public void onProgress(final String text) {
|
||||
try {
|
||||
final WalletFragment walletFragment = (WalletFragment)
|
||||
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||
final WalletFragment walletFragment = getWalletFragment();
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
walletFragment.setProgress(text);
|
||||
@@ -715,8 +724,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
public void onProgress(final int n) {
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
WalletFragment walletFragment = (WalletFragment)
|
||||
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||
WalletFragment walletFragment = getWalletFragment();
|
||||
if (walletFragment != null)
|
||||
walletFragment.setProgress(n);
|
||||
} catch (ClassCastException ex) {
|
||||
@@ -857,8 +865,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
|
||||
void onShareTxInfo() {
|
||||
try {
|
||||
TxFragment fragment = (TxFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
TxFragment fragment = (TxFragment) getCurrentFragment();
|
||||
fragment.shareTxInfo();
|
||||
} catch (ClassCastException ex) {
|
||||
// not in wallet fragment
|
||||
@@ -968,7 +975,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
drawer.closeDrawer(GravityCompat.START);
|
||||
return;
|
||||
}
|
||||
final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
final Fragment fragment = getCurrentFragment();
|
||||
if (fragment instanceof OnBackPressedListener) {
|
||||
if (!((OnBackPressedListener) fragment).onBackPressed()) {
|
||||
super.onBackPressed();
|
||||
|
@@ -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);
|
||||
@@ -363,7 +364,6 @@ public class WalletFragment extends Fragment
|
||||
accountIndex = wallet.getAccountIndex();
|
||||
txlist.scrollToPosition(0);
|
||||
}
|
||||
|
||||
}
|
||||
updateStatus(wallet);
|
||||
}
|
||||
|
@@ -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);
|
||||
@@ -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;
|
||||
@@ -179,7 +180,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;
|
||||
|
@@ -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());
|
||||
|
@@ -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,171 @@
|
||||
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)) {
|
||||
// nothing to migrate, so don't try again
|
||||
setStorageMigrated(context);
|
||||
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