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 labelTextAreaTuple2 = addLabelTextArea(gridPane, ++rowIndex, "Private alert message:", "Enter message"); + TextArea alertMessageTextArea = labelTextAreaTuple2.second; + Label first = labelTextAreaTuple2.first; + first.setMinWidth(150); + + sendButton = new Button("Send private alert message"); + sendButton.setOnAction(e -> { + if (alertMessageTextArea.getText().length() > 0 && keyInputTextField.getText().length() > 0) { + if (sendPrivateNotificationHandler.handle( + new PrivateNotification(alertMessageTextArea.getText()), + offer, + keyInputTextField.getText())) + hide(); + else + new Popup().warning("The key you entered was not correct.").width(300).onClose(() -> blurAgain()).show(); + } + }); + + Button removeAlertMessageButton = new Button("Remove notification"); + removeAlertMessageButton.setOnAction(e -> { + if (keyInputTextField.getText().length() > 0) { + if (removeAlertMessageHandler.handle(keyInputTextField.getText())) + hide(); + else + new Popup().warning("The key you entered was not correct.").width(300).onClose(() -> blurAgain()).show(); + } + }); + + closeButton = new Button("Close"); + closeButton.setOnAction(e -> { + hide(); + closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run()); + }); + + HBox hBox = new HBox(); + hBox.setSpacing(10); + GridPane.setRowIndex(hBox, ++rowIndex); + GridPane.setColumnIndex(hBox, 1); + hBox.getChildren().addAll(sendButton, removeAlertMessageButton, closeButton); + gridPane.getChildren().add(hBox); + GridPane.setMargin(hBox, new Insets(10, 0, 0, 0)); + } + + +} diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.java index 3be17fe8f7..49e807f0dd 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.java @@ -17,6 +17,7 @@ package io.bitsquare.gui.main.portfolio.closedtrades; +import io.bitsquare.alert.PrivateNotificationManager; import io.bitsquare.gui.common.view.ActivatableViewAndModel; import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.components.HyperlinkWithIcon; @@ -52,14 +53,16 @@ public class ClosedTradesView extends ActivatableViewAndModel sortedList; @Inject - public ClosedTradesView(ClosedTradesViewModel model, BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) { + public ClosedTradesView(ClosedTradesViewModel model, BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow, PrivateNotificationManager privateNotificationManager) { super(model); this.formatter = formatter; this.offerDetailsWindow = offerDetailsWindow; this.tradeDetailsWindow = tradeDetailsWindow; + this.privateNotificationManager = privateNotificationManager; } @Override @@ -235,7 +238,7 @@ public class ClosedTradesView extends ActivatableViewAndModel tableView; @FXML @@ -70,10 +72,11 @@ public class PendingTradesView extends ActivatableViewAndModel 0; String tooltipText = hasTraded ? "Trading peers onion address: " + hostName + "\n" + "You have already traded " + numPastTrades + " times with that peer." : "Trading peers onion address: " + hostName; - Node identIcon = new PeerInfoIcon(hostName, tooltipText, numPastTrades); + Node identIcon = new PeerInfoIcon(hostName, tooltipText, numPastTrades, privateNotificationManager, newItem.getTrade().getOffer()); setPadding(new Insets(-2, 0, -2, 0)); if (identIcon != null) setGraphic(identIcon);