1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-05 09:58:42 +02:00

Compare commits

..

3 Commits

Author SHA1 Message Date
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
m2049r
bc4aa0f772 Feature v0.18.2.2 (#900)
* add Ledger Stax

* update block heights

* reestimate restore height only for mainnet

* upgrade to gradle 8.0.1

* upgrade dependencies
2023-05-29 17:01:07 +02:00
71 changed files with 1268 additions and 344 deletions

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 3130
versionName "3.1.3 'Fluorine Fermi'"
targetSdkVersion 33
versionCode 3308
versionName "3.3.8 'Pocket Change'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -123,16 +123,16 @@ static def getId(name) {
dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
implementation 'androidx.core:core:1.10.0'
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.8.0'
implementation 'com.google.android.material:material:1.9.0'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation "com.squareup.okhttp3:okhttp:4.9.3"

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

@@ -43,6 +43,7 @@ import androidx.fragment.app.Fragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.google.android.material.textfield.TextInputLayout;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.FingerprintHelper;
@@ -344,21 +345,23 @@ public class GenerateFragment extends Fragment {
String restoreHeight = etWalletRestoreHeight.getEditText().getText().toString().trim();
if (restoreHeight.isEmpty()) return -1;
try {
// is it a date?
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
parser.setLenient(false);
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
} catch (ParseException ignored) {
}
if ((height < 0) && (restoreHeight.length() == 8))
if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) {
try {
// is it a date without dashes?
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");
// is it a date?
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
parser.setLenient(false);
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
} catch (ParseException ignored) {
}
if ((height < 0) && (restoreHeight.length() == 8))
try {
// is it a date without dashes?
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");
parser.setLenient(false);
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
} catch (ParseException ignored) {
}
}
if (height < 0)
try {
// or is it a height?

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

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

@@ -93,7 +93,7 @@ public class WalletManager {
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, getNetworkType().getValue());
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet);
if (wallet.getStatus().isOk()) {
if (wallet.getStatus().isOk() && (wallet.getNetworkType() == NetworkType.NetworkType_Mainnet)) {
// (Re-)Estimate restore height based on what we know
final long oldHeight = wallet.getRestoreHeight();
// Go back 4 days if we don't have a precise restore height

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

@@ -139,6 +139,20 @@ public class RestoreHeight {
blockheight.put("2022-01-01", 2527316L);
blockheight.put("2022-02-01", 2549605L);
blockheight.put("2022-03-01", 2569711L);
blockheight.put("2022-04-01", 2591995L);
blockheight.put("2022-05-01", 2613603L);
blockheight.put("2022-06-01", 2635840L);
blockheight.put("2022-07-01", 2657395L);
blockheight.put("2022-08-01", 2679705L);
blockheight.put("2022-09-01", 2701991L);
blockheight.put("2022-10-01", 2723607L);
blockheight.put("2022-11-01", 2745899L);
blockheight.put("2022-12-01", 2767427L);
blockheight.put("2023-01-01", 2789763L);
blockheight.put("2023-02-01", 2811996L);
blockheight.put("2023-03-01", 2832118L);
blockheight.put("2023-04-01", 2854365L);
blockheight.put("2023-05-01", 2875972L);
}
public long getHeight(String date) {

View File

@@ -225,7 +225,7 @@ public class ExchangeEditText extends LinearLayout {
// make progress circle gray
pbExchange.getIndeterminateDrawable().
setColorFilter(ThemeHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant),
setColorFilter(ThemeHelper.getThemedColor(getContext(), com.google.android.material.R.attr.colorPrimaryVariant),
android.graphics.PorterDuff.Mode.MULTIPLY);
sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

View File

@@ -177,7 +177,7 @@ public class ExchangeView extends LinearLayout {
// make progress circle gray
pbExchange.getIndeterminateDrawable().
setColorFilter(ThemeHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant),
setColorFilter(ThemeHelper.getThemedColor(getContext(), com.google.android.material.R.attr.colorPrimaryVariant),
android.graphics.PorterDuff.Mode.MULTIPLY);
sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

View File

@@ -23,7 +23,7 @@ public class PasswordEntryView extends TextInputLayout implements TextWatcher {
}
public PasswordEntryView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs, R.attr.textInputStyle);
super(context, attrs, com.google.android.material.R.attr.textInputStyle);
}
public PasswordEntryView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorPrimaryVariant"
android:pathData="M15,4c-4.42,0 -8,3.58 -8,8s3.58,8 8,8 8,-3.58 8,-8 -3.58,-8 -8,-8zM15,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z" />
<path
android:fillColor="?attr/colorPrimaryVariant"
android:pathData="M3,12c0,-2.61 1.67,-4.83 4,-5.65V4.26C3.55,5.15 1,8.27 1,12s2.55,6.85 6,7.74v-2.09c-2.33,-0.82 -4,-3.04 -4,-5.65z" />
</vector>

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