1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-03 08:23:04 +02:00

Compare commits

...

27 Commits

Author SHA1 Message Date
m2049r
b576a9de3d bump version 2021-09-05 21:58:19 +02:00
m2049r
148faa00e4 minor fixes for confirmations indicator (#782) 2021-09-05 21:24:53 +02:00
m2049r
e82b471c14 adaptaions for monero v0.17.2.3 (#781) 2021-09-05 21:23:12 +02:00
m2049r
9ed92e5117 remove defunct strings in arabic 2021-08-07 12:31:42 +02:00
m2049r
303b3aa354 dont update on every single block (#776) 2021-08-07 11:58:54 +02:00
phwright
d801a50962 Arabic Translation (#767)
* Arabic Translation
2021-08-07 11:19:24 +02:00
m2049r
9cd8a75dc6 confirmations indicator (#775) 2021-08-07 11:17:13 +02:00
m2049r
002dfd5d58 rewrite Wallet.isSynchronized() (#774) 2021-08-07 11:04:18 +02:00
paxriel
54e54b2a8a Fixed boost JFrog repo (#772) 2021-08-04 10:37:31 +02:00
m2049r
aa768596a4 bump version 2021-05-21 08:49:13 +02:00
m2049r
c4958f6c54 use settleAmount (#768) 2021-05-21 08:45:45 +02:00
m2049r
2c2a5314d4 fix migration of devices with permission timeouts 2021-05-19 22:20:47 +02:00
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
71 changed files with 1549 additions and 528 deletions

View File

@@ -171,6 +171,10 @@ add_library(wallet-crypto STATIC IMPORTED)
set_target_properties(wallet-crypto PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libwallet-crypto.a)
add_library(cryptonote_format_utils_basic STATIC IMPORTED)
set_target_properties(cryptonote_format_utils_basic PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libcryptonote_format_utils_basic.a)
#############
# System
#############
@@ -193,6 +197,7 @@ target_link_libraries( monerujo
wallet
cryptonote_core
cryptonote_basic
cryptonote_format_utils_basic
mnemonics
ringct
ringct_basic

View File

@@ -2,13 +2,14 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 30
buildToolsVersion '29.0.3'
buildToolsVersion '30.0.3'
ndkVersion '17.2.4988734'
defaultConfig {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 30
versionCode 1001
versionName "2.0.1 'Puginarug'"
versionCode 1100
versionName "2.1.0 'Vertant'"
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,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_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" />
@@ -11,11 +12,11 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:preserveLegacyExternalStorage="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">
@@ -34,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"

View File

@@ -914,7 +914,7 @@ Java_com_m2049r_xmrwallet_model_Wallet_refreshAsync(JNIEnv *env, jobject instanc
//virtual void rescanBlockchainAsync() = 0;
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_rescanBlockchainAsync(JNIEnv *env, jobject instance) {
Java_com_m2049r_xmrwallet_model_Wallet_rescanBlockchainAsyncJ(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->rescanBlockchainAsync();
}

View File

@@ -44,6 +44,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.checkbox.MaterialCheckBox;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.m2049r.xmrwallet.data.DefaultNodes;
import com.m2049r.xmrwallet.data.Node;
@@ -474,6 +475,7 @@ public class LoginActivity extends BaseActivity
@Override
public void onWalletBackup(String walletName) {
Timber.d("backup for wallet ." + walletName + ".");
// overwrite any pending backup request
zipBackup = new ZipBackup(this, walletName);
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
@@ -500,14 +502,18 @@ public class LoginActivity extends BaseActivity
if (data == null) {
// nothing selected
Toast.makeText(this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show();
zipBackup = null;
return;
}
try {
if (zipBackup == null) return; // ignore unsolicited request
zipBackup.writeTo(data.getData());
Toast.makeText(this, getString(R.string.backup_success), Toast.LENGTH_SHORT).show();
} catch (IOException ex) {
Timber.e(ex);
Toast.makeText(this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show();
} finally {
zipBackup = null;
}
} else if (requestCode == RESTORE_BACKUP_INTENT) {
if (data == null) {
@@ -549,12 +555,49 @@ public class LoginActivity extends BaseActivity
}
};
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setMessage(getString(R.string.delete_alert_message))
final AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
final AlertDialog confirm = builder.setMessage(getString(R.string.delete_alert_message))
.setTitle(walletName)
.setPositiveButton(getString(R.string.delete_alert_yes), dialogClickListener)
.setNegativeButton(getString(R.string.delete_alert_no), dialogClickListener)
.setView(View.inflate(builder.getContext(), R.layout.checkbox_confirm, null))
.show();
confirm.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
final MaterialCheckBox checkBox = confirm.findViewById(R.id.checkbox);
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
confirm.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(isChecked);
});
}
@Override
public void onWalletDeleteCache(final String walletName) {
Timber.d("delete cache for wallet ." + walletName + ".");
if (checkServiceRunning()) return;
DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
if (!deleteWalletCache(Helper.getWalletFile(LoginActivity.this, walletName))) {
Toast.makeText(LoginActivity.this, getString(R.string.delete_failed), Toast.LENGTH_LONG).show();
}
break;
case DialogInterface.BUTTON_NEGATIVE:
// do nothing
break;
}
};
final AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
final AlertDialog confirm = builder.setMessage(getString(R.string.deletecache_alert_message))
.setTitle(walletName)
.setPositiveButton(getString(R.string.delete_alert_yes), dialogClickListener)
.setNegativeButton(getString(R.string.delete_alert_no), dialogClickListener)
.setView(View.inflate(builder.getContext(), R.layout.checkbox_confirm, null))
.show();
confirm.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
final MaterialCheckBox checkBox = confirm.findViewById(R.id.checkbox);
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
confirm.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(isChecked);
});
}
void reloadWalletList() {
@@ -1024,6 +1067,18 @@ public class LoginActivity extends BaseActivity
return success;
}
boolean deleteWalletCache(File walletFile) {
Timber.d("deleteWalletCache %s", walletFile.getAbsolutePath());
File dir = walletFile.getParentFile();
String name = walletFile.getName();
boolean success = true;
File cacheFile = new File(dir, name);
if (cacheFile.exists()) {
success = cacheFile.delete();
}
return success;
}
void copyFile(File src, File dst) throws IOException {
try (FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel()) {

View File

@@ -88,6 +88,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void onWalletDelete(String walletName);
void onWalletDeleteCache(String walletName);
void onAddWallet(String type);
void onNodePrefs();
@@ -220,6 +222,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
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);
}

View File

@@ -578,7 +578,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
try {
final WalletFragment walletFragment = getWalletFragment();
if (wallet.isSynchronized()) {
Timber.d("onRefreshed() synced");
releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced
if (!synced) { // first sync
onProgress(-1);

View File

@@ -58,7 +58,6 @@ import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import timber.log.Timber;
@@ -128,7 +127,7 @@ public class WalletFragment extends Fragment
currencies.add(Helper.BASE_CRYPTO);
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);
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner_balance, currencies);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sCurrency.setAdapter(spinnerAdapter);
@@ -346,13 +345,18 @@ public class WalletFragment extends Fragment
// if account index has changed scroll to top?
private int accountIndex = 0;
public void onRefreshed(final Wallet wallet, final boolean full) {
public void onRefreshed(final Wallet wallet, boolean full) {
Timber.d("onRefreshed(%b)", full);
if (adapter.needsTransactionUpdateOnNewBlock()) {
wallet.getHistory().refresh();
full = true;
}
if (full) {
List<TransactionInfo> list = new ArrayList<>();
final long streetHeight = activityCallback.getStreetModeHeight();
Timber.d("StreetHeight=%d", streetHeight);
wallet.getHistory().refresh();
for (TransactionInfo info : wallet.getHistory().getAll()) {
Timber.d("TxHeight=%d, Label=%s", info.blockheight, info.subaddressLabel);
if ((info.isPending || (info.blockheight >= streetHeight))
@@ -561,7 +565,7 @@ public class WalletFragment extends Fragment
//TODO figure out why gunther disappears on return from send although he is still set
if (enable) {
if (streetGunther == null)
streetGunther = ContextCompat.getDrawable(Objects.requireNonNull(getContext()), R.drawable.ic_gunther_streetmode);
streetGunther = ContextCompat.getDrawable(requireContext(), R.drawable.ic_gunther_streetmode);
ivStreetGunther.setImageDrawable(streetGunther);
} else
ivStreetGunther.setImageDrawable(null);

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

@@ -183,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);
@@ -348,7 +349,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private boolean checkAddress() {
boolean ok = checkAddressNoError();
if (!ok) {
if (possibleCryptos.isEmpty()) {
etAddress.setError(getString(R.string.send_address_invalid));
} else {
etAddress.setError(null);
@@ -465,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;
@@ -89,7 +90,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
return view;
}
@Override
public boolean onValidateFields() {
Timber.i(maxBtc + "/" + minBtc);
@@ -179,7 +179,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
tvXmrToParms.setText(xmrParmText);
final long funds = getTotalFunds();
double availableXmr = 1.0 * funds / 1000000000000L;
double availableXmr = 1.0 * funds / Helper.ONE_XMR;
String availBtcString;
String availXmrString;

View File

@@ -359,40 +359,22 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
}
private RequestQuote xmrtoQuote = null;
private int stageARetries = 0;
private final int RETRIES = 3;
private double stageAPrice = 0;
private void processStageA(final RequestQuote requestQuote) {
Timber.d("processCreateOrder %s", requestQuote.getId());
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
// verify the BTC amount is correct (price can change and we can only specify XMR amount)
// verify the BTC amount is correct
if (requestQuote.getBtcAmount() != txDataBtc.getBtcAmount()) {
if (--stageARetries <= 0) {
Timber.d("Failed to get quote");
getView().post(() ->
showStageError(ShiftError.Error.SERVICE.toString(),
getString(R.string.shift_noquote),
getString(R.string.shift_checkamount)));
return; // just stop for now
}
if (stageAPrice == requestQuote.getPrice()) {
// same price but different BTC amount - something else is wrong (e.g. too many decimals)
Timber.d("Price unchanged");
getView().post(() ->
showStageError(ShiftError.Error.SERVICE.toString(),
getString(R.string.shift_noquote),
getString(R.string.shift_checkamount)));
return; // just stop for now
}
stageAPrice = requestQuote.getPrice();
// recalc XMR and try again
txDataBtc.setAmount(txDataBtc.getBtcAmount() / requestQuote.getPrice());
getView().post(this::stageAOneShot);
return; // stageA will run in the main thread
Timber.d("Failed to get quote");
getView().post(() -> showStageError(ShiftError.Error.SERVICE.toString(),
getString(R.string.shift_noquote),
getString(R.string.shift_checkamount)));
return; // just stop for now
}
xmrtoQuote = requestQuote;
txDataBtc.setAmount(xmrtoQuote.getXmrAmount());
getView().post(() -> {
// show data from the actual quote as that is what is used to
NumberFormat df = NumberFormat.getInstance(Locale.US);
df.setMaximumFractionDigits(12);
final String btcAmount = df.format(xmrtoQuote.getBtcAmount());
@@ -438,18 +420,12 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
}
private void stageA() {
stageARetries = RETRIES;
stageAOneShot();
}
private void stageAOneShot() {
if (!isResumed) return;
Timber.d("Request Quote");
xmrtoQuote = null;
xmrtoOrder = null;
showProgress(1, getString(R.string.label_send_progress_xmrto_create));
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
stageAPrice = 0;
ShiftCallback<RequestQuote> callback = new ShiftCallback<RequestQuote>() {
@Override
@@ -473,7 +449,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
}
};
getXmrToApi().requestQuote(txDataBtc.getAmountAsDouble(), callback);
getXmrToApi().requestQuote(txDataBtc.getBtcAmount(), callback);
}
private CreateOrder xmrtoOrder = null;

View File

@@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.Crypto;
import com.m2049r.xmrwallet.data.UserNotes;
@@ -48,7 +49,7 @@ import java.util.TimeZone;
import timber.log.Timber;
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> {
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
private final static SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
private final int outboundColour;
private final int inboundColour;
@@ -77,6 +78,10 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
DATETIME_FORMATTER.setTimeZone(tz);
}
public boolean needsTransactionUpdateOnNewBlock() {
return (infoItems.size() > 0) && !infoItems.get(0).isConfirmed();
}
private static class TransactionInfoDiff extends DiffCallback<TransactionInfo> {
public TransactionInfoDiff(List<TransactionInfo> oldList, List<TransactionInfo> newList) {
@@ -95,6 +100,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
return (oldItem.direction == newItem.direction)
&& (oldItem.isPending == newItem.isPending)
&& (oldItem.isFailed == newItem.isFailed)
&& (oldItem.confirmations == newItem.confirmations)
&& (oldItem.subaddressLabel.equals(newItem.subaddressLabel))
&& (Objects.equals(oldItem.notes, newItem.notes));
}
@@ -149,6 +155,8 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
final TextView tvFee;
final TextView tvPaymentId;
final TextView tvDateTime;
final CircularProgressIndicator pbConfirmations;
final TextView tvConfirmations;
TransactionInfo infoItem;
ViewHolder(View itemView) {
@@ -158,6 +166,9 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
tvFee = itemView.findViewById(R.id.tx_fee);
tvPaymentId = itemView.findViewById(R.id.tx_paymentid);
tvDateTime = itemView.findViewById(R.id.tx_datetime);
pbConfirmations = itemView.findViewById(R.id.pbConfirmations);
pbConfirmations.setMax(TransactionInfo.CONFIRMATION);
tvConfirmations = itemView.findViewById(R.id.tvConfirmations);
}
private String getDateTime(long time) {
@@ -182,7 +193,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
ivTxType.setVisibility(View.GONE);
}
} else {
ivTxType.setVisibility(View.GONE); // gives us more space for the amount
ivTxType.setVisibility(View.GONE);
}
String displayAmount = Helper.getDisplayAmount(infoItem.amount, Helper.DISPLAY_DIGITS_INFO);
@@ -205,12 +216,34 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
this.tvFee.setText(context.getString(R.string.tx_list_failed_text));
tvFee.setVisibility(View.VISIBLE);
setTxColour(failedColour);
pbConfirmations.setVisibility(View.GONE);
tvConfirmations.setVisibility(View.GONE);
} else if (infoItem.isPending) {
setTxColour(pendingColour);
pbConfirmations.setVisibility(View.GONE);
pbConfirmations.setIndeterminate(true);
pbConfirmations.setVisibility(View.VISIBLE);
tvConfirmations.setVisibility(View.GONE);
} else if (infoItem.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(inboundColour);
if (!infoItem.isConfirmed()) {
pbConfirmations.setVisibility(View.VISIBLE);
final int confirmations = (int) infoItem.confirmations;
pbConfirmations.setProgressCompat(confirmations, true);
final String confCount = Integer.toString(confirmations);
tvConfirmations.setText(confCount);
if (confCount.length() == 1) // we only have space for character in the progress circle
tvConfirmations.setVisibility(View.VISIBLE);
else
tvConfirmations.setVisibility(View.GONE);
} else {
pbConfirmations.setVisibility(View.GONE);
tvConfirmations.setVisibility(View.GONE);
}
} else {
setTxColour(outboundColour);
pbConfirmations.setVisibility(View.GONE);
tvConfirmations.setVisibility(View.GONE);
}
String tag = null;

View File

@@ -29,6 +29,8 @@ import lombok.RequiredArgsConstructor;
// this is not the TransactionInfo from the API as that is owned by the TransactionHistory
// this is a POJO for the TransactionInfoAdapter
public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> {
public static final int CONFIRMATION = 10; // blocks
@RequiredArgsConstructor
public enum Direction {
Direction_In(0),
@@ -98,6 +100,10 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
this.transfers = transfers;
}
public boolean isConfirmed() {
return confirmations >= CONFIRMATION;
}
public String getDisplayLabel() {
if (subaddressLabel.isEmpty() || (Subaddress.DEFAULT_LABEL_FORMATTER.matcher(subaddressLabel).matches()))
return ("#" + addressIndex);

View File

@@ -273,12 +273,14 @@ public class Wallet {
public native long getDaemonBlockChainTargetHeight();
public native boolean isSynchronizedJ();
boolean synced = false;
public boolean isSynchronized() {
final long daemonHeight = getDaemonBlockChainHeight();
if (daemonHeight == 0) return false;
return isSynchronizedJ() && (getBlockChainHeight() == daemonHeight);
return synced;
}
public void setSynchronized() {
this.synced = true;
}
public static native String getDisplayAmount(long amount);
@@ -309,7 +311,12 @@ public class Wallet {
public native void refreshAsync();
public native void rescanBlockchainAsync();
public native void rescanBlockchainAsyncJ();
public void rescanBlockchainAsync() {
synced = false;
rescanBlockchainAsyncJ();
}
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
//TODO virtual int autoRefreshInterval() const = 0;

View File

@@ -108,16 +108,16 @@ public class WalletService extends Service {
Timber.d("unconfirmedMoneyReceived() %d @ %s", amount, txId);
}
long lastBlockTime = 0;
int lastTxCount = 0;
private long lastBlockTime = 0;
private int lastTxCount = 0;
public void newBlock(long height) {
Wallet wallet = getWallet();
final Wallet wallet = getWallet();
if (wallet == null) throw new IllegalStateException("No wallet!");
// don't flood with an update for every block ...
if (lastBlockTime < System.currentTimeMillis() - 2000) {
Timber.d("newBlock() @ %d with observer %s", height, observer);
lastBlockTime = System.currentTimeMillis();
Timber.d("newBlock() @ %d with observer %s", height, observer);
if (observer != null) {
boolean fullRefresh = false;
updateDaemonState(wallet, wallet.isSynchronized() ? height : 0);
@@ -145,17 +145,16 @@ public class WalletService extends Service {
updated = true;
}
public void refreshed() {
public void refreshed() { // this means it's synced
Timber.d("refreshed()");
Wallet wallet = getWallet();
final Wallet wallet = getWallet();
if (wallet == null) throw new IllegalStateException("No wallet!");
wallet.setSynchronized();
if (updated) {
updateDaemonState(wallet, wallet.getBlockChainHeight());
wallet.getHistory().refreshWithNotes(wallet);
if (observer != null) {
updateDaemonState(wallet, 0);
wallet.getHistory().refreshWithNotes(wallet);
if (observer != null) {
updated = !observer.onRefreshed(wallet, true);
}
updated = !observer.onRefreshed(wallet, true);
}
}
}

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

@@ -74,10 +74,10 @@ class RequestQuoteImpl implements RequestQuote {
price = jsonObject.getDouble("rate");
}
public static void call(@NonNull final ShiftApiCall api, final double xmrAmount,
public static void call(@NonNull final ShiftApiCall api, final double btcAmount,
@NonNull final ShiftCallback<RequestQuote> callback) {
try {
final JSONObject request = createRequest(xmrAmount);
final JSONObject request = createRequest(btcAmount);
api.call("quotes", request, new NetworkCallback() {
@Override
public void onSuccess(JSONObject jsonObject) {
@@ -104,13 +104,13 @@ class RequestQuoteImpl implements RequestQuote {
* @param xmrAmount how much XMR to shift to BTC
*/
static JSONObject createRequest(final double xmrAmount) throws JSONException {
static JSONObject createRequest(final double btcAmount) throws JSONException {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("depositMethod", "xmr");
jsonObject.put("settleMethod", ServiceHelper.ASSET);
// #sideshift is silly and likes numbers as strings
String amount = AmountFormatter.format(xmrAmount);
jsonObject.put("depositAmount", amount);
String amount = AmountFormatter.format(btcAmount);
jsonObject.put("settleAmount", amount);
return jsonObject;
}

View File

@@ -64,8 +64,8 @@ public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall {
}
@Override
public void requestQuote(final double xmrAmount, @NonNull final ShiftCallback<RequestQuote> callback) {
RequestQuoteImpl.call(this, xmrAmount, callback);
public void requestQuote(final double btcAmount, @NonNull final ShiftCallback<RequestQuote> callback) {
RequestQuoteImpl.call(this, btcAmount, callback);
}
@Override

View File

@@ -80,6 +80,8 @@ public class Helper {
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
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 public final boolean SHOW_EXCHANGERATES = true;
static public final boolean ALLOW_SHIFT = true;
@@ -159,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) {
@@ -195,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);

View File

@@ -31,8 +31,7 @@ public class LegacyStorageHelper {
try {
if (isStorageMigrated(context)) return;
if (!hasReadPermission(context)) {
// nothing to migrate, so don't try again
setStorageMigrated(context);
// can't migrate - don't remember this, as the user may turn on permissions later
return;
}
final File oldRoot = getWalletRoot();
@@ -53,6 +52,7 @@ public class LegacyStorageHelper {
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) {

View File

@@ -30,6 +30,7 @@ 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;
@@ -52,12 +53,13 @@ public class Notice {
R.string.help_xmrto,
1)
);
notices.add(
new Notice(NOTICE_SHOW_LEDGER,
R.string.info_ledger_enabled,
R.string.help_create_ledger,
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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,13 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:paddingEnd="16dp"
android:text="@string/onboarding_agree" />
</FrameLayout>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
@@ -19,13 +20,43 @@
android:layout_weight="9"
android:orientation="horizontal">
<ImageView
android:id="@+id/ivTxType"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center"
android:src="@drawable/ic_xmrto_btc"
android:visibility="visible" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<ImageView
android:id="@+id/ivTxType"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center"
android:src="@drawable/ic_xmrto_btc"
android:visibility="visible" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/pbConfirmations"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="center"
android:indeterminate="false"
android:max="10"
android:progress="8"
android:visibility="visible"
app:indicatorInset="0dp"
app:indicatorSize="30dp"
app:trackThickness="4dp" />
<TextView
android:id="@+id/tvConfirmations"
style="@style/MoneroText.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:paddingBottom="1dp"
android:text="8"
android:visibility="visible" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
@@ -57,8 +88,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="13"
android:gravity="start"
tools:text="0123456789abcdef" />

View File

@@ -22,6 +22,8 @@
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginTop="-8dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:src="@drawable/ic_logo_horizontol_xmrujo"
android:visibility="visible" />

View File

@@ -26,4 +26,8 @@
android:orderInCategory="500"
android:title="@string/menu_info" />
<item
android:id="@+id/action_deletecache"
android:orderInCategory="500"
android:title="@string/menu_deletecache" />
</menu>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:locale="en">
<string name="about_close">أغلق</string>
<string name="about_whoami">أنا مونيرويو</string>
<string name="about_version">النسخة %1$s (%2$d)</string>
<string name="credits_text"><![CDATA[
<b>الاعتمادات</b>
<br/>
m2049r, baltsar777, anhdres, keejef,
rehrar, EarlOfEgo, ErCiccione et al.
<br/><br/>
<a href="https://monerujo.io/">monerujo.io</a>
]]></string>
<string name="privacy_policy"><![CDATA[
<h1>سياسة الخصوصية</h1>
<p>
هذه الصفحة تخبرك بسياستنا بخصوص التجميع، الاستخدام، و الافصاح عن المعلومات الشخصيةالتي نستلمها من مستخدمي تطبيقنا (مونيرويو: محفظة مونيرو)
</p>
<p>باستخدامك هذا التطبيق أنت توافق تجميع و استخدام المعلومات وفقاً لهذه السياسة
</p>
<h2>البيانات المجمعة</h2>
<p> المعلومات الشخصية هي نوع من البيانات التي قد تعرف هوية شخص ما
</p>
<p> مفاتيح مونيرو و العناوين العامة مجمعة و معالجة من قبل التطبيق محليا لغرض معالجة المعاملات و هي ترسل الى شبكة مونيرو في حالة مشفرة
</p>
<p>لا يجمع التطبيق أي معلومات شخصية أخرى</p>
<p> إذا أنت تستخدم خدمة الصراف (اختياري)، مونيرويو يجلب سعر الصرف عبر api coinmarketcap.com. اطلع على سياسة الخصوصية الخاصة بهم على https://coinmarketcap.com/privacy لتفاصيل على كيف تجمع بياناتك التي في الطلب. </p>
<p> عندما تستعمل التطبيق لتدفع إلى عنوانين بيتكوين، فأنت تستعمل خدمة SideShift.ai. اطلع على سياسة الخصوصية الخاصة بهم على https://sideshift.ai للتفاصيل. يرسل مونيرويو عنوان بيتوين و المبلغ إليهم. سيأخذ أيضاً عنوان بروتوكول الإنترنت (ip) الخاص بك.</p>
<h2>صلاحيات التطبيق</h2>
<ul>
<li>الإنترنت: للإتصال بشبكة مونيرو عبر عقدة مونيرو</li>
<li>قرائة ملفات التخزين الخارجي: لقرائة ملفات المحفظة المخزنة على الجهاز</li>
<li>الكتابة إلى التخزين الخارجي: لكتابة ملفات المحفظة المخزنة على الجهاز</li>
<li>قفل الإيقاظ: لإبقاء الجهاز ياقظاً عند التزامن</li>
<li>الكاميرا: لمسح رموز الاستجابة السريعة (qr) لاستلام مونيرو</li>
</ul>
<h2>التغيرات لسياسة الخصوصية هذه</h2>
<p>. يمكن أن نحدث سياسة الخصوصية هذه من حين إلى أخرى. سنقوم بتنبيهك لأاي تغيرات بنشر سياسة الخصوصية الجديدة في التطبيق و على الموقع (www.monerujo.io)
ننصحك أن تراجع هذه السياسة بشكل دوري لأاي تغيرات
<p>آخر تحديث لسياسة الخصوصية هذه كان في 10/11/2017
</p>
<h2>تواصل معنا</h2>
<p>privacy@monerujo.io.إذا كان لديك أي سؤال عن سياستنا أو عن كيفية تجميع بياناتك و معالجتها، الرجاء إرسال بريد الكتروني إلى
</p>
]]></string>
</resources>

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