diff --git a/core/src/main/java/io/bitsquare/alert/Alert.java b/core/src/main/java/io/bitsquare/alert/Alert.java
index a6cb079a9d..3ce8eae782 100644
--- a/core/src/main/java/io/bitsquare/alert/Alert.java
+++ b/core/src/main/java/io/bitsquare/alert/Alert.java
@@ -57,7 +57,7 @@ public final class Alert implements StoragePayload {
}
}
- public void setSigAndStoragePubKey(String signatureAsBase64, PublicKey storagePublicKey) {
+ public void setSigAndPubKey(String signatureAsBase64, PublicKey storagePublicKey) {
this.signatureAsBase64 = signatureAsBase64;
this.storagePublicKey = storagePublicKey;
this.storagePublicKeyBytes = new X509EncodedKeySpec(this.storagePublicKey.getEncoded()).getEncoded();
diff --git a/core/src/main/java/io/bitsquare/alert/AlertManager.java b/core/src/main/java/io/bitsquare/alert/AlertManager.java
index 14528b4ec5..24a697de5a 100644
--- a/core/src/main/java/io/bitsquare/alert/AlertManager.java
+++ b/core/src/main/java/io/bitsquare/alert/AlertManager.java
@@ -125,7 +125,7 @@ public class AlertManager {
private void signAndAddSignatureToAlertMessage(Alert alert) {
String alertMessageAsHex = Utils.HEX.encode(alert.message.getBytes());
String signatureAsBase64 = alertSigningKey.signMessage(alertMessageAsHex);
- alert.setSigAndStoragePubKey(signatureAsBase64, keyRing.getSignatureKeyPair().getPublic());
+ alert.setSigAndPubKey(signatureAsBase64, keyRing.getSignatureKeyPair().getPublic());
}
private boolean verifySignature(Alert alert) {
diff --git a/core/src/main/java/io/bitsquare/alert/AlertModule.java b/core/src/main/java/io/bitsquare/alert/AlertModule.java
index 15224b2e48..d27b32f011 100644
--- a/core/src/main/java/io/bitsquare/alert/AlertModule.java
+++ b/core/src/main/java/io/bitsquare/alert/AlertModule.java
@@ -34,5 +34,7 @@ public class AlertModule extends AppModule {
protected final void configure() {
bind(AlertManager.class).in(Singleton.class);
bind(AlertService.class).in(Singleton.class);
+ bind(PrivateNotificationManager.class).in(Singleton.class);
+ bind(PrivateNotificationService.class).in(Singleton.class);
}
}
diff --git a/core/src/main/java/io/bitsquare/alert/PrivateNotification.java b/core/src/main/java/io/bitsquare/alert/PrivateNotification.java
new file mode 100644
index 0000000000..d84e6d3848
--- /dev/null
+++ b/core/src/main/java/io/bitsquare/alert/PrivateNotification.java
@@ -0,0 +1,63 @@
+/*
+ * This file is part of Bitsquare.
+ *
+ * Bitsquare is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bitsquare is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bitsquare. If not, see .
+ */
+
+package io.bitsquare.alert;
+
+import io.bitsquare.app.Version;
+import io.bitsquare.common.crypto.Sig;
+import io.bitsquare.common.wire.Payload;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.spec.X509EncodedKeySpec;
+
+public final class PrivateNotification implements Payload {
+ // That object is sent over the wire, so we need to take care of version compatibility.
+ private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
+ private static final Logger log = LoggerFactory.getLogger(PrivateNotification.class);
+
+ public final String message;
+ private String signatureAsBase64;
+ private transient PublicKey publicKey;
+ private byte[] publicKeyBytes;
+
+ public PrivateNotification(String message) {
+ this.message = message;
+ }
+
+ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
+ try {
+ in.defaultReadObject();
+ publicKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+ } catch (Throwable t) {
+ log.warn("Exception at readObject: " + t.getMessage());
+ }
+ }
+
+ public void setSigAndPubKey(String signatureAsBase64, PublicKey storagePublicKey) {
+ this.signatureAsBase64 = signatureAsBase64;
+ this.publicKey = storagePublicKey;
+ this.publicKeyBytes = new X509EncodedKeySpec(this.publicKey.getEncoded()).getEncoded();
+ }
+
+ public String getSignatureAsBase64() {
+ return signatureAsBase64;
+ }
+}
diff --git a/core/src/main/java/io/bitsquare/alert/PrivateNotificationManager.java b/core/src/main/java/io/bitsquare/alert/PrivateNotificationManager.java
new file mode 100644
index 0000000000..4cfc99a6c9
--- /dev/null
+++ b/core/src/main/java/io/bitsquare/alert/PrivateNotificationManager.java
@@ -0,0 +1,140 @@
+/*
+ * This file is part of Bitsquare.
+ *
+ * Bitsquare is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bitsquare is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bitsquare. If not, see .
+ */
+
+package io.bitsquare.alert;
+
+import com.google.inject.Inject;
+import io.bitsquare.common.crypto.KeyRing;
+import io.bitsquare.crypto.DecryptedMsgWithPubKey;
+import io.bitsquare.p2p.Message;
+import io.bitsquare.p2p.NodeAddress;
+import io.bitsquare.trade.offer.Offer;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import org.bitcoinj.core.ECKey;
+import org.bitcoinj.core.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.security.SignatureException;
+
+import static org.bitcoinj.core.Utils.HEX;
+
+public class PrivateNotificationManager {
+ private static final Logger log = LoggerFactory.getLogger(PrivateNotificationManager.class);
+
+ private final PrivateNotificationService privateNotificationService;
+ private final KeyRing keyRing;
+ private final ObjectProperty privateNotificationMessageProperty = new SimpleObjectProperty<>();
+
+ // Pub key for developer global privateNotification message
+ private static final String pubKeyAsHex = "02ba7c5de295adfe57b60029f3637a2c6b1d0e969a8aaefb9e0ddc3a7963f26925";
+ private ECKey privateNotificationSigningKey;
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor, Initialization
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Inject
+ public PrivateNotificationManager(PrivateNotificationService privateNotificationService, KeyRing keyRing) {
+ this.privateNotificationService = privateNotificationService;
+ this.keyRing = keyRing;
+
+ privateNotificationService.addDecryptedDirectMessageListener(this::handleMessage);
+ privateNotificationService.addDecryptedMailboxListener(this::handleMessage);
+ }
+
+ private void handleMessage(DecryptedMsgWithPubKey decryptedMsgWithPubKey, NodeAddress senderNodeAddress) {
+ Message message = decryptedMsgWithPubKey.message;
+ if (message instanceof PrivateNotificationMessage) {
+ PrivateNotificationMessage privateNotificationMessage = (PrivateNotificationMessage) message;
+ log.trace("Received privateNotificationMessage: " + privateNotificationMessage);
+ if (privateNotificationMessage.getSenderNodeAddress().equals(senderNodeAddress)) {
+ final PrivateNotification privateNotification = privateNotificationMessage.privateNotification;
+ if (verifySignature(privateNotification))
+ privateNotificationMessageProperty.set(privateNotification);
+ } else {
+ log.warn("Peer address not matching for privateNotificationMessage");
+ }
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // API
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public ReadOnlyObjectProperty privateNotificationProperty() {
+ return privateNotificationMessageProperty;
+ }
+
+ public boolean sendPrivateNotificationMessageIfKeyIsValid(PrivateNotification privateNotification, Offer offer, String privKeyString) {
+ // if there is a previous message we remove that first
+ // if (user.getDevelopersPrivateNotification() != null)
+ // removePrivateNotificationMessageIfKeyIsValid(privKeyString);
+
+ boolean isKeyValid = isKeyValid(privKeyString);
+ if (isKeyValid) {
+ signAndAddSignatureToPrivateNotificationMessage(privateNotification);
+ // user.setDevelopersPrivateNotification(privateNotification);
+ privateNotificationService.sendPrivateNotificationMessage(privateNotification, offer, null, null);
+ }
+
+ return isKeyValid;
+ }
+
+ public boolean removePrivateNotificationMessageIfKeyIsValid(String privKeyString) {
+ /* PrivateNotification developersPrivateNotification = user.getDevelopersPrivateNotification();
+ if (isKeyValid(privKeyString) && developersPrivateNotification != null) {
+ privateNotificationService.removePrivateNotificationMessage(developersPrivateNotification, null, null);
+ user.setDevelopersPrivateNotification(null);
+ return true;
+ } else {
+ return false;
+ }*/
+ return false;
+ }
+
+ private boolean isKeyValid(String privKeyString) {
+ try {
+ privateNotificationSigningKey = ECKey.fromPrivate(new BigInteger(1, HEX.decode(privKeyString)));
+ return pubKeyAsHex.equals(Utils.HEX.encode(privateNotificationSigningKey.getPubKey()));
+ } catch (Throwable t) {
+ return false;
+ }
+ }
+
+ private void signAndAddSignatureToPrivateNotificationMessage(PrivateNotification privateNotification) {
+ String privateNotificationMessageAsHex = Utils.HEX.encode(privateNotification.message.getBytes());
+ String signatureAsBase64 = privateNotificationSigningKey.signMessage(privateNotificationMessageAsHex);
+ privateNotification.setSigAndPubKey(signatureAsBase64, keyRing.getSignatureKeyPair().getPublic());
+ }
+
+ private boolean verifySignature(PrivateNotification privateNotification) {
+ String privateNotificationMessageAsHex = Utils.HEX.encode(privateNotification.message.getBytes());
+ try {
+ ECKey.fromPublicOnly(HEX.decode(pubKeyAsHex)).verifyMessage(privateNotificationMessageAsHex, privateNotification.getSignatureAsBase64());
+ return true;
+ } catch (SignatureException e) {
+ log.warn("verifySignature failed");
+ return false;
+ }
+ }
+}
diff --git a/core/src/main/java/io/bitsquare/alert/PrivateNotificationMessage.java b/core/src/main/java/io/bitsquare/alert/PrivateNotificationMessage.java
new file mode 100644
index 0000000000..90c745b2b6
--- /dev/null
+++ b/core/src/main/java/io/bitsquare/alert/PrivateNotificationMessage.java
@@ -0,0 +1,74 @@
+package io.bitsquare.alert;
+
+import io.bitsquare.app.Version;
+import io.bitsquare.p2p.NodeAddress;
+import io.bitsquare.p2p.messaging.MailboxMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.UUID;
+
+public class PrivateNotificationMessage implements MailboxMessage {
+ // That object is sent over the wire, so we need to take care of version compatibility.
+ private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
+ private static final Logger log = LoggerFactory.getLogger(PrivateNotificationMessage.class);
+ private NodeAddress myNodeAddress;
+ public PrivateNotification privateNotification;
+ private final String uid = UUID.randomUUID().toString();
+ private final int messageVersion = Version.getP2PMessageVersion();
+
+ public PrivateNotificationMessage(PrivateNotification privateNotification, NodeAddress myNodeAddress) {
+ this.myNodeAddress = myNodeAddress;
+ this.privateNotification = privateNotification;
+ }
+
+ @Override
+ public NodeAddress getSenderNodeAddress() {
+ return myNodeAddress;
+ }
+
+ @Override
+ public String getUID() {
+ return uid;
+ }
+
+ @Override
+ public int getMessageVersion() {
+ return messageVersion;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PrivateNotificationMessage)) return false;
+
+ PrivateNotificationMessage that = (PrivateNotificationMessage) o;
+
+ if (messageVersion != that.messageVersion) return false;
+ if (myNodeAddress != null ? !myNodeAddress.equals(that.myNodeAddress) : that.myNodeAddress != null)
+ return false;
+ if (privateNotification != null ? !privateNotification.equals(that.privateNotification) : that.privateNotification != null)
+ return false;
+ return !(uid != null ? !uid.equals(that.uid) : that.uid != null);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = myNodeAddress != null ? myNodeAddress.hashCode() : 0;
+ result = 31 * result + (privateNotification != null ? privateNotification.hashCode() : 0);
+ result = 31 * result + (uid != null ? uid.hashCode() : 0);
+ result = 31 * result + messageVersion;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "PrivateNotificationMessage{" +
+ "myNodeAddress=" + myNodeAddress +
+ ", privateNotification=" + privateNotification +
+ ", uid='" + uid + '\'' +
+ ", messageVersion=" + messageVersion +
+ '}';
+ }
+}
diff --git a/core/src/main/java/io/bitsquare/alert/PrivateNotificationService.java b/core/src/main/java/io/bitsquare/alert/PrivateNotificationService.java
new file mode 100644
index 0000000000..bf07d266f4
--- /dev/null
+++ b/core/src/main/java/io/bitsquare/alert/PrivateNotificationService.java
@@ -0,0 +1,88 @@
+/*
+ * This file is part of Bitsquare.
+ *
+ * Bitsquare is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bitsquare is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bitsquare. If not, see .
+ */
+
+package io.bitsquare.alert;
+
+import io.bitsquare.common.handlers.ErrorMessageHandler;
+import io.bitsquare.common.handlers.ResultHandler;
+import io.bitsquare.p2p.P2PService;
+import io.bitsquare.p2p.messaging.DecryptedDirectMessageListener;
+import io.bitsquare.p2p.messaging.DecryptedMailboxListener;
+import io.bitsquare.p2p.messaging.SendMailboxMessageListener;
+import io.bitsquare.trade.offer.Offer;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+
+/**
+ * Used to load global privateNotification messages.
+ * The message is signed by the project developers private key and use data protection.
+ */
+public class PrivateNotificationService {
+ private static final Logger log = LoggerFactory.getLogger(PrivateNotificationService.class);
+ private final P2PService p2PService;
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor, Initialization
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Inject
+ public PrivateNotificationService(P2PService p2PService) {
+ this.p2PService = p2PService;
+ }
+
+ public void addDecryptedMailboxListener(DecryptedMailboxListener listener) {
+ p2PService.addDecryptedMailboxListener(listener);
+ }
+
+ public void addDecryptedDirectMessageListener(DecryptedDirectMessageListener listener) {
+ p2PService.addDecryptedDirectMessageListener(listener);
+ }
+
+
+ public void sendPrivateNotificationMessage(PrivateNotification privateNotification, Offer offer, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
+ p2PService.sendEncryptedMailboxMessage(offer.getOffererNodeAddress(),
+ offer.getPubKeyRing(),
+ new PrivateNotificationMessage(privateNotification, p2PService.getNetworkNode().getNodeAddress()),
+ new SendMailboxMessageListener() {
+ @Override
+ public void onArrived() {
+ log.trace("PrivateNotificationMessage arrived at peer. PrivateNotificationMessage = " + privateNotification);
+ if (resultHandler != null) resultHandler.handleResult();
+ }
+
+ @Override
+ public void onStoredInMailbox() {
+ log.trace("PrivateNotificationMessage was stored in mailbox. PrivateNotificationMessage = " + privateNotification);
+ if (resultHandler != null) resultHandler.handleResult();
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ if (errorMessageHandler != null)
+ errorMessageHandler.handleErrorMessage("Add privateNotificationMessage failed");
+ }
+ });
+ }
+
+ public void removePrivateNotificationMessage(PrivateNotification privateNotification, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
+ }
+
+}
diff --git a/gui/src/main/java/io/bitsquare/gui/components/PeerInfoIcon.java b/gui/src/main/java/io/bitsquare/gui/components/PeerInfoIcon.java
index e1851b2a9d..326870f4a6 100644
--- a/gui/src/main/java/io/bitsquare/gui/components/PeerInfoIcon.java
+++ b/gui/src/main/java/io/bitsquare/gui/components/PeerInfoIcon.java
@@ -1,6 +1,8 @@
package io.bitsquare.gui.components;
+import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.gui.main.overlays.editor.PeerInfoWithTagEditor;
+import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.Preferences;
import javafx.geometry.Point2D;
import javafx.scene.Group;
@@ -24,6 +26,8 @@ public class PeerInfoIcon extends Group {
private final String hostName;
private final String tooltipText;
private final int numTrades;
+ private PrivateNotificationManager privateNotificationManager;
+ private Offer offer;
private final Map peerTagMap;
private final Label numTradesLabel;
private final double SIZE = 26;
@@ -33,10 +37,12 @@ public class PeerInfoIcon extends Group {
private final Pane tagPane;
private final Pane numTradesPane;
- public PeerInfoIcon(String hostName, String tooltipText, int numTrades) {
+ public PeerInfoIcon(String hostName, String tooltipText, int numTrades, PrivateNotificationManager privateNotificationManager, Offer offer) {
this.hostName = hostName;
this.tooltipText = tooltipText;
this.numTrades = numTrades;
+ this.privateNotificationManager = privateNotificationManager;
+ this.offer = offer;
peerTagMap = Preferences.INSTANCE.getPeerTagMap();
@@ -97,7 +103,7 @@ public class PeerInfoIcon extends Group {
getChildren().addAll(background, avatarImageView, tagPane, numTradesPane);
- setOnMouseClicked(e -> new PeerInfoWithTagEditor()
+ setOnMouseClicked(e -> new PeerInfoWithTagEditor(privateNotificationManager, offer)
.hostName(hostName)
.numTrades(numTrades)
.position(localToScene(new Point2D(0, 0)))
diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java
index 7f4a72f658..8d7c6a4365 100644
--- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java
+++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java
@@ -20,6 +20,8 @@ package io.bitsquare.gui.main;
import com.google.inject.Inject;
import io.bitsquare.alert.Alert;
import io.bitsquare.alert.AlertManager;
+import io.bitsquare.alert.PrivateNotification;
+import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.app.DevFlags;
import io.bitsquare.app.Log;
@@ -101,6 +103,7 @@ public class MainViewModel implements ViewModel {
private final DisputeManager disputeManager;
final Preferences preferences;
private final AlertManager alertManager;
+ private PrivateNotificationManager privateNotificationManager;
private final WalletPasswordWindow walletPasswordWindow;
private final NotificationCenter notificationCenter;
private final TacWindow tacWindow;
@@ -171,7 +174,8 @@ public class MainViewModel implements ViewModel {
PriceFeed priceFeed,
ArbitratorManager arbitratorManager, P2PService p2PService, TradeManager tradeManager,
OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences,
- User user, AlertManager alertManager, WalletPasswordWindow walletPasswordWindow,
+ User user, AlertManager alertManager, PrivateNotificationManager privateNotificationManager,
+ WalletPasswordWindow walletPasswordWindow,
NotificationCenter notificationCenter, TacWindow tacWindow, Clock clock,
KeyRing keyRing, Navigation navigation, BSFormatter formatter) {
this.priceFeed = priceFeed;
@@ -185,6 +189,7 @@ public class MainViewModel implements ViewModel {
this.disputeManager = disputeManager;
this.preferences = preferences;
this.alertManager = alertManager;
+ this.privateNotificationManager = privateNotificationManager;
this.walletPasswordWindow = walletPasswordWindow;
this.notificationCenter = notificationCenter;
this.tacWindow = tacWindow;
@@ -517,6 +522,7 @@ public class MainViewModel implements ViewModel {
openOfferManager.onAllServicesInitialized();
arbitratorManager.onAllServicesInitialized();
alertManager.alertMessageProperty().addListener((observable, oldValue, newValue) -> displayAlertIfPresent(newValue));
+ privateNotificationManager.privateNotificationProperty().addListener((observable, oldValue, newValue) -> displayPrivateNotification(newValue));
displayAlertIfPresent(alertManager.alertMessageProperty().get());
setupBtcNumPeersWatcher();
@@ -865,6 +871,12 @@ public class MainViewModel implements ViewModel {
new DisplayAlertMessageWindow().alertMessage(alert).show();
}
+ private void displayPrivateNotification(PrivateNotification privateNotification) {
+ new Popup<>().headLine("Important notification from Bitsquare developers!")
+ .information(privateNotification.message)
+ .show();
+ }
+
private void swapPendingOfferFundingEntries() {
tradeManager.getAddressEntriesForAvailableBalanceStream()
.filter(addressEntry -> addressEntry.getOfferId() != null)
diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java
index f6ed6fda84..3493130d2a 100644
--- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java
+++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java
@@ -17,6 +17,7 @@
package io.bitsquare.gui.main.offer.offerbook;
+import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
@@ -68,6 +69,7 @@ public class OfferBookView extends ActivatableViewAndModel currencyComboBox;
private ComboBox paymentMethodComboBox;
@@ -87,12 +89,13 @@ public class OfferBookView extends ActivatableViewAndModel 0;
String tooltipText = hasTraded ? "Offerers onion address: " + hostName + "\n" +
"You have already traded " + numPastTrades + " times with that offerer." : "Offerers onion address: " + hostName;
- Node identIcon = new PeerInfoIcon(hostName, tooltipText, numPastTrades);
+ Node identIcon = new PeerInfoIcon(hostName, tooltipText, numPastTrades, privateNotificationManager, newItem.getOffer());
setPadding(new Insets(-2, 0, -2, 0));
if (identIcon != null)
setGraphic(identIcon);
diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/editor/PeerInfoWithTagEditor.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/editor/PeerInfoWithTagEditor.java
index 3cff2a29db..287e88ee2a 100644
--- a/gui/src/main/java/io/bitsquare/gui/main/overlays/editor/PeerInfoWithTagEditor.java
+++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/editor/PeerInfoWithTagEditor.java
@@ -1,8 +1,11 @@
package io.bitsquare.gui.main.overlays.editor;
+import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.overlays.Overlay;
+import io.bitsquare.gui.main.overlays.windows.SendPrivateNotificationWindow;
import io.bitsquare.gui.util.FormBuilder;
+import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.Preferences;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
@@ -16,6 +19,7 @@ import javafx.geometry.Point2D;
import javafx.scene.Camera;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
+import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.GridPane;
import javafx.scene.transform.Rotate;
@@ -38,9 +42,13 @@ public class PeerInfoWithTagEditor extends Overlay {
private String hostName;
private int numTrades;
private ChangeListener focusListener;
+ private PrivateNotificationManager privateNotificationManager;
+ private Offer offer;
- public PeerInfoWithTagEditor() {
+ public PeerInfoWithTagEditor(PrivateNotificationManager privateNotificationManager, Offer offer) {
+ this.privateNotificationManager = privateNotificationManager;
+ this.offer = offer;
width = 400;
type = Type.Undefined;
if (INSTANCE != null)
@@ -123,6 +131,14 @@ public class PeerInfoWithTagEditor extends Overlay {
Map peerTagMap = Preferences.INSTANCE.getPeerTagMap();
String tag = peerTagMap.containsKey(hostName) ? peerTagMap.get(hostName) : "";
inputTextField.setText(tag);
+
+ Button button = FormBuilder.addButton(gridPane, ++rowIndex, "Send private message");
+ button.setOnAction(e -> {
+ new SendPrivateNotificationWindow(offer)
+ .onAddAlertMessage(privateNotificationManager::sendPrivateNotificationMessageIfKeyIsValid)
+ .onRemoveAlertMessage(privateNotificationManager::removePrivateNotificationMessageIfKeyIsValid)
+ .show();
+ });
}
@Override
diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/SendPrivateNotificationWindow.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/SendPrivateNotificationWindow.java
new file mode 100644
index 0000000000..33a86c3229
--- /dev/null
+++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/SendPrivateNotificationWindow.java
@@ -0,0 +1,154 @@
+/*
+ * This file is part of Bitsquare.
+ *
+ * Bitsquare is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bitsquare is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bitsquare. If not, see .
+ */
+
+package io.bitsquare.gui.main.overlays.windows;
+
+import io.bitsquare.alert.PrivateNotification;
+import io.bitsquare.common.util.Tuple2;
+import io.bitsquare.gui.components.InputTextField;
+import io.bitsquare.gui.main.overlays.Overlay;
+import io.bitsquare.gui.main.overlays.popups.Popup;
+import io.bitsquare.trade.offer.Offer;
+import javafx.geometry.Insets;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
+import javafx.scene.input.KeyCode;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static io.bitsquare.gui.util.FormBuilder.addLabelInputTextField;
+import static io.bitsquare.gui.util.FormBuilder.addLabelTextArea;
+
+public class SendPrivateNotificationWindow extends Overlay {
+ private static final Logger log = LoggerFactory.getLogger(SendPrivateNotificationWindow.class);
+ private Button sendButton;
+ private SendPrivateNotificationHandler sendPrivateNotificationHandler;
+ private RemoveAlertMessageHandler removeAlertMessageHandler;
+ private Offer offer;
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Interface
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ public interface SendPrivateNotificationHandler {
+ boolean handle(PrivateNotification privateNotification, Offer offer, String privKey);
+ }
+
+ public interface RemoveAlertMessageHandler {
+ boolean handle(String privKey);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Public API
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public SendPrivateNotificationWindow(Offer offer) {
+ this.offer = offer;
+ type = Type.Attention;
+ }
+
+ public void show() {
+ if (headLine == null)
+ headLine = "Edit ban list";
+
+ width = 600;
+ createGridPane();
+ addHeadLine();
+ addSeparator();
+ addContent();
+ applyStyles();
+ display();
+ }
+
+ public SendPrivateNotificationWindow onAddAlertMessage(SendPrivateNotificationHandler sendPrivateNotificationHandler) {
+ this.sendPrivateNotificationHandler = sendPrivateNotificationHandler;
+ return this;
+ }
+
+ public SendPrivateNotificationWindow onRemoveAlertMessage(RemoveAlertMessageHandler removeAlertMessageHandler) {
+ this.removeAlertMessageHandler = removeAlertMessageHandler;
+ return this;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Protected
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected void setupKeyHandler(Scene scene) {
+ if (!hideCloseButton) {
+ scene.setOnKeyPressed(e -> {
+ if (e.getCode() == KeyCode.ESCAPE) {
+ e.consume();
+ doClose();
+ }
+ });
+ }
+ }
+
+ private void addContent() {
+ InputTextField keyInputTextField = addLabelInputTextField(gridPane, ++rowIndex, "Ban list private key:", 10).second;
+ Tuple2