mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-10-01 01:35:48 -04:00
Compare commits
7 Commits
0a7d03f687
...
256b9db479
Author | SHA1 | Date | |
---|---|---|---|
|
256b9db479 | ||
|
8696c71051 | ||
|
b940021d99 | ||
|
b2a6708ac1 | ||
|
3ffda0fdd1 | ||
|
3e3f3085f8 | ||
|
60b91d3d23 |
60
.github/workflows/build.yml
vendored
60
.github/workflows/build.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
name: cached-localnet
|
||||
path: .localnet
|
||||
- name: Install dependencies
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y rpm
|
||||
@ -52,26 +52,68 @@ jobs:
|
||||
./gradlew clean build --refresh-keys --refresh-dependencies
|
||||
./gradlew packageInstallers
|
||||
working-directory: .
|
||||
|
||||
# get version from jar
|
||||
- name: Set Version Unix
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-13' }}
|
||||
run: |
|
||||
export VERSION=$(ls desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 | grep -Eo 'desktop-[0-9]+\.[0-9]+\.[0-9]+' | sed 's/desktop-//')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
- name: Set Version Windows
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
run: |
|
||||
$VERSION = (Get-ChildItem -Path desktop\build\temp-*/binaries\desktop-*.jar.SHA-256).Name -replace 'desktop-', '' -replace '-.*', ''
|
||||
"VERSION=$VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
shell: powershell
|
||||
|
||||
- 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: |
|
||||
mkdir ${{ github.workspace }}/release
|
||||
if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then
|
||||
mv desktop/build/temp-*/binaries/haveno-*.rpm ${{ github.workspace }}/release
|
||||
mv desktop/build/temp-*/binaries/haveno_*.deb ${{ github.workspace }}/release
|
||||
if [ "${{ matrix.os }}" == "ubuntu-22.04" ]; then
|
||||
mkdir ${{ github.workspace }}/release-rpm
|
||||
mkdir ${{ github.workspace }}/release-deb
|
||||
mv desktop/build/temp-*/binaries/haveno-*.rpm ${{ github.workspace }}/release-rpm/Haveno-${{ env.VERSION }}-x86_64.rpm
|
||||
mv desktop/build/temp-*/binaries/haveno_*.deb ${{ github.workspace }}/release-deb/Haveno-${{ env.VERSION }}-x86_64.deb
|
||||
else
|
||||
mv desktop/build/temp-*/binaries/Haveno-*.dmg ${{ github.workspace }}/release
|
||||
mv desktop/build/temp-*/binaries/Haveno-*.dmg ${{ github.workspace }}/release/Haveno-${{ env.VERSION }}.dmg
|
||||
fi
|
||||
mv desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release
|
||||
cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release
|
||||
cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release-deb
|
||||
cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release-rpm
|
||||
shell: bash
|
||||
- name: Move Release Files on Windows
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
run: |
|
||||
mkdir ${{ github.workspace }}/release
|
||||
Move-Item -Path desktop\build\temp-*/binaries\Haveno-*.exe -Destination ${{ github.workspace }}/release
|
||||
Move-Item -Path desktop\build\temp-*/binaries\Haveno-*.exe -Destination ${{ github.workspace }}/release/Haveno-${{ env.VERSION }}.exe
|
||||
Move-Item -Path desktop\build\temp-*/binaries\desktop-*.jar.SHA-256 -Destination ${{ github.workspace }}/release
|
||||
shell: powershell
|
||||
|
||||
# win
|
||||
- uses: actions/upload-artifact@v3
|
||||
name: "Windows artifacts"
|
||||
if: ${{ matrix.os == 'windows-latest'}}
|
||||
with:
|
||||
name: HavenoInstaller-${{ matrix.os }}
|
||||
name: haveno-windows
|
||||
path: ${{ github.workspace }}/release
|
||||
# macos
|
||||
- uses: actions/upload-artifact@v3
|
||||
name: "macOS artifacts"
|
||||
if: ${{ matrix.os == 'macos-13' }}
|
||||
with:
|
||||
name: haveno-macos
|
||||
path: ${{ github.workspace }}/release
|
||||
# linux
|
||||
- uses: actions/upload-artifact@v3
|
||||
name: "Linux - deb artifact"
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||
with:
|
||||
name: haveno-linux-deb
|
||||
path: ${{ github.workspace }}/release-deb
|
||||
- uses: actions/upload-artifact@v3
|
||||
name: "Linux - rpm artifact"
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||
with:
|
||||
name: haveno-linux-rpm
|
||||
path: ${{ github.workspace }}/release-rpm
|
@ -264,11 +264,11 @@ public class CoreApi {
|
||||
}
|
||||
|
||||
public void startXmrNode(XmrNodeSettings settings) throws IOException {
|
||||
xmrLocalNode.startNode(settings);
|
||||
xmrLocalNode.start(settings);
|
||||
}
|
||||
|
||||
public void stopXmrNode() {
|
||||
xmrLocalNode.stopNode();
|
||||
xmrLocalNode.stop();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -3,7 +3,11 @@ package haveno.core.api;
|
||||
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;
|
||||
@ -46,7 +50,15 @@ public class CoreNotificationService {
|
||||
.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())
|
||||
@ -57,6 +69,7 @@ public class CoreNotificationService {
|
||||
}
|
||||
|
||||
public void sendChatNotification(ChatMessage chatMessage) {
|
||||
HavenoUtils.playChimeSound();
|
||||
sendNotification(NotificationMessage.newBuilder()
|
||||
.setType(NotificationType.CHAT_MESSAGE)
|
||||
.setTimestamp(System.currentTimeMillis())
|
||||
|
@ -628,7 +628,7 @@ public final class XmrConnectionService {
|
||||
if (connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored()) {
|
||||
try {
|
||||
log.info("Starting local node");
|
||||
xmrLocalNode.startMoneroNode();
|
||||
xmrLocalNode.start();
|
||||
} catch (Exception e) {
|
||||
log.error("Unable to start local monero node, error={}\n", e.getMessage(), e);
|
||||
}
|
||||
@ -741,6 +741,12 @@ public final class XmrConnectionService {
|
||||
// connected to daemon
|
||||
isConnected = true;
|
||||
|
||||
// determine if blockchain is syncing locally
|
||||
boolean blockchainSyncing = lastInfo.getHeight().equals(lastInfo.getHeightWithoutBootstrap()) || (lastInfo.getTargetHeight().equals(0l) && lastInfo.getHeightWithoutBootstrap().equals(0l)); // blockchain is syncing if height equals height without bootstrap, or target height and height without bootstrap both equal 0
|
||||
|
||||
// write sync status to preferences
|
||||
preferences.getXmrNodeSettings().setSyncBlockchain(blockchainSyncing);
|
||||
|
||||
// throttle warnings if daemon not synced
|
||||
if (!isSyncedWithinTolerance() && System.currentTimeMillis() - lastLogDaemonNotSyncedTimestamp > HavenoUtils.LOG_DAEMON_NOT_SYNCED_WARN_PERIOD_MS) {
|
||||
log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), getTargetHeight());
|
||||
|
@ -150,16 +150,16 @@ public class XmrLocalNode {
|
||||
/**
|
||||
* Start a local Monero node from settings.
|
||||
*/
|
||||
public void startMoneroNode() throws IOException {
|
||||
public void start() throws IOException {
|
||||
var settings = preferences.getXmrNodeSettings();
|
||||
this.startNode(settings);
|
||||
this.start(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start local Monero node. Throws MoneroError if the node cannot be started.
|
||||
* Persist the settings to preferences if the node started successfully.
|
||||
*/
|
||||
public void startNode(XmrNodeSettings settings) throws IOException {
|
||||
public void start(XmrNodeSettings settings) throws IOException {
|
||||
if (isDetected()) throw new IllegalStateException("Local Monero node already online");
|
||||
|
||||
log.info("Starting local Monero node: " + settings);
|
||||
@ -177,6 +177,11 @@ public class XmrLocalNode {
|
||||
args.add("--bootstrap-daemon-address=" + bootstrapUrl);
|
||||
}
|
||||
|
||||
var syncBlockchain = settings.getSyncBlockchain();
|
||||
if (syncBlockchain != null && !syncBlockchain) {
|
||||
args.add("--no-sync");
|
||||
}
|
||||
|
||||
var flags = settings.getStartupFlags();
|
||||
if (flags != null) {
|
||||
args.addAll(flags);
|
||||
@ -191,7 +196,7 @@ public class XmrLocalNode {
|
||||
* Stop the current local Monero node if we own its process.
|
||||
* Does not remove the last XmrNodeSettings.
|
||||
*/
|
||||
public void stopNode() {
|
||||
public void stop() {
|
||||
if (!isDetected()) throw new IllegalStateException("Local Monero node is not running");
|
||||
if (daemon.getProcess() == null || !daemon.getProcess().isAlive()) throw new IllegalStateException("Cannot stop local Monero node because we don't own its process"); // TODO (woodser): remove isAlive() check after monero-java 0.5.4 which nullifies internal process
|
||||
daemon.stopProcess();
|
||||
|
@ -98,7 +98,7 @@ public class XmrBalanceInfo implements Payload {
|
||||
public String toString() {
|
||||
return "XmrBalanceInfo{" +
|
||||
"balance=" + balance +
|
||||
"unlockedBalance=" + availableBalance +
|
||||
", unlockedBalance=" + availableBalance +
|
||||
", lockedBalance=" + pendingBalance +
|
||||
", reservedOfferBalance=" + reservedOfferBalance +
|
||||
", reservedTradeBalance=" + reservedTradeBalance +
|
||||
|
@ -53,6 +53,7 @@ import haveno.core.alert.Alert;
|
||||
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;
|
||||
@ -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;
|
||||
@ -228,6 +232,7 @@ public class HavenoSetup {
|
||||
User user,
|
||||
AlertManager alertManager,
|
||||
Config config,
|
||||
CoreContext coreContext,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
TorSetup torSetup,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
@ -253,6 +258,7 @@ public class HavenoSetup {
|
||||
this.user = user;
|
||||
this.alertManager = alertManager;
|
||||
this.config = config;
|
||||
this.coreContext = coreContext;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.torSetup = torSetup;
|
||||
this.formatter = formatter;
|
||||
@ -263,6 +269,7 @@ public class HavenoSetup {
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
|
||||
HavenoUtils.havenoSetup = this;
|
||||
HavenoUtils.preferences = preferences;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -199,7 +199,7 @@ public abstract class SupportManager {
|
||||
if (dispute.isClosed()) dispute.reOpen();
|
||||
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
|
||||
} else if (dispute.isClosed()) {
|
||||
trade.pollWalletNormallyForMs(30000); // sync to check for payout
|
||||
trade.pollWalletNormallyForMs(60000); // sync to check for payout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
disputeResult.getChatMessage().setArrived(true);
|
||||
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG);
|
||||
trade.pollWalletNormallyForMs(30000);
|
||||
trade.pollWalletNormallyForMs(60000);
|
||||
requestPersistence(trade);
|
||||
resultHandler.handleResult();
|
||||
}
|
||||
|
@ -361,7 +361,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
requestPersistence(trade);
|
||||
|
||||
// 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.setDisputeState(Trade.DisputeState.DISPUTE_CLOSED);
|
||||
String warningMsg = "Error processing dispute closed message: " + e.getMessage() + "\n\nOpen another dispute to try again (ctrl+o).";
|
||||
|
@ -27,7 +27,9 @@ import haveno.common.crypto.Hash;
|
||||
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;
|
||||
@ -36,9 +38,12 @@ import haveno.core.support.dispute.arbitration.ArbitrationManager;
|
||||
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;
|
||||
@ -53,6 +58,13 @@ import java.util.Date;
|
||||
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;
|
||||
@ -110,11 +122,18 @@ public class HavenoUtils {
|
||||
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;
|
||||
@ -510,19 +529,83 @@ public class HavenoUtils {
|
||||
havenoSetup.getTopErrorMsg().set(msg);
|
||||
}
|
||||
|
||||
public static boolean isConnectionRefused(Exception e) {
|
||||
public static boolean isConnectionRefused(Throwable e) {
|
||||
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");
|
||||
}
|
||||
|
||||
public static boolean isUnresponsive(Exception e) {
|
||||
public static boolean isUnresponsive(Throwable 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");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -649,6 +649,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
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(() -> {
|
||||
@ -1158,7 +1159,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
|
||||
private void handleWalletError(Exception e, MoneroRpcConnection sourceConnection) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -1278,7 +1279,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw 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);
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
@ -1350,14 +1351,13 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
try {
|
||||
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
|
||||
if (result.getSignedMultisigTxHex() == null) throw new IllegalArgumentException("Error signing payout tx, signed multisig hex is null");
|
||||
payoutTxHex = result.getSignedMultisigTxHex();
|
||||
setPayoutTxHex(payoutTxHex);
|
||||
setPayoutTxHex(result.getSignedMultisigTxHex());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
// describe result
|
||||
describedTxSet = wallet.describeMultisigTxSet(payoutTxHex);
|
||||
describedTxSet = wallet.describeMultisigTxSet(getPayoutTxHex());
|
||||
payoutTx = describedTxSet.getTxs().get(0);
|
||||
updatePayout(payoutTx);
|
||||
|
||||
@ -1377,14 +1377,16 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
requestPersistence();
|
||||
|
||||
// submit payout tx
|
||||
if (publish) {
|
||||
boolean doPublish = publish && !isPayoutPublished();
|
||||
if (doPublish) {
|
||||
try {
|
||||
wallet.submitMultisigTxHex(payoutTxHex);
|
||||
wallet.submitMultisigTxHex(getPayoutTxHex());
|
||||
setPayoutStatePublished();
|
||||
} catch (Exception e) {
|
||||
if (isPayoutPublished()) throw new IllegalStateException("Payout tx already published for " + getClass().getSimpleName() + " " + getShortId());
|
||||
if (HavenoUtils.isNotEnoughSigners(e)) throw new IllegalArgumentException(e);
|
||||
throw new RuntimeException("Failed to submit payout tx for " + getClass().getSimpleName() + " " + getId(), e);
|
||||
if (!isPayoutPublished()) {
|
||||
if (HavenoUtils.isTransactionRejected(e) || HavenoUtils.isNotEnoughSigners(e)) throw new IllegalArgumentException(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
|
||||
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());
|
||||
}
|
||||
@ -2840,6 +2843,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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.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;
|
||||
@ -134,7 +133,6 @@ import lombok.Setter;
|
||||
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;
|
||||
|
||||
@ -258,7 +256,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
|
||||
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));
|
||||
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);
|
||||
|
@ -120,7 +120,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||
} catch (Throwable t) {
|
||||
|
||||
// do not reprocess illegal argument
|
||||
if (t instanceof IllegalArgumentException) {
|
||||
if (HavenoUtils.isIllegal(t)) {
|
||||
trade.getSeller().setPaymentReceivedMessage(null); // do not reprocess
|
||||
trade.requestPersistence();
|
||||
}
|
||||
@ -151,6 +151,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||
boolean deferSignAndPublish = trade instanceof ArbitratorTrade && !isSigned && message.isDeferPublishPayout();
|
||||
if (deferSignAndPublish) {
|
||||
log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
trade.pollWalletNormallyForMs(60000);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (trade.isPayoutPublished()) break;
|
||||
HavenoUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5);
|
||||
|
@ -65,10 +65,10 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
|
||||
trade.processPayoutTx(trade.getPayoutTxHex(), false, true);
|
||||
}
|
||||
} 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();
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
@ -132,6 +132,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
||||
private final String xmrNodesFromOptions;
|
||||
@Getter
|
||||
private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode());
|
||||
@Getter
|
||||
private final BooleanProperty useSoundForNotificationsProperty = new SimpleBooleanProperty(prefPayload.isUseSoundForNotifications());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
@ -162,6 +164,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
||||
requestPersistence();
|
||||
});
|
||||
|
||||
useSoundForNotificationsProperty.addListener((ov) -> {
|
||||
prefPayload.setUseSoundForNotifications(useSoundForNotificationsProperty.get());
|
||||
requestPersistence();
|
||||
});
|
||||
|
||||
traditionalCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> {
|
||||
prefPayload.getTraditionalCurrencies().clear();
|
||||
prefPayload.getTraditionalCurrencies().addAll(traditionalCurrenciesAsObservable);
|
||||
@ -259,6 +266,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
||||
// set all properties
|
||||
useAnimationsProperty.set(prefPayload.isUseAnimations());
|
||||
useStandbyModeProperty.set(prefPayload.isUseStandbyMode());
|
||||
useSoundForNotificationsProperty.set(prefPayload.isUseSoundForNotifications());
|
||||
cssThemeProperty.set(prefPayload.getCssTheme());
|
||||
|
||||
|
||||
@ -697,6 +705,10 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
||||
this.useStandbyModeProperty.set(useStandbyMode);
|
||||
}
|
||||
|
||||
public void setUseSoundForNotifications(boolean useSoundForNotifications) {
|
||||
this.useSoundForNotificationsProperty.set(useSoundForNotifications);
|
||||
}
|
||||
|
||||
public void setTakeOfferSelectedPaymentAccountId(String value) {
|
||||
prefPayload.setTakeOfferSelectedPaymentAccountId(value);
|
||||
requestPersistence();
|
||||
@ -946,6 +958,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
||||
|
||||
void setUseStandbyMode(boolean useStandbyMode);
|
||||
|
||||
void setUseSoundForNotifications(boolean useSoundForNotifications);
|
||||
|
||||
void setTakeOfferSelectedPaymentAccountId(String value);
|
||||
|
||||
void setIgnoreDustThreshold(int value);
|
||||
|
@ -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
|
||||
@ -185,6 +186,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
||||
.setUseMarketNotifications(useMarketNotifications)
|
||||
.setUsePriceNotifications(usePriceNotifications)
|
||||
.setUseStandbyMode(useStandbyMode)
|
||||
.setUseSoundForNotifications(useSoundForNotifications)
|
||||
.setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent)
|
||||
.setIgnoreDustThreshold(ignoreDustThreshold)
|
||||
.setClearDataAfterDays(clearDataAfterDays)
|
||||
@ -280,6 +282,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
||||
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(),
|
||||
|
@ -111,6 +111,7 @@ public class Balances {
|
||||
|
||||
public XmrBalanceInfo getBalances() {
|
||||
synchronized (this) {
|
||||
if (availableBalance == null) return null;
|
||||
return new XmrBalanceInfo(availableBalance.longValue() + pendingBalance.longValue(),
|
||||
availableBalance.longValue(),
|
||||
pendingBalance.longValue(),
|
||||
@ -127,6 +128,9 @@ public class Balances {
|
||||
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();
|
||||
@ -160,8 +164,25 @@ public class Balances {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ public class XmrNodeSettings implements PersistableEnvelope {
|
||||
String bootstrapUrl;
|
||||
@Nullable
|
||||
List<String> startupFlags;
|
||||
@Nullable
|
||||
Boolean syncBlockchain;
|
||||
|
||||
public XmrNodeSettings() {
|
||||
}
|
||||
@ -43,7 +45,8 @@ public class XmrNodeSettings implements PersistableEnvelope {
|
||||
return new XmrNodeSettings(
|
||||
proto.getBlockchainPath(),
|
||||
proto.getBootstrapUrl(),
|
||||
proto.getStartupFlagsList());
|
||||
proto.getStartupFlagsList(),
|
||||
proto.getSyncBlockchain());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -52,6 +55,7 @@ public class XmrNodeSettings implements PersistableEnvelope {
|
||||
Optional.ofNullable(blockchainPath).ifPresent(e -> builder.setBlockchainPath(blockchainPath));
|
||||
Optional.ofNullable(bootstrapUrl).ifPresent(e -> builder.setBootstrapUrl(bootstrapUrl));
|
||||
Optional.ofNullable(startupFlags).ifPresent(e -> builder.addAllStartupFlags(startupFlags));
|
||||
Optional.ofNullable(syncBlockchain).ifPresent(e -> builder.setSyncBlockchain(syncBlockchain));
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
BIN
core/src/main/resources/cash_register.wav
Normal file
BIN
core/src/main/resources/cash_register.wav
Normal file
Binary file not shown.
BIN
core/src/main/resources/chime.wav
Normal file
BIN
core/src/main/resources/chime.wav
Normal file
Binary file not shown.
@ -1266,6 +1266,7 @@ setting.preferences.general=General preferences
|
||||
setting.preferences.explorer=Monero Explorer
|
||||
setting.preferences.deviation=Max. deviation from market price
|
||||
setting.preferences.avoidStandbyMode=Avoid standby mode
|
||||
setting.preferences.useSoundForNotifications=Play sounds for notifications
|
||||
setting.preferences.autoConfirmXMR=XMR auto-confirm
|
||||
setting.preferences.autoConfirmEnabled=Enabled
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
|
||||
|
@ -987,6 +987,7 @@ setting.preferences.general=Základní nastavení
|
||||
setting.preferences.explorer=Monero Explorer
|
||||
setting.preferences.deviation=Max. odchylka od tržní ceny
|
||||
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.autoConfirmEnabled=Povoleno
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Požadovaná potvrzení
|
||||
|
@ -987,6 +987,7 @@ setting.preferences.general=Allgemeine Voreinstellungen
|
||||
setting.preferences.explorer=Monero Explorer
|
||||
setting.preferences.deviation=Max. Abweichung vom Marktpreis
|
||||
setting.preferences.avoidStandbyMode=Standby Modus verhindern
|
||||
setting.preferences.useSoundForNotifications=Spiele Geräusche für Benachrichtigungen
|
||||
setting.preferences.autoConfirmXMR=XMR automatische Bestätigung
|
||||
setting.preferences.autoConfirmEnabled=Aktiviert
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Benötigte Bestätigungen
|
||||
|
@ -988,6 +988,7 @@ setting.preferences.general=Preferencias generales
|
||||
setting.preferences.explorer=Explorador Monero
|
||||
setting.preferences.deviation=Desviación máxima del precio de mercado
|
||||
setting.preferences.setting.preferences.avoidStandbyMode=Evitar modo en espera
|
||||
setting.preferences.useSoundForNotifications=Reproducir sonidos para notificaciones
|
||||
setting.preferences.autoConfirmXMR=Autoconfirmación XMR
|
||||
setting.preferences.autoConfirmEnabled=Habilitado
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Confirmaciones requeridas
|
||||
|
@ -984,6 +984,7 @@ setting.preferences.general=اولویتهای عمومی
|
||||
setting.preferences.explorer=Monero Explorer
|
||||
setting.preferences.deviation=حداکثر تفاوت از قیمت روز بازار
|
||||
setting.preferences.avoidStandbyMode=حالت «آماده باش» را نادیده بگیر
|
||||
setting.preferences.useSoundForNotifications=پخش صداها برای اعلانها
|
||||
setting.preferences.autoConfirmXMR=XMR auto-confirm
|
||||
setting.preferences.autoConfirmEnabled=Enabled
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
|
||||
|
@ -989,6 +989,7 @@ setting.preferences.general=Préférences générales
|
||||
setting.preferences.explorer=Exploreur Monero
|
||||
setting.preferences.deviation=Ecart maximal par rapport au prix du marché
|
||||
setting.preferences.avoidStandbyMode=Éviter le mode veille
|
||||
setting.preferences.useSoundForNotifications=Jouer des sons pour les notifications
|
||||
setting.preferences.autoConfirmXMR=Auto-confirmation XMR
|
||||
setting.preferences.autoConfirmEnabled=Activé
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Confirmations requises
|
||||
|
@ -986,6 +986,7 @@ setting.preferences.general=Preferenze generali
|
||||
setting.preferences.explorer=Monero Explorer
|
||||
setting.preferences.deviation=Deviazione massima del prezzo di mercato
|
||||
setting.preferences.avoidStandbyMode=Evita modalità standby
|
||||
setting.preferences.useSoundForNotifications=Riproduci suoni per le notifiche
|
||||
setting.preferences.autoConfirmXMR=XMR auto-confirm
|
||||
setting.preferences.autoConfirmEnabled=Enabled
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
|
||||
|
@ -987,6 +987,7 @@ setting.preferences.general=一般設定
|
||||
setting.preferences.explorer=ビットコインのエクスプローラ
|
||||
setting.preferences.deviation=市場価格からの最大偏差
|
||||
setting.preferences.avoidStandbyMode=スタンバイモードを避ける
|
||||
setting.preferences.useSoundForNotifications=通知音の再生
|
||||
setting.preferences.autoConfirmXMR=XMR自動確認
|
||||
setting.preferences.autoConfirmEnabled=有効されました
|
||||
setting.preferences.autoConfirmRequiredConfirmations=必要承認
|
||||
|
@ -988,6 +988,7 @@ setting.preferences.general=Preferências gerais
|
||||
setting.preferences.explorer=Monero Explorer
|
||||
setting.preferences.deviation=Desvio máx. do preço do mercado
|
||||
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.autoConfirmEnabled=Enabled
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
|
||||
|
@ -985,6 +985,7 @@ setting.preferences.general=Preferências gerais
|
||||
setting.preferences.explorer=Monero Explorer
|
||||
setting.preferences.deviation=Máx. desvio do preço de mercado
|
||||
setting.preferences.avoidStandbyMode=Evite o modo espera
|
||||
setting.preferences.useSoundForNotifications=Reproduzir sons para notificações
|
||||
setting.preferences.autoConfirmXMR=XMR auto-confirm
|
||||
setting.preferences.autoConfirmEnabled=Enabled
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
|
||||
|
@ -984,6 +984,7 @@ setting.preferences.general=Основные настройки
|
||||
setting.preferences.explorer=Monero Explorer
|
||||
setting.preferences.deviation=Макс. отклонение от рыночного курса
|
||||
setting.preferences.avoidStandbyMode=Избегать режима ожидания
|
||||
setting.preferences.useSoundForNotifications=Воспроизводить звуки для уведомлений
|
||||
setting.preferences.autoConfirmXMR=XMR auto-confirm
|
||||
setting.preferences.autoConfirmEnabled=Enabled
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
|
||||
|
@ -984,6 +984,7 @@ setting.preferences.general=การตั้งค่าทั่วไป
|
||||
setting.preferences.explorer=Monero Explorer
|
||||
setting.preferences.deviation=สูงสุด ส่วนเบี่ยงเบนจากราคาตลาด
|
||||
setting.preferences.avoidStandbyMode=หลีกเลี่ยงโหมดแสตนบายด์
|
||||
setting.preferences.useSoundForNotifications=เล่นเสียงสำหรับการแจ้งเตือน
|
||||
setting.preferences.autoConfirmXMR=XMR auto-confirm
|
||||
setting.preferences.autoConfirmEnabled=Enabled
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
|
||||
|
@ -1261,6 +1261,7 @@ setting.preferences.general=Genel tercihler
|
||||
setting.preferences.explorer=Monero Gezgini
|
||||
setting.preferences.deviation=Piyasa fiyatından maksimum sapma
|
||||
setting.preferences.avoidStandbyMode=Bekleme modundan kaçın
|
||||
setting.preferences.useSoundForNotifications=Bildirimler için sesleri çal
|
||||
setting.preferences.autoConfirmXMR=XMR otomatik onay
|
||||
setting.preferences.autoConfirmEnabled=Etkin
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Gerekli onaylar
|
||||
|
@ -986,6 +986,7 @@ setting.preferences.general=Tham khảo chung
|
||||
setting.preferences.explorer=Monero Explorer
|
||||
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.useSoundForNotifications=Phát âm thanh cho thông báo
|
||||
setting.preferences.autoConfirmXMR=XMR auto-confirm
|
||||
setting.preferences.autoConfirmEnabled=Enabled
|
||||
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
|
||||
|
@ -987,6 +987,7 @@ setting.preferences.general=通用偏好
|
||||
setting.preferences.explorer=比特币区块浏览器
|
||||
setting.preferences.deviation=与市场价格最大差价
|
||||
setting.preferences.avoidStandbyMode=避免待机模式
|
||||
setting.preferences.useSoundForNotifications=播放通知声音
|
||||
setting.preferences.autoConfirmXMR=XMR 自动确认
|
||||
setting.preferences.autoConfirmEnabled=启用
|
||||
setting.preferences.autoConfirmRequiredConfirmations=已要求确认
|
||||
|
@ -987,6 +987,7 @@ setting.preferences.general=通用偏好
|
||||
setting.preferences.explorer=比特幣區塊瀏覽器
|
||||
setting.preferences.deviation=與市場價格最大差價
|
||||
setting.preferences.avoidStandbyMode=避免待機模式
|
||||
setting.preferences.useSoundForNotifications=播放通知音效
|
||||
setting.preferences.autoConfirmXMR=XMR 自動確認
|
||||
setting.preferences.autoConfirmEnabled=啟用
|
||||
setting.preferences.autoConfirmRequiredConfirmations=已要求確認
|
||||
|
@ -18,7 +18,6 @@
|
||||
package haveno.desktop.main.account.content.cryptoaccounts;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import haveno.common.crypto.KeyRing;
|
||||
import haveno.common.file.CorruptedStorageFileHandler;
|
||||
import haveno.common.proto.persistable.PersistenceProtoResolver;
|
||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||
@ -55,7 +54,6 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
|
||||
private final String accountsFileName = "CryptoPaymentAccounts";
|
||||
private final PersistenceProtoResolver persistenceProtoResolver;
|
||||
private final CorruptedStorageFileHandler corruptedStorageFileHandler;
|
||||
private final KeyRing keyRing;
|
||||
|
||||
@Inject
|
||||
public CryptoAccountsDataModel(User user,
|
||||
@ -64,8 +62,7 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
|
||||
TradeManager tradeManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
PersistenceProtoResolver persistenceProtoResolver,
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler,
|
||||
KeyRing keyRing) {
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler) {
|
||||
this.user = user;
|
||||
this.preferences = preferences;
|
||||
this.openOfferManager = openOfferManager;
|
||||
@ -73,7 +70,6 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.persistenceProtoResolver = persistenceProtoResolver;
|
||||
this.corruptedStorageFileHandler = corruptedStorageFileHandler;
|
||||
this.keyRing = keyRing;
|
||||
setChangeListener = change -> fillAndSortPaymentAccounts();
|
||||
}
|
||||
|
||||
@ -157,12 +153,12 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
|
||||
ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream()
|
||||
.filter(paymentAccount -> paymentAccount instanceof AssetAccount)
|
||||
.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) {
|
||||
GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing);
|
||||
GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
|
||||
}
|
||||
|
||||
public int getNumPaymentAccounts() {
|
||||
|
@ -18,7 +18,6 @@
|
||||
package haveno.desktop.main.account.content.traditionalaccounts;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import haveno.common.crypto.KeyRing;
|
||||
import haveno.common.file.CorruptedStorageFileHandler;
|
||||
import haveno.common.proto.persistable.PersistenceProtoResolver;
|
||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||
@ -56,7 +55,6 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
|
||||
private final String accountsFileName = "FiatPaymentAccounts";
|
||||
private final PersistenceProtoResolver persistenceProtoResolver;
|
||||
private final CorruptedStorageFileHandler corruptedStorageFileHandler;
|
||||
private final KeyRing keyRing;
|
||||
|
||||
@Inject
|
||||
public TraditionalAccountsDataModel(User user,
|
||||
@ -65,8 +63,7 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
|
||||
TradeManager tradeManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
PersistenceProtoResolver persistenceProtoResolver,
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler,
|
||||
KeyRing keyRing) {
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler) {
|
||||
this.user = user;
|
||||
this.preferences = preferences;
|
||||
this.openOfferManager = openOfferManager;
|
||||
@ -74,7 +71,6 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.persistenceProtoResolver = persistenceProtoResolver;
|
||||
this.corruptedStorageFileHandler = corruptedStorageFileHandler;
|
||||
this.keyRing = keyRing;
|
||||
setChangeListener = change -> fillAndSortPaymentAccounts();
|
||||
}
|
||||
|
||||
@ -159,12 +155,12 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
|
||||
ArrayList<PaymentAccount> accounts = new ArrayList<>(user.getPaymentAccounts().stream()
|
||||
.filter(paymentAccount -> !(paymentAccount instanceof AssetAccount))
|
||||
.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) {
|
||||
GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing);
|
||||
GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
|
||||
}
|
||||
|
||||
public int getNumPaymentAccounts() {
|
||||
|
@ -108,7 +108,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox;
|
||||
|
||||
private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically,
|
||||
avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle,
|
||||
avoidStandbyMode, useSoundForNotifications, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle,
|
||||
notifyOnPreReleaseToggle;
|
||||
private int gridRow = 0;
|
||||
private int displayCurrenciesGridRowIndex = 0;
|
||||
@ -209,7 +209,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void initializeGeneralOptions() {
|
||||
int titledGroupBgRowSpan = displayStandbyModeFeature ? 7 : 6;
|
||||
int titledGroupBgRowSpan = displayStandbyModeFeature ? 8 : 7;
|
||||
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, titledGroupBgRowSpan, Res.get("setting.preferences.general"));
|
||||
GridPane.setColumnSpan(titledGroupBg, 1);
|
||||
|
||||
@ -285,6 +285,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
avoidStandbyMode = addSlideToggleButton(root, ++gridRow,
|
||||
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() {
|
||||
@ -518,6 +521,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
GridPane.setHgrow(resetDontShowAgainButton, Priority.ALWAYS);
|
||||
GridPane.setColumnIndex(resetDontShowAgainButton, 0);
|
||||
}
|
||||
|
||||
private void initializeAutoConfirmOptions() {
|
||||
GridPane autoConfirmGridPane = new GridPane();
|
||||
GridPane.setHgrow(autoConfirmGridPane, Priority.ALWAYS);
|
||||
@ -790,6 +794,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
||||
} else {
|
||||
preferences.setUseStandbyMode(false);
|
||||
}
|
||||
|
||||
useSoundForNotifications.setSelected(preferences.isUseSoundForNotifications());
|
||||
useSoundForNotifications.setOnAction(e -> preferences.setUseSoundForNotifications(useSoundForNotifications.isSelected()));
|
||||
}
|
||||
|
||||
private void activateAutoConfirmPreferences() {
|
||||
|
@ -28,7 +28,6 @@ import com.googlecode.jcsv.writer.internal.CSVWriterBuilder;
|
||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.config.Config;
|
||||
import haveno.common.crypto.KeyRing;
|
||||
import haveno.common.file.CorruptedStorageFileHandler;
|
||||
import haveno.common.persistence.PersistenceManager;
|
||||
import haveno.common.proto.persistable.PersistableEnvelope;
|
||||
@ -168,12 +167,11 @@ public class GUIUtil {
|
||||
Preferences preferences,
|
||||
Stage stage,
|
||||
PersistenceProtoResolver persistenceProtoResolver,
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler,
|
||||
KeyRing keyRing) {
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler) {
|
||||
if (!accounts.isEmpty()) {
|
||||
String directory = getDirectoryFromChooser(preferences, stage);
|
||||
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);
|
||||
persistenceManager.initialize(paymentAccounts, fileName, PersistenceManager.Source.PRIVATE_LOW_PRIO);
|
||||
persistenceManager.persistNow(() -> {
|
||||
@ -193,8 +191,7 @@ public class GUIUtil {
|
||||
Preferences preferences,
|
||||
Stage stage,
|
||||
PersistenceProtoResolver persistenceProtoResolver,
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler,
|
||||
KeyRing keyRing) {
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler) {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
File initDir = new File(preferences.getDirectoryChooserPath());
|
||||
if (initDir.isDirectory()) {
|
||||
@ -207,7 +204,7 @@ public class GUIUtil {
|
||||
if (Paths.get(path).getFileName().toString().equals(fileName)) {
|
||||
String directory = Paths.get(path).getParent().toString();
|
||||
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 -> {
|
||||
StringBuilder msg = new StringBuilder();
|
||||
HashSet<PaymentAccount> paymentAccounts = new HashSet<>();
|
||||
|
@ -1740,6 +1740,7 @@ message PreferencesPayload {
|
||||
string buy_screen_crypto_currency_code = 60;
|
||||
string sell_screen_crypto_currency_code = 61;
|
||||
bool split_offer_output = 62;
|
||||
bool use_sound_for_notifications = 63;
|
||||
}
|
||||
|
||||
message AutoConfirmSettings {
|
||||
@ -1754,6 +1755,7 @@ message XmrNodeSettings {
|
||||
string blockchain_path = 1;
|
||||
string bootstrap_url = 2;
|
||||
repeated string startup_flags = 3;
|
||||
bool sync_blockchain = 4;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
Loading…
Reference in New Issue
Block a user