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

Compare commits

..

25 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
69 changed files with 1529 additions and 516 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 1002
versionName "2.0.2 'Puginarug'"
versionCode 1100
versionName "2.1.0 'Vertant'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -130,7 +131,7 @@ dependencies {
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

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

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

@@ -81,6 +81,7 @@ public class Helper {
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;

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

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>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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