diff --git a/src/main/java/bisq/desktop/bisq.css b/src/main/java/bisq/desktop/bisq.css index ae7a42c387a..dfe4f17cc19 100644 --- a/src/main/java/bisq/desktop/bisq.css +++ b/src/main/java/bisq/desktop/bisq.css @@ -286,6 +286,10 @@ bg color of non edit textFields: fafafa -fx-background-color: -bs-bg-light; } +.text-area { + -fx-prompt-text-fill: derive(-fx-control-inner-background, -30%); +} + #label-url { -fx-cursor: hand; -fx-text-fill: -bs-blue; diff --git a/src/main/java/bisq/desktop/components/paymentmethods/F2FForm.java b/src/main/java/bisq/desktop/components/paymentmethods/F2FForm.java new file mode 100644 index 00000000000..305c9968a69 --- /dev/null +++ b/src/main/java/bisq/desktop/components/paymentmethods/F2FForm.java @@ -0,0 +1,263 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.components.paymentmethods; + +import bisq.desktop.components.InputTextField; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.Layout; +import bisq.desktop.util.validation.F2FValidator; + +import bisq.core.locale.Country; +import bisq.core.locale.CountryUtil; +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.FiatCurrency; +import bisq.core.locale.Region; +import bisq.core.locale.Res; +import bisq.core.locale.TradeCurrency; +import bisq.core.offer.Offer; +import bisq.core.payment.AccountAgeWitnessService; +import bisq.core.payment.CountryBasedPaymentAccount; +import bisq.core.payment.F2FAccount; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.payload.F2FAccountPayload; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.util.BSFormatter; +import bisq.core.util.validation.InputValidator; + +import bisq.common.util.Tuple3; + +import org.apache.commons.lang3.StringUtils; + +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.layout.GridPane; + +import javafx.collections.FXCollections; + +import javafx.util.StringConverter; + +import static bisq.desktop.util.FormBuilder.*; + +public class F2FForm extends PaymentMethodForm { + private final F2FAccount f2fAccount; + private final F2FValidator f2fValidator; + private TextArea extraTextArea; + private InputTextField cityInputTextField; + + public static int addFormForBuyer(GridPane gridPane, int gridRow, + PaymentAccountPayload paymentAccountPayload, Offer offer) { + F2FAccountPayload f2fAccountPayload = (F2FAccountPayload) paymentAccountPayload; + addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.getWithCol("shared.country"), + CountryUtil.getNameAndCode(f2fAccountPayload.getCountryCode())); + addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.getWithCol("payment.f2f.contact"), + f2fAccountPayload.getContact()); + addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.getWithCol("payment.f2f.city"), + offer.getF2FCity()); + TextArea textArea = addLabelTextArea(gridPane, ++gridRow, Res.getWithCol("payment.f2f.extra"), "").second; + textArea.setPrefHeight(60); + textArea.setEditable(false); + textArea.setId("text-area-disabled"); + textArea.setText(offer.getF2FExtraInfo()); + return gridRow; + } + + public F2FForm(PaymentAccount paymentAccount, + AccountAgeWitnessService accountAgeWitnessService, F2FValidator f2fValidator, + InputValidator inputValidator, GridPane gridPane, int gridRow, BSFormatter formatter) { + super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter); + + this.f2fAccount = (F2FAccount) paymentAccount; + this.f2fValidator = f2fValidator; + } + + + @Override + public void addFormForAddAccount() { + gridRowFrom = gridRow + 1; + + Tuple3 tuple3 = addLabelComboBoxComboBox(gridPane, ++gridRow, Res.get("payment.country")); + + //noinspection unchecked,unchecked,unchecked + ComboBox regionComboBox = tuple3.second; + regionComboBox.setPromptText(Res.get("payment.select.region")); + regionComboBox.setConverter(new StringConverter() { + @Override + public String toString(Region region) { + return region.name; + } + + @Override + public Region fromString(String s) { + return null; + } + }); + regionComboBox.setItems(FXCollections.observableArrayList(CountryUtil.getAllRegions())); + + //noinspection unchecked,unchecked,unchecked + ComboBox countryComboBox = tuple3.third; + countryComboBox.setVisibleRowCount(15); + countryComboBox.setDisable(true); + countryComboBox.setPromptText(Res.get("payment.select.country")); + countryComboBox.setConverter(new StringConverter() { + @Override + public String toString(Country country) { + return country.name + " (" + country.code + ")"; + } + + @Override + public Country fromString(String s) { + return null; + } + }); + countryComboBox.setOnAction(e -> { + Country selectedItem = countryComboBox.getSelectionModel().getSelectedItem(); + if (selectedItem != null) { + getCountryBasedPaymentAccount().setCountry(selectedItem); + String countryCode = selectedItem.code; + TradeCurrency currency = CurrencyUtil.getCurrencyByCountryCode(countryCode); + paymentAccount.setSingleTradeCurrency(currency); + currencyComboBox.setDisable(false); + currencyComboBox.getSelectionModel().select(currency); + + updateFromInputs(); + } + }); + + regionComboBox.setOnAction(e -> { + Region selectedItem = regionComboBox.getSelectionModel().getSelectedItem(); + if (selectedItem != null) { + countryComboBox.setDisable(false); + countryComboBox.setItems(FXCollections.observableArrayList(CountryUtil.getAllCountriesForRegion(selectedItem))); + } + }); + + //noinspection unchecked + currencyComboBox = addLabelComboBox(gridPane, ++gridRow, Res.getWithCol("shared.currency")).second; + currencyComboBox.setPromptText(Res.get("list.currency.select")); + currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getAllSortedFiatCurrencies())); + currencyComboBox.setOnAction(e -> { + TradeCurrency selectedItem = currencyComboBox.getSelectionModel().getSelectedItem(); + FiatCurrency defaultCurrency = CurrencyUtil.getCurrencyByCountryCode(countryComboBox.getSelectionModel().getSelectedItem().code); + if (!defaultCurrency.equals(selectedItem)) { + new Popup<>().warning(Res.get("payment.foreign.currency")) + .actionButtonText(Res.get("shared.yes")) + .onAction(() -> { + paymentAccount.setSingleTradeCurrency(selectedItem); + autoFillNameTextField(); + }) + .closeButtonText(Res.get("payment.restore.default")) + .onClose(() -> currencyComboBox.getSelectionModel().select(defaultCurrency)) + .show(); + } else { + paymentAccount.setSingleTradeCurrency(selectedItem); + autoFillNameTextField(); + } + }); + currencyComboBox.setConverter(new StringConverter() { + @Override + public String toString(TradeCurrency currency) { + return currency.getNameAndCode(); + } + + @Override + public TradeCurrency fromString(String string) { + return null; + } + }); + currencyComboBox.setDisable(true); + + InputTextField contactInputTextField = addLabelInputTextField(gridPane, ++gridRow, + Res.getWithCol("payment.f2f.contact")).second; + contactInputTextField.setPromptText(Res.get("payment.f2f.contact.prompt")); + contactInputTextField.setValidator(f2fValidator); + contactInputTextField.textProperty().addListener((ov, oldValue, newValue) -> { + f2fAccount.setContact(newValue); + updateFromInputs(); + }); + + cityInputTextField = addLabelInputTextField(gridPane, ++gridRow, + Res.getWithCol("payment.f2f.city")).second; + cityInputTextField.setPromptText(Res.get("payment.f2f.city.prompt")); + cityInputTextField.setValidator(f2fValidator); + cityInputTextField.textProperty().addListener((ov, oldValue, newValue) -> { + f2fAccount.setCity(newValue); + updateFromInputs(); + }); + + extraTextArea = addLabelTextArea(gridPane, ++gridRow, + Res.getWithCol("payment.f2f.optionalExtra"), "").second; + extraTextArea.setPromptText(Res.get("payment.f2f.extra.prompt")); + extraTextArea.setPrefHeight(60); + //extraTextArea.setValidator(f2fValidator); + extraTextArea.textProperty().addListener((ov, oldValue, newValue) -> { + f2fAccount.setExtraInfo(newValue); + updateFromInputs(); + }); + + addLimitations(); + addAccountNameTextFieldWithAutoFillCheckBox(); + } + + @Override + protected void autoFillNameTextField() { + if (useCustomAccountNameCheckBox != null && !useCustomAccountNameCheckBox.isSelected()) { + String city = cityInputTextField.getText(); + city = StringUtils.abbreviate(city, 9); + String method = Res.get(paymentAccount.getPaymentMethod().getId()); + accountNameTextField.setText(method.concat(": ").concat(city)); + } + } + + @Override + public void addFormForDisplayAccount() { + gridRowFrom = gridRow; + + addLabelTextField(gridPane, gridRow, Res.get("payment.account.name"), + paymentAccount.getAccountName(), Layout.FIRST_ROW_AND_GROUP_DISTANCE); + addLabelTextField(gridPane, ++gridRow, Res.getWithCol("shared.paymentMethod"), + Res.get(paymentAccount.getPaymentMethod().getId())); + addLabelTextField(gridPane, ++gridRow, Res.get("payment.country"), + getCountryBasedPaymentAccount().getCountry() != null ? getCountryBasedPaymentAccount().getCountry().name : ""); + TradeCurrency singleTradeCurrency = paymentAccount.getSingleTradeCurrency(); + String nameAndCode = singleTradeCurrency != null ? singleTradeCurrency.getNameAndCode() : "null"; + addLabelTextField(gridPane, ++gridRow, Res.getWithCol("shared.currency"), nameAndCode); + addLabelTextField(gridPane, ++gridRow, Res.getWithCol("payment.f2f.contact", f2fAccount.getContact()), + f2fAccount.getContact()); + addLabelTextField(gridPane, ++gridRow, Res.getWithCol("payment.f2f.city", f2fAccount.getCity()), + f2fAccount.getCity()); + TextArea textArea = addLabelTextArea(gridPane, ++gridRow, Res.get("payment.f2f.extra"), "").second; + textArea.setText(f2fAccount.getExtraInfo()); + textArea.setPrefHeight(60); + textArea.setEditable(false); + + addLimitations(); + } + + @Override + public void updateAllInputsValid() { + allInputsValid.set(isAccountNameValid() + && f2fValidator.validate(f2fAccount.getContact()).isValid + && f2fValidator.validate(f2fAccount.getCity()).isValid + && f2fAccount.getTradeCurrencies().size() > 0); + } + + private CountryBasedPaymentAccount getCountryBasedPaymentAccount() { + return (CountryBasedPaymentAccount) this.paymentAccount; + } +} diff --git a/src/main/java/bisq/desktop/components/paymentmethods/USPostalMoneyOrderForm.java b/src/main/java/bisq/desktop/components/paymentmethods/USPostalMoneyOrderForm.java index cfe680f389c..2cb7f50d81b 100644 --- a/src/main/java/bisq/desktop/components/paymentmethods/USPostalMoneyOrderForm.java +++ b/src/main/java/bisq/desktop/components/paymentmethods/USPostalMoneyOrderForm.java @@ -36,17 +36,12 @@ import javafx.scene.control.TextArea; import javafx.scene.layout.GridPane; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import static bisq.desktop.util.FormBuilder.addLabelInputTextField; import static bisq.desktop.util.FormBuilder.addLabelTextArea; import static bisq.desktop.util.FormBuilder.addLabelTextField; import static bisq.desktop.util.FormBuilder.addLabelTextFieldWithCopyIcon; public class USPostalMoneyOrderForm extends PaymentMethodForm { - private static final Logger log = LoggerFactory.getLogger(USPostalMoneyOrderForm.class); - private final USPostalMoneyOrderAccount usPostalMoneyOrderAccount; private final USPostalMoneyOrderValidator usPostalMoneyOrderValidator; private TextArea postalAddressTextArea; diff --git a/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java b/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java index 6c4109074da..1127efc4587 100644 --- a/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java +++ b/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java @@ -27,6 +27,7 @@ import bisq.desktop.components.paymentmethods.CashDepositForm; import bisq.desktop.components.paymentmethods.ChaseQuickPayForm; import bisq.desktop.components.paymentmethods.ClearXchangeForm; +import bisq.desktop.components.paymentmethods.F2FForm; import bisq.desktop.components.paymentmethods.FasterPaymentsForm; import bisq.desktop.components.paymentmethods.InteracETransferForm; import bisq.desktop.components.paymentmethods.MoneyBeamForm; @@ -49,6 +50,7 @@ import bisq.desktop.components.paymentmethods.WesternUnionForm; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.FormBuilder; +import bisq.desktop.util.GUIUtil; import bisq.desktop.util.ImageUtil; import bisq.desktop.util.Layout; import bisq.desktop.util.validation.AliPayValidator; @@ -56,6 +58,7 @@ import bisq.desktop.util.validation.CashAppValidator; import bisq.desktop.util.validation.ChaseQuickPayValidator; import bisq.desktop.util.validation.ClearXchangeValidator; +import bisq.desktop.util.validation.F2FValidator; import bisq.desktop.util.validation.IBANValidator; import bisq.desktop.util.validation.InteracETransferValidator; import bisq.desktop.util.validation.MoneyBeamValidator; @@ -73,6 +76,7 @@ import bisq.core.locale.Res; import bisq.core.payment.AccountAgeWitnessService; import bisq.core.payment.ClearXchangeAccount; +import bisq.core.payment.F2FAccount; import bisq.core.payment.MoneyGramAccount; import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccountFactory; @@ -137,6 +141,7 @@ public class FiatAccountsView extends ActivatableViewAndModel paymentAccountsListView; @@ -167,6 +172,7 @@ public FiatAccountsView(FiatAccountsViewModel model, InteracETransferValidator interacETransferValidator, USPostalMoneyOrderValidator usPostalMoneyOrderValidator, WeChatPayValidator weChatPayValidator, + F2FValidator f2FValidator, AccountAgeWitnessService accountAgeWitnessService, BSFormatter formatter) { super(model); @@ -189,6 +195,7 @@ public FiatAccountsView(FiatAccountsViewModel model, this.interacETransferValidator = interacETransferValidator; this.usPostalMoneyOrderValidator = usPostalMoneyOrderValidator; this.weChatPayValidator = weChatPayValidator; + this.f2FValidator = f2FValidator; this.accountAgeWitnessService = accountAgeWitnessService; this.formatter = formatter; } @@ -231,41 +238,51 @@ private void onSaveNewAccount(PaymentAccount paymentAccount) { Coin maxTradeLimitAsCoin = paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin("USD"); Coin maxTradeLimitSecondMonth = maxTradeLimitAsCoin.divide(2L); Coin maxTradeLimitFirstMonth = maxTradeLimitAsCoin.divide(4L); - new Popup<>().information(Res.get("payment.limits.info", - formatter.formatCoinWithCode(maxTradeLimitFirstMonth), - formatter.formatCoinWithCode(maxTradeLimitSecondMonth), - formatter.formatCoinWithCode(maxTradeLimitAsCoin))) - .width(700) - .closeButtonText(Res.get("shared.cancel")) - .actionButtonText(Res.get("shared.iUnderstand")) - .onAction(() -> { - final String currencyName = BisqEnvironment.getBaseCurrencyNetwork().getCurrencyName(); - if (paymentAccount instanceof ClearXchangeAccount) { - new Popup<>().information(Res.get("payment.clearXchange.info", currencyName, currencyName)) - .width(900) - .closeButtonText(Res.get("shared.cancel")) - .actionButtonText(Res.get("shared.iConfirm")) - .onAction(() -> doSaveNewAccount(paymentAccount)) - .show(); - } else if (paymentAccount instanceof WesternUnionAccount) { - new Popup<>().information(Res.get("payment.westernUnion.info")) - .width(700) - .closeButtonText(Res.get("shared.cancel")) - .actionButtonText(Res.get("shared.iUnderstand")) - .onAction(() -> doSaveNewAccount(paymentAccount)) - .show(); - } else if (paymentAccount instanceof MoneyGramAccount) { - new Popup<>().information(Res.get("payment.moneyGram.info")) - .width(700) - .closeButtonText(Res.get("shared.cancel")) - .actionButtonText(Res.get("shared.iUnderstand")) - .onAction(() -> doSaveNewAccount(paymentAccount)) - .show(); - } else { - doSaveNewAccount(paymentAccount); - } - }) - .show(); + if (paymentAccount instanceof F2FAccount) { + new Popup<>().information(Res.get("payment.f2f.info")) + .width(700) + .closeButtonText(Res.get("payment.f2f.info.openURL")) + .onClose(() -> GUIUtil.openWebPage("https://docs.bisq.network/#f2f")) + .actionButtonText(Res.get("shared.iUnderstand")) + .onAction(() -> doSaveNewAccount(paymentAccount)) + .show(); + } else { + new Popup<>().information(Res.get("payment.limits.info", + formatter.formatCoinWithCode(maxTradeLimitFirstMonth), + formatter.formatCoinWithCode(maxTradeLimitSecondMonth), + formatter.formatCoinWithCode(maxTradeLimitAsCoin))) + .width(700) + .closeButtonText(Res.get("shared.cancel")) + .actionButtonText(Res.get("shared.iUnderstand")) + .onAction(() -> { + final String currencyName = BisqEnvironment.getBaseCurrencyNetwork().getCurrencyName(); + if (paymentAccount instanceof ClearXchangeAccount) { + new Popup<>().information(Res.get("payment.clearXchange.info", currencyName, currencyName)) + .width(900) + .closeButtonText(Res.get("shared.cancel")) + .actionButtonText(Res.get("shared.iConfirm")) + .onAction(() -> doSaveNewAccount(paymentAccount)) + .show(); + } else if (paymentAccount instanceof WesternUnionAccount) { + new Popup<>().information(Res.get("payment.westernUnion.info")) + .width(700) + .closeButtonText(Res.get("shared.cancel")) + .actionButtonText(Res.get("shared.iUnderstand")) + .onAction(() -> doSaveNewAccount(paymentAccount)) + .show(); + } else if (paymentAccount instanceof MoneyGramAccount) { + new Popup<>().information(Res.get("payment.moneyGram.info")) + .width(700) + .closeButtonText(Res.get("shared.cancel")) + .actionButtonText(Res.get("shared.iUnderstand")) + .onAction(() -> doSaveNewAccount(paymentAccount)) + .show(); + } else { + doSaveNewAccount(paymentAccount); + } + }) + .show(); + } } private void doSaveNewAccount(PaymentAccount paymentAccount) { @@ -482,6 +499,8 @@ private PaymentMethodForm getPaymentMethodForm(PaymentMethod paymentMethod, Paym return new WesternUnionForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter); case PaymentMethod.CASH_DEPOSIT_ID: return new CashDepositForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter); + case PaymentMethod.F2F_ID: + return new F2FForm(paymentAccount, accountAgeWitnessService, f2FValidator, inputValidator, root, gridRow, formatter); default: log.error("Not supported PaymentMethod: " + paymentMethod); return null; diff --git a/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 22c7e8760e8..8ffaa704490 100644 --- a/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -39,6 +39,7 @@ import bisq.core.payment.AccountAgeWitnessService; import bisq.core.payment.BankAccount; import bisq.core.payment.CountryBasedPaymentAccount; +import bisq.core.payment.F2FAccount; import bisq.core.payment.PaymentAccount; import bisq.core.payment.SameBankAccount; import bisq.core.payment.SepaAccount; @@ -369,6 +370,13 @@ Offer createAndGetOffer() { extraDataMap.put(OfferPayload.REFERRAL_ID, referralIdService.getOptionalReferralId().get()); } + if (paymentAccount instanceof F2FAccount) { + if (extraDataMap == null) + extraDataMap = new HashMap<>(); + extraDataMap.put(OfferPayload.F2F_CITY, ((F2FAccount) paymentAccount).getCity()); + extraDataMap.put(OfferPayload.F2F_EXTRA_INFO, ((F2FAccount) paymentAccount).getExtraInfo()); + } + Coin buyerSecurityDepositAsCoin = buyerSecurityDeposit.get(); checkArgument(buyerSecurityDepositAsCoin.compareTo(Restrictions.getMaxBuyerSecurityDeposit()) <= 0, "securityDeposit must be not exceed " + diff --git a/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 4aa7f1bdeb4..4cc4c349dd7 100644 --- a/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -404,11 +404,15 @@ String getPaymentMethod(OfferBookListItem item) { Offer offer = item.getOffer(); String method = Res.get(offer.getPaymentMethod().getId() + "_SHORT"); String methodCountryCode = offer.getCountryCode(); + if (isF2F(offer)) { + result = method + " (" + methodCountryCode + ", " + offer.getF2FCity() + ")"; + } else { + if (methodCountryCode != null) + result = method + " (" + methodCountryCode + ")"; + else + result = method; + } - if (methodCountryCode != null) - result = method + " (" + methodCountryCode + ")"; - else - result = method; } return result; } @@ -420,37 +424,50 @@ String getPaymentMethodToolTip(OfferBookListItem item) { result = Res.getWithCol("shared.paymentMethod") + " " + Res.get(offer.getPaymentMethod().getId()); result += "\n" + Res.getWithCol("shared.currency") + " " + CurrencyUtil.getNameAndCode(offer.getCurrencyCode()); - String methodCountryCode = offer.getCountryCode(); - if (methodCountryCode != null) { - String bankId = offer.getBankId(); - if (bankId != null && !bankId.equals("null")) { - if (BankUtil.isBankIdRequired(methodCountryCode)) - result += "\n" + Res.get("offerbook.offerersBankId", bankId); - else if (BankUtil.isBankNameRequired(methodCountryCode)) - result += "\n" + Res.get("offerbook.offerersBankName", bankId); - } - } + String countryCode = offer.getCountryCode(); + if (isF2F(offer)) { + if (countryCode != null) { + result += "\n" + Res.get("payment.f2f.offerbook.tooltip.countryAndCity", + CountryUtil.getNameByCode(countryCode), offer.getF2FCity()); - if (methodCountryCode != null) - result += "\n" + Res.get("offerbook.offerersBankSeat", CountryUtil.getNameByCode(methodCountryCode)); + result += "\n" + Res.get("payment.f2f.offerbook.tooltip.extra", offer.getF2FExtraInfo()); + } + } else { + if (countryCode != null) { + String bankId = offer.getBankId(); + if (bankId != null && !bankId.equals("null")) { + if (BankUtil.isBankIdRequired(countryCode)) + result += "\n" + Res.get("offerbook.offerersBankId", bankId); + else if (BankUtil.isBankNameRequired(countryCode)) + result += "\n" + Res.get("offerbook.offerersBankName", bankId); + } + } - List acceptedCountryCodes = offer.getAcceptedCountryCodes(); - List acceptedBanks = offer.getAcceptedBankIds(); - if (acceptedCountryCodes != null && !acceptedCountryCodes.isEmpty()) { - if (CountryUtil.containsAllSepaEuroCountries(acceptedCountryCodes)) - result += "\n" + Res.get("offerbook.offerersAcceptedBankSeatsEuro"); - else - result += "\n" + Res.get("offerbook.offerersAcceptedBankSeats", CountryUtil.getNamesByCodesString(acceptedCountryCodes)); - } else if (acceptedBanks != null && !acceptedBanks.isEmpty()) { - if (offer.getPaymentMethod().equals(PaymentMethod.SAME_BANK)) - result += "\n" + Res.getWithCol("shared.bankName") + " " + acceptedBanks.get(0); - else if (offer.getPaymentMethod().equals(PaymentMethod.SPECIFIC_BANKS)) - result += "\n" + Res.getWithCol("shared.acceptedBanks") + " " + Joiner.on(", ").join(acceptedBanks); + if (countryCode != null) + result += "\n" + Res.get("offerbook.offerersBankSeat", CountryUtil.getNameByCode(countryCode)); + + List acceptedCountryCodes = offer.getAcceptedCountryCodes(); + List acceptedBanks = offer.getAcceptedBankIds(); + if (acceptedCountryCodes != null && !acceptedCountryCodes.isEmpty()) { + if (CountryUtil.containsAllSepaEuroCountries(acceptedCountryCodes)) + result += "\n" + Res.get("offerbook.offerersAcceptedBankSeatsEuro"); + else + result += "\n" + Res.get("offerbook.offerersAcceptedBankSeats", CountryUtil.getNamesByCodesString(acceptedCountryCodes)); + } else if (acceptedBanks != null && !acceptedBanks.isEmpty()) { + if (offer.getPaymentMethod().equals(PaymentMethod.SAME_BANK)) + result += "\n" + Res.getWithCol("shared.bankName") + " " + acceptedBanks.get(0); + else if (offer.getPaymentMethod().equals(PaymentMethod.SPECIFIC_BANKS)) + result += "\n" + Res.getWithCol("shared.acceptedBanks") + " " + Joiner.on(", ").join(acceptedBanks); + } } } return result; } + private boolean isF2F(Offer offer) { + return offer.getPaymentMethod().equals(PaymentMethod.F2F); + } + String getDirectionLabelTooltip(Offer offer) { return formatter.getDirectionWithCodeDetailed(offer.getMirroredDirection(), offer.getCurrencyCode()); } diff --git a/src/main/java/bisq/desktop/main/overlays/windows/OfferDetailsWindow.java b/src/main/java/bisq/desktop/main/overlays/windows/OfferDetailsWindow.java index 6b890dbdaf0..b7c19b4ea69 100644 --- a/src/main/java/bisq/desktop/main/overlays/windows/OfferDetailsWindow.java +++ b/src/main/java/bisq/desktop/main/overlays/windows/OfferDetailsWindow.java @@ -49,6 +49,7 @@ import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; @@ -143,18 +144,19 @@ protected void createGridPane() { private void addContent() { int rows = 5; - List acceptedBanks = offer.getAcceptedBankIds(); boolean showAcceptedBanks = acceptedBanks != null && !acceptedBanks.isEmpty(); List acceptedCountryCodes = offer.getAcceptedCountryCodes(); boolean showAcceptedCountryCodes = acceptedCountryCodes != null && !acceptedCountryCodes.isEmpty(); - + boolean isF2F = offer.getPaymentMethod().equals(PaymentMethod.F2F); if (!takeOfferHandlerOptional.isPresent()) rows++; if (showAcceptedBanks) rows++; if (showAcceptedCountryCodes) rows++; + if (isF2F) + rows += 2; addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.Offer")); @@ -214,8 +216,6 @@ private void addContent() { } } final PaymentMethod paymentMethod = offer.getPaymentMethod(); - final String makerPaymentAccountId = offer.getMakerPaymentAccountId(); - final PaymentAccount paymentAccount = user.getPaymentAccount(makerPaymentAccountId); String bankId = offer.getBankId(); if (bankId == null || bankId.equals("null")) bankId = ""; @@ -224,8 +224,10 @@ private void addContent() { final boolean isSpecificBanks = paymentMethod.equals(PaymentMethod.SPECIFIC_BANKS); final boolean isNationalBanks = paymentMethod.equals(PaymentMethod.NATIONAL_BANK); final boolean isSepa = paymentMethod.equals(PaymentMethod.SEPA); - if (offer.isMyOffer(keyRing) && makerPaymentAccountId != null && paymentAccount != null) { - addLabelTextField(gridPane, ++rowIndex, Res.get("offerDetailsWindow.myTradingAccount"), paymentAccount.getAccountName()); + final String makerPaymentAccountId = offer.getMakerPaymentAccountId(); + final PaymentAccount myPaymentAccount = user.getPaymentAccount(makerPaymentAccountId); + if (offer.isMyOffer(keyRing) && makerPaymentAccountId != null && myPaymentAccount != null) { + addLabelTextField(gridPane, ++rowIndex, Res.get("offerDetailsWindow.myTradingAccount"), myPaymentAccount.getAccountName()); } else { final String method = Res.get(paymentMethod.getId()); String methodWithBankId = method + bankId; @@ -282,12 +284,23 @@ else if (BankUtil.isBankNameRequired(offer.getCountryCode())) } } - rows = 5; + if (isF2F) { + addLabelTextField(gridPane, ++rowIndex, Res.getWithCol("payment.f2f.city"), offer.getF2FCity()); + TextArea textArea = addLabelTextArea(gridPane, ++rowIndex, Res.getWithCol("payment.f2f.extra"), "").second; + textArea.setText(offer.getF2FExtraInfo()); + textArea.setMinHeight(33); + textArea.setMaxHeight(textArea.getMinHeight()); + textArea.setEditable(false); + } + + rows = 4; String paymentMethodCountryCode = offer.getCountryCode(); if (paymentMethodCountryCode != null) rows++; if (offer.getOfferFeePaymentTxId() != null) rows++; + if (!isF2F) + rows++; addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE); addLabelTextFieldWithCopyIcon(gridPane, rowIndex, Res.getWithCol("shared.offerId"), offer.getId(), @@ -305,7 +318,7 @@ else if (BankUtil.isBankNameRequired(offer.getCountryCode())) formatter.formatCoinWithCode(offer.getSellerSecurityDeposit()); addLabelTextField(gridPane, ++rowIndex, Res.getWithCol("shared.securityDeposit"), value); - if (paymentMethodCountryCode != null) + if (paymentMethodCountryCode != null && !isF2F) addLabelTextField(gridPane, ++rowIndex, Res.get("offerDetailsWindow.countryBank"), CountryUtil.getNameAndCode(paymentMethodCountryCode)); diff --git a/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 3bf5c82a488..4a83c23c234 100644 --- a/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -26,6 +26,7 @@ import bisq.desktop.components.paymentmethods.ChaseQuickPayForm; import bisq.desktop.components.paymentmethods.ClearXchangeForm; import bisq.desktop.components.paymentmethods.CryptoCurrencyForm; +import bisq.desktop.components.paymentmethods.F2FForm; import bisq.desktop.components.paymentmethods.FasterPaymentsForm; import bisq.desktop.components.paymentmethods.InteracETransferForm; import bisq.desktop.components.paymentmethods.MoneyBeamForm; @@ -55,6 +56,7 @@ import bisq.core.network.MessageState; import bisq.core.payment.payload.CashDepositAccountPayload; import bisq.core.payment.payload.CryptoCurrencyAccountPayload; +import bisq.core.payment.payload.F2FAccountPayload; import bisq.core.payment.payload.MoneyGramAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentMethod; @@ -75,6 +77,8 @@ import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; +import static com.google.common.base.Preconditions.checkNotNull; + public class BuyerStep2View extends TradeStepView { private Button confirmButton; @@ -257,6 +261,10 @@ protected void addContent() { case PaymentMethod.WESTERN_UNION_ID: gridRow = WesternUnionForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload); break; + case PaymentMethod.F2F_ID: + checkNotNull(model.dataModel.getTrade().getOffer(), "model.dataModel.getTrade().getOffer() must not be null"); + gridRow = F2FForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload, model.dataModel.getTrade().getOffer()); + break; case PaymentMethod.BLOCK_CHAINS_ID: String labelTitle = Res.get("portfolio.pending.step2_buyer.sellersAddress", CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode())); @@ -266,7 +274,8 @@ protected void addContent() { log.error("Not supported PaymentMethod: " + paymentMethodId); } - if (!(paymentAccountPayload instanceof CryptoCurrencyAccountPayload)) + if (!(paymentAccountPayload instanceof CryptoCurrencyAccountPayload) && + !(paymentAccountPayload instanceof F2FAccountPayload)) FormBuilder.addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.getWithCol("shared.reasonForPayment"), model.dataModel.getReference()); @@ -443,7 +452,7 @@ private void showPopup() { message += Res.get("portfolio.pending.step2_buyer.cash", amount) + accountDetails + - paymentDetailsForTradePopup + ".\n" + + paymentDetailsForTradePopup + ".\n\n" + copyPaste + "\n\n" + tradeId + paddedId + assign + @@ -456,7 +465,7 @@ private void showPopup() { message += Res.get("portfolio.pending.step2_buyer.westernUnion", amount) + accountDetails + - paymentDetailsForTradePopup + ".\n" + + paymentDetailsForTradePopup + ".\n\n" + copyPaste + "\n\n" + extra; } else if (paymentAccountPayload instanceof MoneyGramAccountPayload) { @@ -465,23 +474,29 @@ private void showPopup() { message += Res.get("portfolio.pending.step2_buyer.moneyGram", amount) + accountDetails + - paymentDetailsForTradePopup + ".\n" + + paymentDetailsForTradePopup + ".\n\n" + copyPaste + "\n\n" + extra; } else if (paymentAccountPayload instanceof USPostalMoneyOrderAccountPayload) { //noinspection UnusedAssignment message += Res.get("portfolio.pending.step2_buyer.postal", amount) + accountDetails + - paymentDetailsForTradePopup + ".\n" + + paymentDetailsForTradePopup + ".\n\n" + copyPaste + "\n\n" + tradeId + paddedId + assign + refTextWarn; + } else if (paymentAccountPayload instanceof F2FAccountPayload) { + //noinspection UnusedAssignment + message += Res.get("portfolio.pending.step2_buyer.f2f", amount) + + accountDetails + + paymentDetailsForTradePopup + "\n\n" + + copyPaste; } else { //noinspection UnusedAssignment message += Res.get("portfolio.pending.step2_buyer.bank", amount) + accountDetails + - paymentDetailsForTradePopup + ".\n" + + paymentDetailsForTradePopup + ".\n\n" + copyPaste + "\n\n" + tradeId + paddedId + assign + diff --git a/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index f89da41fbf1..1f6694d185b 100644 --- a/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -30,8 +30,10 @@ import bisq.core.payment.payload.BankAccountPayload; import bisq.core.payment.payload.CashDepositAccountPayload; import bisq.core.payment.payload.CryptoCurrencyAccountPayload; +import bisq.core.payment.payload.F2FAccountPayload; import bisq.core.payment.payload.MoneyGramAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.SepaAccountPayload; import bisq.core.payment.payload.SepaInstantAccountPayload; import bisq.core.payment.payload.USPostalMoneyOrderAccountPayload; @@ -194,7 +196,7 @@ protected void addContent() { peersPaymentDetailsTextField.setMouseTransparent(false); peersPaymentDetailsTextField.setTooltip(new Tooltip(peersPaymentDetails)); - if (!isBlockChain) { + if (!isBlockChain && !trade.getOffer().getPaymentMethod().equals(PaymentMethod.F2F)) { addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.getWithCol("shared.reasonForPayment"), model.dataModel.getReference()); GridPane.setRowSpan(titledGroupBg, 4); } @@ -266,8 +268,10 @@ private void onPaymentReceived() { PaymentAccountPayload paymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload(); String message = Res.get("portfolio.pending.step3_seller.onPaymentReceived.part1", CurrencyUtil.getNameByCode(model.dataModel.getCurrencyCode())); if (!(paymentAccountPayload instanceof CryptoCurrencyAccountPayload)) { - if (!(paymentAccountPayload instanceof WesternUnionAccountPayload)) + if (!(paymentAccountPayload instanceof WesternUnionAccountPayload) && + !(paymentAccountPayload instanceof F2FAccountPayload)) { message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.fiat", trade.getShortId()); + } Optional optionalHolderName = getOptionalHolderName(); if (optionalHolderName.isPresent()) { @@ -306,18 +310,22 @@ private void showPopup() { //noinspection UnusedAssignment message = Res.get("portfolio.pending.step3_seller.altcoin", part1, currencyName, address, tradeVolumeWithCode, currencyName); } else { - if (paymentAccountPayload instanceof USPostalMoneyOrderAccountPayload) + if (paymentAccountPayload instanceof USPostalMoneyOrderAccountPayload) { message = Res.get("portfolio.pending.step3_seller.postal", part1, tradeVolumeWithCode, id); - else if (!(paymentAccountPayload instanceof WesternUnionAccountPayload)) + } else if (!(paymentAccountPayload instanceof WesternUnionAccountPayload) && + !(paymentAccountPayload instanceof F2FAccountPayload)) { message = Res.get("portfolio.pending.step3_seller.bank", currencyName, tradeVolumeWithCode, id); + } String part = Res.get("portfolio.pending.step3_seller.openDispute"); if (paymentAccountPayload instanceof CashDepositAccountPayload) message = message + Res.get("portfolio.pending.step3_seller.cash", part); else if (paymentAccountPayload instanceof WesternUnionAccountPayload) - message = message + Res.get("portfolio.pending.step3_seller.westernUnion", part); + message = message + Res.get("portfolio.pending.step3_seller.westernUnion"); else if (paymentAccountPayload instanceof MoneyGramAccountPayload) - message = message + Res.get("portfolio.pending.step3_seller.moneyGram", part); + message = message + Res.get("portfolio.pending.step3_seller.moneyGram"); + else if (paymentAccountPayload instanceof F2FAccountPayload) + message = part1; Optional optionalHolderName = getOptionalHolderName(); if (optionalHolderName.isPresent()) { @@ -365,9 +373,9 @@ else if (paymentAccountPayload instanceof SepaAccountPayload) else if (paymentAccountPayload instanceof SepaInstantAccountPayload) return Optional.of(((SepaInstantAccountPayload) paymentAccountPayload).getHolderName()); else - return Optional.empty(); + return Optional.empty(); } else { - return Optional.empty(); + return Optional.empty(); } } } diff --git a/src/main/java/bisq/desktop/util/validation/F2FValidator.java b/src/main/java/bisq/desktop/util/validation/F2FValidator.java new file mode 100644 index 00000000000..e1ef872b894 --- /dev/null +++ b/src/main/java/bisq/desktop/util/validation/F2FValidator.java @@ -0,0 +1,37 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.util.validation; + +import bisq.core.util.validation.InputValidator; + +public final class F2FValidator extends InputValidator { + + /////////////////////////////////////////////////////////////////////////////////////////// + // Public methods + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public ValidationResult validate(String input) { + // TODO + return super.validate(input); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private methods + /////////////////////////////////////////////////////////////////////////////////////////// +}