Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

play sounds on notifications #1284 #1295

Merged
merged 1 commit into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import com.google.inject.Singleton;
import haveno.core.api.model.TradeInfo;
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.Phase;
import haveno.proto.grpc.NotificationMessage;
import haveno.proto.grpc.NotificationMessage.NotificationType;
import java.util.Iterator;
Expand Down Expand Up @@ -46,7 +50,15 @@ public void sendAppInitializedNotification() {
.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()
.setType(NotificationType.TRADE_UPDATE)
.setTrade(TradeInfo.toTradeInfo(trade).toProtoMessage())
Expand All @@ -57,6 +69,7 @@ public void sendTradeNotification(Trade trade, String title, String message) {
}

public void sendChatNotification(ChatMessage chatMessage) {
HavenoUtils.playChimeSound();
sendNotification(NotificationMessage.newBuilder()
.setType(NotificationType.CHAT_MESSAGE)
.setTimestamp(System.currentTimeMillis())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public static XmrBalanceInfo fromProto(haveno.proto.grpc.XmrBalanceInfo proto) {
public String toString() {
return "XmrBalanceInfo{" +
"balance=" + balance +
"unlockedBalance=" + availableBalance +
", unlockedBalance=" + availableBalance +
", lockedBalance=" + pendingBalance +
", reservedOfferBalance=" + reservedOfferBalance +
", reservedTradeBalance=" + reservedTradeBalance +
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/java/haveno/core/app/HavenoSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import haveno.core.alert.AlertManager;
import haveno.core.alert.PrivateNotificationManager;
import haveno.core.alert.PrivateNotificationPayload;
import haveno.core.api.CoreContext;
import haveno.core.api.XmrConnectionService;
import haveno.core.api.XmrLocalNode;
import haveno.core.locale.Res;
Expand Down Expand Up @@ -131,7 +132,10 @@ public class HavenoSetup {
private final Preferences preferences;
private final User user;
private final AlertManager alertManager;
@Getter
private final Config config;
@Getter
private final CoreContext coreContext;
private final AccountAgeWitnessService accountAgeWitnessService;
private final TorSetup torSetup;
private final CoinFormatter formatter;
Expand Down Expand Up @@ -228,6 +232,7 @@ public HavenoSetup(DomainInitialisation domainInitialisation,
User user,
AlertManager alertManager,
Config config,
CoreContext coreContext,
AccountAgeWitnessService accountAgeWitnessService,
TorSetup torSetup,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
Expand All @@ -253,6 +258,7 @@ public HavenoSetup(DomainInitialisation domainInitialisation,
this.user = user;
this.alertManager = alertManager;
this.config = config;
this.coreContext = coreContext;
this.accountAgeWitnessService = accountAgeWitnessService;
this.torSetup = torSetup;
this.formatter = formatter;
Expand All @@ -263,6 +269,7 @@ public HavenoSetup(DomainInitialisation domainInitialisation,
this.arbitrationManager = arbitrationManager;

HavenoUtils.havenoSetup = this;
HavenoUtils.preferences = preferences;
}

///////////////////////////////////////////////////////////////////////////////////////////
Expand Down
75 changes: 75 additions & 0 deletions core/src/main/java/haveno/core/trade/HavenoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
import haveno.common.crypto.KeyRing;
import haveno.common.crypto.PubKeyRing;
import haveno.common.crypto.Sig;
import haveno.common.file.FileUtil;
import haveno.common.util.Utilities;
import haveno.core.api.CoreNotificationService;
import haveno.core.api.XmrConnectionService;
import haveno.core.app.HavenoSetup;
import haveno.core.offer.OfferPayload;
Expand All @@ -36,9 +38,12 @@
import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator;
import haveno.core.trade.messages.PaymentReceivedMessage;
import haveno.core.trade.messages.PaymentSentMessage;
import haveno.core.user.Preferences;
import haveno.core.util.JsonUtil;
import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.NodeAddress;

import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
Expand All @@ -53,6 +58,13 @@
import java.util.List;
import java.util.Locale;
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 monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroOutput;
Expand Down Expand Up @@ -110,11 +122,18 @@ public static Object getWalletFunctionLock() {
public static XmrWalletService xmrWalletService;
public static XmrConnectionService xmrConnectionService;
public static OpenOfferManager openOfferManager;
public static CoreNotificationService notificationService;
public static Preferences preferences;

public static boolean isSeedNode() {
return havenoSetup == null;
}

public static boolean isDaemon() {
if (isSeedNode()) return true;
return havenoSetup.getCoreContext().isApiUser();
}

@SuppressWarnings("unused")
public static Date getReleaseDate() {
if (RELEASE_DATE == null) return null;
Expand Down Expand Up @@ -533,4 +552,60 @@ public static boolean isTransactionRejected(Throwable e) {
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();
}
}
8 changes: 8 additions & 0 deletions core/src/main/java/haveno/core/trade/Trade.java
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ public void initialize(ProcessModelServiceProvider serviceProvider) {
ThreadUtils.submitToPool(() -> {
if (newValue == Trade.Phase.DEPOSIT_REQUESTED) startPolling();
if (newValue == Trade.Phase.DEPOSITS_PUBLISHED) onDepositsPublished();
if (newValue == Trade.Phase.PAYMENT_SENT) onPaymentSent();
if (isDepositsPublished() && !isPayoutUnlocked()) updatePollPeriod();
if (isPaymentReceived()) {
UserThread.execute(() -> {
Expand Down Expand Up @@ -2833,6 +2834,7 @@ private void onDepositsPublished() {
// close open offer or reset address entries
if (this instanceof MakerTrade) {
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 {
getXmrWalletService().resetAddressEntriesForOpenOffer(getId());
}
Expand All @@ -2841,6 +2843,12 @@ private void onDepositsPublished() {
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
///////////////////////////////////////////////////////////////////////////////////////////
Expand Down
14 changes: 3 additions & 11 deletions core/src/main/java/haveno/core/trade/TradeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
import haveno.core.support.dispute.messages.DisputeClosedMessage;
import haveno.core.support.dispute.messages.DisputeOpenedMessage;
import haveno.core.trade.Trade.DisputeState;
import haveno.core.trade.Trade.Phase;
import haveno.core.trade.failed.FailedTradesManager;
import haveno.core.trade.handlers.TradeResultHandler;
import haveno.core.trade.messages.DepositRequest;
Expand Down Expand Up @@ -134,7 +133,6 @@
import monero.daemon.model.MoneroTx;
import org.bitcoinj.core.Coin;
import org.bouncycastle.crypto.params.KeyParameter;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -258,7 +256,9 @@ public TradeManager(User user,

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;
}


Expand Down Expand Up @@ -599,14 +599,6 @@ private void handleInitTradeRequest(InitTradeRequest request, NodeAddress sender
initTradeAndProtocol(trade, createTradeProtocol(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
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
log.warn("Maker error during trade initialization: " + errorMessage);
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/java/haveno/core/user/Preferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ public boolean isUseTorForXmr() {
private final String xmrNodesFromOptions;
@Getter
private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode());
@Getter
private final BooleanProperty useSoundForNotificationsProperty = new SimpleBooleanProperty(prefPayload.isUseSoundForNotifications());

///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
Expand Down Expand Up @@ -162,6 +164,11 @@ public Preferences(PersistenceManager<PreferencesPayload> persistenceManager,
requestPersistence();
});

useSoundForNotificationsProperty.addListener((ov) -> {
prefPayload.setUseSoundForNotifications(useSoundForNotificationsProperty.get());
requestPersistence();
});

traditionalCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> {
prefPayload.getTraditionalCurrencies().clear();
prefPayload.getTraditionalCurrencies().addAll(traditionalCurrenciesAsObservable);
Expand Down Expand Up @@ -259,6 +266,7 @@ private void setupPreferences() {
// set all properties
useAnimationsProperty.set(prefPayload.isUseAnimations());
useStandbyModeProperty.set(prefPayload.isUseStandbyMode());
useSoundForNotificationsProperty.set(prefPayload.isUseSoundForNotifications());
cssThemeProperty.set(prefPayload.getCssTheme());


Expand Down Expand Up @@ -697,6 +705,10 @@ public void setUseStandbyMode(boolean useStandbyMode) {
this.useStandbyModeProperty.set(useStandbyMode);
}

public void setUseSoundForNotifications(boolean useSoundForNotifications) {
this.useSoundForNotificationsProperty.set(useSoundForNotifications);
}

public void setTakeOfferSelectedPaymentAccountId(String value) {
prefPayload.setTakeOfferSelectedPaymentAccountId(value);
requestPersistence();
Expand Down Expand Up @@ -946,6 +958,8 @@ private interface ExcludesDelegateMethods {

void setUseStandbyMode(boolean useStandbyMode);

void setUseSoundForNotifications(boolean useSoundForNotifications);

void setTakeOfferSelectedPaymentAccountId(String value);

void setIgnoreDustThreshold(int value);
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/java/haveno/core/user/PreferencesPayload.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
private boolean useMarketNotifications = true;
private boolean usePriceNotifications = true;
private boolean useStandbyMode = false;
private boolean useSoundForNotifications = true;
@Nullable
private String rpcUser;
@Nullable
Expand Down Expand Up @@ -185,6 +186,7 @@ public Message toProtoMessage() {
.setUseMarketNotifications(useMarketNotifications)
.setUsePriceNotifications(usePriceNotifications)
.setUseStandbyMode(useStandbyMode)
.setUseSoundForNotifications(useSoundForNotifications)
.setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent)
.setIgnoreDustThreshold(ignoreDustThreshold)
.setClearDataAfterDays(clearDataAfterDays)
Expand Down Expand Up @@ -280,6 +282,7 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co
proto.getUseMarketNotifications(),
proto.getUsePriceNotifications(),
proto.getUseStandbyMode(),
proto.getUseSoundForNotifications(),
proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(),
proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(),
proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(),
Expand Down
23 changes: 22 additions & 1 deletion core/src/main/java/haveno/core/xmr/Balances.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ public void onBalanceChanged(BigInteger balance) {

public XmrBalanceInfo getBalances() {
synchronized (this) {
if (availableBalance == null) return null;
return new XmrBalanceInfo(availableBalance.longValue() + pendingBalance.longValue(),
availableBalance.longValue(),
pendingBalance.longValue(),
Expand All @@ -127,6 +128,9 @@ private void doUpdateBalances() {
synchronized (this) {
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {

// get non-trade balance before
BigInteger balanceSumBefore = getNonTradeBalanceSum();

// get wallet balances
BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getBalance();
availableBalance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getAvailableBalance();
Expand Down Expand Up @@ -160,8 +164,25 @@ private void doUpdateBalances() {
reservedBalance = reservedOfferBalance.add(reservedTradeBalance);

// 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 added core/src/main/resources/cash_register.wav
Binary file not shown.
Binary file added core/src/main/resources/chime.wav
Binary file not shown.
Loading
Loading