support multithreading in api and protocols

close trade wallets while unused for scalability
verify txs do not use unlock height
increase trade init timeout to 60s
This commit is contained in:
woodser 2022-03-31 08:17:58 -04:00
parent fdddc87477
commit bb95b4b1d6
82 changed files with 2786 additions and 2338 deletions

View file

@ -736,8 +736,10 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
});
model.getUpdatedDataReceived().addListener((observable, oldValue, newValue) -> {
p2PNetworkIcon.setOpacity(1);
p2pNetworkProgressBar.setProgress(0);
UserThread.execute(() -> {
p2PNetworkIcon.setOpacity(1);
p2pNetworkProgressBar.setProgress(0);
});
});
p2pNetworkProgressBar = new JFXProgressBar(-1);

View file

@ -240,7 +240,7 @@ public class LockedView extends ActivatableView<VBox, Void> {
private Optional<Tradable> getTradable(LockedListItem item) {
String offerId = item.getAddressEntry().getOfferId();
Optional<Trade> tradeOptional = tradeManager.getTradeById(offerId);
Optional<Trade> tradeOptional = tradeManager.getOpenTrade(offerId);
if (tradeOptional.isPresent()) {
return Optional.of(tradeOptional.get());
} else if (openOfferManager.getOpenOfferById(offerId).isPresent()) {

View file

@ -239,7 +239,7 @@ public class ReservedView extends ActivatableView<VBox, Void> {
private Optional<Tradable> getTradable(ReservedListItem item) {
String offerId = item.getAddressEntry().getOfferId();
Optional<Trade> tradeOptional = tradeManager.getTradeById(offerId);
Optional<Trade> tradeOptional = tradeManager.getOpenTrade(offerId);
if (tradeOptional.isPresent()) {
return Optional.of(tradeOptional.get());
} else if (openOfferManager.getOpenOfferById(offerId).isPresent()) {

View file

@ -32,9 +32,7 @@ import javafx.collections.ObservableList;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import monero.daemon.model.MoneroTx;
import monero.wallet.model.MoneroTxWallet;
@ -82,14 +80,14 @@ class TransactionAwareTrade implements TransactionAwareTradable {
private boolean isMakerDepositTx(String txId) {
return Optional.ofNullable(trade.getMakerDepositTx())
.map(MoneroTxWallet::getHash)
.map(MoneroTx::getHash)
.map(hash -> hash.equals(txId))
.orElse(false);
}
private boolean isTakerDepositTx(String txId) {
return Optional.ofNullable(trade.getTakerDepositTx())
.map(MoneroTxWallet::getHash)
.map(MoneroTx::getHash)
.map(hash -> hash.equals(txId))
.orElse(false);
}

View file

@ -382,17 +382,19 @@ public class OfferBookChartView extends ActivatableViewAndModel<VBox, OfferBookC
}
private void updateChartData() {
seriesBuy.getData().clear();
seriesSell.getData().clear();
areaChart.getData().clear();
UserThread.execute(() -> {
seriesBuy.getData().clear();
seriesSell.getData().clear();
areaChart.getData().clear();
boolean isCrypto = CurrencyUtil.isCryptoCurrency(model.getCurrencyCode());
boolean isCrypto = CurrencyUtil.isCryptoCurrency(model.getCurrencyCode());
// crypto: left-sell, right-buy. fiat: left-buy, right-sell
seriesBuy.getData().addAll(filterOutliersBuy(model.getBuyData(), isCrypto));
seriesSell.getData().addAll(filterOutliersSell(model.getSellData(), isCrypto));
// crypto: left-sell, right-buy. fiat: left-buy, right-sell
seriesBuy.getData().addAll(filterOutliersBuy(model.getBuyData(), isCrypto));
seriesSell.getData().addAll(filterOutliersSell(model.getSellData(), isCrypto));
areaChart.getData().addAll(List.of(seriesBuy, seriesSell));
areaChart.getData().addAll(List.of(seriesBuy, seriesSell));
});
}
List<XYChart.Data<Number, Number>> filterOutliersBuy(List<XYChart.Data<Number, Number>> buy, boolean isCrypto) {

View file

@ -62,7 +62,7 @@ import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.CoinFormatter;
import bisq.network.p2p.NodeAddress;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import bisq.common.config.Config;
import bisq.common.util.Tuple3;
@ -305,7 +305,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
GridPane.setMargin(nrOfOffersLabel, new Insets(10, 0, 0, 0));
root.getChildren().add(nrOfOffersLabel);
offerListListener = c -> nrOfOffersLabel.setText(Res.get("offerbook.nrOffers", model.getOfferList().size()));
offerListListener = c -> UserThread.execute(() -> nrOfOffersLabel.setText(Res.get("offerbook.nrOffers", model.getOfferList().size())));
// Fixes incorrect ordering of Available offers:
// https://github.com/bisq-network/bisq-desktop/issues/588

View file

@ -51,7 +51,7 @@ import bisq.network.p2p.P2PService;
import bisq.network.p2p.network.CloseConnectionReason;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.network.ConnectionListener;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import org.bitcoinj.core.Coin;
@ -524,23 +524,25 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
}
private void updateSpinnerInfo() {
if (!showPayFundsScreenDisplayed.get() ||
offerWarning.get() != null ||
errorMessage.get() != null ||
showTransactionPublishedScreen.get()) {
spinnerInfoText.set("");
} else if (dataModel.getIsBtcWalletFunded().get()) {
spinnerInfoText.set("");
/* if (dataModel.isFeeFromFundingTxSufficient.get()) {
UserThread.execute(() -> {
if (!showPayFundsScreenDisplayed.get() ||
offerWarning.get() != null ||
errorMessage.get() != null ||
showTransactionPublishedScreen.get()) {
spinnerInfoText.set("");
} else if (dataModel.getIsBtcWalletFunded().get()) {
spinnerInfoText.set("");
/* if (dataModel.isFeeFromFundingTxSufficient.get()) {
spinnerInfoText.set("");
} else {
spinnerInfoText.set("Check if funding tx miner fee is sufficient...");
}*/
} else {
spinnerInfoText.set("Check if funding tx miner fee is sufficient...");
}*/
} else {
spinnerInfoText.set(Res.get("shared.waitingForFunds"));
}
spinnerInfoText.set(Res.get("shared.waitingForFunds"));
}
isWaitingForFunds.set(!spinnerInfoText.get().isEmpty());
isWaitingForFunds.set(!spinnerInfoText.get().isEmpty());
});
}
private void addListeners() {

View file

@ -515,47 +515,49 @@ public abstract class Overlay<T extends Overlay<T>> {
if (owner != null) {
Scene rootScene = owner.getScene();
if (rootScene != null) {
Scene scene = new Scene(getRootContainer());
scene.getStylesheets().setAll(rootScene.getStylesheets());
scene.setFill(Color.TRANSPARENT);
UserThread.execute(() -> {
Scene scene = new Scene(getRootContainer());
scene.getStylesheets().setAll(rootScene.getStylesheets());
scene.setFill(Color.TRANSPARENT);
setupKeyHandler(scene);
setupKeyHandler(scene);
stage = new Stage();
stage.setScene(scene);
Window window = rootScene.getWindow();
setModality();
stage.initStyle(StageStyle.TRANSPARENT);
stage.setOnCloseRequest(event -> {
event.consume();
doClose();
stage = new Stage();
stage.setScene(scene);
Window window = rootScene.getWindow();
setModality();
stage.initStyle(StageStyle.TRANSPARENT);
stage.setOnCloseRequest(event -> {
event.consume();
doClose();
});
stage.sizeToScene();
stage.show();
layout();
addEffectToBackground();
// On Linux the owner stage does not move the child stage as it does on Mac
// So we need to apply centerPopup. Further with fast movements the handler loses
// the latest position, with a delay it fixes that.
// Also on Mac sometimes the popups are positioned outside of the main app, so keep it for all OS
positionListener = (observable, oldValue, newValue) -> {
if (stage != null) {
layout();
if (centerTime != null)
centerTime.stop();
centerTime = UserThread.runAfter(this::layout, 3);
}
};
window.xProperty().addListener(positionListener);
window.yProperty().addListener(positionListener);
window.widthProperty().addListener(positionListener);
animateDisplay();
isDisplayed = true;
});
stage.sizeToScene();
stage.show();
layout();
addEffectToBackground();
// On Linux the owner stage does not move the child stage as it does on Mac
// So we need to apply centerPopup. Further with fast movements the handler loses
// the latest position, with a delay it fixes that.
// Also on Mac sometimes the popups are positioned outside of the main app, so keep it for all OS
positionListener = (observable, oldValue, newValue) -> {
if (stage != null) {
layout();
if (centerTime != null)
centerTime.stop();
centerTime = UserThread.runAfter(this::layout, 3);
}
};
window.xProperty().addListener(positionListener);
window.yProperty().addListener(positionListener);
window.widthProperty().addListener(positionListener);
animateDisplay();
isDisplayed = true;
}
}
}

View file

@ -64,7 +64,9 @@ public class Notification extends Overlay<Notification> {
if (autoClose && autoCloseTimer == null)
autoCloseTimer = UserThread.runAfter(this::doClose, 6);
stage.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> doClose());
UserThread.execute(() -> {
stage.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> doClose());
});
}
@Override

View file

@ -54,7 +54,7 @@ import bisq.core.trade.protocol.SellerProtocol;
import bisq.core.user.Preferences;
import bisq.network.p2p.P2PService;
import bisq.common.UserThread;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.PubKeyRingProvider;
import bisq.common.handlers.ErrorMessageHandler;
@ -87,8 +87,7 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.daemon.model.MoneroTx;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
@ -360,54 +359,56 @@ public class PendingTradesDataModel extends ActivatableDataModel {
}
private void doSelectItem(@Nullable PendingTradesListItem item) {
if (selectedTrade != null)
selectedTrade.stateProperty().removeListener(tradeStateChangeListener);
UserThread.execute(() -> {
if (selectedTrade != null)
selectedTrade.stateProperty().removeListener(tradeStateChangeListener);
if (item != null) {
selectedTrade = item.getTrade();
if (selectedTrade == null) {
log.error("selectedTrade is null");
return;
}
MoneroTxWallet makerDepositTx = selectedTrade.getMakerDepositTx();
MoneroTxWallet takerDepositTx = selectedTrade.getTakerDepositTx();
String tradeId = selectedTrade.getId();
tradeStateChangeListener = (observable, oldValue, newValue) -> {
if (makerDepositTx != null && takerDepositTx != null) { // TODO (woodser): this treats separate deposit ids as one unit, being both available or unavailable
makerTxId.set(makerDepositTx.getHash());
takerTxId.set(takerDepositTx.getHash());
notificationCenter.setSelectedTradeId(tradeId);
selectedTrade.stateProperty().removeListener(tradeStateChangeListener);
} else {
makerTxId.set("");
takerTxId.set("");
if (item != null) {
selectedTrade = item.getTrade();
if (selectedTrade == null) {
log.error("selectedTrade is null");
return;
}
};
selectedTrade.stateProperty().addListener(tradeStateChangeListener);
Offer offer = selectedTrade.getOffer();
if (offer == null) {
log.error("offer is null");
return;
}
MoneroTx makerDepositTx = selectedTrade.getMakerDepositTx();
MoneroTx takerDepositTx = selectedTrade.getTakerDepositTx();
String tradeId = selectedTrade.getId();
tradeStateChangeListener = (observable, oldValue, newValue) -> {
if (makerDepositTx != null && takerDepositTx != null) { // TODO (woodser): this treats separate deposit ids as one unit, being both available or unavailable
makerTxId.set(makerDepositTx.getHash());
takerTxId.set(takerDepositTx.getHash());
notificationCenter.setSelectedTradeId(tradeId);
selectedTrade.stateProperty().removeListener(tradeStateChangeListener);
} else {
makerTxId.set("");
takerTxId.set("");
}
};
selectedTrade.stateProperty().addListener(tradeStateChangeListener);
isMaker = tradeManager.isMyOffer(offer);
if (makerDepositTx != null && takerDepositTx != null) {
makerTxId.set(makerDepositTx.getHash());
takerTxId.set(takerDepositTx.getHash());
Offer offer = selectedTrade.getOffer();
if (offer == null) {
log.error("offer is null");
return;
}
isMaker = tradeManager.isMyOffer(offer);
if (makerDepositTx != null && takerDepositTx != null) {
makerTxId.set(makerDepositTx.getHash());
takerTxId.set(takerDepositTx.getHash());
} else {
makerTxId.set("");
takerTxId.set("");
}
notificationCenter.setSelectedTradeId(tradeId);
} else {
selectedTrade = null;
makerTxId.set("");
takerTxId.set("");
notificationCenter.setSelectedTradeId(null);
}
notificationCenter.setSelectedTradeId(tradeId);
} else {
selectedTrade = null;
makerTxId.set("");
takerTxId.set("");
notificationCenter.setSelectedTradeId(null);
}
selectedItemProperty.set(item);
selectedItemProperty.set(item);
});
}
private void tryOpenDispute(boolean isSupportTicket) {
@ -458,8 +459,9 @@ public class PendingTradesDataModel extends ActivatableDataModel {
byte[] payoutTxSerialized = null;
String payoutTxHashAsString = null;
MoneroTxWallet payoutTx = trade.getPayoutTx();
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
String updatedMultisigHex = multisigWallet.getMultisigHex();
xmrWalletService.closeMultisigWallet(trade.getId()); // close multisig wallet
if (payoutTx != null) {
// payoutTxSerialized = payoutTx.bitcoinSerialize(); // TODO (woodser): no need to pass serialized txs for xmr
// payoutTxHashAsString = payoutTx.getHashAsString();

View file

@ -363,7 +363,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
}
private void updateMoveTradeToFailedColumnState() {
moveTradeToFailedColumn.setVisible(model.dataModel.list.stream().anyMatch(item -> isMaybeInvalidTrade(item.getTrade())));
UserThread.execute(() -> moveTradeToFailedColumn.setVisible(model.dataModel.list.stream().anyMatch(item -> isMaybeInvalidTrade(item.getTrade()))));
}
private boolean isMaybeInvalidTrade(Trade trade) {

View file

@ -137,7 +137,8 @@ public class BuyerStep2View extends TradeStepView {
showPopup();
} else if (state.ordinal() <= Trade.State.BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG.ordinal()) {
if (!trade.hasFailed()) {
switch (state) {
UserThread.execute(() -> {
switch (state) {
case BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED:
case BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG:
busyAnimation.play();
@ -169,7 +170,8 @@ public class BuyerStep2View extends TradeStepView {
busyAnimation.stop();
statusLabel.setText(Res.get("shared.sendingConfirmationAgain"));
break;
}
}
});
} else {
log.warn("Trade contains error message {}", trade.getErrorMessage());
statusLabel.setText("");

View file

@ -471,43 +471,45 @@ public class ChatView extends AnchorPane {
}
private void updateMsgState(ChatMessage message) {
boolean visible;
AwesomeIcon icon = null;
String text = null;
statusIcon.getStyleClass().add("status-icon");
statusInfoLabel.getStyleClass().add("status-icon");
statusHBox.setOpacity(1);
log.debug("updateMsgState msg-{}, ack={}, arrived={}", message.getMessage(),
message.acknowledgedProperty().get(), message.arrivedProperty().get());
if (message.acknowledgedProperty().get()) {
visible = true;
icon = AwesomeIcon.OK_SIGN;
text = Res.get("support.acknowledged");
} else if (message.ackErrorProperty().get() != null) {
visible = true;
icon = AwesomeIcon.EXCLAMATION_SIGN;
text = Res.get("support.error", message.ackErrorProperty().get());
statusIcon.getStyleClass().add("error-text");
statusInfoLabel.getStyleClass().add("error-text");
} else if (message.arrivedProperty().get()) {
visible = true;
icon = AwesomeIcon.OK;
text = Res.get("support.arrived");
} else if (message.storedInMailboxProperty().get()) {
visible = true;
icon = AwesomeIcon.ENVELOPE;
text = Res.get("support.savedInMailbox");
} else {
visible = false;
log.debug("updateMsgState called but no msg state available. message={}", message);
}
UserThread.execute(() -> {
boolean visible;
AwesomeIcon icon = null;
String text = null;
statusIcon.getStyleClass().add("status-icon");
statusInfoLabel.getStyleClass().add("status-icon");
statusHBox.setOpacity(1);
log.debug("updateMsgState msg-{}, ack={}, arrived={}", message.getMessage(),
message.acknowledgedProperty().get(), message.arrivedProperty().get());
if (message.acknowledgedProperty().get()) {
visible = true;
icon = AwesomeIcon.OK_SIGN;
text = Res.get("support.acknowledged");
} else if (message.ackErrorProperty().get() != null) {
visible = true;
icon = AwesomeIcon.EXCLAMATION_SIGN;
text = Res.get("support.error", message.ackErrorProperty().get());
statusIcon.getStyleClass().add("error-text");
statusInfoLabel.getStyleClass().add("error-text");
} else if (message.arrivedProperty().get()) {
visible = true;
icon = AwesomeIcon.OK;
text = Res.get("support.arrived");
} else if (message.storedInMailboxProperty().get()) {
visible = true;
icon = AwesomeIcon.ENVELOPE;
text = Res.get("support.savedInMailbox");
} else {
visible = false;
log.debug("updateMsgState called but no msg state available. message={}", message);
}
statusHBox.setVisible(visible);
if (visible) {
AwesomeDude.setIcon(statusIcon, icon, "14");
statusIcon.setTooltip(new Tooltip(text));
statusInfoLabel.setText(text);
}
statusHBox.setVisible(visible);
if (visible) {
AwesomeDude.setIcon(statusIcon, icon, "14");
statusIcon.setTooltip(new Tooltip(text));
statusInfoLabel.setText(text);
}
});
}
};
}

View file

@ -85,76 +85,78 @@ public class DisputeChatPopup {
}
public void openChat(Dispute selectedDispute, DisputeSession concreteDisputeSession, String counterpartyName) {
closeChat();
this.selectedDispute = selectedDispute;
selectedDispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
disputeManager.requestPersistence();
ChatView chatView = new ChatView(disputeManager, formatter, counterpartyName);
chatView.setAllowAttachments(true);
chatView.setDisplayHeader(false);
chatView.initialize();
AnchorPane pane = new AnchorPane(chatView);
pane.setPrefSize(760, 500);
AnchorPane.setLeftAnchor(chatView, 10d);
AnchorPane.setRightAnchor(chatView, 10d);
AnchorPane.setTopAnchor(chatView, -20d);
AnchorPane.setBottomAnchor(chatView, 10d);
pane.getStyleClass().add("dispute-chat-border");
Button closeDisputeButton = null;
if (!selectedDispute.isClosed() && !disputeManager.isTrader(selectedDispute)) {
closeDisputeButton = new AutoTooltipButton(Res.get("support.closeTicket"));
closeDisputeButton.setOnAction(e -> chatCallback.onCloseDisputeFromChatWindow(selectedDispute));
}
chatView.display(concreteDisputeSession, closeDisputeButton, pane.widthProperty());
chatView.activate();
chatView.scrollToBottom();
chatPopupStage = new Stage();
chatPopupStage.setTitle(Res.get("disputeChat.chatWindowTitle", selectedDispute.getShortTradeId())
+ " " + selectedDispute.getRoleString());
StackPane owner = MainView.getRootContainer();
Scene rootScene = owner.getScene();
chatPopupStage.initOwner(rootScene.getWindow());
chatPopupStage.initModality(Modality.NONE);
chatPopupStage.initStyle(StageStyle.DECORATED);
chatPopupStage.setOnHiding(event -> {
chatView.deactivate();
// at close we set all as displayed. While open we ignore updates of the numNewMsg in the list icon.
UserThread.execute(() -> {
closeChat();
this.selectedDispute = selectedDispute;
selectedDispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
disputeManager.requestPersistence();
chatPopupStage = null;
});
Scene scene = new Scene(pane);
CssTheme.loadSceneStyles(scene, preferences.getCssTheme(), false);
scene.addEventHandler(KeyEvent.KEY_RELEASED, ev -> {
if (ev.getCode() == KeyCode.ESCAPE) {
ev.consume();
chatPopupStage.hide();
ChatView chatView = new ChatView(disputeManager, formatter, counterpartyName);
chatView.setAllowAttachments(true);
chatView.setDisplayHeader(false);
chatView.initialize();
AnchorPane pane = new AnchorPane(chatView);
pane.setPrefSize(760, 500);
AnchorPane.setLeftAnchor(chatView, 10d);
AnchorPane.setRightAnchor(chatView, 10d);
AnchorPane.setTopAnchor(chatView, -20d);
AnchorPane.setBottomAnchor(chatView, 10d);
pane.getStyleClass().add("dispute-chat-border");
Button closeDisputeButton = null;
if (!selectedDispute.isClosed() && !disputeManager.isTrader(selectedDispute)) {
closeDisputeButton = new AutoTooltipButton(Res.get("support.closeTicket"));
closeDisputeButton.setOnAction(e -> chatCallback.onCloseDisputeFromChatWindow(selectedDispute));
}
chatView.display(concreteDisputeSession, closeDisputeButton, pane.widthProperty());
chatView.activate();
chatView.scrollToBottom();
chatPopupStage = new Stage();
chatPopupStage.setTitle(Res.get("disputeChat.chatWindowTitle", selectedDispute.getShortTradeId())
+ " " + selectedDispute.getRoleString());
StackPane owner = MainView.getRootContainer();
Scene rootScene = owner.getScene();
chatPopupStage.initOwner(rootScene.getWindow());
chatPopupStage.initModality(Modality.NONE);
chatPopupStage.initStyle(StageStyle.DECORATED);
chatPopupStage.setOnHiding(event -> {
chatView.deactivate();
// at close we set all as displayed. While open we ignore updates of the numNewMsg in the list icon.
selectedDispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
disputeManager.requestPersistence();
chatPopupStage = null;
});
Scene scene = new Scene(pane);
CssTheme.loadSceneStyles(scene, preferences.getCssTheme(), false);
scene.addEventHandler(KeyEvent.KEY_RELEASED, ev -> {
if (ev.getCode() == KeyCode.ESCAPE) {
ev.consume();
chatPopupStage.hide();
}
});
chatPopupStage.setScene(scene);
chatPopupStage.setOpacity(0);
chatPopupStage.show();
xPositionListener = (observable, oldValue, newValue) -> chatPopupStageXPosition = (double) newValue;
chatPopupStage.xProperty().addListener(xPositionListener);
yPositionListener = (observable, oldValue, newValue) -> chatPopupStageYPosition = (double) newValue;
chatPopupStage.yProperty().addListener(yPositionListener);
if (chatPopupStageXPosition == -1) {
Window rootSceneWindow = rootScene.getWindow();
double titleBarHeight = rootSceneWindow.getHeight() - rootScene.getHeight();
chatPopupStage.setX(Math.round(rootSceneWindow.getX() + (owner.getWidth() - chatPopupStage.getWidth() / 4 * 3)));
chatPopupStage.setY(Math.round(rootSceneWindow.getY() + titleBarHeight + (owner.getHeight() - chatPopupStage.getHeight() / 4 * 3)));
} else {
chatPopupStage.setX(chatPopupStageXPosition);
chatPopupStage.setY(chatPopupStageYPosition);
}
// Delay display to next render frame to avoid that the popup is first quickly displayed in default position
// and after a short moment in the correct position
UserThread.execute(() -> chatPopupStage.setOpacity(1));
});
chatPopupStage.setScene(scene);
chatPopupStage.setOpacity(0);
chatPopupStage.show();
xPositionListener = (observable, oldValue, newValue) -> chatPopupStageXPosition = (double) newValue;
chatPopupStage.xProperty().addListener(xPositionListener);
yPositionListener = (observable, oldValue, newValue) -> chatPopupStageYPosition = (double) newValue;
chatPopupStage.yProperty().addListener(yPositionListener);
if (chatPopupStageXPosition == -1) {
Window rootSceneWindow = rootScene.getWindow();
double titleBarHeight = rootSceneWindow.getHeight() - rootScene.getHeight();
chatPopupStage.setX(Math.round(rootSceneWindow.getX() + (owner.getWidth() - chatPopupStage.getWidth() / 4 * 3)));
chatPopupStage.setY(Math.round(rootSceneWindow.getY() + titleBarHeight + (owner.getHeight() - chatPopupStage.getHeight() / 4 * 3)));
} else {
chatPopupStage.setX(chatPopupStageXPosition);
chatPopupStage.setY(chatPopupStageYPosition);
}
// Delay display to next render frame to avoid that the popup is first quickly displayed in default position
// and after a short moment in the correct position
UserThread.execute(() -> chatPopupStage.setOpacity(1));
}
}

View file

@ -1131,7 +1131,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
super.updateItem(item, empty);
if (item != null && !empty) {
Optional<Trade> tradeOptional = tradeManager.getTradeById(item.getTradeId());
Optional<Trade> tradeOptional = tradeManager.getOpenTrade(item.getTradeId());
if (tradeOptional.isPresent()) {
field = new HyperlinkWithIcon(item.getShortTradeId());
field.setMouseTransparent(false);
@ -1349,31 +1349,33 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
@Override
public void updateItem(final Dispute item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (closedProperty != null) {
closedProperty.removeListener(listener);
}
UserThread.execute(() -> {
if (item != null && !empty) {
if (closedProperty != null) {
closedProperty.removeListener(listener);
}
listener = (observable, oldValue, newValue) -> {
setText(newValue ? Res.get("support.closed") : Res.get("support.open"));
listener = (observable, oldValue, newValue) -> {
setText(newValue ? Res.get("support.closed") : Res.get("support.open"));
if (getTableRow() != null)
getTableRow().setOpacity(newValue && item.getBadgeCountProperty().get() == 0 ? 0.4 : 1);
if (item.isClosed() && item == chatPopup.getSelectedDispute())
chatPopup.closeChat(); // close the chat popup when the associated ticket is closed
};
closedProperty = item.isClosedProperty();
closedProperty.addListener(listener);
boolean isClosed = item.isClosed();
setText(isClosed ? Res.get("support.closed") : Res.get("support.open"));
if (getTableRow() != null)
getTableRow().setOpacity(newValue && item.getBadgeCountProperty().get() == 0 ? 0.4 : 1);
if (item.isClosed() && item == chatPopup.getSelectedDispute())
chatPopup.closeChat(); // close the chat popup when the associated ticket is closed
};
closedProperty = item.isClosedProperty();
closedProperty.addListener(listener);
boolean isClosed = item.isClosed();
setText(isClosed ? Res.get("support.closed") : Res.get("support.open"));
if (getTableRow() != null)
getTableRow().setOpacity(isClosed && item.getBadgeCountProperty().get() == 0 ? 0.4 : 1);
} else {
if (closedProperty != null) {
closedProperty.removeListener(listener);
closedProperty = null;
getTableRow().setOpacity(isClosed && item.getBadgeCountProperty().get() == 0 ? 0.4 : 1);
} else {
if (closedProperty != null) {
closedProperty.removeListener(listener);
closedProperty = null;
}
setText("");
}
setText("");
}
});
}
};
}
@ -1389,27 +1391,29 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
}
private void updateChatMessageCount(Dispute dispute, JFXBadge chatBadge) {
if (chatBadge == null)
return;
// when the chat popup is active, we do not display new message count indicator for that item
if (chatPopup.isChatShown() && selectedDispute != null && dispute.getId().equals(selectedDispute.getId())) {
chatBadge.setText("");
chatBadge.setEnabled(false);
chatBadge.refreshBadge();
// have to UserThread.execute or the new message will be sent to peer as "read"
UserThread.execute(() -> dispute.setChatMessagesSeen(senderFlag()));
return;
}
UserThread.execute(() -> {
if (chatBadge == null)
return;
// when the chat popup is active, we do not display new message count indicator for that item
if (chatPopup.isChatShown() && selectedDispute != null && dispute.getId().equals(selectedDispute.getId())) {
chatBadge.setText("");
chatBadge.setEnabled(false);
chatBadge.refreshBadge();
// have to UserThread.execute or the new message will be sent to peer as "read"
UserThread.execute(() -> dispute.setChatMessagesSeen(senderFlag()));
return;
}
if (dispute.unreadMessageCount(senderFlag()) > 0) {
chatBadge.setText(String.valueOf(dispute.unreadMessageCount(senderFlag())));
chatBadge.setEnabled(true);
} else {
chatBadge.setText("");
chatBadge.setEnabled(false);
}
chatBadge.refreshBadge();
dispute.refreshAlertLevel(senderFlag());
if (dispute.unreadMessageCount(senderFlag()) > 0) {
chatBadge.setText(String.valueOf(dispute.unreadMessageCount(senderFlag())));
chatBadge.setEnabled(true);
} else {
chatBadge.setText("");
chatBadge.setEnabled(false);
}
chatBadge.refreshBadge();
dispute.refreshAlertLevel(senderFlag());
});
}
private String getCounterpartyName() {

View file

@ -92,12 +92,12 @@ public class Transitions {
public void fadeOutAndRemove(Node node, int duration, EventHandler<ActionEvent> handler) {
FadeTransition fade = fadeOut(node, getDuration(duration));
fade.setInterpolator(Interpolator.EASE_IN);
fade.setOnFinished(actionEvent -> {
fade.setOnFinished(actionEvent -> UserThread.execute(() -> {
((Pane) (node.getParent())).getChildren().remove(node);
//Profiler.printMsgWithTime("fadeOutAndRemove");
if (handler != null)
handler.handle(actionEvent);
});
}));
}
// Blur