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

Compare commits

..

3 Commits

Author SHA1 Message Date
m2049r
a6e9d0e77c use yadio for pricing "exotic" fiat (#921)
* yadio api

* add more currencies

* fix es typo

* bump version & fix cicleci build
2023-12-10 14:24:35 +01:00
m2049r
17df7c3faf Pocket Change V2 (#914)
* show PC on btc confirm
* random slots to refill
2023-08-28 19:24:45 +02:00
m2049r
bf1829f775 PocketChange (#901) 2023-06-05 05:03:51 +02:00
79 changed files with 1765 additions and 417 deletions

View File

@@ -3,7 +3,7 @@ jobs:
build:
working_directory: ~/code
docker:
- image: cimg/android:2022.03-ndk
- image: cimg/android:2023.12-ndk
environment:
JVM_OPTS: -Xmx3200m
steps:

View File

@@ -1,15 +1,15 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 33
buildToolsVersion '33.0.2'
ndkVersion '17.2.4988734'
defaultConfig {
applicationId "com.m2049r.xmrwallet"
buildToolsVersion = '34.0.0'
compileSdk 33
minSdkVersion 21
targetSdkVersion 31
versionCode 3200
versionName "3.2.0 'Decoy Selection'"
targetSdkVersion 33
versionCode 3310
versionName "3.3.10 'Argentina'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -126,11 +126,11 @@ dependencies {
implementation 'androidx.core:core:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'com.google.android.material:material:1.9.0'

View File

@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />

File diff suppressed because it is too large Load Diff

View File

@@ -658,11 +658,11 @@ public class LoginActivity extends BaseActivity
break;
case NetworkType_Testnet:
toolbar.setSubtitle(getString(R.string.connect_testnet));
toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, R.attr.colorPrimaryDark));
toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, androidx.appcompat.R.attr.colorPrimaryDark));
break;
case NetworkType_Stagenet:
toolbar.setSubtitle(getString(R.string.connect_stagenet));
toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, R.attr.colorPrimaryDark));
toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, androidx.appcompat.R.attr.colorPrimaryDark));
break;
default:
throw new IllegalStateException("NetworkType unknown: " + net);

View File

@@ -79,6 +79,7 @@ public class TxFragment extends Fragment {
private TextView tvTxPaymentId;
private TextView tvTxBlockheight;
private TextView tvTxAmount;
private TextView tvTxPocketChangeAmount;
private TextView tvTxFee;
private TextView tvTxTransfers;
private TextView etTxNotes;
@@ -116,6 +117,7 @@ public class TxFragment extends Fragment {
tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
tvTxBlockheight = view.findViewById(R.id.tvTxBlockheight);
tvTxAmount = view.findViewById(R.id.tvTxAmount);
tvTxPocketChangeAmount = view.findViewById(R.id.tvTxPocketChangeAmount);
tvTxFee = view.findViewById(R.id.tvTxFee);
tvTxTransfers = view.findViewById(R.id.tvTxTransfers);
etTxNotes = view.findViewById(R.id.etTxNotes);
@@ -249,8 +251,10 @@ public class TxFragment extends Fragment {
}
String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-");
long realAmount = info.amount;
tvTxAmount.setText(sign + Wallet.getDisplayAmount(realAmount));
tvTxAmount.setText(sign + Wallet.getDisplayAmount(info.amount));
final long pcAmount = info.getPocketChangeAmount();
tvTxPocketChangeAmount.setVisibility(pcAmount > 0 ? View.VISIBLE : View.GONE);
tvTxPocketChangeAmount.setText(getString(R.string.pocketchange_tx_detail, Wallet.getDisplayAmount(pcAmount)));
if ((info.fee > 0)) {
String fee = Wallet.getDisplayAmount(info.fee);

View File

@@ -52,8 +52,8 @@ import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.Subaddress;
import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.dialog.PocketChangeFragment;
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
import com.m2049r.xmrwallet.fragment.send.SendFragment;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
@@ -82,7 +82,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
WalletFragment.DrawerLocker,
NavigationView.OnNavigationItemSelectedListener,
SubaddressFragment.Listener,
SubaddressInfoFragment.Listener {
SubaddressInfoFragment.Listener,
PocketChangeFragment.Listener {
public static final String REQUEST_ID = "id";
public static final String REQUEST_PW = "pw";
@@ -285,8 +286,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
onWalletRescan();
} else if (itemId == R.id.action_info) {
onWalletDetails();
} else if (itemId == R.id.action_credits) {
CreditsFragment.display(getSupportFragmentManager());
} else if (itemId == R.id.action_share) {
onShareTxInfo();
} else if (itemId == R.id.action_help_tx_info) {
@@ -301,6 +300,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
HelpFragment.display(getSupportFragmentManager(), R.string.help_send);
} else if (itemId == R.id.action_rename) {
onAccountRename();
} else if (itemId == R.id.action_pocketchange) {
PocketChangeFragment.display(getSupportFragmentManager(), getWallet().getPocketChangeSetting());
} else if (itemId == R.id.action_subaddresses) {
showSubaddresses(true);
} else if (itemId == R.id.action_streetmode) {
@@ -422,7 +423,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
break;
case NetworkType_Stagenet:
case NetworkType_Testnet:
toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, R.attr.colorPrimaryDark));
toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, androidx.appcompat.R.attr.colorPrimaryDark));
break;
default:
throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType());
@@ -646,6 +647,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
haveWallet = true;
invalidateOptionsMenu();
loadPocketChangeSettings();
if (requestStreetMode) onEnableStreetMode();
final WalletFragment walletFragment = getWalletFragment();
@@ -1104,6 +1107,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
public void setAccountIndex(int accountIndex) {
getWallet().setAccountIndex(accountIndex);
loadPocketChangeSettings();
selectedSubaddressIndex = 0;
}
@@ -1214,4 +1218,19 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
b.putInt("subaddressIndex", subaddressIndex);
replaceFragmentWithTransition(view, new SubaddressInfoFragment(), null, b);
}
@Override
public void setPocketChange(Wallet.PocketChangeSetting setting) {
SharedPreferences.Editor editor = getPrefs().edit();
editor.putString(getWallet().getAddress() + "_PC", setting.toPrefString());
editor.apply();
getWallet().setPocketChangeSetting(setting);
}
public void loadPocketChangeSettings() {
final String settings = getPrefs().getString(getWallet().getAddress() + "_PC", "0");
getWallet().setPocketChangeSetting(Wallet.PocketChangeSetting.from(settings));
}
}

View File

@@ -112,7 +112,7 @@ public class WalletFragment extends Fragment
flExchange = view.findViewById(R.id.flExchange);
((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable().
setColorFilter(
ThemeHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant),
ThemeHelper.getThemedColor(getContext(), com.google.android.material.R.attr.colorPrimaryVariant),
android.graphics.PorterDuff.Mode.MULTIPLY);
tvProgress = view.findViewById(R.id.tvProgress);

View File

@@ -22,18 +22,24 @@ import android.content.res.Configuration;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStateManagerControl;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper;
import com.m2049r.xmrwallet.util.ServiceHelper;
import java.util.Arrays;
import timber.log.Timber;
public class XmrWalletApplication extends Application {
@Override
@OptIn(markerClass = FragmentStateManagerControl.class)
public void onCreate() {
super.onCreate();
FragmentManager.enableNewStateManager(false);

View File

@@ -270,7 +270,7 @@ public class NodeInfo extends Node {
(hostAddress.isOnion() ? "&nbsp;.onion&nbsp;&nbsp;" : ""), " " + info));
view.setText(text);
if (isError)
view.setTextColor(ThemeHelper.getThemedColor(ctx, R.attr.colorError));
view.setTextColor(ThemeHelper.getThemedColor(ctx, androidx.appcompat.R.attr.colorError));
else
view.setTextColor(ThemeHelper.getThemedColor(ctx, android.R.attr.textColorSecondary));
}

File diff suppressed because it is too large Load Diff

View File

@@ -41,10 +41,6 @@ public class TxDataBtc extends TxData {
super();
}
public TxDataBtc(TxDataBtc txDataBtc) {
super(txDataBtc);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);

View File

@@ -0,0 +1,133 @@
/*
* Copyright (c) 2023 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.dialog;
import android.app.Dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.slider.Slider;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
public class PocketChangeFragment extends DialogFragment implements Slider.OnChangeListener {
static final String TAG = "PocketChangeFragment";
static final String ENABLED = "enabled";
static final String TICK = "tick";
public static PocketChangeFragment newInstance(boolean enabled, int tick) {
PocketChangeFragment fragment = new PocketChangeFragment();
Bundle bundle = new Bundle();
bundle.putInt(ENABLED, enabled ? 1 : 0);
bundle.putInt(TICK, tick);
fragment.setArguments(bundle);
return fragment;
}
public static void display(FragmentManager fm, @NonNull Wallet.PocketChangeSetting setting) {
FragmentTransaction ft = fm.beginTransaction();
Fragment prev = fm.findFragmentByTag(TAG);
if (prev != null) {
ft.remove(prev);
}
PocketChangeFragment.newInstance(setting.isEnabled(), getTick(setting.getAmount())).show(ft, TAG);
}
SwitchMaterial switchPocketChange;
Slider slider;
TextView tvProgressLabel;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_pocketchange_setting, null);
boolean enabled = false;
int progress = 0;
Bundle arguments = getArguments();
if (arguments != null) {
enabled = arguments.getInt(ENABLED) > 0;
progress = arguments.getInt(TICK);
}
final View llAmount = view.findViewById(R.id.llAmount);
switchPocketChange = view.findViewById(R.id.switchPocketChange);
switchPocketChange.setOnCheckedChangeListener((buttonView, isChecked) -> llAmount.setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE));
slider = view.findViewById(R.id.seekbar);
slider.addOnChangeListener(this);
switchPocketChange.setChecked(enabled);
tvProgressLabel = view.findViewById(R.id.seekbar_value);
slider.setValue(progress);
llAmount.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
onValueChange(slider, slider.getValue(), false);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity())
.setView(view)
.setPositiveButton(R.string.label_apply,
(dialog, whichButton) -> {
final FragmentActivity activity = getActivity();
if (activity instanceof Listener) {
((Listener) activity).setPocketChange(Wallet.PocketChangeSetting.of(switchPocketChange.isChecked(), getAmount()));
}
}
);
return builder.create();
}
private long getAmount() {
return Wallet.getAmountFromDouble(getAmount((int) slider.getValue()));
}
private static final double[] AMOUNTS = {0.1, 0.2, 0.3, 0.5, 0.8, 1.3};
private static double getAmount(int i) {
return AMOUNTS[i];
}
// find the closest amount we have
private static int getTick(long amount) {
int enabled = amount > 0 ? 1 : -1;
amount = Math.abs(amount);
double lastDiff = Double.MAX_VALUE;
for (int i = 0; i < AMOUNTS.length; i++) {
final double diff = Math.abs(Helper.ONE_XMR * AMOUNTS[i] - amount);
if (lastDiff < diff) return i - 1;
lastDiff = diff;
}
return enabled * (AMOUNTS.length - 1);
}
@Override
public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) {
tvProgressLabel.setText(getString(R.string.pocketchange_amount, getAmount((int) value)));
}
public interface Listener {
void setPocketChange(Wallet.PocketChangeSetting setting);
}
}

View File

@@ -411,10 +411,10 @@ public class SendAddressWizardFragment extends SendWizardFragment {
if (txData instanceof TxDataBtc) {
((TxDataBtc) txData).setBtcAddress(etAddress.getEditText().getText().toString());
((TxDataBtc) txData).setBtcSymbol(selectedCrypto.getSymbol());
txData.setDestinationAddress(null);
txData.setDestination(null);
ServiceHelper.ASSET = selectedCrypto.getSymbol().toLowerCase();
} else {
txData.setDestinationAddress(etAddress.getEditText().getText().toString());
txData.setDestination(etAddress.getEditText().getText().toString());
ServiceHelper.ASSET = null;
}
txData.setUserNotes(new UserNotes(etNotes.getEditText().getText().toString()));

View File

@@ -23,6 +23,8 @@ import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.TxData;

View File

@@ -74,6 +74,9 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
private View llConfirmSend;
private Button bSend;
private View pbProgressSend;
private TextView tvTxChange;
private View llPocketChange;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -92,6 +95,8 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
tvTxFee = view.findViewById(R.id.tvTxFee);
tvTxTotal = view.findViewById(R.id.tvTxTotal);
tvTxChange = view.findViewById(R.id.tvTxChange);
llPocketChange = view.findViewById(R.id.llPocketChange);
llStageA = view.findViewById(R.id.llStageA);
evStageA = view.findViewById(R.id.evStageA);
@@ -217,6 +222,13 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee()));
tvTxTotal.setText(Wallet.getDisplayAmount(
pendingTransaction.getFee() + pendingTransaction.getAmount()));
final long change = pendingTransaction.getPocketChange();
if (change > 0) {
llPocketChange.setVisibility(View.VISIBLE);
tvTxChange.setText(Wallet.getDisplayAmount(change));
} else {
llPocketChange.setVisibility(View.GONE);
}
updateSendButton();
});
} else {
@@ -348,7 +360,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
}
showProgress(3, getString(R.string.label_send_progress_create_tx));
final TxData txData = sendListener.getTxData();
txData.setDestinationAddress(xmrtoOrder.getXmrAddress());
txData.setDestination(xmrtoOrder.getXmrAddress());
txData.setAmount(xmrtoOrder.getXmrAmount());
getActivityCallback().onPrepareSend(xmrtoOrder.getOrderId(), txData);
}

View File

@@ -140,7 +140,7 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
isResumed = true;
btcData = (TxDataBtc) sendListener.getTxData();
tvTxAddress.setText(btcData.getDestinationAddress());
tvTxAddress.setText(btcData.getDestination());
final PendingTx committedTx = sendListener.getCommittedTx();
if (committedTx != null) {

View File

@@ -64,12 +64,14 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
private TextView tvTxAddress;
private TextView tvTxNotes;
private TextView tvTxAmount;
private TextView tvTxChange;
private TextView tvTxFee;
private TextView tvTxTotal;
private View llProgress;
private View bSend;
private View llConfirmSend;
private View pbProgressSend;
private View llPocketChange;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -83,12 +85,14 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
tvTxAddress = view.findViewById(R.id.tvTxAddress);
tvTxNotes = view.findViewById(R.id.tvTxNotes);
tvTxAmount = view.findViewById(R.id.tvTxAmount);
tvTxChange = view.findViewById(R.id.tvTxChange);
tvTxFee = view.findViewById(R.id.tvTxFee);
tvTxTotal = view.findViewById(R.id.tvTxTotal);
llProgress = view.findViewById(R.id.llProgress);
pbProgressSend = view.findViewById(R.id.pbProgressSend);
llConfirmSend = view.findViewById(R.id.llConfirmSend);
llPocketChange = view.findViewById(R.id.llPocketChange);
bSend = view.findViewById(R.id.bSend);
bSend.setEnabled(false);
@@ -181,7 +185,7 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
isResumed = true;
final TxData txData = sendListener.getTxData();
tvTxAddress.setText(txData.getDestinationAddress());
tvTxAddress.setText(txData.getDestination());
UserNotes notes = sendListener.getTxData().getUserNotes();
if ((notes != null) && (!notes.note.isEmpty())) {
tvTxNotes.setText(notes.note);
@@ -206,7 +210,14 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
tvTxAmount.setText(getString(R.string.street_sweep_amount));
tvTxTotal.setText(getString(R.string.street_sweep_amount));
} else {
tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount()));
tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getNetAmount()));
final long change = pendingTransaction.getPocketChange();
if (change > 0) {
llPocketChange.setVisibility(View.VISIBLE);
tvTxChange.setText(Wallet.getDisplayAmount(change));
} else {
llPocketChange.setVisibility(View.GONE);
}
tvTxTotal.setText(Wallet.getDisplayAmount(
pendingTransaction.getFee() + pendingTransaction.getAmount()));
}

View File

@@ -108,7 +108,7 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
Helper.hideKeyboard(getActivity());
final TxData txData = sendListener.getTxData();
tvTxAddress.setText(txData.getDestinationAddress());
tvTxAddress.setText(txData.getDestination());
final PendingTx committedTx = sendListener.getCommittedTx();
if (committedTx != null) {

View File

@@ -189,7 +189,6 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
}
private void showLock() {
Timber.d("UNLOCK %d:%d", infoItem.unlockTime, infoItem.blockheight);
if (infoItem.unlockTime == 0) {
ivLock.setVisibility(View.GONE);
return;
@@ -220,7 +219,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
ivTxType.setVisibility(View.GONE);
}
String displayAmount = Helper.getDisplayAmount(infoItem.amount, Helper.DISPLAY_DIGITS_INFO);
String displayAmount = Helper.getDisplayAmount(infoItem.getNetAmount(), Helper.DISPLAY_DIGITS_INFO);
if (infoItem.direction == TransactionInfo.Direction.Direction_Out) {
tvAmount.setText(context.getString(R.string.tx_list_amount_negative, displayAmount));
} else {

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2023 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.model;
import java.util.List;
import timber.log.Timber;
public class Coins {
static {
System.loadLibrary("monerujo");
}
private long handle;
public Coins(long handle) {
this.handle = handle;
}
public List<CoinsInfo> getAll(int accountIndex, boolean unspentOnly) {
return refresh(accountIndex, unspentOnly);
}
private native List<CoinsInfo> refresh(int accountIndex, boolean unspentOnly);
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2023 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.model;
import lombok.Value;
// this is not the CoinsInfo from the API as that is owned by the Coins object
// this is a POJO
@Value
public class CoinsInfo {
int accountIndex;
int addressIndex;
long amount;
long blockheight;
String txHash;
boolean spent;
boolean frozen;
long unlockTime;
boolean unlocked;
public boolean isSpendable() {
return !spent && unlocked;
}
}

View File

@@ -16,6 +16,9 @@
package com.m2049r.xmrwallet.model;
import lombok.Getter;
import lombok.Setter;
public class PendingTransaction {
static {
System.loadLibrary("monerujo");
@@ -63,8 +66,6 @@ public class PendingTransaction {
Priority(int value) {
this.value = value;
}
}
public Status getStatus() {
@@ -95,4 +96,11 @@ public class PendingTransaction {
public native long getTxCount();
@Getter
@Setter
private long pocketChange;
public long getNetAmount() {
return getAmount() - pocketChange;
}
}

View File

@@ -17,8 +17,10 @@
package com.m2049r.xmrwallet.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import timber.log.Timber;
@@ -61,22 +63,14 @@ public class TransactionHistory {
private List<TransactionInfo> transactions = new ArrayList<>();
void refreshWithNotes(Wallet wallet) {
public void refreshWithNotes(Wallet wallet) {
refresh();
loadNotes(wallet);
}
private void refresh() {
List<TransactionInfo> transactionInfos = refreshJ();
Timber.d("refresh size=%d", transactionInfos.size());
for (Iterator<TransactionInfo> iterator = transactionInfos.iterator(); iterator.hasNext(); ) {
TransactionInfo info = iterator.next();
if (info.accountIndex != accountIndex) {
iterator.remove();
}
}
transactions = transactionInfos;
transactions = refreshJ(accountIndex);
}
private native List<TransactionInfo> refreshJ();
private native List<TransactionInfo> refreshJ(int accountIndex);
}

View File

@@ -101,6 +101,23 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
this.unlockTime = unlockTime;
this.subaddressLabel = subaddressLabel;
this.transfers = transfers;
calcNetAmount();
}
@Getter
private long netAmount;
public long getPocketChangeAmount() {
return amount - netAmount;
}
private void calcNetAmount() {
netAmount = amount;
if ((direction == TransactionInfo.Direction.Direction_Out) && (transfers != null)) {
for (int i = 1; i < transfers.size(); i++) {
netAmount -= transfers.get(i).amount;
}
}
}
public boolean isConfirmed() {
@@ -138,6 +155,7 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
out.writeString(txKey);
out.writeString(notes);
out.writeString(address);
out.writeLong(netAmount);
}
public static final Parcelable.Creator<TransactionInfo> CREATOR = new Parcelable.Creator<TransactionInfo>() {
@@ -169,6 +187,7 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
txKey = in.readString();
notes = in.readString();
address = in.readString();
netAmount = in.readLong();
}
@Override

View File

@@ -25,10 +25,13 @@ import com.m2049r.xmrwallet.data.TxData;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.Value;
import timber.log.Timber;
public class Wallet {
@@ -67,9 +70,7 @@ public class Wallet {
}
public boolean isOk() {
return (getStatus() == StatusEnum.Status_Ok)
&& ((getConnectionStatus() == null) ||
(getConnectionStatus() == ConnectionStatus.ConnectionStatus_Connected));
return (getStatus() == StatusEnum.Status_Ok) && ((getConnectionStatus() == null) || (getConnectionStatus() == ConnectionStatus.ConnectionStatus_Connected));
}
@Override
@@ -110,23 +111,17 @@ public class Wallet {
@RequiredArgsConstructor
@Getter
public enum Device {
Device_Undefined(0, 0),
Device_Software(50, 200),
Device_Ledger(5, 20);
Device_Undefined(0, 0), Device_Software(50, 200), Device_Ledger(5, 20);
private final int accountLookahead;
private final int subaddressLookahead;
}
public enum StatusEnum {
Status_Ok,
Status_Error,
Status_Critical
Status_Ok, Status_Error, Status_Critical
}
public enum ConnectionStatus {
ConnectionStatus_Disconnected,
ConnectionStatus_Connected,
ConnectionStatus_WrongVersion
ConnectionStatus_Disconnected, ConnectionStatus_Connected, ConnectionStatus_WrongVersion
}
public native String getSeed(String offset);
@@ -168,16 +163,14 @@ public class Wallet {
private native String getAddressJ(int accountIndex, int addressIndex);
public Subaddress getSubaddressObject(int accountIndex, int subAddressIndex) {
return new Subaddress(accountIndex, subAddressIndex,
getSubaddress(subAddressIndex), getSubaddressLabel(subAddressIndex));
return new Subaddress(accountIndex, subAddressIndex, getSubaddress(subAddressIndex), getSubaddressLabel(subAddressIndex));
}
public Subaddress getSubaddressObject(int subAddressIndex) {
Subaddress subaddress = getSubaddressObject(accountIndex, subAddressIndex);
long amount = 0;
for (TransactionInfo info : getHistory().getAll()) {
if ((info.addressIndex == subAddressIndex)
&& (info.direction == TransactionInfo.Direction.Direction_In)) {
if ((info.addressIndex == subAddressIndex) && (info.direction == TransactionInfo.Direction.Direction_In)) {
amount += info.amount;
}
}
@@ -217,13 +210,10 @@ public class Wallet {
// virtual std::string keysFilename() const = 0;
public boolean init(long upper_transaction_size_limit) {
return initJ(WalletManager.getInstance().getDaemonAddress(), upper_transaction_size_limit,
WalletManager.getInstance().getDaemonUsername(),
WalletManager.getInstance().getDaemonPassword());
return initJ(WalletManager.getInstance().getDaemonAddress(), upper_transaction_size_limit, WalletManager.getInstance().getDaemonUsername(), WalletManager.getInstance().getDaemonPassword());
}
private native boolean initJ(String daemon_address, long upper_transaction_size_limit,
String daemon_username, String daemon_password);
private native boolean initJ(String daemon_address, long upper_transaction_size_limit, String daemon_username, String daemon_password);
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
@@ -335,36 +325,23 @@ public class Wallet {
}
}
public PendingTransaction createTransaction(TxData txData) {
return createTransaction(
txData.getDestinationAddress(),
txData.getAmount(),
txData.getMixin(),
txData.getPriority());
}
private native long createTransactionMultDest(String[] destinations, String payment_id, long[] amounts, int mixin_count, int priority, int accountIndex, int[] subaddresses);
public PendingTransaction createTransaction(String dst_addr,
long amount, int mixin_count,
PendingTransaction.Priority priority) {
public PendingTransaction createTransaction(TxData txData) {
disposePendingTransaction();
int _priority = priority.getValue();
long txHandle =
(amount == SWEEP_ALL ?
createSweepTransaction(dst_addr, "", mixin_count, _priority,
accountIndex) :
createTransactionJ(dst_addr, "", amount, mixin_count, _priority,
accountIndex));
int _priority = txData.getPriority().getValue();
final boolean sweepAll = txData.getAmount() == SWEEP_ALL;
Timber.d("TxData: %s", txData);
long txHandle = (sweepAll ? createSweepTransaction(txData.getDestination(), "", txData.getMixin(), _priority, accountIndex) :
createTransactionMultDest(txData.getDestinations(), "", txData.getAmounts(), txData.getMixin(), _priority, accountIndex, txData.getSubaddresses()));
pendingTransaction = new PendingTransaction(txHandle);
pendingTransaction.setPocketChange(txData.getPocketChangeAmount());
return pendingTransaction;
}
private native long createTransactionJ(String dst_addr, String payment_id,
long amount, int mixin_count,
int priority, int accountIndex);
private native long createTransactionJ(String dst_addr, String payment_id, long amount, int mixin_count, int priority, int accountIndex);
private native long createSweepTransaction(String dst_addr, String payment_id,
int mixin_count,
int priority, int accountIndex);
private native long createSweepTransaction(String dst_addr, String payment_id, int mixin_count, int priority, int accountIndex);
public PendingTransaction createSweepUnmixableTransaction() {
@@ -381,7 +358,13 @@ public class Wallet {
public native void disposeTransaction(PendingTransaction pendingTransaction);
//virtual bool exportKeyImages(const std::string &filename) = 0;
public long estimateTransactionFee(TxData txData) {
return estimateTransactionFee(txData.getDestinations(), txData.getAmounts(), txData.getPriority().getValue());
}
private native long estimateTransactionFee(String[] destinations, long[] amounts, int priority);
//virtual bool exportKeyImages(const std::string &filename) = 0;
//virtual bool importKeyImages(const std::string &filename) = 0;
@@ -403,6 +386,22 @@ public class Wallet {
}
//virtual AddressBook * addressBook() const = 0;
public List<CoinsInfo> getCoinsInfos(boolean unspentOnly) {
return getCoins().getAll(accountIndex, unspentOnly);
}
private Coins coins = null;
private Coins getCoins() {
if (coins == null) {
coins = new Coins(getCoinsJ());
}
return coins;
}
private native long getCoinsJ();
//virtual void setListener(WalletListener *) = 0;
private native long setListenerJ(WalletListener listener);
@@ -444,8 +443,7 @@ public class Wallet {
if (label.equals(NEW_ACCOUNT_NAME)) {
String address = getAddress(accountIndex);
int len = address.length();
label = address.substring(0, 6) +
"\u2026" + address.substring(len - 6, len);
label = address.substring(0, 6) + "\u2026" + address.substring(len - 6, len);
}
return label;
}
@@ -504,4 +502,22 @@ public class Wallet {
private native int getDeviceTypeJ();
@Getter
@Setter
PocketChangeSetting pocketChangeSetting = PocketChangeSetting.of(false, 0);
@Value(staticConstructor = "of")
static public class PocketChangeSetting {
boolean enabled;
long amount;
public String toPrefString() {
return Long.toString((enabled ? 1 : -1) * amount);
}
static public PocketChangeSetting from(String prefString) {
long value = Long.parseLong(prefString);
return of(value > 0, Math.abs(value));
}
}
}

View File

@@ -318,9 +318,10 @@ public class WalletService extends Service {
TxData txData = extras.getParcelable(REQUEST_CMD_TX_DATA);
String txTag = extras.getString(REQUEST_CMD_TX_TAG);
assert txData != null;
txData.createPocketChange(myWallet);
PendingTransaction pendingTransaction = myWallet.createTransaction(txData);
PendingTransaction.Status status = pendingTransaction.getStatus();
Timber.d("transaction status %s", status);
if (status != PendingTransaction.Status.Status_Ok) {
Timber.w("Create Transaction failed: %s", pendingTransaction.getErrorString());
}

View File

@@ -33,5 +33,6 @@ public interface ExchangeApi {
void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback);
String getName();
}

View File

@@ -67,9 +67,9 @@ public class ExchangeApiImpl implements ExchangeApi {
// data is daily and is refreshed around 16:00 CET every working day
}
public static boolean isSameDay(Calendar calendar, Calendar anotherCalendar) {
return (calendar.get(Calendar.YEAR) == anotherCalendar.get(Calendar.YEAR)) &&
(calendar.get(Calendar.DAY_OF_YEAR) == anotherCalendar.get(Calendar.DAY_OF_YEAR));
@Override
public String getName() {
return "ecb";
}
@Override
@@ -121,12 +121,12 @@ public class ExchangeApiImpl implements ExchangeApi {
final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(baseUrl);
httpRequest.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(final Call call, final IOException ex) {
public void onFailure(@NonNull final Call call, @NonNull final IOException ex) {
callback.onError(ex);
}
@Override
public void onResponse(final Call call, final Response response) throws IOException {
public void onResponse(@NonNull final Call call, @NonNull final Response response) throws IOException {
if (response.isSuccessful()) {
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
@@ -178,11 +178,12 @@ public class ExchangeApiImpl implements ExchangeApi {
Element cube = (Element) node;
if (cube.hasAttribute("time")) { // a time Cube
final Date time = DATE_FORMAT.parse(cube.getAttribute("time"));
assert time != null;
date.setTime(time);
} else if (cube.hasAttribute("currency")
&& cube.hasAttribute("rate")) { // a rate Cube
String currency = cube.getAttribute("currency");
double rate = Double.valueOf(cube.getAttribute("rate"));
double rate = Double.parseDouble(cube.getAttribute("rate"));
entries.put(currency, rate);
} // else an empty Cube - ignore
}
@@ -191,13 +192,10 @@ public class ExchangeApiImpl implements ExchangeApi {
Timber.d(ex);
}
synchronized (this) {
if (date != null) {
fetchDate = Calendar.getInstance(TimeZone.getTimeZone("CET"));
fxDate = date;
fxEntries.clear();
fxEntries.putAll(entries);
}
// else don't change what we have
fetchDate = Calendar.getInstance(TimeZone.getTimeZone("CET"));
fxDate = date;
fxEntries.clear();
fxEntries.putAll(entries);
}
}
}

View File

@@ -51,6 +51,11 @@ public class ExchangeApiImpl implements ExchangeApi {
this(HttpUrl.parse("https://api.kraken.com/0/public/Ticker"));
}
@Override
public String getName() {
return "kraken";
}
@Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 m2049r@monerujo.io
* Copyright (c) 2019-2023 m2049r@monerujo.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
// https://developer.android.com/training/basics/network-ops/xml
package com.m2049r.xmrwallet.service.exchange.krakenEcb;
package com.m2049r.xmrwallet.service.exchange.krakenFiat;
import androidx.annotation.NonNull;
@@ -24,23 +24,39 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ServiceHelper;
import timber.log.Timber;
/*
Gets the XMR/EUR rate from kraken and then gets the EUR/fiat rate from the ECB
Gets the XMR/EUR rate from kraken and then gets the EUR/fiat rate from the yadio
*/
public class ExchangeApiImpl implements ExchangeApi {
static public final String BASE_FIAT = "EUR";
final ExchangeApi krakenApi = new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl();
private ExchangeApi getFiatApi(String symbol) {
return ServiceHelper.getFiatApi(symbol);
}
@Override
public String getName() {
return krakenApi.getName() + "+";
}
public String getRealName(String fiatService) {
return getName() + fiatService;
}
@Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) {
Timber.d("B=%s Q=%s", baseCurrency, quoteCurrency);
if (baseCurrency.equals(quoteCurrency)) {
Timber.d("BASE=QUOTE=1");
callback.onSuccess(new ExchangeRateImpl(baseCurrency, quoteCurrency, 1.0));
callback.onSuccess(new ExchangeRateImpl(getName(), baseCurrency, quoteCurrency, 1.0));
return;
}
@@ -52,24 +68,21 @@ public class ExchangeApiImpl implements ExchangeApi {
final String quote = Helper.BASE_CRYPTO.equals(baseCurrency) ? quoteCurrency : baseCurrency;
final ExchangeApi krakenApi =
new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl();
krakenApi.queryExchangeRate(Helper.BASE_CRYPTO, BASE_FIAT, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate krakenRate) {
Timber.d("kraken = %f", krakenRate.getRate());
final ExchangeApi ecbApi =
new com.m2049r.xmrwallet.service.exchange.ecb.ExchangeApiImpl();
ecbApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() {
final ExchangeApi fiatApi = getFiatApi(quote);
fiatApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate ecbRate) {
Timber.d("ECB = %f", ecbRate.getRate());
double rate = ecbRate.getRate() * krakenRate.getRate();
public void onSuccess(final ExchangeRate fiatRate) {
Timber.d("FIAT = %f", fiatRate.getRate());
double rate = fiatRate.getRate() * krakenRate.getRate();
Timber.d("Q=%s QC=%s", quote, quoteCurrency);
if (!quote.equals(quoteCurrency)) rate = 1.0d / rate;
Timber.d("rate = %f", rate);
final ExchangeRate exchangeRate =
new ExchangeRateImpl(baseCurrency, quoteCurrency, rate);
new ExchangeRateImpl(getRealName(fiatApi.getName()), baseCurrency, quoteCurrency, rate);
callback.onSuccess(exchangeRate);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2019-2023 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.exchange.krakenFiat;
import androidx.annotation.NonNull;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import lombok.Getter;
class ExchangeRateImpl implements ExchangeRate {
@Getter
private final String serviceName;
@Getter
private final String baseCurrency;
@Getter
private final String quoteCurrency;
@Getter
private final double rate;
ExchangeRateImpl(@NonNull final String serviceName, @NonNull final String baseCurrency, @NonNull final String quoteCurrency, double rate) {
super();
this.serviceName = serviceName;
this.baseCurrency = baseCurrency;
this.quoteCurrency = quoteCurrency;
this.rate = rate;
}
}

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