Compare commits

...

4 Commits

Author SHA1 Message Date
woodser
f2f25f65f7 play sounds on notifications #1284 2024-09-30 10:13:21 -04:00
woodser
3ffda0fdd1 fix account export and import without key ring #1221 2024-09-30 10:11:47 -04:00
woodser
3e3f3085f8 fix not enough signers on process payout tx 2024-09-30 10:11:39 -04:00
woodser
60b91d3d23 fix build artifacts for ubuntu-22.04 2024-09-30 09:40:24 -04:00
38 changed files with 216 additions and 60 deletions

View File

@ -37,7 +37,7 @@ jobs:
name: cached-localnet name: cached-localnet
path: .localnet path: .localnet
- name: Install dependencies - name: Install dependencies
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-22.04' }}
run: | run: |
sudo apt update sudo apt update
sudo apt install -y rpm sudo apt install -y rpm
@ -53,10 +53,10 @@ jobs:
./gradlew packageInstallers ./gradlew packageInstallers
working-directory: . working-directory: .
- name: Move Release Files on Unix - name: Move Release Files on Unix
if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-13' }} if: ${{ matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-13' }}
run: | run: |
mkdir ${{ github.workspace }}/release mkdir ${{ github.workspace }}/release
if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then if [ "${{ matrix.os }}" == "ubuntu-22.04" ]; then
mv desktop/build/temp-*/binaries/haveno-*.rpm ${{ github.workspace }}/release mv desktop/build/temp-*/binaries/haveno-*.rpm ${{ github.workspace }}/release
mv desktop/build/temp-*/binaries/haveno_*.deb ${{ github.workspace }}/release mv desktop/build/temp-*/binaries/haveno_*.deb ${{ github.workspace }}/release
else else

View File

@ -3,7 +3,11 @@ package haveno.core.api;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import haveno.core.api.model.TradeInfo; import haveno.core.api.model.TradeInfo;
import haveno.core.support.messages.ChatMessage; import haveno.core.support.messages.ChatMessage;
import haveno.core.trade.HavenoUtils;
import haveno.core.trade.MakerTrade;
import haveno.core.trade.SellerTrade;
import haveno.core.trade.Trade; import haveno.core.trade.Trade;
import haveno.core.trade.Trade.Phase;
import haveno.proto.grpc.NotificationMessage; import haveno.proto.grpc.NotificationMessage;
import haveno.proto.grpc.NotificationMessage.NotificationType; import haveno.proto.grpc.NotificationMessage.NotificationType;
import java.util.Iterator; import java.util.Iterator;
@ -46,7 +50,15 @@ public class CoreNotificationService {
.build()); .build());
} }
public void sendTradeNotification(Trade trade, String title, String message) { public void sendTradeNotification(Trade trade, Phase phase, String title, String message) {
// play chime when maker's trade is taken
if (trade instanceof MakerTrade && phase == Trade.Phase.DEPOSITS_PUBLISHED) HavenoUtils.playChimeSound();
// play chime when seller sees buyer confirm payment sent
if (trade instanceof SellerTrade && phase == Trade.Phase.PAYMENT_SENT) HavenoUtils.playChimeSound();
// send notification
sendNotification(NotificationMessage.newBuilder() sendNotification(NotificationMessage.newBuilder()
.setType(NotificationType.TRADE_UPDATE) .setType(NotificationType.TRADE_UPDATE)
.setTrade(TradeInfo.toTradeInfo(trade).toProtoMessage()) .setTrade(TradeInfo.toTradeInfo(trade).toProtoMessage())
@ -57,6 +69,7 @@ public class CoreNotificationService {
} }
public void sendChatNotification(ChatMessage chatMessage) { public void sendChatNotification(ChatMessage chatMessage) {
HavenoUtils.playChimeSound();
sendNotification(NotificationMessage.newBuilder() sendNotification(NotificationMessage.newBuilder()
.setType(NotificationType.CHAT_MESSAGE) .setType(NotificationType.CHAT_MESSAGE)
.setTimestamp(System.currentTimeMillis()) .setTimestamp(System.currentTimeMillis())

View File

@ -98,7 +98,7 @@ public class XmrBalanceInfo implements Payload {
public String toString() { public String toString() {
return "XmrBalanceInfo{" + return "XmrBalanceInfo{" +
"balance=" + balance + "balance=" + balance +
"unlockedBalance=" + availableBalance + ", unlockedBalance=" + availableBalance +
", lockedBalance=" + pendingBalance + ", lockedBalance=" + pendingBalance +
", reservedOfferBalance=" + reservedOfferBalance + ", reservedOfferBalance=" + reservedOfferBalance +
", reservedTradeBalance=" + reservedTradeBalance + ", reservedTradeBalance=" + reservedTradeBalance +

View File

@ -53,6 +53,7 @@ import haveno.core.alert.Alert;
import haveno.core.alert.AlertManager; import haveno.core.alert.AlertManager;
import haveno.core.alert.PrivateNotificationManager; import haveno.core.alert.PrivateNotificationManager;
import haveno.core.alert.PrivateNotificationPayload; import haveno.core.alert.PrivateNotificationPayload;
import haveno.core.api.CoreContext;
import haveno.core.api.XmrConnectionService; import haveno.core.api.XmrConnectionService;
import haveno.core.api.XmrLocalNode; import haveno.core.api.XmrLocalNode;
import haveno.core.locale.Res; import haveno.core.locale.Res;
@ -131,7 +132,10 @@ public class HavenoSetup {
private final Preferences preferences; private final Preferences preferences;
private final User user; private final User user;
private final AlertManager alertManager; private final AlertManager alertManager;
@Getter
private final Config config; private final Config config;
@Getter
private final CoreContext coreContext;
private final AccountAgeWitnessService accountAgeWitnessService; private final AccountAgeWitnessService accountAgeWitnessService;
private final TorSetup torSetup; private final TorSetup torSetup;
private final CoinFormatter formatter; private final CoinFormatter formatter;
@ -228,6 +232,7 @@ public class HavenoSetup {
User user, User user,
AlertManager alertManager, AlertManager alertManager,
Config config, Config config,
CoreContext coreContext,
AccountAgeWitnessService accountAgeWitnessService, AccountAgeWitnessService accountAgeWitnessService,
TorSetup torSetup, TorSetup torSetup,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
@ -253,6 +258,7 @@ public class HavenoSetup {
this.user = user; this.user = user;
this.alertManager = alertManager; this.alertManager = alertManager;
this.config = config; this.config = config;
this.coreContext = coreContext;
this.accountAgeWitnessService = accountAgeWitnessService; this.accountAgeWitnessService = accountAgeWitnessService;
this.torSetup = torSetup; this.torSetup = torSetup;
this.formatter = formatter; this.formatter = formatter;
@ -263,6 +269,7 @@ public class HavenoSetup {
this.arbitrationManager = arbitrationManager; this.arbitrationManager = arbitrationManager;
HavenoUtils.havenoSetup = this; HavenoUtils.havenoSetup = this;
HavenoUtils.preferences = preferences;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -199,7 +199,7 @@ public abstract class SupportManager {
if (dispute.isClosed()) dispute.reOpen(); if (dispute.isClosed()) dispute.reOpen();
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED); trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
} else if (dispute.isClosed()) { } else if (dispute.isClosed()) {
trade.pollWalletNormallyForMs(30000); // sync to check for payout trade.pollWalletNormallyForMs(60000); // sync to check for payout
} }
} }
} }

View File

@ -854,7 +854,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// the state, as that is displayed to the user and we only persist that msg // the state, as that is displayed to the user and we only persist that msg
disputeResult.getChatMessage().setArrived(true); disputeResult.getChatMessage().setArrived(true);
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG); trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG);
trade.pollWalletNormallyForMs(30000); trade.pollWalletNormallyForMs(60000);
requestPersistence(trade); requestPersistence(trade);
resultHandler.handleResult(); resultHandler.handleResult();
} }

View File

@ -361,7 +361,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
requestPersistence(trade); requestPersistence(trade);
// nack bad message and do not reprocess // nack bad message and do not reprocess
if (e instanceof IllegalArgumentException || e instanceof IllegalStateException) { if (HavenoUtils.isIllegal(e)) {
trade.getArbitrator().setDisputeClosedMessage(null); // message is processed trade.getArbitrator().setDisputeClosedMessage(null); // message is processed
trade.setDisputeState(Trade.DisputeState.DISPUTE_CLOSED); trade.setDisputeState(Trade.DisputeState.DISPUTE_CLOSED);
String warningMsg = "Error processing dispute closed message: " + e.getMessage() + "\n\nOpen another dispute to try again (ctrl+o)."; String warningMsg = "Error processing dispute closed message: " + e.getMessage() + "\n\nOpen another dispute to try again (ctrl+o).";

View File

@ -27,7 +27,9 @@ import haveno.common.crypto.Hash;
import haveno.common.crypto.KeyRing; import haveno.common.crypto.KeyRing;
import haveno.common.crypto.PubKeyRing; import haveno.common.crypto.PubKeyRing;
import haveno.common.crypto.Sig; import haveno.common.crypto.Sig;
import haveno.common.file.FileUtil;
import haveno.common.util.Utilities; import haveno.common.util.Utilities;
import haveno.core.api.CoreNotificationService;
import haveno.core.api.XmrConnectionService; import haveno.core.api.XmrConnectionService;
import haveno.core.app.HavenoSetup; import haveno.core.app.HavenoSetup;
import haveno.core.offer.OfferPayload; import haveno.core.offer.OfferPayload;
@ -36,9 +38,12 @@ import haveno.core.support.dispute.arbitration.ArbitrationManager;
import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator; import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator;
import haveno.core.trade.messages.PaymentReceivedMessage; import haveno.core.trade.messages.PaymentReceivedMessage;
import haveno.core.trade.messages.PaymentSentMessage; import haveno.core.trade.messages.PaymentSentMessage;
import haveno.core.user.Preferences;
import haveno.core.util.JsonUtil; import haveno.core.util.JsonUtil;
import haveno.core.xmr.wallet.XmrWalletService; import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.NodeAddress; import haveno.network.p2p.NodeAddress;
import java.io.File;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.InetAddress; import java.net.InetAddress;
@ -53,6 +58,13 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroRpcConnection; import monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroOutput; import monero.daemon.model.MoneroOutput;
@ -110,11 +122,18 @@ public class HavenoUtils {
public static XmrWalletService xmrWalletService; public static XmrWalletService xmrWalletService;
public static XmrConnectionService xmrConnectionService; public static XmrConnectionService xmrConnectionService;
public static OpenOfferManager openOfferManager; public static OpenOfferManager openOfferManager;
public static CoreNotificationService notificationService;
public static Preferences preferences;
public static boolean isSeedNode() { public static boolean isSeedNode() {
return havenoSetup == null; return havenoSetup == null;
} }
public static boolean isDaemon() {
if (isSeedNode()) return true;
return havenoSetup.getCoreContext().isApiUser();
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static Date getReleaseDate() { public static Date getReleaseDate() {
if (RELEASE_DATE == null) return null; if (RELEASE_DATE == null) return null;
@ -510,19 +529,83 @@ public class HavenoUtils {
havenoSetup.getTopErrorMsg().set(msg); havenoSetup.getTopErrorMsg().set(msg);
} }
public static boolean isConnectionRefused(Exception e) { public static boolean isConnectionRefused(Throwable e) {
return e != null && e.getMessage().contains("Connection refused"); return e != null && e.getMessage().contains("Connection refused");
} }
public static boolean isReadTimeout(Exception e) { public static boolean isReadTimeout(Throwable e) {
return e != null && e.getMessage().contains("Read timed out"); return e != null && e.getMessage().contains("Read timed out");
} }
public static boolean isUnresponsive(Exception e) { public static boolean isUnresponsive(Throwable e) {
return isConnectionRefused(e) || isReadTimeout(e); return isConnectionRefused(e) || isReadTimeout(e);
} }
public static boolean isNotEnoughSigners(Exception e) { public static boolean isNotEnoughSigners(Throwable e) {
return e != null && e.getMessage().contains("Not enough signers"); return e != null && e.getMessage().contains("Not enough signers");
} }
public static boolean isTransactionRejected(Throwable e) {
return e != null && e.getMessage().contains("was rejected");
}
public static boolean isIllegal(Throwable e) {
return e instanceof IllegalArgumentException || e instanceof IllegalStateException;
}
public static void playChimeSound() {
playAudioFile("chime.wav");
}
public static void playCashRegisterSound() {
playAudioFile("cash_register.wav");
}
private static void playAudioFile(String fileName) {
if (isDaemon()) return; // ignore if running as daemon
if (!preferences.getUseSoundForNotificationsProperty().get()) return; // ignore if sounds disabled
new Thread(() -> {
try {
// get audio file
File wavFile = new File(havenoSetup.getConfig().appDataDir, fileName);
if (!wavFile.exists()) FileUtil.resourceToFile(fileName, wavFile);
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(wavFile);
// get original format
AudioFormat baseFormat = audioInputStream.getFormat();
// set target format: PCM_SIGNED, 16-bit
AudioFormat targetFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(),
16, // 16-bit instead of 32-bit float
baseFormat.getChannels(),
baseFormat.getChannels() * 2, // Frame size: 2 bytes per channel (16-bit)
baseFormat.getSampleRate(),
false // Little-endian
);
// convert audio to target format
AudioInputStream convertedStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
// play audio
DataLine.Info info = new DataLine.Info(SourceDataLine.class, targetFormat);
SourceDataLine sourceLine = (SourceDataLine) AudioSystem.getLine(info);
sourceLine.open(targetFormat);
sourceLine.start();
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = convertedStream.read(buffer, 0, buffer.length)) != -1) {
sourceLine.write(buffer, 0, bytesRead);
}
sourceLine.drain();
sourceLine.close();
convertedStream.close();
audioInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
} }

View File

@ -649,6 +649,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
ThreadUtils.submitToPool(() -> { ThreadUtils.submitToPool(() -> {
if (newValue == Trade.Phase.DEPOSIT_REQUESTED) startPolling(); if (newValue == Trade.Phase.DEPOSIT_REQUESTED) startPolling();
if (newValue == Trade.Phase.DEPOSITS_PUBLISHED) onDepositsPublished(); if (newValue == Trade.Phase.DEPOSITS_PUBLISHED) onDepositsPublished();
if (newValue == Trade.Phase.PAYMENT_SENT) onPaymentSent();
if (isDepositsPublished() && !isPayoutUnlocked()) updatePollPeriod(); if (isDepositsPublished() && !isPayoutUnlocked()) updatePollPeriod();
if (isPaymentReceived()) { if (isPaymentReceived()) {
UserThread.execute(() -> { UserThread.execute(() -> {
@ -1158,7 +1159,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
private void handleWalletError(Exception e, MoneroRpcConnection sourceConnection) { private void handleWalletError(Exception e, MoneroRpcConnection sourceConnection) {
if (HavenoUtils.isUnresponsive(e)) forceCloseWallet(); // wallet can be stuck a while if (HavenoUtils.isUnresponsive(e)) forceCloseWallet(); // wallet can be stuck a while
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection(sourceConnection); if (!HavenoUtils.isIllegal(e) && xmrConnectionService.isConnected()) requestSwitchToNextBestConnection(sourceConnection);
getWallet(); // re-open wallet getWallet(); // re-open wallet
} }
@ -1278,7 +1279,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
} catch (IllegalArgumentException | IllegalStateException e) { } catch (IllegalArgumentException | IllegalStateException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to process payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage()); log.warn("Failed to process payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage(), e);
handleWalletError(e, sourceConnection); handleWalletError(e, sourceConnection);
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e; if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
@ -1350,14 +1351,13 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
try { try {
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex); MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
if (result.getSignedMultisigTxHex() == null) throw new IllegalArgumentException("Error signing payout tx, signed multisig hex is null"); if (result.getSignedMultisigTxHex() == null) throw new IllegalArgumentException("Error signing payout tx, signed multisig hex is null");
payoutTxHex = result.getSignedMultisigTxHex(); setPayoutTxHex(result.getSignedMultisigTxHex());
setPayoutTxHex(payoutTxHex);
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
// describe result // describe result
describedTxSet = wallet.describeMultisigTxSet(payoutTxHex); describedTxSet = wallet.describeMultisigTxSet(getPayoutTxHex());
payoutTx = describedTxSet.getTxs().get(0); payoutTx = describedTxSet.getTxs().get(0);
updatePayout(payoutTx); updatePayout(payoutTx);
@ -1377,14 +1377,16 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
requestPersistence(); requestPersistence();
// submit payout tx // submit payout tx
if (publish) { boolean doPublish = publish && !isPayoutPublished();
if (doPublish) {
try { try {
wallet.submitMultisigTxHex(payoutTxHex); wallet.submitMultisigTxHex(getPayoutTxHex());
setPayoutStatePublished(); setPayoutStatePublished();
} catch (Exception e) { } catch (Exception e) {
if (isPayoutPublished()) throw new IllegalStateException("Payout tx already published for " + getClass().getSimpleName() + " " + getShortId()); if (!isPayoutPublished()) {
if (HavenoUtils.isNotEnoughSigners(e)) throw new IllegalArgumentException(e); if (HavenoUtils.isTransactionRejected(e) || HavenoUtils.isNotEnoughSigners(e)) throw new IllegalArgumentException(e);
throw new RuntimeException("Failed to submit payout tx for " + getClass().getSimpleName() + " " + getId(), e); throw new RuntimeException("Failed to submit payout tx for " + getClass().getSimpleName() + " " + getId() + ", error=" + e.getMessage(), e);
}
} }
} }
} }
@ -2832,6 +2834,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
// close open offer or reset address entries // close open offer or reset address entries
if (this instanceof MakerTrade) { if (this instanceof MakerTrade) {
processModel.getOpenOfferManager().closeOpenOffer(getOffer()); processModel.getOpenOfferManager().closeOpenOffer(getOffer());
HavenoUtils.notificationService.sendTradeNotification(this, Phase.DEPOSITS_PUBLISHED, "Offer Taken", "Your offer " + offer.getId() + " has been accepted"); // TODO (woodser): use language translation
} else { } else {
getXmrWalletService().resetAddressEntriesForOpenOffer(getId()); getXmrWalletService().resetAddressEntriesForOpenOffer(getId());
} }
@ -2840,6 +2843,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
ThreadUtils.submitToPool(() -> xmrWalletService.freezeOutputs(getSelf().getReserveTxKeyImages())); ThreadUtils.submitToPool(() -> xmrWalletService.freezeOutputs(getSelf().getReserveTxKeyImages()));
} }
private void onPaymentSent() {
if (this instanceof SellerTrade) {
HavenoUtils.notificationService.sendTradeNotification(this, Phase.PAYMENT_SENT, "Payment Sent", "The buyer has sent the payment"); // TODO (woodser): use language translation
}
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER // PROTO BUFFER
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -68,7 +68,6 @@ import haveno.core.support.dispute.mediation.mediator.MediatorManager;
import haveno.core.support.dispute.messages.DisputeClosedMessage; import haveno.core.support.dispute.messages.DisputeClosedMessage;
import haveno.core.support.dispute.messages.DisputeOpenedMessage; import haveno.core.support.dispute.messages.DisputeOpenedMessage;
import haveno.core.trade.Trade.DisputeState; import haveno.core.trade.Trade.DisputeState;
import haveno.core.trade.Trade.Phase;
import haveno.core.trade.failed.FailedTradesManager; import haveno.core.trade.failed.FailedTradesManager;
import haveno.core.trade.handlers.TradeResultHandler; import haveno.core.trade.handlers.TradeResultHandler;
import haveno.core.trade.messages.DepositRequest; import haveno.core.trade.messages.DepositRequest;
@ -134,7 +133,6 @@ import lombok.Setter;
import monero.daemon.model.MoneroTx; import monero.daemon.model.MoneroTx;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -258,7 +256,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
failedTradesManager.setUnFailTradeCallback(this::unFailTrade); failedTradesManager.setUnFailTradeCallback(this::unFailTrade);
xmrWalletService.setTradeManager(this); // TODO: better way to set references
xmrWalletService.setTradeManager(this); // TODO: set reference in HavenoUtils for consistency
HavenoUtils.notificationService = notificationService;
} }
@ -599,14 +599,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
initTradeAndProtocol(trade, createTradeProtocol(trade)); initTradeAndProtocol(trade, createTradeProtocol(trade));
addTrade(trade); addTrade(trade);
// notify on phase changes
// TODO (woodser): save subscription, bind on startup
EasyBind.subscribe(trade.statePhaseProperty(), phase -> {
if (phase == Phase.DEPOSITS_PUBLISHED) {
notificationService.sendTradeNotification(trade, "Offer Taken", "Your offer " + offer.getId() + " has been accepted"); // TODO (woodser): use language translation
}
});
// process with protocol // process with protocol
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> { ((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
log.warn("Maker error during trade initialization: " + errorMessage); log.warn("Maker error during trade initialization: " + errorMessage);

View File

@ -120,7 +120,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
} catch (Throwable t) { } catch (Throwable t) {
// do not reprocess illegal argument // do not reprocess illegal argument
if (t instanceof IllegalArgumentException) { if (HavenoUtils.isIllegal(t)) {
trade.getSeller().setPaymentReceivedMessage(null); // do not reprocess trade.getSeller().setPaymentReceivedMessage(null); // do not reprocess
trade.requestPersistence(); trade.requestPersistence();
} }
@ -151,6 +151,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
boolean deferSignAndPublish = trade instanceof ArbitratorTrade && !isSigned && message.isDeferPublishPayout(); boolean deferSignAndPublish = trade instanceof ArbitratorTrade && !isSigned && message.isDeferPublishPayout();
if (deferSignAndPublish) { if (deferSignAndPublish) {
log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId()); log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
trade.pollWalletNormallyForMs(60000);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
if (trade.isPayoutPublished()) break; if (trade.isPayoutPublished()) break;
HavenoUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5); HavenoUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5);

View File

@ -65,10 +65,10 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
trade.processPayoutTx(trade.getPayoutTxHex(), false, true); trade.processPayoutTx(trade.getPayoutTxHex(), false, true);
} }
} catch (IllegalArgumentException | IllegalStateException e) { } catch (IllegalArgumentException | IllegalStateException e) {
log.warn("Illegal state or argument verifying, signing, and publishing payout tx for {} {}: {}. Creating new unsigned payout tx", trade.getClass().getSimpleName(), trade.getId(), e.getMessage()); log.warn("Illegal state or argument verifying, signing, and publishing payout tx for {} {}: {}. Creating new unsigned payout tx", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
createUnsignedPayoutTx(); createUnsignedPayoutTx();
} catch (Exception e) { } catch (Exception e) {
log.warn("Error verifying, signing, and publishing payout tx for trade {}: {}", trade.getId(), e.getMessage()); log.warn("Error verifying, signing, and publishing payout tx for trade {}: {}", trade.getId(), e.getMessage(), e);
throw e; throw e;
} }
} }

View File

@ -132,6 +132,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
private final String xmrNodesFromOptions; private final String xmrNodesFromOptions;
@Getter @Getter
private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode()); private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode());
@Getter
private final BooleanProperty useSoundForNotificationsProperty = new SimpleBooleanProperty(prefPayload.isUseSoundForNotifications());
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
@ -162,6 +164,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
requestPersistence(); requestPersistence();
}); });
useSoundForNotificationsProperty.addListener((ov) -> {
prefPayload.setUseSoundForNotifications(useSoundForNotificationsProperty.get());
requestPersistence();
});
traditionalCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> { traditionalCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> {
prefPayload.getTraditionalCurrencies().clear(); prefPayload.getTraditionalCurrencies().clear();
prefPayload.getTraditionalCurrencies().addAll(traditionalCurrenciesAsObservable); prefPayload.getTraditionalCurrencies().addAll(traditionalCurrenciesAsObservable);
@ -259,6 +266,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
// set all properties // set all properties
useAnimationsProperty.set(prefPayload.isUseAnimations()); useAnimationsProperty.set(prefPayload.isUseAnimations());
useStandbyModeProperty.set(prefPayload.isUseStandbyMode()); useStandbyModeProperty.set(prefPayload.isUseStandbyMode());
useSoundForNotificationsProperty.set(prefPayload.isUseSoundForNotifications());
cssThemeProperty.set(prefPayload.getCssTheme()); cssThemeProperty.set(prefPayload.getCssTheme());
@ -697,6 +705,10 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
this.useStandbyModeProperty.set(useStandbyMode); this.useStandbyModeProperty.set(useStandbyMode);
} }
public void setUseSoundForNotifications(boolean useSoundForNotifications) {
this.useSoundForNotificationsProperty.set(useSoundForNotifications);
}
public void setTakeOfferSelectedPaymentAccountId(String value) { public void setTakeOfferSelectedPaymentAccountId(String value) {
prefPayload.setTakeOfferSelectedPaymentAccountId(value); prefPayload.setTakeOfferSelectedPaymentAccountId(value);
requestPersistence(); requestPersistence();
@ -946,6 +958,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setUseStandbyMode(boolean useStandbyMode); void setUseStandbyMode(boolean useStandbyMode);
void setUseSoundForNotifications(boolean useSoundForNotifications);
void setTakeOfferSelectedPaymentAccountId(String value); void setTakeOfferSelectedPaymentAccountId(String value);
void setIgnoreDustThreshold(int value); void setIgnoreDustThreshold(int value);

View File

@ -108,6 +108,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
private boolean useMarketNotifications = true; private boolean useMarketNotifications = true;
private boolean usePriceNotifications = true; private boolean usePriceNotifications = true;
private boolean useStandbyMode = false; private boolean useStandbyMode = false;
private boolean useSoundForNotifications = true;
@Nullable @Nullable
private String rpcUser; private String rpcUser;
@Nullable @Nullable
@ -185,6 +186,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
.setUseMarketNotifications(useMarketNotifications) .setUseMarketNotifications(useMarketNotifications)
.setUsePriceNotifications(usePriceNotifications) .setUsePriceNotifications(usePriceNotifications)
.setUseStandbyMode(useStandbyMode) .setUseStandbyMode(useStandbyMode)
.setUseSoundForNotifications(useSoundForNotifications)
.setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent) .setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent)
.setIgnoreDustThreshold(ignoreDustThreshold) .setIgnoreDustThreshold(ignoreDustThreshold)
.setClearDataAfterDays(clearDataAfterDays) .setClearDataAfterDays(clearDataAfterDays)
@ -280,6 +282,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
proto.getUseMarketNotifications(), proto.getUseMarketNotifications(),
proto.getUsePriceNotifications(), proto.getUsePriceNotifications(),
proto.getUseStandbyMode(), proto.getUseStandbyMode(),
proto.getUseSoundForNotifications(),
proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(), proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(),
proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(), proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(),
proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(), proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(),

View File

@ -111,6 +111,7 @@ public class Balances {
public XmrBalanceInfo getBalances() { public XmrBalanceInfo getBalances() {
synchronized (this) { synchronized (this) {
if (availableBalance == null) return null;
return new XmrBalanceInfo(availableBalance.longValue() + pendingBalance.longValue(), return new XmrBalanceInfo(availableBalance.longValue() + pendingBalance.longValue(),
availableBalance.longValue(), availableBalance.longValue(),
pendingBalance.longValue(), pendingBalance.longValue(),
@ -127,6 +128,9 @@ public class Balances {
synchronized (this) { synchronized (this) {
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) { synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
// get non-trade balance before
BigInteger balanceSumBefore = getNonTradeBalanceSum();
// get wallet balances // get wallet balances
BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getBalance(); BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getBalance();
availableBalance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getAvailableBalance(); availableBalance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getAvailableBalance();
@ -160,8 +164,25 @@ public class Balances {
reservedBalance = reservedOfferBalance.add(reservedTradeBalance); reservedBalance = reservedOfferBalance.add(reservedTradeBalance);
// notify balance update // notify balance update
UserThread.execute(() -> updateCounter.set(updateCounter.get() + 1)); UserThread.execute(() -> {
// check if funds received
boolean fundsReceived = balanceSumBefore != null && getNonTradeBalanceSum().compareTo(balanceSumBefore) > 0;
if (fundsReceived) {
HavenoUtils.playCashRegisterSound();
} }
// increase counter to notify listeners
updateCounter.set(updateCounter.get() + 1);
});
}
}
}
private BigInteger getNonTradeBalanceSum() {
synchronized (this) {
if (availableBalance == null) return null;
return availableBalance.add(pendingBalance).add(reservedOfferBalance);
} }
} }
} }

Binary file not shown.

Binary file not shown.

View File

@ -1266,6 +1266,7 @@ setting.preferences.general=General preferences
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Max. deviation from market price setting.preferences.deviation=Max. deviation from market price
setting.preferences.avoidStandbyMode=Avoid standby mode setting.preferences.avoidStandbyMode=Avoid standby mode
setting.preferences.useSoundForNotifications=Play sounds for notifications
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations

View File

@ -987,6 +987,7 @@ setting.preferences.general=Základní nastavení
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Max. odchylka od tržní ceny setting.preferences.deviation=Max. odchylka od tržní ceny
setting.preferences.avoidStandbyMode=Vyhněte se pohotovostnímu režimu setting.preferences.avoidStandbyMode=Vyhněte se pohotovostnímu režimu
setting.preferences.useSoundForNotifications=Přehrávat zvuky pro upozornění
setting.preferences.autoConfirmXMR=Automatické potvrzení XMR setting.preferences.autoConfirmXMR=Automatické potvrzení XMR
setting.preferences.autoConfirmEnabled=Povoleno setting.preferences.autoConfirmEnabled=Povoleno
setting.preferences.autoConfirmRequiredConfirmations=Požadovaná potvrzení setting.preferences.autoConfirmRequiredConfirmations=Požadovaná potvrzení

View File

@ -987,6 +987,7 @@ setting.preferences.general=Allgemeine Voreinstellungen
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Max. Abweichung vom Marktpreis setting.preferences.deviation=Max. Abweichung vom Marktpreis
setting.preferences.avoidStandbyMode=Standby Modus verhindern setting.preferences.avoidStandbyMode=Standby Modus verhindern
setting.preferences.useSoundForNotifications=Spiele Geräusche für Benachrichtigungen
setting.preferences.autoConfirmXMR=XMR automatische Bestätigung setting.preferences.autoConfirmXMR=XMR automatische Bestätigung
setting.preferences.autoConfirmEnabled=Aktiviert setting.preferences.autoConfirmEnabled=Aktiviert
setting.preferences.autoConfirmRequiredConfirmations=Benötigte Bestätigungen setting.preferences.autoConfirmRequiredConfirmations=Benötigte Bestätigungen

View File

@ -988,6 +988,7 @@ setting.preferences.general=Preferencias generales
setting.preferences.explorer=Explorador Monero setting.preferences.explorer=Explorador Monero
setting.preferences.deviation=Desviación máxima del precio de mercado setting.preferences.deviation=Desviación máxima del precio de mercado
setting.preferences.setting.preferences.avoidStandbyMode=Evitar modo en espera setting.preferences.setting.preferences.avoidStandbyMode=Evitar modo en espera
setting.preferences.useSoundForNotifications=Reproducir sonidos para notificaciones
setting.preferences.autoConfirmXMR=Autoconfirmación XMR setting.preferences.autoConfirmXMR=Autoconfirmación XMR
setting.preferences.autoConfirmEnabled=Habilitado setting.preferences.autoConfirmEnabled=Habilitado
setting.preferences.autoConfirmRequiredConfirmations=Confirmaciones requeridas setting.preferences.autoConfirmRequiredConfirmations=Confirmaciones requeridas

View File

@ -984,6 +984,7 @@ setting.preferences.general=اولویت‌های عمومی
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=حداکثر تفاوت از قیمت روز بازار setting.preferences.deviation=حداکثر تفاوت از قیمت روز بازار
setting.preferences.avoidStandbyMode=حالت «آماده باش» را نادیده بگیر setting.preferences.avoidStandbyMode=حالت «آماده باش» را نادیده بگیر
setting.preferences.useSoundForNotifications=پخش صداها برای اعلان‌ها
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations

View File

@ -989,6 +989,7 @@ setting.preferences.general=Préférences générales
setting.preferences.explorer=Exploreur Monero setting.preferences.explorer=Exploreur Monero
setting.preferences.deviation=Ecart maximal par rapport au prix du marché setting.preferences.deviation=Ecart maximal par rapport au prix du marché
setting.preferences.avoidStandbyMode=Éviter le mode veille setting.preferences.avoidStandbyMode=Éviter le mode veille
setting.preferences.useSoundForNotifications=Jouer des sons pour les notifications
setting.preferences.autoConfirmXMR=Auto-confirmation XMR setting.preferences.autoConfirmXMR=Auto-confirmation XMR
setting.preferences.autoConfirmEnabled=Activé setting.preferences.autoConfirmEnabled=Activé
setting.preferences.autoConfirmRequiredConfirmations=Confirmations requises setting.preferences.autoConfirmRequiredConfirmations=Confirmations requises

View File

@ -986,6 +986,7 @@ setting.preferences.general=Preferenze generali
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Deviazione massima del prezzo di mercato setting.preferences.deviation=Deviazione massima del prezzo di mercato
setting.preferences.avoidStandbyMode=Evita modalità standby setting.preferences.avoidStandbyMode=Evita modalità standby
setting.preferences.useSoundForNotifications=Riproduci suoni per le notifiche
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations

View File

@ -987,6 +987,7 @@ setting.preferences.general=一般設定
setting.preferences.explorer=ビットコインのエクスプローラ setting.preferences.explorer=ビットコインのエクスプローラ
setting.preferences.deviation=市場価格からの最大偏差 setting.preferences.deviation=市場価格からの最大偏差
setting.preferences.avoidStandbyMode=スタンバイモードを避ける setting.preferences.avoidStandbyMode=スタンバイモードを避ける
setting.preferences.useSoundForNotifications=通知音の再生
setting.preferences.autoConfirmXMR=XMR自動確認 setting.preferences.autoConfirmXMR=XMR自動確認
setting.preferences.autoConfirmEnabled=有効されました setting.preferences.autoConfirmEnabled=有効されました
setting.preferences.autoConfirmRequiredConfirmations=必要承認 setting.preferences.autoConfirmRequiredConfirmations=必要承認

View File

@ -988,6 +988,7 @@ setting.preferences.general=Preferências gerais
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Desvio máx. do preço do mercado setting.preferences.deviation=Desvio máx. do preço do mercado
setting.preferences.avoidStandbyMode=Impedir modo de economia de energia setting.preferences.avoidStandbyMode=Impedir modo de economia de energia
setting.preferences.useSoundForNotifications=Reproduzir sons para notificações
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations

View File

@ -985,6 +985,7 @@ setting.preferences.general=Preferências gerais
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Máx. desvio do preço de mercado setting.preferences.deviation=Máx. desvio do preço de mercado
setting.preferences.avoidStandbyMode=Evite o modo espera setting.preferences.avoidStandbyMode=Evite o modo espera
setting.preferences.useSoundForNotifications=Reproduzir sons para notificações
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations

View File

@ -984,6 +984,7 @@ setting.preferences.general=Основные настройки
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Макс. отклонение от рыночного курса setting.preferences.deviation=Макс. отклонение от рыночного курса
setting.preferences.avoidStandbyMode=Избегать режима ожидания setting.preferences.avoidStandbyMode=Избегать режима ожидания
setting.preferences.useSoundForNotifications=Воспроизводить звуки для уведомлений
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations

View File

@ -984,6 +984,7 @@ setting.preferences.general=การตั้งค่าทั่วไป
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=สูงสุด ส่วนเบี่ยงเบนจากราคาตลาด setting.preferences.deviation=สูงสุด ส่วนเบี่ยงเบนจากราคาตลาด
setting.preferences.avoidStandbyMode=หลีกเลี่ยงโหมดแสตนบายด์ setting.preferences.avoidStandbyMode=หลีกเลี่ยงโหมดแสตนบายด์
setting.preferences.useSoundForNotifications=เล่นเสียงสำหรับการแจ้งเตือน
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations

View File

@ -1261,6 +1261,7 @@ setting.preferences.general=Genel tercihler
setting.preferences.explorer=Monero Gezgini setting.preferences.explorer=Monero Gezgini
setting.preferences.deviation=Piyasa fiyatından maksimum sapma setting.preferences.deviation=Piyasa fiyatından maksimum sapma
setting.preferences.avoidStandbyMode=Bekleme modundan kaçın setting.preferences.avoidStandbyMode=Bekleme modundan kaçın
setting.preferences.useSoundForNotifications=Bildirimler için sesleri çal
setting.preferences.autoConfirmXMR=XMR otomatik onay setting.preferences.autoConfirmXMR=XMR otomatik onay
setting.preferences.autoConfirmEnabled=Etkin setting.preferences.autoConfirmEnabled=Etkin
setting.preferences.autoConfirmRequiredConfirmations=Gerekli onaylar setting.preferences.autoConfirmRequiredConfirmations=Gerekli onaylar

View File

@ -986,6 +986,7 @@ setting.preferences.general=Tham khảo chung
setting.preferences.explorer=Monero Explorer setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Sai lệch tối đa so với giá thị trường setting.preferences.deviation=Sai lệch tối đa so với giá thị trường
setting.preferences.avoidStandbyMode=Tránh để chế độ chờ setting.preferences.avoidStandbyMode=Tránh để chế độ chờ
setting.preferences.useSoundForNotifications=Phát âm thanh cho thông báo
setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmRequiredConfirmations=Required confirmations

View File

@ -987,6 +987,7 @@ setting.preferences.general=通用偏好
setting.preferences.explorer=比特币区块浏览器 setting.preferences.explorer=比特币区块浏览器
setting.preferences.deviation=与市场价格最大差价 setting.preferences.deviation=与市场价格最大差价
setting.preferences.avoidStandbyMode=避免待机模式 setting.preferences.avoidStandbyMode=避免待机模式
setting.preferences.useSoundForNotifications=播放通知声音
setting.preferences.autoConfirmXMR=XMR 自动确认 setting.preferences.autoConfirmXMR=XMR 自动确认
setting.preferences.autoConfirmEnabled=启用 setting.preferences.autoConfirmEnabled=启用
setting.preferences.autoConfirmRequiredConfirmations=已要求确认 setting.preferences.autoConfirmRequiredConfirmations=已要求确认

View File

@ -987,6 +987,7 @@ setting.preferences.general=通用偏好
setting.preferences.explorer=比特幣區塊瀏覽器 setting.preferences.explorer=比特幣區塊瀏覽器
setting.preferences.deviation=與市場價格最大差價 setting.preferences.deviation=與市場價格最大差價
setting.preferences.avoidStandbyMode=避免待機模式 setting.preferences.avoidStandbyMode=避免待機模式
setting.preferences.useSoundForNotifications=播放通知音效
setting.preferences.autoConfirmXMR=XMR 自動確認 setting.preferences.autoConfirmXMR=XMR 自動確認
setting.preferences.autoConfirmEnabled=啟用 setting.preferences.autoConfirmEnabled=啟用
setting.preferences.autoConfirmRequiredConfirmations=已要求確認 setting.preferences.autoConfirmRequiredConfirmations=已要求確認

View File

@ -18,7 +18,6 @@
package haveno.desktop.main.account.content.cryptoaccounts; package haveno.desktop.main.account.content.cryptoaccounts;
import com.google.inject.Inject; import com.google.inject.Inject;
import haveno.common.crypto.KeyRing;
import haveno.common.file.CorruptedStorageFileHandler; import haveno.common.file.CorruptedStorageFileHandler;
import haveno.common.proto.persistable.PersistenceProtoResolver; import haveno.common.proto.persistable.PersistenceProtoResolver;
import haveno.core.account.witness.AccountAgeWitnessService; import haveno.core.account.witness.AccountAgeWitnessService;
@ -55,7 +54,6 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
private final String accountsFileName = "CryptoPaymentAccounts"; private final String accountsFileName = "CryptoPaymentAccounts";
private final PersistenceProtoResolver persistenceProtoResolver; private final PersistenceProtoResolver persistenceProtoResolver;
private final CorruptedStorageFileHandler corruptedStorageFileHandler; private final CorruptedStorageFileHandler corruptedStorageFileHandler;
private final KeyRing keyRing;
@Inject @Inject
public CryptoAccountsDataModel(User user, public CryptoAccountsDataModel(User user,
@ -64,8 +62,7 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
TradeManager tradeManager, TradeManager tradeManager,
AccountAgeWitnessService accountAgeWitnessService, AccountAgeWitnessService accountAgeWitnessService,
PersistenceProtoResolver persistenceProtoResolver, PersistenceProtoResolver persistenceProtoResolver,
CorruptedStorageFileHandler corruptedStorageFileHandler, CorruptedStorageFileHandler corruptedStorageFileHandler) {
KeyRing keyRing) {
this.user = user; this.user = user;
this.preferences = preferences; this.preferences = preferences;
this.openOfferManager = openOfferManager; this.openOfferManager = openOfferManager;
@ -73,7 +70,6 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
this.accountAgeWitnessService = accountAgeWitnessService; this.accountAgeWitnessService = accountAgeWitnessService;
this.persistenceProtoResolver = persistenceProtoResolver; this.persistenceProtoResolver = persistenceProtoResolver;
this.corruptedStorageFileHandler = corruptedStorageFileHandler; this.corruptedStorageFileHandler = corruptedStorageFileHandler;
this.keyRing = keyRing;
setChangeListener = change -> fillAndSortPaymentAccounts(); setChangeListener = change -> fillAndSortPaymentAccounts();
} }
@ -157,12 +153,12 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream() ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream()
.filter(paymentAccount -> paymentAccount instanceof AssetAccount) .filter(paymentAccount -> paymentAccount instanceof AssetAccount)
.collect(Collectors.toList())); .collect(Collectors.toList()));
GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing); GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
} }
} }
public void importAccounts(Stage stage) { public void importAccounts(Stage stage) {
GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing); GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
} }
public int getNumPaymentAccounts() { public int getNumPaymentAccounts() {

View File

@ -18,7 +18,6 @@
package haveno.desktop.main.account.content.traditionalaccounts; package haveno.desktop.main.account.content.traditionalaccounts;
import com.google.inject.Inject; import com.google.inject.Inject;
import haveno.common.crypto.KeyRing;
import haveno.common.file.CorruptedStorageFileHandler; import haveno.common.file.CorruptedStorageFileHandler;
import haveno.common.proto.persistable.PersistenceProtoResolver; import haveno.common.proto.persistable.PersistenceProtoResolver;
import haveno.core.account.witness.AccountAgeWitnessService; import haveno.core.account.witness.AccountAgeWitnessService;
@ -56,7 +55,6 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
private final String accountsFileName = "FiatPaymentAccounts"; private final String accountsFileName = "FiatPaymentAccounts";
private final PersistenceProtoResolver persistenceProtoResolver; private final PersistenceProtoResolver persistenceProtoResolver;
private final CorruptedStorageFileHandler corruptedStorageFileHandler; private final CorruptedStorageFileHandler corruptedStorageFileHandler;
private final KeyRing keyRing;
@Inject @Inject
public TraditionalAccountsDataModel(User user, public TraditionalAccountsDataModel(User user,
@ -65,8 +63,7 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
TradeManager tradeManager, TradeManager tradeManager,
AccountAgeWitnessService accountAgeWitnessService, AccountAgeWitnessService accountAgeWitnessService,
PersistenceProtoResolver persistenceProtoResolver, PersistenceProtoResolver persistenceProtoResolver,
CorruptedStorageFileHandler corruptedStorageFileHandler, CorruptedStorageFileHandler corruptedStorageFileHandler) {
KeyRing keyRing) {
this.user = user; this.user = user;
this.preferences = preferences; this.preferences = preferences;
this.openOfferManager = openOfferManager; this.openOfferManager = openOfferManager;
@ -74,7 +71,6 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
this.accountAgeWitnessService = accountAgeWitnessService; this.accountAgeWitnessService = accountAgeWitnessService;
this.persistenceProtoResolver = persistenceProtoResolver; this.persistenceProtoResolver = persistenceProtoResolver;
this.corruptedStorageFileHandler = corruptedStorageFileHandler; this.corruptedStorageFileHandler = corruptedStorageFileHandler;
this.keyRing = keyRing;
setChangeListener = change -> fillAndSortPaymentAccounts(); setChangeListener = change -> fillAndSortPaymentAccounts();
} }
@ -159,12 +155,12 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream() ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream()
.filter(paymentAccount -> !(paymentAccount instanceof AssetAccount)) .filter(paymentAccount -> !(paymentAccount instanceof AssetAccount))
.collect(Collectors.toList())); .collect(Collectors.toList()));
GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing); GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
} }
} }
public void importAccounts(Stage stage) { public void importAccounts(Stage stage) {
GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing); GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
} }
public int getNumPaymentAccounts() { public int getNumPaymentAccounts() {

View File

@ -108,7 +108,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox; private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox;
private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically, private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically,
avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle, avoidStandbyMode, useSoundForNotifications, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle,
notifyOnPreReleaseToggle; notifyOnPreReleaseToggle;
private int gridRow = 0; private int gridRow = 0;
private int displayCurrenciesGridRowIndex = 0; private int displayCurrenciesGridRowIndex = 0;
@ -209,7 +209,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void initializeGeneralOptions() { private void initializeGeneralOptions() {
int titledGroupBgRowSpan = displayStandbyModeFeature ? 7 : 6; int titledGroupBgRowSpan = displayStandbyModeFeature ? 8 : 7;
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, titledGroupBgRowSpan, Res.get("setting.preferences.general")); TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, titledGroupBgRowSpan, Res.get("setting.preferences.general"));
GridPane.setColumnSpan(titledGroupBg, 1); GridPane.setColumnSpan(titledGroupBg, 1);
@ -285,6 +285,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
avoidStandbyMode = addSlideToggleButton(root, ++gridRow, avoidStandbyMode = addSlideToggleButton(root, ++gridRow,
Res.get("setting.preferences.avoidStandbyMode")); Res.get("setting.preferences.avoidStandbyMode"));
} }
useSoundForNotifications = addSlideToggleButton(root, ++gridRow,
Res.get("setting.preferences.useSoundForNotifications"), Layout.GROUP_DISTANCE * -1); // TODO: why must negative value be used to place toggle consistently?
} }
private void initializeSeparator() { private void initializeSeparator() {
@ -518,6 +521,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
GridPane.setHgrow(resetDontShowAgainButton, Priority.ALWAYS); GridPane.setHgrow(resetDontShowAgainButton, Priority.ALWAYS);
GridPane.setColumnIndex(resetDontShowAgainButton, 0); GridPane.setColumnIndex(resetDontShowAgainButton, 0);
} }
private void initializeAutoConfirmOptions() { private void initializeAutoConfirmOptions() {
GridPane autoConfirmGridPane = new GridPane(); GridPane autoConfirmGridPane = new GridPane();
GridPane.setHgrow(autoConfirmGridPane, Priority.ALWAYS); GridPane.setHgrow(autoConfirmGridPane, Priority.ALWAYS);
@ -790,6 +794,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
} else { } else {
preferences.setUseStandbyMode(false); preferences.setUseStandbyMode(false);
} }
useSoundForNotifications.setSelected(preferences.isUseSoundForNotifications());
useSoundForNotifications.setOnAction(e -> preferences.setUseSoundForNotifications(useSoundForNotifications.isSelected()));
} }
private void activateAutoConfirmPreferences() { private void activateAutoConfirmPreferences() {

View File

@ -28,7 +28,6 @@ import com.googlecode.jcsv.writer.internal.CSVWriterBuilder;
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
import haveno.common.UserThread; import haveno.common.UserThread;
import haveno.common.config.Config; import haveno.common.config.Config;
import haveno.common.crypto.KeyRing;
import haveno.common.file.CorruptedStorageFileHandler; import haveno.common.file.CorruptedStorageFileHandler;
import haveno.common.persistence.PersistenceManager; import haveno.common.persistence.PersistenceManager;
import haveno.common.proto.persistable.PersistableEnvelope; import haveno.common.proto.persistable.PersistableEnvelope;
@ -168,12 +167,11 @@ public class GUIUtil {
Preferences preferences, Preferences preferences,
Stage stage, Stage stage,
PersistenceProtoResolver persistenceProtoResolver, PersistenceProtoResolver persistenceProtoResolver,
CorruptedStorageFileHandler corruptedStorageFileHandler, CorruptedStorageFileHandler corruptedStorageFileHandler) {
KeyRing keyRing) {
if (!accounts.isEmpty()) { if (!accounts.isEmpty()) {
String directory = getDirectoryFromChooser(preferences, stage); String directory = getDirectoryFromChooser(preferences, stage);
if (!directory.isEmpty()) { if (!directory.isEmpty()) {
PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, keyRing); PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, null);
PaymentAccountList paymentAccounts = new PaymentAccountList(accounts); PaymentAccountList paymentAccounts = new PaymentAccountList(accounts);
persistenceManager.initialize(paymentAccounts, fileName, PersistenceManager.Source.PRIVATE_LOW_PRIO); persistenceManager.initialize(paymentAccounts, fileName, PersistenceManager.Source.PRIVATE_LOW_PRIO);
persistenceManager.persistNow(() -> { persistenceManager.persistNow(() -> {
@ -193,8 +191,7 @@ public class GUIUtil {
Preferences preferences, Preferences preferences,
Stage stage, Stage stage,
PersistenceProtoResolver persistenceProtoResolver, PersistenceProtoResolver persistenceProtoResolver,
CorruptedStorageFileHandler corruptedStorageFileHandler, CorruptedStorageFileHandler corruptedStorageFileHandler) {
KeyRing keyRing) {
FileChooser fileChooser = new FileChooser(); FileChooser fileChooser = new FileChooser();
File initDir = new File(preferences.getDirectoryChooserPath()); File initDir = new File(preferences.getDirectoryChooserPath());
if (initDir.isDirectory()) { if (initDir.isDirectory()) {
@ -207,7 +204,7 @@ public class GUIUtil {
if (Paths.get(path).getFileName().toString().equals(fileName)) { if (Paths.get(path).getFileName().toString().equals(fileName)) {
String directory = Paths.get(path).getParent().toString(); String directory = Paths.get(path).getParent().toString();
preferences.setDirectoryChooserPath(directory); preferences.setDirectoryChooserPath(directory);
PersistenceManager<PaymentAccountList> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, keyRing); PersistenceManager<PaymentAccountList> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, null);
persistenceManager.readPersisted(fileName, persisted -> { persistenceManager.readPersisted(fileName, persisted -> {
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
HashSet<PaymentAccount> paymentAccounts = new HashSet<>(); HashSet<PaymentAccount> paymentAccounts = new HashSet<>();

View File

@ -1740,6 +1740,7 @@ message PreferencesPayload {
string buy_screen_crypto_currency_code = 60; string buy_screen_crypto_currency_code = 60;
string sell_screen_crypto_currency_code = 61; string sell_screen_crypto_currency_code = 61;
bool split_offer_output = 62; bool split_offer_output = 62;
bool use_sound_for_notifications = 63;
} }
message AutoConfirmSettings { message AutoConfirmSettings {