1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-04 17:28:42 +02:00

Compare commits

...

27 Commits

Author SHA1 Message Date
m2049r
669516c60b add USE_FINGERPRINT again (#766)
and update sdk
2021-05-06 00:03:39 +02:00
m2049r
a56a29a6c4 bump version 2021-05-02 10:36:52 +02:00
m2049r
4e31f47482 new logo (#764) 2021-05-02 09:56:26 +02:00
m2049r
c1f14f9653 update gradle & deps & bump version (#760) 2021-04-25 18:24:30 +02:00
m2049r
2746c52d7b confirm checkboxes for delete confirmation dialogs (#759) 2021-04-25 14:26:46 +02:00
Baltsar
5df323bacb Update Swedish strings.xml (#754) 2021-04-23 23:32:31 +02:00
m2049r
776cc26377 refactor magic number (#756) 2021-04-23 08:57:08 +02:00
m2049r
bdfb6a90b6 update & clean build (#755) 2021-04-22 20:17:02 +02:00
m2049r
6db44dfab1 cleanup backup code (#753) 2021-04-21 19:54:20 +02:00
m2049r
c68ac7db6d bump version 2021-04-20 17:08:51 +02:00
Katant Savelev
e09862e940 Ru update for #751 (#752) 2021-04-20 17:01:01 +02:00
m2049r
c7bd7469a1 reset wallet by deleting wallet cache file (#751) 2021-04-20 13:06:14 +02:00
Katant Savelev
b39857fd2e Russian translation update (#746)
* Ru update

* Better external libs build guide

* Update BUILDING-external-libs.md
2021-04-20 12:50:48 +02:00
m2049r
8170f823ab bump version 2021-04-19 18:41:09 +02:00
m2049r
38c0ead45c random fixes (#747) 2021-04-19 17:39:12 +02:00
m2049r
1d027c1694 fix translation 2021-04-18 19:39:00 +02:00
Vlad
45dc21fbf7 Update Romanian translation (#739)
Co-authored-by: m2049r <m2049r@monerujo.io>
2021-04-18 19:19:42 +02:00
netrik182
d4f4de234a pt-br translation of new strings (#735) 2021-04-18 19:17:29 +02:00
m2049r
394d5471e3 show total incoming amount (#745) 2021-04-18 19:16:52 +02:00
m2049r
beb098adb3 build version 2021-04-18 17:38:28 +02:00
m2049r
fda370d35b bump version 2021-04-18 16:57:58 +02:00
m2049r
99681e1bbb fix permissions Q needs to read our old wallet files (#744) 2021-04-18 16:55:14 +02:00
m2049r
21f44380b1 remove persmissions 2021-04-18 13:29:16 +02:00
m2049r
f4c1af1bb8 Some refactoring (#742)
* configure enabling of shift & exchange
* refactor qr logo code
* fix rename wallet
2021-04-17 21:03:32 +02:00
m2049r
c002b81ebd use default mixin 2021-04-15 19:10:56 +02:00
m2049r
d2f07ba3b6 migrate to scoped storage and API 30 (#741) 2021-04-15 19:10:01 +02:00
m2049r
24fc27b09e show new incoming tx in subaddress details (#734) 2021-03-23 19:05:30 +01:00
75 changed files with 2050 additions and 1484 deletions

View File

@@ -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'

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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();

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();

View File

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

View File

@@ -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());

View File

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

View File

@@ -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.

View File

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

View File

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

View File

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

View 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());
}
}

View 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) + ")";
}
}

View File

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

View File

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