diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index a01a79f4de..a27c8ef1a6 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -101,6 +101,8 @@ shared.dontRemoveOffer=Don't remove offer shared.editOffer=Edit offer shared.duplicateOffer=Duplicate offer shared.openLargeQRWindow=Open large QR code window +shared.showSepaQRCode=Show SEPA QR Code +shared.maximizedToProtectPrivacy=In order to protect your privacy, the QR Code will be shown maximized over the application window. shared.chooseTradingAccount=Choose trading account shared.faq=Visit FAQ page shared.yesCancel=Yes, cancel diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/GeneralSepaForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/GeneralSepaForm.java index f09c936fb1..fb5fb5804e 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/GeneralSepaForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/GeneralSepaForm.java @@ -1,10 +1,15 @@ package bisq.desktop.components.paymentmethods; +import bisq.desktop.components.AutoTooltipButton; import bisq.desktop.components.AutoTooltipCheckBox; +import bisq.desktop.main.overlays.windows.QRCodeWindow; import bisq.desktop.util.FormBuilder; +import bisq.desktop.util.GUIUtil; +import bisq.desktop.util.Layout; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.locale.Country; +import bisq.core.locale.CountryUtil; import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.payment.CountryBasedPaymentAccount; @@ -14,9 +19,13 @@ import bisq.core.util.validation.InputValidator; import org.apache.commons.lang3.StringUtils; +import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; +import de.jensd.fx.glyphs.materialdesignicons.utils.MaterialDesignIconFactory; + import com.jfoenix.controls.JFXComboBox; import com.jfoenix.controls.JFXTextField; +import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.TextField; @@ -25,18 +34,50 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; +import javafx.geometry.Insets; + import javafx.util.StringConverter; import java.util.List; import java.util.Objects; +import lombok.extern.slf4j.Slf4j; + +import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon; import static bisq.desktop.util.FormBuilder.addTopLabelWithVBox; +@Slf4j public abstract class GeneralSepaForm extends PaymentMethodForm { static final String BIC = "BIC"; static final String IBAN = "IBAN"; + public static int addFormForBuyer(GridPane gridPane, int gridRow, String amount, + String countryCode, String recipient, String bic, String iban) { + Button showQrCodeButton = new AutoTooltipButton(Res.get("shared.showSepaQRCode"), + MaterialDesignIconFactory.get().createIcon(MaterialDesignIcon.QRCODE, "2.0em")); + GridPane.setRowIndex(showQrCodeButton, gridRow); + GridPane.setColumnIndex(showQrCodeButton, 0); + gridPane.getChildren().add(showQrCodeButton); + GridPane.setMargin(showQrCodeButton, new Insets(66 + Layout.FLOATING_LABEL_DISTANCE, 0, 0, 0)); + showQrCodeButton.setStyle("-fx-pref-height: 27; -fx-padding: 4 4 4 4;"); + GridPane.setColumnIndex(showQrCodeButton, 1); + showQrCodeButton.setOnMouseClicked(e -> GUIUtil.showMaximizedToProtectPrivacyMessage(() -> + new QRCodeWindow(constructQRCodeString(bic, iban, recipient, amount)) + .withoutText() + .setWindowDimensions(gridPane.getScene().getWindow().getWidth() * 1.05, + gridPane.getScene().getWindow().getHeight() * 1.05) + .show())); + + addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.get("payment.account.owner"), recipient); + addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, BIC, bic); + addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, + Res.get("payment.bank.country"), CountryUtil.getNameAndCode(countryCode)); + addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, IBAN, iban); + + return gridRow; + } + GeneralSepaForm(PaymentAccount paymentAccount, AccountAgeWitnessService accountAgeWitnessService, InputValidator inputValidator, GridPane gridPane, int gridRow, CoinFormatter formatter) { super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter); } @@ -134,4 +175,14 @@ public abstract class GeneralSepaForm extends PaymentMethodForm { abstract boolean isCountryAccepted(String countryCode); protected abstract String getIban(); + + // see https://en.wikipedia.org/wiki/EPC_QR_code + private static String constructQRCodeString(String bic, String iban, String recipient, String amountCcy) { + String paymentBase = "BCD\n001\n1\nSCT\n" + bic + "\n" + recipient + "\n" + iban; + String[] amountSplit = amountCcy.split(" "); + if (amountSplit.length == 2) { + return paymentBase + "\n" + amountSplit[1] + amountSplit[0]; // ccy and amount combined, EPC_QR_code spec + } + return paymentBase; + } } diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/SepaForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/SepaForm.java index 5e13059e21..aff01b7128 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/SepaForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/SepaForm.java @@ -45,25 +45,17 @@ import java.util.List; import java.util.Optional; import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField; -import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon; public class SepaForm extends GeneralSepaForm { public static int addFormForBuyer(GridPane gridPane, int gridRow, - PaymentAccountPayload paymentAccountPayload) { + PaymentAccountPayload paymentAccountPayload, String amount) { SepaAccountPayload sepaAccountPayload = (SepaAccountPayload) paymentAccountPayload; - - final String title = Res.get("payment.account.owner"); - final String value = sepaAccountPayload.getHolderName(); - addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, title, value); - - addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, - Res.get("payment.bank.country"), - CountryUtil.getNameAndCode(sepaAccountPayload.getCountryCode())); - // IBAN, BIC will not be translated - addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, IBAN, sepaAccountPayload.getIban()); - addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, BIC, sepaAccountPayload.getBic()); - return gridRow; + return GeneralSepaForm.addFormForBuyer(gridPane, gridRow, amount, + sepaAccountPayload.getCountryCode(), + sepaAccountPayload.getHolderName(), + sepaAccountPayload.getBic(), + sepaAccountPayload.getIban()); } private final SepaAccount sepaAccount; diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/SepaInstantForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/SepaInstantForm.java index b70e1e5944..8718549106 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/SepaInstantForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/SepaInstantForm.java @@ -45,25 +45,17 @@ import java.util.List; import java.util.Optional; import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField; -import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon; public class SepaInstantForm extends GeneralSepaForm { public static int addFormForBuyer(GridPane gridPane, int gridRow, - PaymentAccountPayload paymentAccountPayload) { + PaymentAccountPayload paymentAccountPayload, String amount) { SepaInstantAccountPayload sepaInstantAccountPayload = (SepaInstantAccountPayload) paymentAccountPayload; - - final String title = Res.get("payment.account.owner"); - final String value = sepaInstantAccountPayload.getHolderName(); - addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, title, value); - - addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, - Res.get("payment.bank.country"), - CountryUtil.getNameAndCode(sepaInstantAccountPayload.getCountryCode())); - // IBAN, BIC will not be translated - addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, IBAN, sepaInstantAccountPayload.getIban()); - addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, BIC, sepaInstantAccountPayload.getBic()); - return gridRow; + return GeneralSepaForm.addFormForBuyer(gridPane, gridRow, amount, + sepaInstantAccountPayload.getCountryCode(), + sepaInstantAccountPayload.getHolderName(), + sepaInstantAccountPayload.getBic(), + sepaInstantAccountPayload.getIban()); } private final SepaInstantAccount sepaInstantAccount; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/QRCodeWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/QRCodeWindow.java index 9d5ba8ff75..f8801e9e36 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/QRCodeWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/QRCodeWindow.java @@ -27,32 +27,21 @@ import net.glxn.qrgen.image.ImageType; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; import javafx.geometry.HPos; import java.io.ByteArrayInputStream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class QRCodeWindow extends Overlay { - private static final Logger log = LoggerFactory.getLogger(QRCodeWindow.class); - private final ImageView qrCodeImageView; + private ImageView qrCodeImageView; private final String bitcoinAddressOrURI; + private boolean showTextRepresentation = true; + private double prefWidth = 500, prefHeight = 500; public QRCodeWindow(String bitcoinAddressOrURI) { this.bitcoinAddressOrURI = bitcoinAddressOrURI; - final byte[] imageBytes = QRCode - .from(bitcoinAddressOrURI) - .withSize(250, 250) - .to(ImageType.PNG) - .stream() - .toByteArray(); - Image qrImage = new Image(new ByteArrayInputStream(imageBytes)); - qrCodeImageView = new ImageView(qrImage); - type = Type.Information; - width = 468; headLine(Res.get("qRCodeWindow.headline")); message(Res.get("qRCodeWindow.msg")); } @@ -60,8 +49,23 @@ public class QRCodeWindow extends Overlay { @Override public void show() { createGridPane(); + gridPane.setPrefWidth(prefWidth); + gridPane.setMinHeight(prefHeight); addHeadLine(); + Region spacer = new Region(); + spacer.setMinHeight(prefHeight / 8); + gridPane.add(spacer, 0, ++rowIndex); + + final byte[] imageBytes = QRCode + .from(bitcoinAddressOrURI) + .withSize((int) prefWidth / 2, (int) prefHeight / 2) + .to(ImageType.PNG) + .stream() + .toByteArray(); + Image qrImage = new Image(new ByteArrayInputStream(imageBytes)); + qrCodeImageView = new ImageView(qrImage); + GridPane.setRowIndex(qrCodeImageView, ++rowIndex); GridPane.setColumnSpan(qrCodeImageView, 2); GridPane.setHalignment(qrCodeImageView, HPos.CENTER); @@ -69,11 +73,24 @@ public class QRCodeWindow extends Overlay { message = bitcoinAddressOrURI.replace("%20", " ").replace("?", "\n?").replace("&", "\n&"); setTruncatedMessage(); - addMessage(); - GridPane.setHalignment(messageLabel, HPos.CENTER); + if (showTextRepresentation) { + addMessage(); + GridPane.setHalignment(messageLabel, HPos.CENTER); + } addButtons(); applyStyles(); display(); } + + public QRCodeWindow withoutText() { + showTextRepresentation = false; + return this; + } + + public QRCodeWindow setWindowDimensions(double width, double height) { + this.prefWidth = width; + this.prefHeight = height; + return this; + } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 61f031e2a7..8eb48afa00 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -283,10 +283,10 @@ public class BuyerStep2View extends TradeStepView { gridRow = PerfectMoneyForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload); break; case PaymentMethod.SEPA_ID: - gridRow = SepaForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload); + gridRow = SepaForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload, model.getFiatVolume()); break; case PaymentMethod.SEPA_INSTANT_ID: - gridRow = SepaInstantForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload); + gridRow = SepaInstantForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload, model.getFiatVolume()); break; case PaymentMethod.FASTER_PAYMENTS_ID: gridRow = FasterPaymentsForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload); diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 1d2f5b77b5..e921faa1fc 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -743,6 +743,20 @@ public class GUIUtil { return t.cast(parent); } + public static void showMaximizedToProtectPrivacyMessage(Runnable runnable) { + String msg = Res.get("shared.maximizedToProtectPrivacy"); + String id = "shared.maximizedToProtectPrivacy"; + if (preferences.showAgain(id)) { + new Popup().information(msg) + .onClose(runnable) + .useIUnderstandButton() + .show(); + DontShowAgainLookup.dontShowAgain(id, true); + } else { + runnable.run(); + } + } + public static void showTakeOfferFromUnsignedAccountWarning() { String key = "confirmTakeOfferFromUnsignedAccount"; new Popup().warning(Res.get("payment.takeOfferFromUnsignedAccount.warning"))