mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-03 08:23:04 +02:00
Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b576a9de3d | ||
![]() |
148faa00e4 | ||
![]() |
e82b471c14 | ||
![]() |
9ed92e5117 | ||
![]() |
303b3aa354 | ||
![]() |
d801a50962 | ||
![]() |
9cd8a75dc6 | ||
![]() |
002dfd5d58 | ||
![]() |
54e54b2a8a | ||
![]() |
aa768596a4 | ||
![]() |
c4958f6c54 | ||
![]() |
2c2a5314d4 | ||
![]() |
669516c60b | ||
![]() |
a56a29a6c4 | ||
![]() |
4e31f47482 | ||
![]() |
c1f14f9653 | ||
![]() |
2746c52d7b | ||
![]() |
5df323bacb | ||
![]() |
776cc26377 | ||
![]() |
bdfb6a90b6 | ||
![]() |
6db44dfab1 | ||
![]() |
c68ac7db6d | ||
![]() |
e09862e940 | ||
![]() |
c7bd7469a1 | ||
![]() |
b39857fd2e | ||
![]() |
8170f823ab | ||
![]() |
38c0ead45c |
@@ -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
|
||||
|
@@ -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'
|
||||
|
@@ -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"
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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()) {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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() {
|
||||
|
@@ -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));
|
||||
|
@@ -139,7 +139,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
Timber.d("onResumeFragment()");
|
||||
Helper.showKeyboard(getActivity());
|
||||
final long funds = getTotalFunds();
|
||||
maxFunds = 1.0 * funds / 1000000000000L;
|
||||
maxFunds = 1.0 * funds / Helper.ONE_XMR;
|
||||
if (!sendListener.getActivityCallback().isStreetMode()) {
|
||||
tvFunds.setText(getString(R.string.send_available,
|
||||
Wallet.getDisplayAmount(funds)));
|
||||
|
@@ -34,6 +34,7 @@ import com.m2049r.xmrwallet.service.shift.ShiftException;
|
||||
import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters;
|
||||
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
|
||||
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.OkHttpHelper;
|
||||
import com.m2049r.xmrwallet.util.ServiceHelper;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
|
||||
@@ -89,7 +90,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onValidateFields() {
|
||||
Timber.i(maxBtc + "/" + minBtc);
|
||||
@@ -179,7 +179,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
tvXmrToParms.setText(xmrParmText);
|
||||
|
||||
final long funds = getTotalFunds();
|
||||
double availableXmr = 1.0 * funds / 1000000000000L;
|
||||
double availableXmr = 1.0 * funds / Helper.ONE_XMR;
|
||||
|
||||
String availBtcString;
|
||||
String availXmrString;
|
||||
|
@@ -359,40 +359,22 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
|
||||
private RequestQuote xmrtoQuote = null;
|
||||
private int stageARetries = 0;
|
||||
private final int RETRIES = 3;
|
||||
private double stageAPrice = 0;
|
||||
|
||||
private void processStageA(final RequestQuote requestQuote) {
|
||||
Timber.d("processCreateOrder %s", requestQuote.getId());
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
// verify the BTC amount is correct (price can change and we can only specify XMR amount)
|
||||
// verify the BTC amount is correct
|
||||
if (requestQuote.getBtcAmount() != txDataBtc.getBtcAmount()) {
|
||||
if (--stageARetries <= 0) {
|
||||
Timber.d("Failed to get quote");
|
||||
getView().post(() ->
|
||||
showStageError(ShiftError.Error.SERVICE.toString(),
|
||||
getString(R.string.shift_noquote),
|
||||
getString(R.string.shift_checkamount)));
|
||||
return; // just stop for now
|
||||
}
|
||||
if (stageAPrice == requestQuote.getPrice()) {
|
||||
// same price but different BTC amount - something else is wrong (e.g. too many decimals)
|
||||
Timber.d("Price unchanged");
|
||||
getView().post(() ->
|
||||
showStageError(ShiftError.Error.SERVICE.toString(),
|
||||
getString(R.string.shift_noquote),
|
||||
getString(R.string.shift_checkamount)));
|
||||
return; // just stop for now
|
||||
}
|
||||
stageAPrice = requestQuote.getPrice();
|
||||
// recalc XMR and try again
|
||||
txDataBtc.setAmount(txDataBtc.getBtcAmount() / requestQuote.getPrice());
|
||||
getView().post(this::stageAOneShot);
|
||||
return; // stageA will run in the main thread
|
||||
Timber.d("Failed to get quote");
|
||||
getView().post(() -> showStageError(ShiftError.Error.SERVICE.toString(),
|
||||
getString(R.string.shift_noquote),
|
||||
getString(R.string.shift_checkamount)));
|
||||
return; // just stop for now
|
||||
}
|
||||
xmrtoQuote = requestQuote;
|
||||
txDataBtc.setAmount(xmrtoQuote.getXmrAmount());
|
||||
getView().post(() -> {
|
||||
// show data from the actual quote as that is what is used to
|
||||
NumberFormat df = NumberFormat.getInstance(Locale.US);
|
||||
df.setMaximumFractionDigits(12);
|
||||
final String btcAmount = df.format(xmrtoQuote.getBtcAmount());
|
||||
@@ -438,18 +420,12 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
|
||||
private void stageA() {
|
||||
stageARetries = RETRIES;
|
||||
stageAOneShot();
|
||||
}
|
||||
|
||||
private void stageAOneShot() {
|
||||
if (!isResumed) return;
|
||||
Timber.d("Request Quote");
|
||||
xmrtoQuote = null;
|
||||
xmrtoOrder = null;
|
||||
showProgress(1, getString(R.string.label_send_progress_xmrto_create));
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
stageAPrice = 0;
|
||||
|
||||
ShiftCallback<RequestQuote> callback = new ShiftCallback<RequestQuote>() {
|
||||
@Override
|
||||
@@ -473,7 +449,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
};
|
||||
|
||||
getXmrToApi().requestQuote(txDataBtc.getAmountAsDouble(), callback);
|
||||
getXmrToApi().requestQuote(txDataBtc.getBtcAmount(), callback);
|
||||
}
|
||||
|
||||
private CreateOrder xmrtoOrder = null;
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -167,7 +167,7 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
private Calendar fetchDate = null;
|
||||
|
||||
synchronized private ExchangeRate getRate(String currency) throws ExchangeException {
|
||||
Timber.e("Getting %s", currency);
|
||||
Timber.d("Getting %s", currency);
|
||||
final Double rate = fxEntries.get(currency);
|
||||
if (rate == null) throw new ExchangeException(404, "Currency not supported: " + currency);
|
||||
return new ExchangeRateImpl(currency, rate, fxDate.getTime());
|
||||
|
@@ -74,10 +74,10 @@ class RequestQuoteImpl implements RequestQuote {
|
||||
price = jsonObject.getDouble("rate");
|
||||
}
|
||||
|
||||
public static void call(@NonNull final ShiftApiCall api, final double xmrAmount,
|
||||
public static void call(@NonNull final ShiftApiCall api, final double btcAmount,
|
||||
@NonNull final ShiftCallback<RequestQuote> callback) {
|
||||
try {
|
||||
final JSONObject request = createRequest(xmrAmount);
|
||||
final JSONObject request = createRequest(btcAmount);
|
||||
api.call("quotes", request, new NetworkCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject jsonObject) {
|
||||
@@ -104,13 +104,13 @@ class RequestQuoteImpl implements RequestQuote {
|
||||
* @param xmrAmount how much XMR to shift to BTC
|
||||
*/
|
||||
|
||||
static JSONObject createRequest(final double xmrAmount) throws JSONException {
|
||||
static JSONObject createRequest(final double btcAmount) throws JSONException {
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("depositMethod", "xmr");
|
||||
jsonObject.put("settleMethod", ServiceHelper.ASSET);
|
||||
// #sideshift is silly and likes numbers as strings
|
||||
String amount = AmountFormatter.format(xmrAmount);
|
||||
jsonObject.put("depositAmount", amount);
|
||||
String amount = AmountFormatter.format(btcAmount);
|
||||
jsonObject.put("settleAmount", amount);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
@@ -64,8 +64,8 @@ public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestQuote(final double xmrAmount, @NonNull final ShiftCallback<RequestQuote> callback) {
|
||||
RequestQuoteImpl.call(this, xmrAmount, callback);
|
||||
public void requestQuote(final double btcAmount, @NonNull final ShiftCallback<RequestQuote> callback) {
|
||||
RequestQuoteImpl.call(this, btcAmount, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -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);
|
||||
|
@@ -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) {
|
||||
|
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
13
app/src/main/res/layout/checkbox_confirm.xml
Normal file
13
app/src/main/res/layout/checkbox_confirm.xml
Normal 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>
|
@@ -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" />
|
||||
|
@@ -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" />
|
||||
|
||||
|
@@ -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>
|
48
app/src/main/res/values-ar/about.xml
Normal file
48
app/src/main/res/values-ar/about.xml
Normal 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
Reference in New Issue
Block a user