mirror of
https://github.com/bisq-network/bisq.git
synced 2025-03-26 20:30:55 +01:00
Merge branch 'master' into 01-misc
This commit is contained in:
commit
25fbd3518e
40 changed files with 1110 additions and 340 deletions
|
@ -182,9 +182,11 @@ class CoreOffersService {
|
|||
double buyerSecurityDeposit,
|
||||
boolean useSavingsWallet,
|
||||
Consumer<Transaction> resultHandler) {
|
||||
// TODO add support for triggerPrice parameter. If value is 0 it is interpreted as not used. Its an optional value
|
||||
openOfferManager.placeOffer(offer,
|
||||
buyerSecurityDeposit,
|
||||
useSavingsWallet,
|
||||
0,
|
||||
resultHandler::accept,
|
||||
log::error);
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import bisq.core.notifications.alerts.TradeEvents;
|
|||
import bisq.core.notifications.alerts.market.MarketAlerts;
|
||||
import bisq.core.notifications.alerts.price.PriceAlert;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.offer.TriggerPriceService;
|
||||
import bisq.core.payment.RevolutAccount;
|
||||
import bisq.core.payment.TradeLimits;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
|
@ -106,6 +107,7 @@ public class DomainInitialisation {
|
|||
private final MarketAlerts marketAlerts;
|
||||
private final User user;
|
||||
private final DaoStateSnapshotService daoStateSnapshotService;
|
||||
private final TriggerPriceService triggerPriceService;
|
||||
|
||||
@Inject
|
||||
public DomainInitialisation(ClockWatcher clockWatcher,
|
||||
|
@ -141,7 +143,8 @@ public class DomainInitialisation {
|
|||
PriceAlert priceAlert,
|
||||
MarketAlerts marketAlerts,
|
||||
User user,
|
||||
DaoStateSnapshotService daoStateSnapshotService) {
|
||||
DaoStateSnapshotService daoStateSnapshotService,
|
||||
TriggerPriceService triggerPriceService) {
|
||||
this.clockWatcher = clockWatcher;
|
||||
this.tradeLimits = tradeLimits;
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
|
@ -176,6 +179,7 @@ public class DomainInitialisation {
|
|||
this.marketAlerts = marketAlerts;
|
||||
this.user = user;
|
||||
this.daoStateSnapshotService = daoStateSnapshotService;
|
||||
this.triggerPriceService = triggerPriceService;
|
||||
}
|
||||
|
||||
public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
|
||||
|
@ -254,6 +258,7 @@ public class DomainInitialisation {
|
|||
disputeMsgEvents.onAllServicesInitialized();
|
||||
priceAlert.onAllServicesInitialized();
|
||||
marketAlerts.onAllServicesInitialized();
|
||||
triggerPriceService.onAllServicesInitialized();
|
||||
|
||||
if (revolutAccountsUpdateHandler != null) {
|
||||
revolutAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream()
|
||||
|
|
|
@ -69,9 +69,18 @@ public final class OpenOffer implements Tradable {
|
|||
@Nullable
|
||||
private NodeAddress refundAgentNodeAddress;
|
||||
|
||||
// Added in v1.5.3.
|
||||
// If market price reaches that trigger price the offer gets deactivated
|
||||
@Getter
|
||||
private final long triggerPrice;
|
||||
|
||||
public OpenOffer(Offer offer) {
|
||||
this(offer, 0);
|
||||
}
|
||||
|
||||
public OpenOffer(Offer offer, long triggerPrice) {
|
||||
this.offer = offer;
|
||||
this.triggerPrice = triggerPrice;
|
||||
state = State.AVAILABLE;
|
||||
}
|
||||
|
||||
|
@ -83,12 +92,14 @@ public final class OpenOffer implements Tradable {
|
|||
State state,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress) {
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
long triggerPrice) {
|
||||
this.offer = offer;
|
||||
this.state = state;
|
||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||
this.mediatorNodeAddress = mediatorNodeAddress;
|
||||
this.refundAgentNodeAddress = refundAgentNodeAddress;
|
||||
this.triggerPrice = triggerPrice;
|
||||
|
||||
if (this.state == State.RESERVED)
|
||||
setState(State.AVAILABLE);
|
||||
|
@ -98,6 +109,7 @@ public final class OpenOffer implements Tradable {
|
|||
public protobuf.Tradable toProtoMessage() {
|
||||
protobuf.OpenOffer.Builder builder = protobuf.OpenOffer.newBuilder()
|
||||
.setOffer(offer.toProtoMessage())
|
||||
.setTriggerPrice(triggerPrice)
|
||||
.setState(protobuf.OpenOffer.State.valueOf(state.name()));
|
||||
|
||||
Optional.ofNullable(arbitratorNodeAddress).ifPresent(nodeAddress -> builder.setArbitratorNodeAddress(nodeAddress.toProtoMessage()));
|
||||
|
@ -112,7 +124,8 @@ public final class OpenOffer implements Tradable {
|
|||
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()),
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
|
||||
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null);
|
||||
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
|
||||
proto.getTriggerPrice());
|
||||
}
|
||||
|
||||
|
||||
|
@ -178,6 +191,7 @@ public final class OpenOffer implements Tradable {
|
|||
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
||||
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
|
||||
",\n triggerPrice=" + triggerPrice +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -358,6 +358,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
public void placeOffer(Offer offer,
|
||||
double buyerSecurityDeposit,
|
||||
boolean useSavingsWallet,
|
||||
long triggerPrice,
|
||||
TransactionResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
checkNotNull(offer.getMakerFee(), "makerFee must not be null");
|
||||
|
@ -382,7 +383,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
|
||||
model,
|
||||
transaction -> {
|
||||
OpenOffer openOffer = new OpenOffer(offer);
|
||||
OpenOffer openOffer = new OpenOffer(offer, triggerPrice);
|
||||
openOffers.add(openOffer);
|
||||
requestPersistence();
|
||||
resultHandler.handleResult(transaction);
|
||||
|
@ -486,6 +487,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
|
||||
public void editOpenOfferPublish(Offer editedOffer,
|
||||
long triggerPrice,
|
||||
OpenOffer.State originalState,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
|
@ -498,7 +500,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
openOffer.setState(OpenOffer.State.CANCELED);
|
||||
openOffers.remove(openOffer);
|
||||
|
||||
OpenOffer editedOpenOffer = new OpenOffer(editedOffer);
|
||||
OpenOffer editedOpenOffer = new OpenOffer(editedOffer, triggerPrice);
|
||||
editedOpenOffer.setState(originalState);
|
||||
|
||||
openOffers.add(editedOpenOffer);
|
||||
|
@ -855,7 +857,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
updatedOffer.setPriceFeedService(priceFeedService);
|
||||
updatedOffer.setState(originalOfferState);
|
||||
|
||||
OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer);
|
||||
OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, originalOpenOffer.getTriggerPrice());
|
||||
updatedOpenOffer.setState(originalOpenOfferState);
|
||||
openOffers.add(updatedOpenOffer);
|
||||
requestPersistence();
|
||||
|
|
163
core/src/main/java/bisq/core/offer/TriggerPriceService.java
Normal file
163
core/src/main/java/bisq/core/offer/TriggerPriceService.java
Normal file
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.offer;
|
||||
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
|
||||
import bisq.common.util.MathUtils;
|
||||
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.common.util.MathUtils.roundDoubleToLong;
|
||||
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class TriggerPriceService {
|
||||
private final OpenOfferManager openOfferManager;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final Map<String, Set<OpenOffer>> openOffersByCurrency = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
public TriggerPriceService(OpenOfferManager openOfferManager, PriceFeedService priceFeedService) {
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.priceFeedService = priceFeedService;
|
||||
}
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
openOfferManager.getObservableList().addListener((ListChangeListener<OpenOffer>) c -> {
|
||||
c.next();
|
||||
if (c.wasAdded()) {
|
||||
onAddedOpenOffers(c.getAddedSubList());
|
||||
}
|
||||
if (c.wasRemoved()) {
|
||||
onRemovedOpenOffers(c.getRemoved());
|
||||
}
|
||||
});
|
||||
onAddedOpenOffers(openOfferManager.getObservableList());
|
||||
|
||||
priceFeedService.updateCounterProperty().addListener((observable, oldValue, newValue) -> onPriceFeedChanged());
|
||||
onPriceFeedChanged();
|
||||
}
|
||||
|
||||
private void onPriceFeedChanged() {
|
||||
openOffersByCurrency.keySet().stream()
|
||||
.map(priceFeedService::getMarketPrice)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(marketPrice -> openOffersByCurrency.containsKey(marketPrice.getCurrencyCode()))
|
||||
.forEach(marketPrice -> {
|
||||
openOffersByCurrency.get(marketPrice.getCurrencyCode()).stream()
|
||||
.filter(openOffer -> !openOffer.isDeactivated())
|
||||
.forEach(openOffer -> checkPriceThreshold(marketPrice, openOffer));
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean wasTriggered(MarketPrice marketPrice, OpenOffer openOffer) {
|
||||
Price price = openOffer.getOffer().getPrice();
|
||||
if (price == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String currencyCode = openOffer.getOffer().getCurrencyCode();
|
||||
boolean cryptoCurrency = CurrencyUtil.isCryptoCurrency(currencyCode);
|
||||
int smallestUnitExponent = cryptoCurrency ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
long marketPriceAsLong = roundDoubleToLong(
|
||||
scaleUpByPowerOf10(marketPrice.getPrice(), smallestUnitExponent));
|
||||
long triggerPrice = openOffer.getTriggerPrice();
|
||||
if (triggerPrice <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OfferPayload.Direction direction = openOffer.getOffer().getDirection();
|
||||
boolean isSellOffer = direction == OfferPayload.Direction.SELL;
|
||||
boolean condition = isSellOffer && !cryptoCurrency || !isSellOffer && cryptoCurrency;
|
||||
return condition ?
|
||||
marketPriceAsLong < triggerPrice :
|
||||
marketPriceAsLong > triggerPrice;
|
||||
}
|
||||
|
||||
private void checkPriceThreshold(MarketPrice marketPrice, OpenOffer openOffer) {
|
||||
if (wasTriggered(marketPrice, openOffer)) {
|
||||
String currencyCode = openOffer.getOffer().getCurrencyCode();
|
||||
int smallestUnitExponent = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
long triggerPrice = openOffer.getTriggerPrice();
|
||||
|
||||
log.info("Market price exceeded the trigger price of the open offer.\n" +
|
||||
"We deactivate the open offer with ID {}.\nCurrency: {};\nOffer direction: {};\n" +
|
||||
"Market price: {};\nTrigger price: {}",
|
||||
openOffer.getOffer().getShortId(),
|
||||
currencyCode,
|
||||
openOffer.getOffer().getDirection(),
|
||||
marketPrice.getPrice(),
|
||||
MathUtils.scaleDownByPowerOf10(triggerPrice, smallestUnitExponent)
|
||||
);
|
||||
|
||||
openOfferManager.deactivateOpenOffer(openOffer, () -> {
|
||||
}, errorMessage -> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void onAddedOpenOffers(List<? extends OpenOffer> openOffers) {
|
||||
openOffers.forEach(openOffer -> {
|
||||
String currencyCode = openOffer.getOffer().getCurrencyCode();
|
||||
openOffersByCurrency.putIfAbsent(currencyCode, new HashSet<>());
|
||||
openOffersByCurrency.get(currencyCode).add(openOffer);
|
||||
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(openOffer.getOffer().getCurrencyCode());
|
||||
if (marketPrice != null) {
|
||||
checkPriceThreshold(marketPrice, openOffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onRemovedOpenOffers(List<? extends OpenOffer> openOffers) {
|
||||
openOffers.forEach(openOffer -> {
|
||||
String currencyCode = openOffer.getOffer().getCurrencyCode();
|
||||
if (openOffersByCurrency.containsKey(currencyCode)) {
|
||||
Set<OpenOffer> set = openOffersByCurrency.get(currencyCode);
|
||||
set.remove(openOffer);
|
||||
if (set.isEmpty()) {
|
||||
openOffersByCurrency.remove(currencyCode);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.support.dispute.agent;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Slf4j
|
||||
public class DisputeAgentLookupMap {
|
||||
|
||||
// See also: https://bisq.wiki/Finding_your_mediator
|
||||
@Nullable
|
||||
public static String getKeyBaseUserName(String fullAddress) {
|
||||
switch (fullAddress) {
|
||||
case "sjlho4zwp3gecspf.onion:9999":
|
||||
return "leo816";
|
||||
case "wizbisqzd7ku25di7p2ztsajioabihlnyp5lq5av66tmu7do2dke2tid.onion:9999":
|
||||
return "wiz";
|
||||
case "apbp7ubuyezav4hy.onion:9999":
|
||||
return "bisq_knight";
|
||||
case "a56olqlmmpxrn5q34itq5g5tb5d3fg7vxekpbceq7xqvfl3cieocgsyd.onion:9999":
|
||||
return "leo816";
|
||||
case "3z5jnirlccgxzoxc6zwkcgwj66bugvqplzf6z2iyd5oxifiaorhnanqd.onion:9999":
|
||||
return "refundagent2";
|
||||
default:
|
||||
log.warn("No user name for dispute agent with address {} found.", fullAddress);
|
||||
return Res.get("shared.na");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -171,7 +171,7 @@ public class FormattingUtils {
|
|||
return formatMarketPrice(price, 8);
|
||||
}
|
||||
|
||||
private static String formatMarketPrice(double price, int precision) {
|
||||
public static String formatMarketPrice(double price, int precision) {
|
||||
return formatRoundedDoubleWithPrecision(price, precision);
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,6 @@ shared.selectTradingAccount=Select trading account
|
|||
shared.fundFromSavingsWalletButton=Transfer funds from Bisq wallet
|
||||
shared.fundFromExternalWalletButton=Open your external wallet for funding
|
||||
shared.openDefaultWalletFailed=Failed to open a Bitcoin wallet application. Are you sure you have one installed?
|
||||
shared.distanceInPercent=Distance in % from market price
|
||||
shared.belowInPercent=Below % from market price
|
||||
shared.aboveInPercent=Above % from market price
|
||||
shared.enterPercentageValue=Enter % value
|
||||
|
@ -455,6 +454,13 @@ createOffer.warning.buyAboveMarketPrice=You will always pay {0}% more than the c
|
|||
createOffer.tradeFee.descriptionBTCOnly=Trade fee
|
||||
createOffer.tradeFee.descriptionBSQEnabled=Select trade fee currency
|
||||
|
||||
createOffer.triggerPrice.prompt=Set optional trigger price
|
||||
createOffer.triggerPrice.label=Deactivate offer if market price is {0}
|
||||
createOffer.triggerPrice.tooltip=As protecting against drastic price movements you can set a trigger price which \
|
||||
deactivates the offer if the market price reaches that value.
|
||||
createOffer.triggerPrice.invalid.tooLow=Value must be higher than {0}
|
||||
createOffer.triggerPrice.invalid.tooHigh=Value must be lower than {0}
|
||||
|
||||
# new entries
|
||||
createOffer.placeOfferButton=Review: Place offer to {0} bitcoin
|
||||
createOffer.createOfferFundWalletInfo.headline=Fund your offer
|
||||
|
@ -551,6 +557,11 @@ takeOffer.tac=With taking this offer I agree to the trade conditions as defined
|
|||
# Offerbook / Edit offer
|
||||
####################################################################
|
||||
|
||||
openOffer.header.triggerPrice=Trigger price
|
||||
openOffer.triggerPrice=Trigger price {0}
|
||||
openOffer.triggered=The offer has been deactivated because the market price reached your trigger price.\n\
|
||||
Please edit the offer to define a new trigger price
|
||||
|
||||
editOffer.setPrice=Set price
|
||||
editOffer.confirmEdit=Confirm: Edit offer
|
||||
editOffer.publishOffer=Publishing your offer.
|
||||
|
@ -1114,6 +1125,7 @@ support.error=Receiver could not process message. Error: {0}
|
|||
support.buyerAddress=BTC buyer address
|
||||
support.sellerAddress=BTC seller address
|
||||
support.role=Role
|
||||
support.agent=Support agent
|
||||
support.state=State
|
||||
support.closed=Closed
|
||||
support.open=Open
|
||||
|
|
|
@ -19,6 +19,7 @@ package bisq.desktop.components;
|
|||
|
||||
import bisq.desktop.components.controlsfx.control.PopOver;
|
||||
|
||||
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
|
||||
import javafx.scene.Node;
|
||||
|
@ -30,23 +31,18 @@ import javafx.beans.property.StringProperty;
|
|||
|
||||
import lombok.Getter;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.getIcon;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class InfoInputTextField extends AnchorPane {
|
||||
|
||||
private final StringProperty text = new SimpleStringProperty();
|
||||
|
||||
@Getter
|
||||
private final InputTextField inputTextField;
|
||||
@Getter
|
||||
private final Label infoIcon;
|
||||
@Getter
|
||||
private final Label warningIcon;
|
||||
@Getter
|
||||
private final Label privacyIcon;
|
||||
|
||||
private Label currentIcon;
|
||||
private PopOverWrapper popoverWrapper = new PopOverWrapper();
|
||||
private final Label icon;
|
||||
private final PopOverWrapper popoverWrapper = new PopOverWrapper();
|
||||
@Nullable
|
||||
private Node node;
|
||||
|
||||
public InfoInputTextField() {
|
||||
this(0);
|
||||
|
@ -56,79 +52,67 @@ public class InfoInputTextField extends AnchorPane {
|
|||
super();
|
||||
|
||||
inputTextField = new InputTextField(inputLineExtension);
|
||||
|
||||
infoIcon = getIcon(AwesomeIcon.INFO_SIGN);
|
||||
infoIcon.setLayoutY(3);
|
||||
infoIcon.getStyleClass().addAll("icon", "info");
|
||||
|
||||
warningIcon = getIcon(AwesomeIcon.WARNING_SIGN);
|
||||
warningIcon.setLayoutY(3);
|
||||
warningIcon.getStyleClass().addAll("icon", "warning");
|
||||
|
||||
privacyIcon = getIcon(AwesomeIcon.EYE_CLOSE);
|
||||
privacyIcon.setLayoutY(3);
|
||||
privacyIcon.getStyleClass().addAll("icon", "info");
|
||||
|
||||
AnchorPane.setLeftAnchor(infoIcon, 7.0);
|
||||
AnchorPane.setLeftAnchor(warningIcon, 7.0);
|
||||
AnchorPane.setLeftAnchor(privacyIcon, 7.0);
|
||||
AnchorPane.setRightAnchor(inputTextField, 0.0);
|
||||
AnchorPane.setLeftAnchor(inputTextField, 0.0);
|
||||
|
||||
hideIcons();
|
||||
icon = new Label();
|
||||
icon.setLayoutY(3);
|
||||
AnchorPane.setLeftAnchor(icon, 7.0);
|
||||
icon.setOnMouseEntered(e -> {
|
||||
if (node != null) {
|
||||
popoverWrapper.showPopOver(() -> checkNotNull(createPopOver()));
|
||||
}
|
||||
});
|
||||
icon.setOnMouseExited(e -> {
|
||||
if (node != null) {
|
||||
popoverWrapper.hidePopOver();
|
||||
}
|
||||
});
|
||||
|
||||
getChildren().addAll(inputTextField, infoIcon, warningIcon, privacyIcon);
|
||||
hideIcon();
|
||||
|
||||
getChildren().addAll(inputTextField, icon);
|
||||
}
|
||||
|
||||
|
||||
private void hideIcons() {
|
||||
infoIcon.setManaged(false);
|
||||
infoIcon.setVisible(false);
|
||||
warningIcon.setManaged(false);
|
||||
warningIcon.setVisible(false);
|
||||
privacyIcon.setManaged(false);
|
||||
privacyIcon.setVisible(false);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Public
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void setContentForInfoPopOver(Node node) {
|
||||
currentIcon = infoIcon;
|
||||
|
||||
hideIcons();
|
||||
setActionHandlers(node);
|
||||
setContentForPopOver(node, AwesomeIcon.INFO_SIGN);
|
||||
}
|
||||
|
||||
public void setContentForWarningPopOver(Node node) {
|
||||
currentIcon = warningIcon;
|
||||
|
||||
hideIcons();
|
||||
setActionHandlers(node);
|
||||
setContentForPopOver(node, AwesomeIcon.WARNING_SIGN, "warning");
|
||||
}
|
||||
|
||||
public void setContentForPrivacyPopOver(Node node) {
|
||||
currentIcon = privacyIcon;
|
||||
|
||||
hideIcons();
|
||||
setActionHandlers(node);
|
||||
setContentForPopOver(node, AwesomeIcon.EYE_CLOSE);
|
||||
}
|
||||
|
||||
public void hideInfoContent() {
|
||||
currentIcon = null;
|
||||
hideIcons();
|
||||
public void setContentForPopOver(Node node, AwesomeIcon awesomeIcon) {
|
||||
setContentForPopOver(node, awesomeIcon, null);
|
||||
}
|
||||
|
||||
public void setContentForPopOver(Node node, AwesomeIcon awesomeIcon, @Nullable String style) {
|
||||
this.node = node;
|
||||
AwesomeDude.setIcon(icon, awesomeIcon);
|
||||
icon.getStyleClass().addAll("icon", style == null ? "info" : style);
|
||||
icon.setManaged(true);
|
||||
icon.setVisible(true);
|
||||
}
|
||||
|
||||
public void hideIcon() {
|
||||
icon.setManaged(false);
|
||||
icon.setVisible(false);
|
||||
}
|
||||
|
||||
public void setIconsRightAligned() {
|
||||
AnchorPane.clearConstraints(infoIcon);
|
||||
AnchorPane.clearConstraints(warningIcon);
|
||||
AnchorPane.clearConstraints(privacyIcon);
|
||||
AnchorPane.clearConstraints(icon);
|
||||
AnchorPane.clearConstraints(inputTextField);
|
||||
|
||||
AnchorPane.setRightAnchor(infoIcon, 7.0);
|
||||
AnchorPane.setRightAnchor(warningIcon, 7.0);
|
||||
AnchorPane.setRightAnchor(privacyIcon, 7.0);
|
||||
AnchorPane.setRightAnchor(icon, 7.0);
|
||||
AnchorPane.setLeftAnchor(inputTextField, 0.0);
|
||||
AnchorPane.setRightAnchor(inputTextField, 0.0);
|
||||
}
|
||||
|
@ -146,7 +130,7 @@ public class InfoInputTextField extends AnchorPane {
|
|||
return text.get();
|
||||
}
|
||||
|
||||
public final StringProperty textProperty() {
|
||||
public StringProperty textProperty() {
|
||||
return text;
|
||||
}
|
||||
|
||||
|
@ -155,28 +139,18 @@ public class InfoInputTextField extends AnchorPane {
|
|||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void setActionHandlers(Node node) {
|
||||
|
||||
if (node != null) {
|
||||
currentIcon.setManaged(true);
|
||||
currentIcon.setVisible(true);
|
||||
|
||||
// As we don't use binding here we need to recreate it on mouse over to reflect the current state
|
||||
currentIcon.setOnMouseEntered(e -> popoverWrapper.showPopOver(() -> createPopOver(node)));
|
||||
currentIcon.setOnMouseExited(e -> popoverWrapper.hidePopOver());
|
||||
private PopOver createPopOver() {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private PopOver createPopOver(Node node) {
|
||||
node.getStyleClass().add("default-text");
|
||||
|
||||
PopOver popover = new PopOver(node);
|
||||
if (currentIcon.getScene() != null) {
|
||||
if (icon.getScene() != null) {
|
||||
popover.setDetachable(false);
|
||||
popover.setArrowLocation(PopOver.ArrowLocation.LEFT_TOP);
|
||||
popover.setArrowIndent(5);
|
||||
|
||||
popover.show(currentIcon, -17);
|
||||
popover.show(icon, -17);
|
||||
}
|
||||
return popover;
|
||||
}
|
||||
|
|
233
desktop/src/main/java/bisq/desktop/main/PriceUtil.java
Normal file
233
desktop/src/main/java/bisq/desktop/main/PriceUtil.java
Normal file
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.desktop.main;
|
||||
|
||||
import bisq.desktop.util.validation.AltcoinValidator;
|
||||
import bisq.desktop.util.validation.FiatPriceValidator;
|
||||
import bisq.desktop.util.validation.MonetaryValidator;
|
||||
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.AveragePriceUtil;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.validation.InputValidator;
|
||||
|
||||
import bisq.common.util.MathUtils;
|
||||
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.desktop.main.shared.ChatView.log;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Singleton
|
||||
public class PriceUtil {
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final Preferences preferences;
|
||||
@Nullable
|
||||
private Price bsq30DayAveragePrice;
|
||||
|
||||
@Inject
|
||||
public PriceUtil(PriceFeedService priceFeedService,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
Preferences preferences) {
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
public static MonetaryValidator getPriceValidator(boolean isFiatCurrency) {
|
||||
return isFiatCurrency ?
|
||||
new FiatPriceValidator() :
|
||||
new AltcoinValidator();
|
||||
}
|
||||
|
||||
public static InputValidator.ValidationResult isTriggerPriceValid(String triggerPriceAsString,
|
||||
Price price,
|
||||
boolean isSellOffer,
|
||||
boolean isFiatCurrency) {
|
||||
if (triggerPriceAsString == null || triggerPriceAsString.isEmpty()) {
|
||||
return new InputValidator.ValidationResult(true);
|
||||
}
|
||||
|
||||
InputValidator.ValidationResult result = getPriceValidator(isFiatCurrency).validate(triggerPriceAsString);
|
||||
if (!result.isValid) {
|
||||
return result;
|
||||
}
|
||||
|
||||
long triggerPriceAsLong = PriceUtil.getMarketPriceAsLong(triggerPriceAsString, price.getCurrencyCode());
|
||||
long priceAsLong = price.getValue();
|
||||
String priceAsString = FormattingUtils.formatPrice(price);
|
||||
if ((isSellOffer && isFiatCurrency) || (!isSellOffer && !isFiatCurrency)) {
|
||||
if (triggerPriceAsLong >= priceAsLong) {
|
||||
return new InputValidator.ValidationResult(false,
|
||||
Res.get("createOffer.triggerPrice.invalid.tooHigh", priceAsString));
|
||||
} else {
|
||||
return new InputValidator.ValidationResult(true);
|
||||
}
|
||||
} else {
|
||||
if (triggerPriceAsLong <= priceAsLong) {
|
||||
return new InputValidator.ValidationResult(false,
|
||||
Res.get("createOffer.triggerPrice.invalid.tooLow", priceAsString));
|
||||
} else {
|
||||
return new InputValidator.ValidationResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void recalculateBsq30DayAveragePrice() {
|
||||
bsq30DayAveragePrice = null;
|
||||
bsq30DayAveragePrice = getBsq30DayAveragePrice();
|
||||
}
|
||||
|
||||
public Price getBsq30DayAveragePrice() {
|
||||
if (bsq30DayAveragePrice == null) {
|
||||
bsq30DayAveragePrice = AveragePriceUtil.getAveragePriceTuple(preferences,
|
||||
tradeStatisticsManager, 30).second;
|
||||
}
|
||||
return bsq30DayAveragePrice;
|
||||
}
|
||||
|
||||
public boolean hasMarketPrice(Offer offer) {
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
Price price = offer.getPrice();
|
||||
return price != null && marketPrice != null && marketPrice.isRecentExternalPriceAvailable();
|
||||
}
|
||||
|
||||
public Optional<Double> getMarketBasedPrice(Offer offer,
|
||||
OfferPayload.Direction direction) {
|
||||
if (offer.isUseMarketBasedPrice()) {
|
||||
return Optional.of(offer.getMarketPriceMargin());
|
||||
}
|
||||
|
||||
if (!hasMarketPrice(offer)) {
|
||||
if (offer.getCurrencyCode().equals("BSQ")) {
|
||||
Price bsq30DayAveragePrice = getBsq30DayAveragePrice();
|
||||
if (bsq30DayAveragePrice.isPositive()) {
|
||||
double scaled = MathUtils.scaleDownByPowerOf10(bsq30DayAveragePrice.getValue(), 8);
|
||||
return calculatePercentage(offer, scaled, direction);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
log.trace("We don't have a market price. " +
|
||||
"That case could only happen if you don't have a price feed.");
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
double marketPriceAsDouble = checkNotNull(marketPrice).getPrice();
|
||||
return calculatePercentage(offer, marketPriceAsDouble, direction);
|
||||
}
|
||||
|
||||
public Optional<Double> calculatePercentage(Offer offer,
|
||||
double marketPrice,
|
||||
OfferPayload.Direction direction) {
|
||||
// If the offer did not use % price we calculate % from current market price
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
Price price = offer.getPrice();
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
long priceAsLong = checkNotNull(price).getValue();
|
||||
double scaled = MathUtils.scaleDownByPowerOf10(priceAsLong, precision);
|
||||
double value;
|
||||
if (direction == OfferPayload.Direction.SELL) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
if (marketPrice == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = 1 - scaled / marketPrice;
|
||||
} else {
|
||||
if (marketPrice == 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = scaled / marketPrice - 1;
|
||||
}
|
||||
} else {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
if (marketPrice == 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = scaled / marketPrice - 1;
|
||||
} else {
|
||||
if (marketPrice == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = 1 - scaled / marketPrice;
|
||||
}
|
||||
}
|
||||
return Optional.of(value);
|
||||
}
|
||||
|
||||
public static long getMarketPriceAsLong(String inputValue, String currencyCode) {
|
||||
if (inputValue == null || inputValue.isEmpty() || currencyCode == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
int precision = getMarketPricePrecision(currencyCode);
|
||||
String stringValue = reformatMarketPrice(inputValue, currencyCode);
|
||||
return ParsingUtils.parsePriceStringToLong(currencyCode, stringValue, precision);
|
||||
} catch (Throwable t) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static String reformatMarketPrice(String inputValue, String currencyCode) {
|
||||
if (inputValue == null || inputValue.isEmpty() || currencyCode == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
double priceAsDouble = ParsingUtils.parseNumberStringToDouble(inputValue);
|
||||
int precision = getMarketPricePrecision(currencyCode);
|
||||
return FormattingUtils.formatRoundedDoubleWithPrecision(priceAsDouble, precision);
|
||||
}
|
||||
|
||||
public static String formatMarketPrice(long price, String currencyCode) {
|
||||
int marketPricePrecision = getMarketPricePrecision(currencyCode);
|
||||
double scaled = MathUtils.scaleDownByPowerOf10(price, marketPricePrecision);
|
||||
return FormattingUtils.formatMarketPrice(scaled, marketPricePrecision);
|
||||
}
|
||||
|
||||
public static int getMarketPricePrecision(String currencyCode) {
|
||||
return CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import bisq.desktop.common.view.ActivatableView;
|
|||
import bisq.desktop.common.view.FxmlView;
|
||||
import bisq.desktop.components.InfoInputTextField;
|
||||
import bisq.desktop.components.InputTextField;
|
||||
import bisq.desktop.main.PriceUtil;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.main.overlays.windows.WebCamWindow;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
|
@ -33,7 +34,6 @@ import bisq.desktop.util.validation.PercentageNumberValidator;
|
|||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.notifications.MobileMessage;
|
||||
import bisq.core.notifications.MobileNotificationService;
|
||||
import bisq.core.notifications.alerts.DisputeMsgEvents;
|
||||
|
@ -693,6 +693,7 @@ public class MobileNotificationsView extends ActivatableView<GridPane, Void> {
|
|||
currencyComboBox.getSelectionModel().select(optionalTradeCurrency.get());
|
||||
onSelectedTradeCurrency();
|
||||
|
||||
priceAlertHighInputTextField.setText(PriceUtil.formatMarketPrice(priceAlertFilter.getHigh(), currencyCode));
|
||||
priceAlertHighInputTextField.setText(FormattingUtils.formatMarketPrice(priceAlertFilter.getHigh() / 10000d, currencyCode));
|
||||
priceAlertLowInputTextField.setText(FormattingUtils.formatMarketPrice(priceAlertFilter.getLow() / 10000d, currencyCode));
|
||||
} else {
|
||||
|
@ -742,37 +743,13 @@ public class MobileNotificationsView extends ActivatableView<GridPane, Void> {
|
|||
}
|
||||
|
||||
private long getPriceAsLong(InputTextField inputTextField) {
|
||||
try {
|
||||
String inputValue = inputTextField.getText();
|
||||
if (inputValue != null && !inputValue.isEmpty() && selectedPriceAlertTradeCurrency != null) {
|
||||
double priceAsDouble = ParsingUtils.parseNumberStringToDouble(inputValue);
|
||||
String currencyCode = selectedPriceAlertTradeCurrency;
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT : 2;
|
||||
// We want to use the converted value not the inout value as we apply the converted value at focus out.
|
||||
// E.g. if input is 5555.5555 it will be rounded to 5555.55 and we use that as the value for comparing
|
||||
// low and high price...
|
||||
String stringValue = FormattingUtils.formatRoundedDoubleWithPrecision(priceAsDouble, precision);
|
||||
return ParsingUtils.parsePriceStringToLong(currencyCode, stringValue, precision);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
return 0;
|
||||
}
|
||||
return PriceUtil.getMarketPriceAsLong(inputTextField.getText(), selectedPriceAlertTradeCurrency);
|
||||
}
|
||||
|
||||
private void applyPriceFormatting(InputTextField inputTextField) {
|
||||
try {
|
||||
String inputValue = inputTextField.getText();
|
||||
if (inputValue != null && !inputValue.isEmpty() && selectedPriceAlertTradeCurrency != null) {
|
||||
double priceAsDouble = ParsingUtils.parseNumberStringToDouble(inputValue);
|
||||
String currencyCode = selectedPriceAlertTradeCurrency;
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT : 2;
|
||||
String stringValue = FormattingUtils.formatRoundedDoubleWithPrecision(priceAsDouble, precision);
|
||||
inputTextField.setText(stringValue);
|
||||
}
|
||||
String reformattedPrice = PriceUtil.reformatMarketPrice(inputTextField.getText(), selectedPriceAlertTradeCurrency);
|
||||
inputTextField.setText(reformattedPrice);
|
||||
} catch (Throwable ignore) {
|
||||
updatePriceAlertFields();
|
||||
}
|
||||
|
|
|
@ -179,7 +179,7 @@ public class LockedView extends ActivatableView<VBox, Void> {
|
|||
exportButton.setOnAction(event -> {
|
||||
ObservableList<TableColumn<LockedListItem, ?>> tableColumns = tableView.getColumns();
|
||||
int reportColumns = tableColumns.size();
|
||||
CSVEntryConverter<LockedListItem> headerConverter = transactionsListItem -> {
|
||||
CSVEntryConverter<LockedListItem> headerConverter = item -> {
|
||||
String[] columns = new String[reportColumns];
|
||||
for (int i = 0; i < columns.length; i++)
|
||||
columns[i] = ((AutoTooltipLabel) tableColumns.get(i).getGraphic()).getText();
|
||||
|
|
|
@ -179,7 +179,7 @@ public class ReservedView extends ActivatableView<VBox, Void> {
|
|||
exportButton.setOnAction(event -> {
|
||||
ObservableList<TableColumn<ReservedListItem, ?>> tableColumns = tableView.getColumns();
|
||||
int reportColumns = tableColumns.size();
|
||||
CSVEntryConverter<ReservedListItem> headerConverter = transactionsListItem -> {
|
||||
CSVEntryConverter<ReservedListItem> headerConverter = item -> {
|
||||
String[] columns = new String[reportColumns];
|
||||
for (int i = 0; i < columns.length; i++)
|
||||
columns[i] = ((AutoTooltipLabel) tableColumns.get(i).getGraphic()).getText();
|
||||
|
|
|
@ -212,7 +212,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
exportButton.setOnAction(event -> {
|
||||
final ObservableList<TableColumn<TransactionsListItem, ?>> tableColumns = tableView.getColumns();
|
||||
final int reportColumns = tableColumns.size() - 1; // CSV report excludes the last column (an icon)
|
||||
CSVEntryConverter<TransactionsListItem> headerConverter = transactionsListItem -> {
|
||||
CSVEntryConverter<TransactionsListItem> headerConverter = item -> {
|
||||
String[] columns = new String[reportColumns];
|
||||
for (int i = 0; i < columns.length; i++)
|
||||
columns[i] = ((AutoTooltipLabel) tableColumns.get(i).getGraphic()).getText();
|
||||
|
|
|
@ -378,7 +378,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
int reportColumns = tableColumns.size() + 1;
|
||||
|
||||
boolean showAllTradeCurrencies = model.showAllTradeCurrenciesProperty.get();
|
||||
CSVEntryConverter<TradeStatistics3ListItem> headerConverter = transactionsListItem -> {
|
||||
CSVEntryConverter<TradeStatistics3ListItem> headerConverter = item -> {
|
||||
String[] columns = new String[reportColumns];
|
||||
columns[0] = "Epoch time in ms";
|
||||
for (int i = 0; i < tableColumns.size(); i++) {
|
||||
|
|
|
@ -88,6 +88,8 @@ import java.util.Optional;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
@ -129,6 +131,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
|||
boolean isTabSelected;
|
||||
protected double marketPriceMargin = 0;
|
||||
private Coin txFeeFromFeeService = Coin.ZERO;
|
||||
@Getter
|
||||
private boolean marketPriceAvailable;
|
||||
private int feeTxVsize = TxFeeEstimationService.TYPICAL_TX_WITH_1_INPUT_VSIZE;
|
||||
protected boolean allowAmountUpdate = true;
|
||||
|
@ -137,6 +140,9 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
|||
private final Predicate<ObjectProperty<Coin>> isNonZeroAmount = (c) -> c.get() != null && !c.get().isZero();
|
||||
private final Predicate<ObjectProperty<Price>> isNonZeroPrice = (p) -> p.get() != null && !p.get().isZero();
|
||||
private final Predicate<ObjectProperty<Volume>> isNonZeroVolume = (v) -> v.get() != null && !v.get().isZero();
|
||||
@Getter
|
||||
protected long triggerPrice;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
|
@ -315,6 +321,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
|||
openOfferManager.placeOffer(offer,
|
||||
buyerSecurityDeposit.get(),
|
||||
useSavingsWallet,
|
||||
triggerPrice,
|
||||
resultHandler,
|
||||
log::error);
|
||||
}
|
||||
|
@ -467,6 +474,14 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
|||
return direction;
|
||||
}
|
||||
|
||||
boolean isSellOffer() {
|
||||
return direction == OfferPayload.Direction.SELL;
|
||||
}
|
||||
|
||||
boolean isBuyOffer() {
|
||||
return direction == OfferPayload.Direction.BUY;
|
||||
}
|
||||
|
||||
AddressEntry getAddressEntry() {
|
||||
return addressEntry;
|
||||
}
|
||||
|
@ -595,10 +610,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
|||
return isBuyOffer() ? getBuyerSecurityDepositAsCoin() : getSellerSecurityDepositAsCoin();
|
||||
}
|
||||
|
||||
boolean isBuyOffer() {
|
||||
return offerUtil.isBuyOffer(getDirection());
|
||||
}
|
||||
|
||||
public Coin getTxFee() {
|
||||
if (isCurrencyForMakerFeeBtc())
|
||||
return txFeeFromFeeService;
|
||||
|
@ -668,6 +679,18 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
|||
return tradeCurrencyCode;
|
||||
}
|
||||
|
||||
public String getCurrencyCode() {
|
||||
return tradeCurrencyCode.get();
|
||||
}
|
||||
|
||||
boolean isCryptoCurrency() {
|
||||
return CurrencyUtil.isCryptoCurrency(tradeCurrencyCode.get());
|
||||
}
|
||||
|
||||
boolean isFiatCurrency() {
|
||||
return CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get());
|
||||
}
|
||||
|
||||
ReadOnlyBooleanProperty getUseMarketBasedPrice() {
|
||||
return useMarketBasedPrice;
|
||||
}
|
||||
|
@ -751,4 +774,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
|||
public boolean isMinBuyerSecurityDeposit() {
|
||||
return !getBuyerSecurityDepositAsCoin().isGreaterThan(Restrictions.getMinBuyerSecurityDepositAsCoin());
|
||||
}
|
||||
|
||||
public void setTriggerPrice(long triggerPrice) {
|
||||
this.triggerPrice = triggerPrice;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ import org.bitcoinj.core.Coin;
|
|||
import net.glxn.qrgen.QRCode;
|
||||
import net.glxn.qrgen.image.ImageType;
|
||||
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
||||
|
||||
import javafx.scene.Node;
|
||||
|
@ -133,32 +134,31 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
private BusyAnimation waitingForFundsSpinner;
|
||||
private AutoTooltipButton nextButton, cancelButton1, cancelButton2, placeOfferButton;
|
||||
private Button priceTypeToggleButton;
|
||||
private InputTextField fixedPriceTextField, marketBasedPriceTextField;
|
||||
private InputTextField fixedPriceTextField, marketBasedPriceTextField, triggerPriceInputTextField;
|
||||
protected InputTextField amountTextField, minAmountTextField, volumeTextField, buyerSecurityDepositInputTextField;
|
||||
private TextField currencyTextField;
|
||||
private AddressTextField addressTextField;
|
||||
private BalanceTextField balanceTextField;
|
||||
private FundsTextField totalToPayTextField;
|
||||
private Label amountDescriptionLabel, priceCurrencyLabel, priceDescriptionLabel, volumeDescriptionLabel,
|
||||
waitingForFundsLabel, marketBasedPriceLabel, percentagePriceDescription, tradeFeeDescriptionLabel,
|
||||
waitingForFundsLabel, marketBasedPriceLabel, percentagePriceDescriptionLabel, tradeFeeDescriptionLabel,
|
||||
resultLabel, tradeFeeInBtcLabel, tradeFeeInBsqLabel, xLabel, fakeXLabel, buyerSecurityDepositLabel,
|
||||
buyerSecurityDepositPercentageLabel;
|
||||
buyerSecurityDepositPercentageLabel, triggerPriceCurrencyLabel, triggerPriceDescriptionLabel;
|
||||
protected Label amountBtcLabel, volumeCurrencyLabel, minAmountBtcLabel;
|
||||
private ComboBox<PaymentAccount> paymentAccountsComboBox;
|
||||
private ComboBox<TradeCurrency> currencyComboBox;
|
||||
private ImageView qrCodeImageView;
|
||||
private VBox currencySelection, fixedPriceBox, percentagePriceBox,
|
||||
currencyTextFieldBox;
|
||||
private VBox currencySelection, fixedPriceBox, percentagePriceBox, currencyTextFieldBox, triggerPriceVBox;
|
||||
private HBox fundingHBox, firstRowHBox, secondRowHBox, placeOfferBox, amountValueCurrencyBox,
|
||||
priceAsPercentageValueCurrencyBox, volumeValueCurrencyBox, priceValueCurrencyBox,
|
||||
minAmountValueCurrencyBox, advancedOptionsBox;
|
||||
minAmountValueCurrencyBox, advancedOptionsBox, triggerPriceHBox;
|
||||
|
||||
private Subscription isWaitingForFundsSubscription, balanceSubscription;
|
||||
private ChangeListener<Boolean> amountFocusedListener, minAmountFocusedListener, volumeFocusedListener,
|
||||
buyerSecurityDepositFocusedListener, priceFocusedListener, placeOfferCompletedListener,
|
||||
priceAsPercentageFocusedListener, getShowWalletFundedNotificationListener,
|
||||
tradeFeeInBtcToggleListener, tradeFeeInBsqToggleListener, tradeFeeVisibleListener,
|
||||
isMinBuyerSecurityDepositListener;
|
||||
isMinBuyerSecurityDepositListener, triggerPriceFocusedListener;
|
||||
private ChangeListener<Coin> missingCoinListener;
|
||||
private ChangeListener<String> tradeCurrencyCodeListener, errorMessageListener,
|
||||
marketPriceMarginListener, volumeListener, buyerSecurityDepositInBTCListener;
|
||||
|
@ -170,10 +170,11 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
private final List<Node> editOfferElements = new ArrayList<>();
|
||||
private boolean clearXchangeWarningDisplayed, fasterPaymentsWarningDisplayed, isActivated;
|
||||
private InfoInputTextField marketBasedPriceInfoInputTextField, volumeInfoInputTextField,
|
||||
buyerSecurityDepositInfoInputTextField;
|
||||
buyerSecurityDepositInfoInputTextField, triggerPriceInfoInputTextField;
|
||||
private AutoTooltipSlideToggleButton tradeFeeInBtcToggle, tradeFeeInBsqToggle;
|
||||
private Text xIcon, fakeXIcon;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -237,7 +238,6 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
if (waitingForFundsSpinner != null)
|
||||
waitingForFundsSpinner.play();
|
||||
|
||||
//directionLabel.setText(model.getDirectionLabel());
|
||||
amountDescriptionLabel.setText(model.getAmountDescription());
|
||||
addressTextField.setAddress(model.getAddressAsString());
|
||||
addressTextField.setPaymentLabel(model.getPaymentLabel());
|
||||
|
@ -261,6 +261,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
tradeFeeInBsqToggle.setVisible(false);
|
||||
tradeFeeInBsqToggle.setManaged(false);
|
||||
}
|
||||
|
||||
Label popOverLabel = OfferViewUtil.createPopOverLabel(Res.get("createOffer.triggerPrice.tooltip"));
|
||||
triggerPriceInfoInputTextField.setContentForPopOver(popOverLabel, AwesomeIcon.SHIELD);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,14 +308,11 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
}
|
||||
|
||||
if (direction == OfferPayload.Direction.BUY) {
|
||||
|
||||
placeOfferButton.setId("buy-button-big");
|
||||
placeOfferButton.updateText(Res.get("createOffer.placeOfferButton", Res.get("shared.buy")));
|
||||
percentagePriceDescription.setText(Res.get("shared.belowInPercent"));
|
||||
} else {
|
||||
placeOfferButton.setId("sell-button-big");
|
||||
placeOfferButton.updateText(Res.get("createOffer.placeOfferButton", Res.get("shared.sell")));
|
||||
percentagePriceDescription.setText(Res.get("shared.aboveInPercent"));
|
||||
}
|
||||
|
||||
updatePriceToggle();
|
||||
|
@ -449,8 +449,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
private void updateOfferElementsStyle() {
|
||||
GridPane.setColumnSpan(firstRowHBox, 2);
|
||||
|
||||
final String activeInputStyle = "input-with-border";
|
||||
final String readOnlyInputStyle = "input-with-border-readonly";
|
||||
String activeInputStyle = "input-with-border";
|
||||
String readOnlyInputStyle = "input-with-border-readonly";
|
||||
amountValueCurrencyBox.getStyleClass().remove(activeInputStyle);
|
||||
amountValueCurrencyBox.getStyleClass().add(readOnlyInputStyle);
|
||||
priceAsPercentageValueCurrencyBox.getStyleClass().remove(activeInputStyle);
|
||||
|
@ -461,6 +461,12 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
priceValueCurrencyBox.getStyleClass().add(readOnlyInputStyle);
|
||||
minAmountValueCurrencyBox.getStyleClass().remove(activeInputStyle);
|
||||
minAmountValueCurrencyBox.getStyleClass().add(readOnlyInputStyle);
|
||||
triggerPriceHBox.getStyleClass().remove(activeInputStyle);
|
||||
triggerPriceHBox.getStyleClass().add(readOnlyInputStyle);
|
||||
|
||||
GridPane.setColumnSpan(secondRowHBox, 1);
|
||||
priceTypeToggleButton.setVisible(false);
|
||||
HBox.setMargin(priceTypeToggleButton, new Insets(16, -14, 0, 0));
|
||||
|
||||
resultLabel.getStyleClass().add("small");
|
||||
xLabel.getStyleClass().add("small");
|
||||
|
@ -542,7 +548,10 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
|
||||
private void addBindings() {
|
||||
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> CurrencyUtil.getCounterCurrency(model.tradeCurrencyCode.get()), model.tradeCurrencyCode));
|
||||
|
||||
triggerPriceCurrencyLabel.textProperty().bind(createStringBinding(() ->
|
||||
CurrencyUtil.getCounterCurrency(model.tradeCurrencyCode.get()), model.tradeCurrencyCode));
|
||||
triggerPriceDescriptionLabel.textProperty().bind(model.triggerPriceDescription);
|
||||
percentagePriceDescriptionLabel.textProperty().bind(model.percentagePriceDescription);
|
||||
marketBasedPriceLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty());
|
||||
volumeCurrencyLabel.textProperty().bind(model.tradeCurrencyCode);
|
||||
priceDescriptionLabel.textProperty().bind(createStringBinding(() -> CurrencyUtil.getPriceWithCurrencyCode(model.tradeCurrencyCode.get(), "shared.fixedPriceInCurForCur"), model.tradeCurrencyCode));
|
||||
|
@ -550,6 +559,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
amountTextField.textProperty().bindBidirectional(model.amount);
|
||||
minAmountTextField.textProperty().bindBidirectional(model.minAmount);
|
||||
fixedPriceTextField.textProperty().bindBidirectional(model.price);
|
||||
triggerPriceInputTextField.textProperty().bindBidirectional(model.triggerPrice);
|
||||
marketBasedPriceTextField.textProperty().bindBidirectional(model.marketPriceMargin);
|
||||
volumeTextField.textProperty().bindBidirectional(model.volume);
|
||||
volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
|
||||
|
@ -568,6 +578,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
amountTextField.validationResultProperty().bind(model.amountValidationResult);
|
||||
minAmountTextField.validationResultProperty().bind(model.minAmountValidationResult);
|
||||
fixedPriceTextField.validationResultProperty().bind(model.priceValidationResult);
|
||||
triggerPriceInputTextField.validationResultProperty().bind(model.triggerPriceValidationResult);
|
||||
volumeTextField.validationResultProperty().bind(model.volumeValidationResult);
|
||||
buyerSecurityDepositInputTextField.validationResultProperty().bind(model.buyerSecurityDepositValidationResult);
|
||||
|
||||
|
@ -590,16 +601,16 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
|
||||
private void removeBindings() {
|
||||
priceCurrencyLabel.textProperty().unbind();
|
||||
fixedPriceTextField.disableProperty().unbind();
|
||||
priceCurrencyLabel.disableProperty().unbind();
|
||||
marketBasedPriceTextField.disableProperty().unbind();
|
||||
marketBasedPriceLabel.disableProperty().unbind();
|
||||
triggerPriceCurrencyLabel.textProperty().unbind();
|
||||
triggerPriceDescriptionLabel.textProperty().unbind();
|
||||
percentagePriceDescriptionLabel.textProperty().unbind();
|
||||
volumeCurrencyLabel.textProperty().unbind();
|
||||
priceDescriptionLabel.textProperty().unbind();
|
||||
volumeDescriptionLabel.textProperty().unbind();
|
||||
amountTextField.textProperty().unbindBidirectional(model.amount);
|
||||
minAmountTextField.textProperty().unbindBidirectional(model.minAmount);
|
||||
fixedPriceTextField.textProperty().unbindBidirectional(model.price);
|
||||
triggerPriceInputTextField.textProperty().unbindBidirectional(model.triggerPrice);
|
||||
marketBasedPriceTextField.textProperty().unbindBidirectional(model.marketPriceMargin);
|
||||
marketBasedPriceLabel.prefWidthProperty().unbind();
|
||||
volumeTextField.textProperty().unbindBidirectional(model.volume);
|
||||
|
@ -619,6 +630,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
amountTextField.validationResultProperty().unbind();
|
||||
minAmountTextField.validationResultProperty().unbind();
|
||||
fixedPriceTextField.validationResultProperty().unbind();
|
||||
triggerPriceInputTextField.validationResultProperty().unbind();
|
||||
volumeTextField.validationResultProperty().unbind();
|
||||
buyerSecurityDepositInputTextField.validationResultProperty().unbind();
|
||||
|
||||
|
@ -686,6 +698,11 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
buyerSecurityDepositInputTextField.setText(model.buyerSecurityDeposit.get());
|
||||
};
|
||||
|
||||
triggerPriceFocusedListener = (o, oldValue, newValue) -> {
|
||||
model.onFocusOutTriggerPriceTextField(oldValue, newValue);
|
||||
triggerPriceInputTextField.setText(model.triggerPrice.get());
|
||||
};
|
||||
|
||||
errorMessageListener = (o, oldValue, newValue) -> {
|
||||
if (newValue != null)
|
||||
UserThread.runAfter(() -> new Popup().error(Res.get("createOffer.amountPriceBox.error.message", model.errorMessage.get()))
|
||||
|
@ -699,6 +716,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
fixedPriceTextField.clear();
|
||||
marketBasedPriceTextField.clear();
|
||||
volumeTextField.clear();
|
||||
triggerPriceInputTextField.clear();
|
||||
};
|
||||
|
||||
placeOfferCompletedListener = (o, oldValue, newValue) -> {
|
||||
|
@ -743,7 +761,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
|
||||
buyerSecurityDepositInBTCListener = (observable, oldValue, newValue) -> {
|
||||
if (!newValue.equals("")) {
|
||||
Label depositInBTCInfo = createPopoverLabel(model.getSecurityDepositPopOverLabel(newValue));
|
||||
Label depositInBTCInfo = OfferViewUtil.createPopOverLabel(model.getSecurityDepositPopOverLabel(newValue));
|
||||
buyerSecurityDepositInfoInputTextField.setContentForInfoPopOver(depositInBTCInfo);
|
||||
} else {
|
||||
buyerSecurityDepositInfoInputTextField.setContentForInfoPopOver(null);
|
||||
|
@ -752,9 +770,10 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
|
||||
volumeListener = (observable, oldValue, newValue) -> {
|
||||
if (!newValue.equals("") && CurrencyUtil.isFiatCurrency(model.tradeCurrencyCode.get())) {
|
||||
volumeInfoInputTextField.setContentForPrivacyPopOver(createPopoverLabel(Res.get("offerbook.info.roundedFiatVolume")));
|
||||
Label popOverLabel = OfferViewUtil.createPopOverLabel(Res.get("offerbook.info.roundedFiatVolume"));
|
||||
volumeInfoInputTextField.setContentForPrivacyPopOver(popOverLabel);
|
||||
} else {
|
||||
volumeInfoInputTextField.hideInfoContent();
|
||||
volumeInfoInputTextField.hideIcon();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -780,7 +799,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
} else {
|
||||
tooltip = Res.get("createOffer.info.buyAtMarketPrice");
|
||||
}
|
||||
final Label atMarketPriceLabel = createPopoverLabel(tooltip);
|
||||
final Label atMarketPriceLabel = OfferViewUtil.createPopOverLabel(tooltip);
|
||||
marketBasedPriceInfoInputTextField.setContentForInfoPopOver(atMarketPriceLabel);
|
||||
} else if (newValue.contains("-")) {
|
||||
if (model.isSellOffer()) {
|
||||
|
@ -788,7 +807,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
} else {
|
||||
tooltip = Res.get("createOffer.warning.buyAboveMarketPrice", newValue.substring(1));
|
||||
}
|
||||
final Label negativePercentageLabel = createPopoverLabel(tooltip);
|
||||
final Label negativePercentageLabel = OfferViewUtil.createPopOverLabel(tooltip);
|
||||
marketBasedPriceInfoInputTextField.setContentForWarningPopOver(negativePercentageLabel);
|
||||
} else if (!newValue.equals("")) {
|
||||
if (model.isSellOffer()) {
|
||||
|
@ -796,7 +815,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
} else {
|
||||
tooltip = Res.get("createOffer.info.buyBelowMarketPrice", newValue);
|
||||
}
|
||||
Label positivePercentageLabel = createPopoverLabel(tooltip);
|
||||
Label positivePercentageLabel = OfferViewUtil.createPopOverLabel(tooltip);
|
||||
marketBasedPriceInfoInputTextField.setContentForInfoPopOver(positivePercentageLabel);
|
||||
}
|
||||
}
|
||||
|
@ -850,14 +869,6 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
}
|
||||
}
|
||||
|
||||
private Label createPopoverLabel(String text) {
|
||||
final Label label = new Label(text);
|
||||
label.setPrefWidth(300);
|
||||
label.setWrapText(true);
|
||||
label.setPadding(new Insets(10));
|
||||
return label;
|
||||
}
|
||||
|
||||
protected void updatePriceToggle() {
|
||||
int marketPriceAvailableValue = model.marketPriceAvailableProperty.get();
|
||||
if (marketPriceAvailableValue > -1) {
|
||||
|
@ -887,6 +898,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
amountTextField.focusedProperty().addListener(amountFocusedListener);
|
||||
minAmountTextField.focusedProperty().addListener(minAmountFocusedListener);
|
||||
fixedPriceTextField.focusedProperty().addListener(priceFocusedListener);
|
||||
triggerPriceInputTextField.focusedProperty().addListener(triggerPriceFocusedListener);
|
||||
marketBasedPriceTextField.focusedProperty().addListener(priceAsPercentageFocusedListener);
|
||||
volumeTextField.focusedProperty().addListener(volumeFocusedListener);
|
||||
buyerSecurityDepositInputTextField.focusedProperty().addListener(buyerSecurityDepositFocusedListener);
|
||||
|
@ -921,6 +933,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
amountTextField.focusedProperty().removeListener(amountFocusedListener);
|
||||
minAmountTextField.focusedProperty().removeListener(minAmountFocusedListener);
|
||||
fixedPriceTextField.focusedProperty().removeListener(priceFocusedListener);
|
||||
triggerPriceInputTextField.focusedProperty().removeListener(triggerPriceFocusedListener);
|
||||
marketBasedPriceTextField.focusedProperty().removeListener(priceAsPercentageFocusedListener);
|
||||
volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
|
||||
buyerSecurityDepositInputTextField.focusedProperty().removeListener(buyerSecurityDepositFocusedListener);
|
||||
|
@ -1294,10 +1307,10 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
marketBasedPriceLabel = priceAsPercentageTuple.third;
|
||||
editOfferElements.add(marketBasedPriceLabel);
|
||||
Tuple2<Label, VBox> priceAsPercentageInputBoxTuple = getTradeInputBox(priceAsPercentageValueCurrencyBox,
|
||||
Res.get("shared.distanceInPercent"));
|
||||
percentagePriceDescription = priceAsPercentageInputBoxTuple.first;
|
||||
model.getPercentagePriceDescription());
|
||||
percentagePriceDescriptionLabel = priceAsPercentageInputBoxTuple.first;
|
||||
|
||||
getSmallIconForLabel(MaterialDesignIcon.CHART_LINE, percentagePriceDescription, "small-icon-label");
|
||||
getSmallIconForLabel(MaterialDesignIcon.CHART_LINE, percentagePriceDescriptionLabel, "small-icon-label");
|
||||
|
||||
percentagePriceBox = priceAsPercentageInputBoxTuple.second;
|
||||
|
||||
|
@ -1356,6 +1369,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
if (!secondRowHBox.getChildren().contains(fixedPriceBox))
|
||||
secondRowHBox.getChildren().add(2, fixedPriceBox);
|
||||
}
|
||||
|
||||
triggerPriceVBox.setVisible(!fixedPriceSelected);
|
||||
model.onFixPriceToggleChange(fixedPriceSelected);
|
||||
}
|
||||
|
||||
private void addSecondRow() {
|
||||
|
@ -1387,7 +1403,6 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
|
||||
Tuple2<Label, VBox> amountInputBoxTuple = getTradeInputBox(minAmountValueCurrencyBox, Res.get("createOffer.amountPriceBox.minAmountDescription"));
|
||||
|
||||
|
||||
fakeXLabel = new Label();
|
||||
fakeXIcon = getIconForLabel(MaterialDesignIcon.CLOSE, "2em", fakeXLabel);
|
||||
fakeXLabel.getStyleClass().add("opaque-icon-character");
|
||||
|
@ -1396,16 +1411,28 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
// Fixed/Percentage toggle
|
||||
priceTypeToggleButton = getIconButton(MaterialDesignIcon.SWAP_VERTICAL);
|
||||
editOfferElements.add(priceTypeToggleButton);
|
||||
HBox.setMargin(priceTypeToggleButton, new Insets(16, 0, 0, 0));
|
||||
|
||||
HBox.setMargin(priceTypeToggleButton, new Insets(16, 1.5, 0, 0));
|
||||
priceTypeToggleButton.setOnAction((actionEvent) ->
|
||||
updatePriceToggleButtons(model.getDataModel().getUseMarketBasedPrice().getValue()));
|
||||
|
||||
secondRowHBox = new HBox();
|
||||
// triggerPrice
|
||||
Tuple3<HBox, InfoInputTextField, Label> triggerPriceTuple3 = getEditableValueBoxWithInfo(Res.get("createOffer.triggerPrice.prompt"));
|
||||
triggerPriceHBox = triggerPriceTuple3.first;
|
||||
triggerPriceInfoInputTextField = triggerPriceTuple3.second;
|
||||
editOfferElements.add(triggerPriceInfoInputTextField);
|
||||
triggerPriceInputTextField = triggerPriceInfoInputTextField.getInputTextField();
|
||||
triggerPriceCurrencyLabel = triggerPriceTuple3.third;
|
||||
editOfferElements.add(triggerPriceCurrencyLabel);
|
||||
Tuple2<Label, VBox> triggerPriceTuple2 = getTradeInputBox(triggerPriceHBox, model.getTriggerPriceDescriptionLabel());
|
||||
triggerPriceDescriptionLabel = triggerPriceTuple2.first;
|
||||
triggerPriceDescriptionLabel.setPrefWidth(290);
|
||||
triggerPriceVBox = triggerPriceTuple2.second;
|
||||
|
||||
secondRowHBox = new HBox();
|
||||
secondRowHBox.setSpacing(5);
|
||||
secondRowHBox.setAlignment(Pos.CENTER_LEFT);
|
||||
secondRowHBox.getChildren().addAll(amountInputBoxTuple.second, fakeXLabel, fixedPriceBox, priceTypeToggleButton);
|
||||
secondRowHBox.getChildren().addAll(amountInputBoxTuple.second, fakeXLabel, fixedPriceBox, priceTypeToggleButton, triggerPriceVBox);
|
||||
GridPane.setColumnSpan(secondRowHBox, 2);
|
||||
GridPane.setRowIndex(secondRowHBox, ++gridRow);
|
||||
GridPane.setColumnIndex(secondRowHBox, 0);
|
||||
GridPane.setMargin(secondRowHBox, new Insets(0, 10, 10, 0));
|
||||
|
|
|
@ -20,6 +20,7 @@ package bisq.desktop.main.offer;
|
|||
import bisq.desktop.Navigation;
|
||||
import bisq.desktop.common.model.ActivatableWithDataModel;
|
||||
import bisq.desktop.main.MainView;
|
||||
import bisq.desktop.main.PriceUtil;
|
||||
import bisq.desktop.main.funds.FundsView;
|
||||
import bisq.desktop.main.funds.deposit.DepositView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
|
@ -124,6 +125,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
// The domain (dataModel) uses always the same price model (otherCurrencyBTC)
|
||||
// If we would change the price representation in the domain we would not be backward compatible
|
||||
public final StringProperty price = new SimpleStringProperty();
|
||||
public final StringProperty triggerPrice = new SimpleStringProperty("");
|
||||
final StringProperty tradeFee = new SimpleStringProperty();
|
||||
final StringProperty tradeFeeInBtcWithFiat = new SimpleStringProperty();
|
||||
final StringProperty tradeFeeInBsqWithFiat = new SimpleStringProperty();
|
||||
|
@ -143,6 +145,8 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
final StringProperty errorMessage = new SimpleStringProperty();
|
||||
final StringProperty tradeCurrencyCode = new SimpleStringProperty();
|
||||
final StringProperty waitingForFundsText = new SimpleStringProperty("");
|
||||
final StringProperty triggerPriceDescription = new SimpleStringProperty("");
|
||||
final StringProperty percentagePriceDescription = new SimpleStringProperty("");
|
||||
|
||||
final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty(true);
|
||||
final BooleanProperty cancelButtonDisabled = new SimpleBooleanProperty();
|
||||
|
@ -156,6 +160,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> minAmountValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> priceValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> triggerPriceValidationResult = new SimpleObjectProperty<>(new InputValidator.ValidationResult(true));
|
||||
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> buyerSecurityDepositValidationResult = new SimpleObjectProperty<>();
|
||||
|
||||
|
@ -277,12 +282,15 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
totalToPay.bind(createStringBinding(() -> btcFormatter.formatCoinWithCode(dataModel.totalToPayAsCoinProperty().get()),
|
||||
dataModel.totalToPayAsCoinProperty()));
|
||||
|
||||
|
||||
tradeAmount.bind(createStringBinding(() -> btcFormatter.formatCoinWithCode(dataModel.getAmount().get()),
|
||||
dataModel.getAmount()));
|
||||
|
||||
|
||||
tradeCurrencyCode.bind(dataModel.getTradeCurrencyCode());
|
||||
|
||||
triggerPriceDescription.bind(createStringBinding(this::getTriggerPriceDescriptionLabel,
|
||||
dataModel.getTradeCurrencyCode()));
|
||||
percentagePriceDescription.bind(createStringBinding(this::getPercentagePriceDescription,
|
||||
dataModel.getTradeCurrencyCode()));
|
||||
}
|
||||
|
||||
private void removeBindings() {
|
||||
|
@ -291,6 +299,8 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
tradeCurrencyCode.unbind();
|
||||
volumeDescriptionLabel.unbind();
|
||||
volumePromptLabel.unbind();
|
||||
triggerPriceDescription.unbind();
|
||||
percentagePriceDescription.unbind();
|
||||
}
|
||||
|
||||
private void createListeners() {
|
||||
|
@ -769,12 +779,49 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
}
|
||||
}
|
||||
|
||||
void onFocusOutTriggerPriceTextField(boolean oldValue, boolean newValue) {
|
||||
if (oldValue && !newValue) {
|
||||
onTriggerPriceTextFieldChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void onTriggerPriceTextFieldChanged() {
|
||||
String triggerPriceAsString = triggerPrice.get();
|
||||
|
||||
// Error field does not update if there was an error and then another different error
|
||||
// if not reset here. Not clear why...
|
||||
triggerPriceValidationResult.set(new InputValidator.ValidationResult(true));
|
||||
|
||||
InputValidator.ValidationResult result = PriceUtil.isTriggerPriceValid(triggerPriceAsString,
|
||||
dataModel.getPrice().get(),
|
||||
dataModel.isSellOffer(),
|
||||
dataModel.isFiatCurrency());
|
||||
triggerPriceValidationResult.set(result);
|
||||
updateButtonDisableState();
|
||||
if (result.isValid) {
|
||||
// In case of 0 or empty string we set the string to empty string and data value to 0
|
||||
long triggerPriceAsLong = PriceUtil.getMarketPriceAsLong(triggerPriceAsString, dataModel.getCurrencyCode());
|
||||
dataModel.setTriggerPrice(triggerPriceAsLong);
|
||||
if (dataModel.getTriggerPrice() == 0) {
|
||||
triggerPrice.set("");
|
||||
} else {
|
||||
triggerPrice.set(PriceUtil.formatMarketPrice(dataModel.getTriggerPrice(), dataModel.getCurrencyCode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onFixPriceToggleChange(boolean fixedPriceSelected) {
|
||||
updateButtonDisableState();
|
||||
if (!fixedPriceSelected) {
|
||||
onTriggerPriceTextFieldChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void onFocusOutPriceTextField(boolean oldValue, boolean newValue) {
|
||||
if (oldValue && !newValue) {
|
||||
InputValidator.ValidationResult result = isPriceInputValid(price.get());
|
||||
boolean isValid = result.isValid;
|
||||
priceValidationResult.set(result);
|
||||
if (isValid) {
|
||||
if (result.isValid) {
|
||||
setPriceToModel();
|
||||
ignorePriceStringListener = true;
|
||||
if (dataModel.getPrice().get() != null)
|
||||
|
@ -808,8 +855,11 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
marketPriceMargin.set(FormattingUtils.formatRoundedDoubleWithPrecision(dataModel.getMarketPriceMargin() * 100, 2));
|
||||
}
|
||||
|
||||
// We want to trigger a recalculation of the volume
|
||||
UserThread.execute(() -> onFocusOutVolumeTextField(true, false));
|
||||
// We want to trigger a recalculation of the volume, as well as update trigger price validation
|
||||
UserThread.execute(() -> {
|
||||
onFocusOutVolumeTextField(true, false);
|
||||
onTriggerPriceTextFieldChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void onFocusOutVolumeTextField(boolean oldValue, boolean newValue) {
|
||||
|
@ -1052,6 +1102,33 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
return dataModel;
|
||||
}
|
||||
|
||||
String getTriggerPriceDescriptionLabel() {
|
||||
String details;
|
||||
if (dataModel.isBuyOffer()) {
|
||||
details = dataModel.isCryptoCurrency() ?
|
||||
Res.get("account.notifications.marketAlert.message.msg.below") :
|
||||
Res.get("account.notifications.marketAlert.message.msg.above");
|
||||
} else {
|
||||
details = dataModel.isCryptoCurrency() ?
|
||||
Res.get("account.notifications.marketAlert.message.msg.above") :
|
||||
Res.get("account.notifications.marketAlert.message.msg.below");
|
||||
}
|
||||
return Res.get("createOffer.triggerPrice.label", details);
|
||||
}
|
||||
|
||||
String getPercentagePriceDescription() {
|
||||
if (dataModel.isBuyOffer()) {
|
||||
return dataModel.isCryptoCurrency() ?
|
||||
Res.get("shared.aboveInPercent") :
|
||||
Res.get("shared.belowInPercent");
|
||||
} else {
|
||||
return dataModel.isCryptoCurrency() ?
|
||||
Res.get("shared.belowInPercent") :
|
||||
Res.get("shared.aboveInPercent");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1195,8 +1272,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
}
|
||||
}
|
||||
|
||||
private void updateButtonDisableState() {
|
||||
log.debug("updateButtonDisableState");
|
||||
void updateButtonDisableState() {
|
||||
boolean inputDataValid = isBtcInputValid(amount.get()).isValid &&
|
||||
isBtcInputValid(minAmount.get()).isValid &&
|
||||
isPriceInputValid(price.get()).isValid &&
|
||||
|
@ -1206,6 +1282,10 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
isVolumeInputValid(DisplayUtils.formatVolume(dataModel.getMinVolume().get())).isValid &&
|
||||
dataModel.isMinAmountLessOrEqualAmount();
|
||||
|
||||
if (dataModel.useMarketBasedPrice.get() && dataModel.isMarketPriceAvailable()) {
|
||||
inputDataValid = inputDataValid && triggerPriceValidationResult.get().isValid;
|
||||
}
|
||||
|
||||
// validating the percentage deposit value only makes sense if it is actually used
|
||||
if (!dataModel.isMinBuyerSecurityDeposit()) {
|
||||
inputDataValid = inputDataValid && securityDepositValidator.validate(buyerSecurityDeposit.get()).isValid;
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.desktop.main.offer;
|
||||
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
|
||||
/**
|
||||
* Reusable methods for CreateOfferView, TakeOfferView or other related views
|
||||
*/
|
||||
public class OfferViewUtil {
|
||||
public static Label createPopOverLabel(String text) {
|
||||
final Label label = new Label(text);
|
||||
label.setPrefWidth(300);
|
||||
label.setWrapText(true);
|
||||
label.setLineSpacing(1);
|
||||
label.setPadding(new Insets(10));
|
||||
return label;
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ package bisq.desktop.main.offer.offerbook;
|
|||
import bisq.desktop.Navigation;
|
||||
import bisq.desktop.common.model.ActivatableViewModel;
|
||||
import bisq.desktop.main.MainView;
|
||||
import bisq.desktop.main.PriceUtil;
|
||||
import bisq.desktop.main.settings.SettingsView;
|
||||
import bisq.desktop.main.settings.preferences.PreferencesView;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
|
@ -35,7 +36,6 @@ import bisq.core.locale.CurrencyUtil;
|
|||
import bisq.core.locale.GlobalSettings;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.Offer;
|
||||
|
@ -44,14 +44,11 @@ import bisq.core.offer.OpenOfferManager;
|
|||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.PaymentAccountUtil;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.AveragePriceUtil;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
@ -62,10 +59,8 @@ import bisq.network.p2p.P2PService;
|
|||
import bisq.common.app.Version;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.util.MathUtils;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
|
@ -96,10 +91,6 @@ import java.util.stream.Collectors;
|
|||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
class OfferBookViewModel extends ActivatableViewModel {
|
||||
private final OpenOfferManager openOfferManager;
|
||||
|
@ -113,7 +104,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
private final FilterManager filterManager;
|
||||
final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final Navigation navigation;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final PriceUtil priceUtil;
|
||||
private final CoinFormatter btcFormatter;
|
||||
private final BsqFormatter bsqFormatter;
|
||||
|
||||
|
@ -139,8 +130,6 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
final IntegerProperty maxPlacesForPrice = new SimpleIntegerProperty();
|
||||
final IntegerProperty maxPlacesForMarketPriceMargin = new SimpleIntegerProperty();
|
||||
boolean showAllPaymentMethods = true;
|
||||
@Nullable
|
||||
private Price bsq30DayAveragePrice;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -159,7 +148,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
FilterManager filterManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
Navigation navigation,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
PriceUtil priceUtil,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||
BsqFormatter bsqFormatter) {
|
||||
super();
|
||||
|
@ -175,7 +164,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
this.filterManager = filterManager;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.navigation = navigation;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.priceUtil = priceUtil;
|
||||
this.btcFormatter = btcFormatter;
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
|
||||
|
@ -239,12 +228,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
applyFilterPredicate();
|
||||
setMarketPriceFeedCurrency();
|
||||
|
||||
// Null check needed for tests passing null for tradeStatisticsManager
|
||||
if (tradeStatisticsManager != null) {
|
||||
bsq30DayAveragePrice = AveragePriceUtil.getAveragePriceTuple(preferences,
|
||||
tradeStatisticsManager,
|
||||
30).second;
|
||||
}
|
||||
priceUtil.recalculateBsq30DayAveragePrice();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -390,77 +374,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
}
|
||||
|
||||
public Optional<Double> getMarketBasedPrice(Offer offer) {
|
||||
if (offer.isUseMarketBasedPrice()) {
|
||||
return Optional.of(offer.getMarketPriceMargin());
|
||||
}
|
||||
|
||||
if (!hasMarketPrice(offer)) {
|
||||
if (offer.getCurrencyCode().equals("BSQ")) {
|
||||
if (bsq30DayAveragePrice != null && bsq30DayAveragePrice.isPositive()) {
|
||||
double scaled = MathUtils.scaleDownByPowerOf10(bsq30DayAveragePrice.getValue(), 8);
|
||||
return calculatePercentage(offer, scaled);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
log.trace("We don't have a market price. " +
|
||||
"That case could only happen if you don't have a price feed.");
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
double marketPriceAsDouble = checkNotNull(marketPrice).getPrice();
|
||||
return calculatePercentage(offer, marketPriceAsDouble);
|
||||
}
|
||||
|
||||
protected Optional<Double> calculatePercentage(Offer offer, double marketPrice) {
|
||||
// If the offer did not use % price we calculate % from current market price
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
Price price = offer.getPrice();
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
long priceAsLong = checkNotNull(price).getValue();
|
||||
double scaled = MathUtils.scaleDownByPowerOf10(priceAsLong, precision);
|
||||
|
||||
double value;
|
||||
if (direction == OfferPayload.Direction.SELL) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
if (marketPrice == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = 1 - scaled / marketPrice;
|
||||
} else {
|
||||
if (marketPrice == 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = scaled / marketPrice - 1;
|
||||
}
|
||||
} else {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
if (marketPrice == 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = scaled / marketPrice - 1;
|
||||
} else {
|
||||
if (marketPrice == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = 1 - scaled / marketPrice;
|
||||
}
|
||||
}
|
||||
return Optional.of(value);
|
||||
}
|
||||
|
||||
public boolean hasMarketPrice(Offer offer) {
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
Price price = offer.getPrice();
|
||||
return price != null && marketPrice != null && marketPrice.isRecentExternalPriceAvailable();
|
||||
return priceUtil.getMarketBasedPrice(offer, direction);
|
||||
}
|
||||
|
||||
String formatMarketPriceMargin(Offer offer, boolean decimalAligned) {
|
||||
|
|
|
@ -500,10 +500,18 @@ class TakeOfferDataModel extends OfferDataModel {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isBuyOffer() {
|
||||
boolean isBuyOffer() {
|
||||
return getDirection() == OfferPayload.Direction.BUY;
|
||||
}
|
||||
|
||||
boolean isSellOffer() {
|
||||
return getDirection() == OfferPayload.Direction.SELL;
|
||||
}
|
||||
|
||||
boolean isCryptoCurrency() {
|
||||
return CurrencyUtil.isCryptoCurrency(getCurrencyCode());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Coin getTakerFee(boolean isCurrencyForTakerFeeBtc) {
|
||||
Coin amount = this.amount.get();
|
||||
|
|
|
@ -37,6 +37,7 @@ import bisq.desktop.main.dao.wallet.receive.BsqReceiveView;
|
|||
import bisq.desktop.main.funds.FundsView;
|
||||
import bisq.desktop.main.funds.withdrawal.WithdrawalView;
|
||||
import bisq.desktop.main.offer.OfferView;
|
||||
import bisq.desktop.main.offer.OfferViewUtil;
|
||||
import bisq.desktop.main.overlays.notifications.Notification;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
||||
|
@ -347,13 +348,12 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
takeOfferButton.setId("buy-button-big");
|
||||
takeOfferButton.updateText(Res.get("takeOffer.takeOfferButton", Res.get("shared.buy")));
|
||||
nextButton.setId("buy-button");
|
||||
priceAsPercentageDescription.setText(Res.get("shared.aboveInPercent"));
|
||||
} else {
|
||||
takeOfferButton.setId("sell-button-big");
|
||||
nextButton.setId("sell-button");
|
||||
takeOfferButton.updateText(Res.get("takeOffer.takeOfferButton", Res.get("shared.sell")));
|
||||
priceAsPercentageDescription.setText(Res.get("shared.belowInPercent"));
|
||||
}
|
||||
priceAsPercentageDescription.setText(model.getPercentagePriceDescription());
|
||||
|
||||
boolean showComboBox = model.getPossiblePaymentAccounts().size() > 1;
|
||||
paymentAccountsComboBox.setVisible(showComboBox);
|
||||
|
@ -383,8 +383,10 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
addressTextField.setPaymentLabel(model.getPaymentLabel());
|
||||
addressTextField.setAddress(model.dataModel.getAddressEntry().getAddressString());
|
||||
|
||||
if (CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()))
|
||||
volumeInfoTextField.setContentForPrivacyPopOver(createPopoverLabel(Res.get("offerbook.info.roundedFiatVolume")));
|
||||
if (CurrencyUtil.isFiatCurrency(offer.getCurrencyCode())) {
|
||||
Label popOverLabel = OfferViewUtil.createPopOverLabel(Res.get("offerbook.info.roundedFiatVolume"));
|
||||
volumeInfoTextField.setContentForPrivacyPopOver(popOverLabel);
|
||||
}
|
||||
|
||||
if (offer.getPrice() == null)
|
||||
new Popup().warning(Res.get("takeOffer.noPriceFeedAvailable"))
|
||||
|
@ -1121,8 +1123,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
priceAsPercentageTextField = priceAsPercentageTuple.second;
|
||||
priceAsPercentageLabel = priceAsPercentageTuple.third;
|
||||
|
||||
Tuple2<Label, VBox> priceAsPercentageInputBoxTuple = getTradeInputBox(priceAsPercentageValueCurrencyBox,
|
||||
Res.get("shared.distanceInPercent"));
|
||||
Tuple2<Label, VBox> priceAsPercentageInputBoxTuple = getTradeInputBox(priceAsPercentageValueCurrencyBox, "");
|
||||
priceAsPercentageDescription = priceAsPercentageInputBoxTuple.first;
|
||||
|
||||
getSmallIconForLabel(MaterialDesignIcon.CHART_LINE, priceAsPercentageDescription, "small-icon-label");
|
||||
|
@ -1273,14 +1274,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
return infoGridPane;
|
||||
}
|
||||
|
||||
private Label createPopoverLabel(String text) {
|
||||
final Label label = new Label(text);
|
||||
label.setPrefWidth(300);
|
||||
label.setWrapText(true);
|
||||
label.setPadding(new Insets(10));
|
||||
return label;
|
||||
}
|
||||
|
||||
private void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) {
|
||||
Label label = new AutoTooltipLabel(labelText);
|
||||
TextField textField = new TextField(value);
|
||||
|
|
|
@ -753,4 +753,16 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
ComboBox<PaymentAccount> paymentAccountsComboBox) {
|
||||
return GUIUtil.getPaymentAccountListCellFactory(paymentAccountsComboBox, accountAgeWitnessService);
|
||||
}
|
||||
|
||||
String getPercentagePriceDescription() {
|
||||
if (dataModel.isBuyOffer()) {
|
||||
return dataModel.isCryptoCurrency() ?
|
||||
Res.get("shared.aboveInPercent") :
|
||||
Res.get("shared.belowInPercent");
|
||||
} else {
|
||||
return dataModel.isCryptoCurrency() ?
|
||||
Res.get("shared.belowInPercent") :
|
||||
Res.get("shared.aboveInPercent");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import bisq.core.payment.payload.PaymentMethod;
|
|||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeList;
|
||||
import bisq.core.support.dispute.DisputeManager;
|
||||
import bisq.core.support.dispute.agent.DisputeAgentLookupMap;
|
||||
import bisq.core.support.dispute.arbitration.ArbitrationManager;
|
||||
import bisq.core.support.dispute.mediation.MediationManager;
|
||||
import bisq.core.support.dispute.refund.RefundManager;
|
||||
|
@ -193,17 +194,20 @@ public class ContractWindow extends Overlay<ContractWindow> {
|
|||
sellerPaymentAccountPayload.getPaymentDetails()).second.setMouseTransparent(false);
|
||||
|
||||
String title = "";
|
||||
String agentKeyBaseUserName = "";
|
||||
if (dispute.getSupportType() != null) {
|
||||
switch (dispute.getSupportType()) {
|
||||
case ARBITRATION:
|
||||
title = Res.get("shared.selectedArbitrator");
|
||||
break;
|
||||
case MEDIATION:
|
||||
agentKeyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(contract.getMediatorNodeAddress().getFullAddress());
|
||||
title = Res.get("shared.selectedMediator");
|
||||
break;
|
||||
case TRADE:
|
||||
break;
|
||||
case REFUND:
|
||||
agentKeyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(contract.getRefundAgentNodeAddress().getFullAddress());
|
||||
title = Res.get("shared.selectedRefundAgent");
|
||||
break;
|
||||
}
|
||||
|
@ -212,7 +216,8 @@ public class ContractWindow extends Overlay<ContractWindow> {
|
|||
if (disputeManager != null) {
|
||||
NodeAddress agentNodeAddress = disputeManager.getAgentNodeAddress(dispute);
|
||||
if (agentNodeAddress != null) {
|
||||
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, title, agentNodeAddress.getFullAddress());
|
||||
String value = agentKeyBaseUserName + " (" + agentNodeAddress.getFullAddress() + ")";
|
||||
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, title, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import bisq.core.locale.CurrencyUtil;
|
|||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.support.dispute.agent.DisputeAgentLookupMap;
|
||||
import bisq.core.support.dispute.arbitration.ArbitrationManager;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
|
@ -346,6 +347,7 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
|
|||
textArea.setText(trade.getContractAsJson());
|
||||
String data = "Contract as json:\n";
|
||||
data += trade.getContractAsJson();
|
||||
data += "\n\nOther detail data:";
|
||||
data += "\n\nBuyerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getBuyerMultiSigPubKey());
|
||||
data += "\nSellerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getSellerMultiSigPubKey());
|
||||
if (CurrencyUtil.isFiatCurrency(offer.getCurrencyCode())) {
|
||||
|
@ -358,6 +360,9 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
|
|||
data += "\n\nRaw deposit transaction as hex:\n" + depositTxAsHex;
|
||||
}
|
||||
|
||||
data += "\n\nSelected mediator: " + DisputeAgentLookupMap.getKeyBaseUserName(contract.getMediatorNodeAddress().getFullAddress());
|
||||
data += "\nSelected arbitrator (refund agent): " + DisputeAgentLookupMap.getKeyBaseUserName(contract.getRefundAgentNodeAddress().getFullAddress());
|
||||
|
||||
textArea.setText(data);
|
||||
textArea.setPrefHeight(50);
|
||||
textArea.setEditable(false);
|
||||
|
|
|
@ -254,7 +254,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
numItems.setText(Res.get("shared.numItemsLabel", sortedList.size()));
|
||||
exportButton.setOnAction(event -> {
|
||||
final ObservableList<TableColumn<ClosedTradableListItem, ?>> tableColumns = tableView.getColumns();
|
||||
CSVEntryConverter<ClosedTradableListItem> headerConverter = transactionsListItem -> {
|
||||
CSVEntryConverter<ClosedTradableListItem> headerConverter = item -> {
|
||||
String[] columns = new String[ColumnNames.values().length];
|
||||
for (ColumnNames m : ColumnNames.values()) {
|
||||
columns[m.ordinal()] = m.toString();
|
||||
|
|
|
@ -170,7 +170,10 @@ class EditOfferDataModel extends MutableOfferDataModel {
|
|||
setPrice(offer.getPrice());
|
||||
setVolume(offer.getVolume());
|
||||
setUseMarketBasedPrice(offer.isUseMarketBasedPrice());
|
||||
if (offer.isUseMarketBasedPrice()) setMarketPriceMargin(offer.getMarketPriceMargin());
|
||||
setTriggerPrice(openOffer.getTriggerPrice());
|
||||
if (offer.isUseMarketBasedPrice()) {
|
||||
setMarketPriceMargin(offer.getMarketPriceMargin());
|
||||
}
|
||||
}
|
||||
|
||||
public void onStartEditOffer(ErrorMessageHandler errorMessageHandler) {
|
||||
|
@ -227,7 +230,7 @@ class EditOfferDataModel extends MutableOfferDataModel {
|
|||
editedOffer.setPriceFeedService(priceFeedService);
|
||||
editedOffer.setState(Offer.State.AVAILABLE);
|
||||
|
||||
openOfferManager.editOpenOfferPublish(editedOffer, initialState, () -> {
|
||||
openOfferManager.editOpenOfferPublish(editedOffer, triggerPrice, initialState, () -> {
|
||||
openOffer = null;
|
||||
resultHandler.handleResult();
|
||||
}, errorMessageHandler);
|
||||
|
|
|
@ -28,6 +28,7 @@ import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
|||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.user.DontShowAgainLookup;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
|
@ -206,8 +207,13 @@ public class EditOfferView extends MutableOfferView<EditOfferViewModel> {
|
|||
spinnerInfoLabel.setText(Res.get("editOffer.publishOffer"));
|
||||
//edit offer
|
||||
model.onPublishOffer(() -> {
|
||||
log.debug("Edit offer was successful");
|
||||
new Popup().feedback(Res.get("editOffer.success")).show();
|
||||
String key = "editOfferSuccess";
|
||||
if (DontShowAgainLookup.showAgain(key)) {
|
||||
new Popup()
|
||||
.feedback(Res.get("editOffer.success"))
|
||||
.dontShowAgainId(key)
|
||||
.show();
|
||||
}
|
||||
spinnerInfoLabel.setText("");
|
||||
busyAnimation.stop();
|
||||
close();
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package bisq.desktop.main.portfolio.editoffer;
|
||||
|
||||
import bisq.desktop.Navigation;
|
||||
import bisq.desktop.main.PriceUtil;
|
||||
import bisq.desktop.main.offer.MutableOfferViewModel;
|
||||
import bisq.desktop.util.validation.AltcoinValidator;
|
||||
import bisq.desktop.util.validation.BsqValidator;
|
||||
|
@ -79,7 +80,17 @@ class EditOfferViewModel extends MutableOfferViewModel<EditOfferDataModel> {
|
|||
@Override
|
||||
public void activate() {
|
||||
super.activate();
|
||||
|
||||
dataModel.populateData();
|
||||
|
||||
long triggerPriceAsLong = dataModel.getTriggerPrice();
|
||||
dataModel.setTriggerPrice(triggerPriceAsLong);
|
||||
if (triggerPriceAsLong > 0) {
|
||||
triggerPrice.set(PriceUtil.formatMarketPrice(triggerPriceAsLong, dataModel.getCurrencyCode()));
|
||||
} else {
|
||||
triggerPrice.set("");
|
||||
}
|
||||
onTriggerPriceTextFieldChanged();
|
||||
}
|
||||
|
||||
public void applyOpenOffer(OpenOffer openOffer) {
|
||||
|
|
|
@ -201,7 +201,7 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
|
|||
exportButton.setOnAction(event -> {
|
||||
ObservableList<TableColumn<FailedTradesListItem, ?>> tableColumns = tableView.getColumns();
|
||||
int reportColumns = tableColumns.size() - 1; // CSV report excludes the last column (an icon)
|
||||
CSVEntryConverter<FailedTradesListItem> headerConverter = transactionsListItem -> {
|
||||
CSVEntryConverter<FailedTradesListItem> headerConverter = item -> {
|
||||
String[] columns = new String[reportColumns];
|
||||
for (int i = 0; i < columns.length; i++)
|
||||
columns[i] = ((AutoTooltipLabel) tableColumns.get(i).getGraphic()).getText();
|
||||
|
|
|
@ -23,6 +23,7 @@ import bisq.core.offer.Offer;
|
|||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.offer.TriggerPriceService;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
|
@ -98,5 +99,7 @@ class OpenOffersDataModel extends ActivatableDataModel {
|
|||
list.sort((o1, o2) -> o2.getOffer().getDate().compareTo(o1.getOffer().getDate()));
|
||||
}
|
||||
|
||||
|
||||
boolean wasTriggered(OpenOffer openOffer) {
|
||||
return TriggerPriceService.wasTriggered(priceFeedService.getMarketPrice(openOffer.getOffer().getCurrencyCode()), openOffer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,13 +47,15 @@
|
|||
<TableColumn fx:id="marketColumn" minWidth="75"/>
|
||||
<TableColumn fx:id="priceColumn" minWidth="100"/>
|
||||
<TableColumn fx:id="deviationColumn" minWidth="70"/>
|
||||
<TableColumn fx:id="triggerPriceColumn" minWidth="90"/>
|
||||
<TableColumn fx:id="amountColumn" minWidth="110"/>
|
||||
<TableColumn fx:id="volumeColumn" minWidth="110"/>
|
||||
<TableColumn fx:id="paymentMethodColumn" minWidth="120" maxWidth="170"/>
|
||||
<TableColumn fx:id="directionColumn" minWidth="70"/>
|
||||
<TableColumn fx:id="deactivateItemColumn" minWidth="60" maxWidth="60" sortable="false"/>
|
||||
<TableColumn fx:id="editItemColumn" minWidth="50" maxWidth="60" sortable="false"/>
|
||||
<TableColumn fx:id="removeItemColumn" minWidth="50" maxWidth="60" sortable="false"/>
|
||||
<TableColumn fx:id="editItemColumn" minWidth="30" maxWidth="30" sortable="false"/>
|
||||
<TableColumn fx:id="triggerIconColumn" minWidth="30" maxWidth="30" sortable="false"/>
|
||||
<TableColumn fx:id="removeItemColumn" minWidth="30" maxWidth="30" sortable="false"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
<HBox spacing="10">
|
||||
|
|
|
@ -79,7 +79,7 @@ import java.util.Comparator;
|
|||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.getIconButton;
|
||||
import static bisq.desktop.util.FormBuilder.getRegularIconButton;
|
||||
|
||||
@FxmlView
|
||||
public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersViewModel> {
|
||||
|
@ -89,7 +89,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
@FXML
|
||||
TableColumn<OpenOfferListItem, OpenOfferListItem> priceColumn, deviationColumn, amountColumn, volumeColumn,
|
||||
marketColumn, directionColumn, dateColumn, offerIdColumn, deactivateItemColumn,
|
||||
removeItemColumn, editItemColumn, paymentMethodColumn;
|
||||
removeItemColumn, editItemColumn, triggerPriceColumn, triggerIconColumn, paymentMethodColumn;
|
||||
@FXML
|
||||
HBox searchBox;
|
||||
@FXML
|
||||
|
@ -113,6 +113,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
private FilteredList<OpenOfferListItem> filteredList;
|
||||
private ChangeListener<String> filterTextFieldListener;
|
||||
private PortfolioView.OpenOfferActionHandler openOfferActionHandler;
|
||||
private ChangeListener<Number> widthListener;
|
||||
|
||||
@Inject
|
||||
public OpenOffersView(OpenOffersViewModel model, Navigation navigation, OfferDetailsWindow offerDetailsWindow) {
|
||||
|
@ -123,6 +124,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
|
||||
@Override
|
||||
public void initialize() {
|
||||
widthListener = (observable, oldValue, newValue) -> onWidthChange((double) newValue);
|
||||
paymentMethodColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.paymentMethod")));
|
||||
priceColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.price")));
|
||||
deviationColumn.setGraphic(new AutoTooltipTableColumn<>(Res.get("shared.deviation"),
|
||||
|
@ -133,6 +135,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
directionColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.offerType")));
|
||||
dateColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.dateTime")));
|
||||
offerIdColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.offerId")));
|
||||
triggerPriceColumn.setGraphic(new AutoTooltipLabel(Res.get("openOffer.header.triggerPrice")));
|
||||
deactivateItemColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.enabled")));
|
||||
editItemColumn.setGraphic(new AutoTooltipLabel(""));
|
||||
removeItemColumn.setGraphic(new AutoTooltipLabel(""));
|
||||
|
@ -148,6 +151,8 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
setDateColumnCellFactory();
|
||||
setDeactivateColumnCellFactory();
|
||||
setEditColumnCellFactory();
|
||||
setTriggerIconColumnCellFactory();
|
||||
setTriggerPriceColumnCellFactory();
|
||||
setRemoveColumnCellFactory();
|
||||
|
||||
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
@ -158,8 +163,8 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
marketColumn.setComparator(Comparator.comparing(model::getMarketLabel));
|
||||
amountColumn.setComparator(Comparator.comparing(o -> o.getOffer().getAmount()));
|
||||
priceColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPrice(), Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
deviationColumn.setComparator(Comparator.comparing(o ->
|
||||
o.getOffer().isUseMarketBasedPrice() ? o.getOffer().getMarketPriceMargin() : 1,
|
||||
deviationColumn.setComparator(Comparator.comparing(model::getPriceDeviationAsDouble, Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
triggerPriceColumn.setComparator(Comparator.comparing(o -> o.getOpenOffer().getTriggerPrice(),
|
||||
Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
volumeColumn.setComparator(Comparator.comparing(o -> o.getOffer().getVolume(), Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
dateColumn.setComparator(Comparator.comparing(o -> o.getOffer().getDate()));
|
||||
|
@ -205,8 +210,8 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
numItems.setText(Res.get("shared.numItemsLabel", sortedList.size()));
|
||||
exportButton.setOnAction(event -> {
|
||||
ObservableList<TableColumn<OpenOfferListItem, ?>> tableColumns = tableView.getColumns();
|
||||
int reportColumns = tableColumns.size() - 2; // CSV report excludes the last columns (icons)
|
||||
CSVEntryConverter<OpenOfferListItem> headerConverter = transactionsListItem -> {
|
||||
int reportColumns = tableColumns.size() - 3; // CSV report excludes the last columns (icons)
|
||||
CSVEntryConverter<OpenOfferListItem> headerConverter = item -> {
|
||||
String[] columns = new String[reportColumns];
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
Node graphic = tableColumns.get(i).getGraphic();
|
||||
|
@ -229,11 +234,12 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
columns[2] = model.getMarketLabel(item);
|
||||
columns[3] = model.getPrice(item);
|
||||
columns[4] = model.getPriceDeviation(item);
|
||||
columns[5] = model.getAmount(item);
|
||||
columns[6] = model.getVolume(item);
|
||||
columns[7] = model.getPaymentMethod(item);
|
||||
columns[8] = model.getDirectionLabel(item);
|
||||
columns[9] = String.valueOf(!item.getOpenOffer().isDeactivated());
|
||||
columns[5] = model.getTriggerPrice(item);
|
||||
columns[6] = model.getAmount(item);
|
||||
columns[7] = model.getVolume(item);
|
||||
columns[8] = model.getPaymentMethod(item);
|
||||
columns[9] = model.getDirectionLabel(item);
|
||||
columns[10] = String.valueOf(!item.getOpenOffer().isDeactivated());
|
||||
return columns;
|
||||
};
|
||||
|
||||
|
@ -247,6 +253,18 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
|
||||
filterTextField.textProperty().addListener(filterTextFieldListener);
|
||||
applyFilteredListPredicate(filterTextField.getText());
|
||||
|
||||
root.widthProperty().addListener(widthListener);
|
||||
onWidthChange(root.getWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
sortedList.comparatorProperty().unbind();
|
||||
exportButton.setOnAction(null);
|
||||
|
||||
filterTextField.textProperty().removeListener(filterTextFieldListener);
|
||||
root.widthProperty().removeListener(widthListener);
|
||||
}
|
||||
|
||||
private void updateSelectToggleButtonState() {
|
||||
|
@ -266,14 +284,6 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
sortedList.comparatorProperty().unbind();
|
||||
exportButton.setOnAction(null);
|
||||
|
||||
filterTextField.textProperty().removeListener(filterTextFieldListener);
|
||||
}
|
||||
|
||||
private void applyFilteredListPredicate(String filterString) {
|
||||
filteredList.setPredicate(item -> {
|
||||
if (filterString.isEmpty())
|
||||
|
@ -314,6 +324,10 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
});
|
||||
}
|
||||
|
||||
private void onWidthChange(double width) {
|
||||
triggerPriceColumn.setVisible(width > 1200);
|
||||
}
|
||||
|
||||
private void onDeactivateOpenOffer(OpenOffer openOffer) {
|
||||
if (model.isBootstrappedOrShowPopup()) {
|
||||
model.onDeactivateOpenOffer(openOffer,
|
||||
|
@ -327,7 +341,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
}
|
||||
|
||||
private void onActivateOpenOffer(OpenOffer openOffer) {
|
||||
if (model.isBootstrappedOrShowPopup()) {
|
||||
if (model.isBootstrappedOrShowPopup() && !model.dataModel.wasTriggered(openOffer)) {
|
||||
model.onActivateOpenOffer(openOffer,
|
||||
() -> log.debug("Activate offer was successful"),
|
||||
(message) -> {
|
||||
|
@ -504,7 +518,33 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
|
||||
if (item != null) {
|
||||
if (model.isDeactivated(item)) getStyleClass().add("offer-disabled");
|
||||
setGraphic(new AutoTooltipLabel(model.getPriceDeviation(item)));
|
||||
AutoTooltipLabel autoTooltipLabel = new AutoTooltipLabel(model.getPriceDeviation(item));
|
||||
autoTooltipLabel.setOpacity(item.getOffer().isUseMarketBasedPrice() ? 1 : 0.4);
|
||||
setGraphic(autoTooltipLabel);
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setTriggerPriceColumnCellFactory() {
|
||||
triggerPriceColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue()));
|
||||
triggerPriceColumn.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<OpenOfferListItem, OpenOfferListItem> call(
|
||||
TableColumn<OpenOfferListItem, OpenOfferListItem> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final OpenOfferListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
getStyleClass().removeAll("offer-disabled");
|
||||
if (item != null) {
|
||||
if (model.isDeactivated(item)) getStyleClass().add("offer-disabled");
|
||||
setGraphic(new AutoTooltipLabel(model.getTriggerPrice(item)));
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
|
@ -633,20 +673,23 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
OpenOffer openOffer = item.getOpenOffer();
|
||||
if (checkBox == null) {
|
||||
checkBox = new AutoTooltipSlideToggleButton();
|
||||
checkBox.setPadding(new Insets(-7, 0, -7, 0));
|
||||
checkBox.setGraphic(iconView);
|
||||
}
|
||||
checkBox.setDisable(model.dataModel.wasTriggered(openOffer));
|
||||
checkBox.setOnAction(event -> {
|
||||
if (item.getOpenOffer().isDeactivated()) {
|
||||
onActivateOpenOffer(item.getOpenOffer());
|
||||
if (openOffer.isDeactivated()) {
|
||||
onActivateOpenOffer(openOffer);
|
||||
} else {
|
||||
onDeactivateOpenOffer(item.getOpenOffer());
|
||||
onDeactivateOpenOffer(openOffer);
|
||||
}
|
||||
updateState(item.getOpenOffer());
|
||||
updateState(openOffer);
|
||||
tableView.refresh();
|
||||
});
|
||||
updateState(item.getOpenOffer());
|
||||
updateState(openOffer);
|
||||
setGraphic(checkBox);
|
||||
} else {
|
||||
setGraphic(null);
|
||||
|
@ -677,7 +720,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
|
||||
if (item != null && !empty) {
|
||||
if (button == null) {
|
||||
button = getIconButton(MaterialDesignIcon.DELETE_FOREVER, "delete");
|
||||
button = getRegularIconButton(MaterialDesignIcon.DELETE_FOREVER, "delete");
|
||||
button.setTooltip(new Tooltip(Res.get("shared.removeOffer")));
|
||||
setGraphic(button);
|
||||
}
|
||||
|
@ -695,6 +738,48 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
});
|
||||
}
|
||||
|
||||
private void setTriggerIconColumnCellFactory() {
|
||||
triggerIconColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue()));
|
||||
triggerIconColumn.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<OpenOfferListItem, OpenOfferListItem> call(TableColumn<OpenOfferListItem, OpenOfferListItem> column) {
|
||||
return new TableCell<>() {
|
||||
Button button;
|
||||
|
||||
@Override
|
||||
public void updateItem(final OpenOfferListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
if (button == null) {
|
||||
button = getRegularIconButton(MaterialDesignIcon.SHIELD_HALF_FULL);
|
||||
boolean triggerPriceSet = item.getOpenOffer().getTriggerPrice() > 0;
|
||||
button.setVisible(triggerPriceSet);
|
||||
|
||||
if (model.dataModel.wasTriggered(item.getOpenOffer())) {
|
||||
button.getGraphic().getStyleClass().add("warning");
|
||||
button.setTooltip(new Tooltip(Res.get("openOffer.triggered")));
|
||||
} else {
|
||||
button.getGraphic().getStyleClass().remove("warning");
|
||||
button.setTooltip(new Tooltip(Res.get("openOffer.triggerPrice", model.getTriggerPrice(item))));
|
||||
}
|
||||
setGraphic(button);
|
||||
}
|
||||
button.setOnAction(event -> onEditOpenOffer(item.getOpenOffer()));
|
||||
} else {
|
||||
setGraphic(null);
|
||||
if (button != null) {
|
||||
button.setOnAction(null);
|
||||
button = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setEditColumnCellFactory() {
|
||||
editItemColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue()));
|
||||
editItemColumn.setCellFactory(
|
||||
|
@ -710,7 +795,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
|||
|
||||
if (item != null && !empty) {
|
||||
if (button == null) {
|
||||
button = getIconButton(MaterialDesignIcon.PENCIL);
|
||||
button = getRegularIconButton(MaterialDesignIcon.PENCIL);
|
||||
button.setTooltip(new Tooltip(Res.get("shared.editOffer")));
|
||||
setGraphic(button);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package bisq.desktop.main.portfolio.openoffer;
|
|||
|
||||
import bisq.desktop.common.model.ActivatableWithDataModel;
|
||||
import bisq.desktop.common.model.ViewModel;
|
||||
import bisq.desktop.main.PriceUtil;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
|
@ -46,6 +47,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
|
||||
class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel> implements ViewModel {
|
||||
private final P2PService p2PService;
|
||||
private final PriceUtil priceUtil;
|
||||
private final CoinFormatter btcFormatter;
|
||||
private final BsqFormatter bsqFormatter;
|
||||
|
||||
|
@ -53,20 +55,31 @@ class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel>
|
|||
@Inject
|
||||
public OpenOffersViewModel(OpenOffersDataModel dataModel,
|
||||
P2PService p2PService,
|
||||
PriceUtil priceUtil,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||
BsqFormatter bsqFormatter) {
|
||||
super(dataModel);
|
||||
|
||||
this.p2PService = p2PService;
|
||||
this.priceUtil = priceUtil;
|
||||
this.btcFormatter = btcFormatter;
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
}
|
||||
|
||||
void onActivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
@Override
|
||||
protected void activate() {
|
||||
priceUtil.recalculateBsq30DayAveragePrice();
|
||||
}
|
||||
|
||||
void onActivateOpenOffer(OpenOffer openOffer,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
dataModel.onActivateOpenOffer(openOffer, resultHandler, errorMessageHandler);
|
||||
}
|
||||
|
||||
void onDeactivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
void onDeactivateOpenOffer(OpenOffer openOffer,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
dataModel.onDeactivateOpenOffer(openOffer, resultHandler, errorMessageHandler);
|
||||
}
|
||||
|
||||
|
@ -100,14 +113,15 @@ class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel>
|
|||
}
|
||||
|
||||
String getPriceDeviation(OpenOfferListItem item) {
|
||||
if ((item == null))
|
||||
return "";
|
||||
Offer offer = item.getOffer();
|
||||
if (offer.isUseMarketBasedPrice()) {
|
||||
return FormattingUtils.formatPercentagePrice(offer.getMarketPriceMargin());
|
||||
} else {
|
||||
return Res.get("shared.na");
|
||||
}
|
||||
return priceUtil.getMarketBasedPrice(offer, offer.getMirroredDirection())
|
||||
.map(FormattingUtils::formatPercentagePrice)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
Double getPriceDeviationAsDouble(OpenOfferListItem item) {
|
||||
Offer offer = item.getOffer();
|
||||
return priceUtil.getMarketBasedPrice(offer, offer.getMirroredDirection()).orElse(0d);
|
||||
}
|
||||
|
||||
String getVolume(OpenOfferListItem item) {
|
||||
|
@ -157,4 +171,18 @@ class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel>
|
|||
btcFormatter.formatCoinWithCode(offer.getMakerFee()) :
|
||||
bsqFormatter.formatCoinWithCode(offer.getMakerFee());
|
||||
}
|
||||
|
||||
String getTriggerPrice(OpenOfferListItem item) {
|
||||
if ((item == null)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Offer offer = item.getOffer();
|
||||
long triggerPrice = item.getOpenOffer().getTriggerPrice();
|
||||
if (!offer.isUseMarketBasedPrice() || triggerPrice <= 0) {
|
||||
return Res.get("shared.na");
|
||||
} else {
|
||||
return PriceUtil.formatMarketPrice(triggerPrice, offer.getCurrencyCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import bisq.core.support.dispute.DisputeList;
|
|||
import bisq.core.support.dispute.DisputeManager;
|
||||
import bisq.core.support.dispute.DisputeResult;
|
||||
import bisq.core.support.dispute.DisputeSession;
|
||||
import bisq.core.support.dispute.agent.DisputeAgentLookupMap;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.support.messages.ChatMessage;
|
||||
|
@ -910,6 +911,8 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
TableColumn<Dispute, Dispute> roleColumn = getRoleColumn();
|
||||
tableView.getColumns().add(roleColumn);
|
||||
|
||||
maybeAddAgentColumn();
|
||||
|
||||
stateColumn = getStateColumn();
|
||||
tableView.getColumns().add(stateColumn);
|
||||
|
||||
|
@ -923,6 +926,15 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
tableView.getSortOrder().add(dateColumn);
|
||||
}
|
||||
|
||||
protected void maybeAddAgentColumn() {
|
||||
// Only relevant client views will impl it
|
||||
}
|
||||
|
||||
// Relevant client views will override that
|
||||
protected NodeAddress getAgentNodeAddress(Contract contract) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private TableColumn<Dispute, Dispute> getSelectColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("shared.select"));
|
||||
column.setMinWidth(80);
|
||||
|
@ -1076,7 +1088,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
private TableColumn<Dispute, Dispute> getBuyerOnionAddressColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("support.buyerAddress")) {
|
||||
{
|
||||
setMinWidth(190);
|
||||
setMinWidth(160);
|
||||
}
|
||||
};
|
||||
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
|
||||
|
@ -1102,7 +1114,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
private TableColumn<Dispute, Dispute> getSellerOnionAddressColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("support.sellerAddress")) {
|
||||
{
|
||||
setMinWidth(190);
|
||||
setMinWidth(160);
|
||||
}
|
||||
};
|
||||
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
|
||||
|
@ -1216,6 +1228,40 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
return column;
|
||||
}
|
||||
|
||||
protected TableColumn<Dispute, Dispute> getAgentColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("support.agent")) {
|
||||
{
|
||||
setMinWidth(70);
|
||||
}
|
||||
};
|
||||
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<Dispute, Dispute> call(TableColumn<Dispute, Dispute> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final Dispute item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
NodeAddress agentNodeAddress = getAgentNodeAddress(item.getContract());
|
||||
if (agentNodeAddress == null) {
|
||||
setText(Res.get("shared.na"));
|
||||
return;
|
||||
}
|
||||
|
||||
String keyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(agentNodeAddress.getFullAddress());
|
||||
setText(keyBaseUserName);
|
||||
} else {
|
||||
setText("");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
return column;
|
||||
}
|
||||
|
||||
private TableColumn<Dispute, Dispute> getStateColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("support.state")) {
|
||||
{
|
||||
|
|
|
@ -35,16 +35,21 @@ import bisq.core.support.dispute.mediation.MediationManager;
|
|||
import bisq.core.support.dispute.mediation.MediationSession;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
@FxmlView
|
||||
public class MediationClientView extends DisputeClientView {
|
||||
@Inject
|
||||
|
@ -103,4 +108,15 @@ public class MediationClientView extends DisputeClientView {
|
|||
.onAction(this::reOpenDispute)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NodeAddress getAgentNodeAddress(Contract contract) {
|
||||
return contract.getMediatorNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maybeAddAgentColumn() {
|
||||
TableColumn<Dispute, Dispute> agentColumn = getAgentColumn();
|
||||
tableView.getColumns().add(agentColumn);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,16 +33,21 @@ import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
|||
import bisq.core.support.dispute.refund.RefundManager;
|
||||
import bisq.core.support.dispute.refund.RefundSession;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
@FxmlView
|
||||
public class RefundClientView extends DisputeClientView {
|
||||
@Inject
|
||||
|
@ -73,4 +78,15 @@ public class RefundClientView extends DisputeClientView {
|
|||
protected DisputeSession getConcreteDisputeChatSession(Dispute dispute) {
|
||||
return new RefundSession(dispute, disputeManager.isTrader(dispute));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NodeAddress getAgentNodeAddress(Contract contract) {
|
||||
return contract.getRefundAgentNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maybeAddAgentColumn() {
|
||||
TableColumn<Dispute, Dispute> agentColumn = getAgentColumn();
|
||||
tableView.getColumns().add(agentColumn);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1221,8 +1221,7 @@ public class FormBuilder {
|
|||
int rowIndex,
|
||||
String titleTextfield,
|
||||
String titleCombobox
|
||||
)
|
||||
{
|
||||
) {
|
||||
return addTopLabelTextFieldAutocompleteComboBox(gridPane, rowIndex, titleTextfield, titleCombobox, 0);
|
||||
}
|
||||
|
||||
|
@ -1232,8 +1231,7 @@ public class FormBuilder {
|
|||
String titleTextfield,
|
||||
String titleCombobox,
|
||||
double top
|
||||
)
|
||||
{
|
||||
) {
|
||||
HBox hBox = new HBox();
|
||||
hBox.setSpacing(10);
|
||||
|
||||
|
@ -2145,6 +2143,14 @@ public class FormBuilder {
|
|||
return getIconButton(icon, styleClass, "2em");
|
||||
}
|
||||
|
||||
public static Button getRegularIconButton(GlyphIcons icon) {
|
||||
return getIconButton(icon, "highlight", "1.6em");
|
||||
}
|
||||
|
||||
public static Button getRegularIconButton(GlyphIcons icon, String styleClass) {
|
||||
return getIconButton(icon, styleClass, "1.6em");
|
||||
}
|
||||
|
||||
public static Button getIconButton(GlyphIcons icon, String styleClass, String iconSize) {
|
||||
if (icon.fontFamily().equals(MATERIAL_DESIGN_ICONS)) {
|
||||
Button iconButton = MaterialDesignIconFactory.get().createIconButton(icon,
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package bisq.desktop.main.offer.offerbook;
|
||||
|
||||
import bisq.desktop.main.PriceUtil;
|
||||
|
||||
import bisq.core.locale.Country;
|
||||
import bisq.core.locale.CryptoCurrency;
|
||||
import bisq.core.locale.FiatCurrency;
|
||||
|
@ -41,6 +43,7 @@ import bisq.core.payment.payload.SepaAccountPayload;
|
|||
import bisq.core.payment.payload.SpecificBanksAccountPayload;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
import bisq.core.util.coin.ImmutableCoinFormatter;
|
||||
|
@ -90,6 +93,13 @@ public class OfferBookViewModelTest {
|
|||
Res.setBaseCurrencyName(usd.getName());
|
||||
}
|
||||
|
||||
private PriceUtil getPriceUtil() {
|
||||
PriceFeedService priceFeedService = mock(PriceFeedService.class);
|
||||
TradeStatisticsManager tradeStatisticsManager = mock(TradeStatisticsManager.class);
|
||||
when(tradeStatisticsManager.getObservableTradeStatisticsSet()).thenReturn(FXCollections.observableSet());
|
||||
return new PriceUtil(priceFeedService, tradeStatisticsManager, empty);
|
||||
}
|
||||
|
||||
@Ignore("PaymentAccountPayload needs to be set (has been changed with PB changes)")
|
||||
public void testIsAnyPaymentAccountValidForOffer() {
|
||||
Collection<PaymentAccount> paymentAccounts;
|
||||
|
@ -229,7 +239,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForAmount.intValue());
|
||||
}
|
||||
|
||||
|
@ -243,7 +253,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(6, model.maxPlacesForAmount.intValue());
|
||||
|
@ -261,7 +271,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(15, model.maxPlacesForAmount.intValue());
|
||||
|
@ -280,7 +290,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForVolume.intValue());
|
||||
}
|
||||
|
||||
|
@ -294,7 +304,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(5, model.maxPlacesForVolume.intValue());
|
||||
|
@ -312,7 +322,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(9, model.maxPlacesForVolume.intValue());
|
||||
|
@ -331,7 +341,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForPrice.intValue());
|
||||
}
|
||||
|
||||
|
@ -345,7 +355,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(7, model.maxPlacesForPrice.intValue());
|
||||
|
@ -363,7 +373,7 @@ public class OfferBookViewModelTest {
|
|||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForMarketPriceMargin.intValue());
|
||||
}
|
||||
|
||||
|
@ -391,7 +401,7 @@ public class OfferBookViewModelTest {
|
|||
offerBookListItems.addAll(item1, item2);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, priceFeedService,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(8, model.maxPlacesForMarketPriceMargin.intValue()); //" (1.97%)"
|
||||
|
@ -412,7 +422,7 @@ public class OfferBookViewModelTest {
|
|||
when(priceFeedService.getMarketPrice(anyString())).thenReturn(new MarketPrice("USD", 12684.0450, Instant.now().getEpochSecond(), true));
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter());
|
||||
|
||||
final OfferBookListItem item = make(btcBuyItem.but(
|
||||
with(useMarketBasedPrice, true),
|
||||
|
|
|
@ -1331,6 +1331,7 @@ message OpenOffer {
|
|||
NodeAddress arbitrator_node_address = 3;
|
||||
NodeAddress mediator_node_address = 4;
|
||||
NodeAddress refund_agent_node_address = 5;
|
||||
int64 trigger_price = 6;
|
||||
}
|
||||
|
||||
message Tradable {
|
||||
|
|
Loading…
Add table
Reference in a new issue