/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.overlays.windows;
import io.bitsquare.arbitration.Dispute;
import io.bitsquare.arbitration.DisputeManager;
import io.bitsquare.arbitration.DisputeResult;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.exceptions.TransactionVerificationException;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Tuple2;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.overlays.Overlay;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.gui.util.Transitions;
import io.bitsquare.trade.Contract;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.ExchangeRate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import static io.bitsquare.gui.util.FormBuilder.*;
public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
private static final Logger log = LoggerFactory.getLogger(DisputeSummaryWindow.class);
private final BSFormatter formatter;
private final DisputeManager disputeManager;
private final WalletService walletService;
private final TradeWalletService tradeWalletService;
private Dispute dispute;
private Optional<Runnable> finalizeDisputeHandlerOptional = Optional.empty();
private ToggleGroup tradeAmountToggleGroup;
private DisputeResult disputeResult;
private RadioButton buyerIsWinnerRadioButton, sellerIsWinnerRadioButton, shareRadioButton, customRadioButton, loserPaysFeeRadioButton, splitFeeRadioButton,
waiveFeeRadioButton, reasonWasBugRadioButton, reasonWasUsabilityIssueRadioButton, reasonProtocolViolationRadioButton, reasonNoReplyRadioButton, reasonWasScamRadioButton, reasonWasOtherRadioButton;
private Optional<Dispute> peersDisputeOptional;
private Coin arbitratorPayoutAmount, winnerPayoutAmount, loserPayoutAmount, stalematePayoutAmount;
private ToggleGroup feeToggleGroup, reasonToggleGroup;
private String role;
private TextArea summaryNotesTextArea;
// keep a reference to not get GCed
private ObjectBinding<Tuple2<DisputeResult.DisputeFeePolicy, Toggle>> feePaymentPolicyChanged;
private ChangeListener<Tuple2<DisputeResult.DisputeFeePolicy, Toggle>> feePaymentPolicyListener;
private ChangeListener<Boolean> shareRadioButtonSelectedListener, customRadioButtonSelectedListener;
private ChangeListener<Toggle> feeToggleSelectionListener, reasonToggleSelectionListener;
private InputTextField buyerPayoutAmountInputTextField, sellerPayoutAmountInputTextField, arbitratorPayoutAmountInputTextField;
private ChangeListener<String> buyerPayoutAmountListener, sellerPayoutAmountListener, arbitratorPayoutAmountListener;
private CheckBox isLoserPublisherCheckBox;
///////////////////////////////////////////////////////////////////////////////////////////
// Public API
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public DisputeSummaryWindow(BSFormatter formatter, DisputeManager disputeManager, WalletService walletService, TradeWalletService tradeWalletService) {
this.formatter = formatter;
this.disputeManager = disputeManager;
this.walletService = walletService;
this.tradeWalletService = tradeWalletService;
type = Type.Confirmation;
}
public void show(Dispute dispute) {
this.dispute = dispute;
rowIndex = -1;
width = 850;
createGridPane();
addContent();
display();
}
public DisputeSummaryWindow onFinalizeDispute(Runnable finalizeDisputeHandler) {
this.finalizeDisputeHandlerOptional = Optional.of(finalizeDisputeHandler);
return this;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void cleanup() {
if (feePaymentPolicyChanged != null)
feePaymentPolicyChanged.removeListener(feePaymentPolicyListener);
if (shareRadioButton != null)
shareRadioButton.selectedProperty().removeListener(shareRadioButtonSelectedListener);
if (feeToggleGroup != null)
feeToggleGroup.selectedToggleProperty().removeListener(feeToggleSelectionListener);
if (reasonToggleGroup != null)
reasonToggleGroup.selectedToggleProperty().removeListener(reasonToggleSelectionListener);
if (customRadioButton != null)
customRadioButton.selectedProperty().removeListener(customRadioButtonSelectedListener);
removePayoutAmountListeners();
}
@Override
protected void setupKeyHandler(Scene scene) {
if (!hideCloseButton) {
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ESCAPE) {
e.consume();
doClose();
}
});
}
}
@Override
protected void createGridPane() {
super.createGridPane();
gridPane.setPadding(new Insets(35, 40, 30, 40));
gridPane.setStyle("-fx-background-color: -bs-content-bg-grey;" +
"-fx-background-radius: 5 5 5 5;" +
"-fx-effect: dropshadow(gaussian, #999, 10, 0, 0, 0);" +
"-fx-background-insets: 10;"
);
}
private void addContent() {
Contract contract = dispute.getContract();
if (dispute.disputeResultProperty().get() == null)
disputeResult = new DisputeResult(dispute.getTradeId(), dispute.getTraderId());
else
disputeResult = dispute.disputeResultProperty().get();
peersDisputeOptional = disputeManager.getDisputesAsObservableList().stream()
.filter(d -> dispute.getTradeId().equals(d.getTradeId()) && dispute.getTraderId() != d.getTraderId()).findFirst();
addInfoPane();
if (!dispute.isSupportTicket())
addCheckboxes();
addTradeAmountPayoutControls();
addFeeControls();
addPayoutAmountTextFields();
addReasonControls();
boolean applyPeersDisputeResult = peersDisputeOptional.isPresent() && peersDisputeOptional.get().isClosed();
if (applyPeersDisputeResult) {
// If the other peers dispute has been closed we apply the result to ourselves
DisputeResult peersDisputeResult = peersDisputeOptional.get().disputeResultProperty().get();
disputeResult.setBuyerPayoutAmount(peersDisputeResult.getBuyerPayoutAmount());
disputeResult.setSellerPayoutAmount(peersDisputeResult.getSellerPayoutAmount());
disputeResult.setArbitratorPayoutAmount(peersDisputeResult.getArbitratorPayoutAmount());
disputeResult.setDisputeFeePolicy(peersDisputeResult.getDisputeFeePolicy());
disputeResult.setWinner(peersDisputeResult.getWinner());
disputeResult.setLoserIsPublisher(peersDisputeResult.isLoserPublisher());
disputeResult.setReason(peersDisputeResult.getReason());
disputeResult.setSummaryNotes(peersDisputeResult.summaryNotesProperty().get());
if (disputeResult.getBuyerPayoutAmount() != null) {
log.debug("buyerPayoutAmount " + disputeResult.getBuyerPayoutAmount().toFriendlyString());
log.debug("sellerPayoutAmount " + disputeResult.getSellerPayoutAmount().toFriendlyString());
log.debug("arbitratorPayoutAmount " + disputeResult.getArbitratorPayoutAmount().toFriendlyString());
}
buyerIsWinnerRadioButton.setDisable(true);
sellerIsWinnerRadioButton.setDisable(true);
shareRadioButton.setDisable(true);
customRadioButton.setDisable(true);
loserPaysFeeRadioButton.setDisable(true);
splitFeeRadioButton.setDisable(true);
waiveFeeRadioButton.setDisable(true);
buyerPayoutAmountInputTextField.setDisable(true);
sellerPayoutAmountInputTextField.setDisable(true);
arbitratorPayoutAmountInputTextField.setDisable(true);
buyerPayoutAmountInputTextField.setEditable(false);
sellerPayoutAmountInputTextField.setEditable(false);
arbitratorPayoutAmountInputTextField.setEditable(false);
reasonWasBugRadioButton.setDisable(true);
reasonWasUsabilityIssueRadioButton.setDisable(true);
reasonProtocolViolationRadioButton.setDisable(true);
reasonNoReplyRadioButton.setDisable(true);
reasonWasScamRadioButton.setDisable(true);
reasonWasOtherRadioButton.setDisable(true);
isLoserPublisherCheckBox.setDisable(true);
isLoserPublisherCheckBox.setSelected(peersDisputeResult.isLoserPublisher());
calculatePayoutAmounts(disputeResult.getDisputeFeePolicy());
applyTradeAmountRadioButtonStates();
} else {
applyPayoutAmounts(disputeResult.disputeFeePolicyProperty().get(), tradeAmountToggleGroup.selectedToggleProperty().get());
feePaymentPolicyChanged = Bindings.createObjectBinding(
() -> new Tuple2<>(disputeResult.disputeFeePolicyProperty().get(), tradeAmountToggleGroup.selectedToggleProperty().get()),
disputeResult.disputeFeePolicyProperty(),
tradeAmountToggleGroup.selectedToggleProperty());
feePaymentPolicyListener = (observable, oldValue, newValue) -> applyPayoutAmounts(newValue.first, newValue.second);
feePaymentPolicyChanged.addListener(feePaymentPolicyListener);
isLoserPublisherCheckBox.setSelected(false);
}
setFeeRadioButtonState();
setReasonRadioButtonState();
addSummaryNotes();
addButtons(contract);
}
private void addInfoPane() {
Contract contract = dispute.getContract();
addTitledGroupBg(gridPane, ++rowIndex, 16, "Summary");
addLabelTextField(gridPane, rowIndex, "Trade ID:", dispute.getShortTradeId(), Layout.FIRST_ROW_DISTANCE);
addLabelTextField(gridPane, ++rowIndex, "Ticket opening date:", formatter.formatDateTime(dispute.getOpeningDate()));
if (dispute.isDisputeOpenerIsOfferer()) {
if (dispute.isDisputeOpenerIsBuyer())
role = "BTC Buyer/offerer";
else
role = "BTC Seller/offerer";
} else {
if (dispute.isDisputeOpenerIsBuyer())
role = "BTC Buyer/taker";
else
role = "BTC Seller/taker";
}
addLabelTextField(gridPane, ++rowIndex, "Trader's role:", role);
addLabelTextField(gridPane, ++rowIndex, "Trade amount:", formatter.formatCoinWithCode(contract.getTradeAmount()));
addLabelTextField(gridPane, ++rowIndex, "Trade price:", formatter.formatPrice(contract.getTradePrice()));
addLabelTextField(gridPane, ++rowIndex, "Trade volume:", formatter.formatVolumeWithCode(new ExchangeRate(contract.getTradePrice()).coinToFiat(contract.getTradeAmount())));
}
private void addCheckboxes() {
Label evidenceLabel = addLabel(gridPane, ++rowIndex, "Evidence:", 10);
GridPane.setValignment(evidenceLabel, VPos.TOP);
CheckBox tamperProofCheckBox = new CheckBox("Tamper proof evidence");
CheckBox idVerificationCheckBox = new CheckBox("ID Verification");
CheckBox screenCastCheckBox = new CheckBox("Video/Screencast");
tamperProofCheckBox.selectedProperty().bindBidirectional(disputeResult.tamperProofEvidenceProperty());
idVerificationCheckBox.selectedProperty().bindBidirectional(disputeResult.idVerificationProperty());
screenCastCheckBox.selectedProperty().bindBidirectional(disputeResult.screenCastProperty());
FlowPane checkBoxPane = new FlowPane();
checkBoxPane.setHgap(20);
checkBoxPane.setVgap(5);
checkBoxPane.getChildren().addAll(tamperProofCheckBox, idVerificationCheckBox, screenCastCheckBox);
GridPane.setRowIndex(checkBoxPane, rowIndex);
GridPane.setColumnIndex(checkBoxPane, 1);
GridPane.setMargin(checkBoxPane, new Insets(10, 0, 0, 0));
gridPane.getChildren().add(checkBoxPane);
}
private void addTradeAmountPayoutControls() {
Label distributionLabel = addLabel(gridPane, ++rowIndex, "Trade amount payout:", 10);
GridPane.setValignment(distributionLabel, VPos.TOP);
buyerIsWinnerRadioButton = new RadioButton("BTC buyer gets trade amount payout");
sellerIsWinnerRadioButton = new RadioButton("BTC seller gets trade amount payout");
shareRadioButton = new RadioButton("Both gets half trade amount payout");
customRadioButton = new RadioButton("Custom payout");
VBox radioButtonPane = new VBox();
radioButtonPane.setSpacing(10);
radioButtonPane.getChildren().addAll(buyerIsWinnerRadioButton, sellerIsWinnerRadioButton, shareRadioButton, customRadioButton);
GridPane.setRowIndex(radioButtonPane, rowIndex);
GridPane.setColumnIndex(radioButtonPane, 1);
GridPane.setMargin(radioButtonPane, new Insets(10, 0, 0, 0));
gridPane.getChildren().add(radioButtonPane);
tradeAmountToggleGroup = new ToggleGroup();
buyerIsWinnerRadioButton.setToggleGroup(tradeAmountToggleGroup);
sellerIsWinnerRadioButton.setToggleGroup(tradeAmountToggleGroup);
shareRadioButton.setToggleGroup(tradeAmountToggleGroup);
customRadioButton.setToggleGroup(tradeAmountToggleGroup);
shareRadioButtonSelectedListener = (observable, oldValue, newValue) -> {
if (newValue) {
loserPaysFeeRadioButton.setSelected(false);
if (splitFeeRadioButton != null && !dispute.isSupportTicket())
splitFeeRadioButton.setSelected(true);
if (waiveFeeRadioButton != null && dispute.isSupportTicket())
waiveFeeRadioButton.setSelected(true);
}
loserPaysFeeRadioButton.setDisable(newValue);
};
shareRadioButton.selectedProperty().addListener(shareRadioButtonSelectedListener);
buyerPayoutAmountListener = (observable1, oldValue1, newValue1) -> applyCustomAmounts(buyerPayoutAmountInputTextField);
sellerPayoutAmountListener = (observable1, oldValue1, newValue1) -> applyCustomAmounts(sellerPayoutAmountInputTextField);
arbitratorPayoutAmountListener = (observable1, oldValue1, newValue1) -> applyCustomAmounts(arbitratorPayoutAmountInputTextField);
customRadioButtonSelectedListener = (observable, oldValue, newValue) -> {
buyerPayoutAmountInputTextField.setEditable(newValue);
sellerPayoutAmountInputTextField.setEditable(newValue);
arbitratorPayoutAmountInputTextField.setEditable(newValue);
loserPaysFeeRadioButton.setDisable(newValue);
splitFeeRadioButton.setDisable(newValue);
waiveFeeRadioButton.setDisable(newValue);
if (newValue) {
buyerPayoutAmountInputTextField.textProperty().addListener(buyerPayoutAmountListener);
sellerPayoutAmountInputTextField.textProperty().addListener(sellerPayoutAmountListener);
arbitratorPayoutAmountInputTextField.textProperty().addListener(arbitratorPayoutAmountListener);
} else {
removePayoutAmountListeners();
}
};
customRadioButton.selectedProperty().addListener(customRadioButtonSelectedListener);
}
private void removePayoutAmountListeners() {
if (buyerPayoutAmountInputTextField != null && buyerPayoutAmountListener != null)
buyerPayoutAmountInputTextField.textProperty().removeListener(buyerPayoutAmountListener);
if (sellerPayoutAmountInputTextField != null && sellerPayoutAmountListener != null)
sellerPayoutAmountInputTextField.textProperty().removeListener(sellerPayoutAmountListener);
if (arbitratorPayoutAmountInputTextField != null && arbitratorPayoutAmountListener != null)
arbitratorPayoutAmountInputTextField.textProperty().removeListener(arbitratorPayoutAmountListener);
}
private boolean isPayoutAmountValid() {
Coin buyerAmount = formatter.parseToCoin(buyerPayoutAmountInputTextField.getText());
Coin sellerAmount = formatter.parseToCoin(sellerPayoutAmountInputTextField.getText());
Coin arbitratorAmount = formatter.parseToCoin(arbitratorPayoutAmountInputTextField.getText());
Coin securityDeposit = FeePolicy.getSecurityDeposit(dispute.getContract().offer);
Coin tradeAmount = dispute.getContract().getTradeAmount();
Coin available = tradeAmount.add(securityDeposit).add(securityDeposit);
Coin totalAmount = buyerAmount.add(sellerAmount).add(arbitratorAmount);
return (totalAmount.compareTo(available) == 0);
}
private void applyCustomAmounts(InputTextField inputTextField) {
Coin securityDeposit = FeePolicy.getSecurityDeposit(dispute.getContract().offer);
Coin tradeAmount = dispute.getContract().getTradeAmount();
Coin buyerAmount = formatter.parseToCoin(buyerPayoutAmountInputTextField.getText());
Coin sellerAmount = formatter.parseToCoin(sellerPayoutAmountInputTextField.getText());
Coin arbitratorAmount = formatter.parseToCoin(arbitratorPayoutAmountInputTextField.getText());
Coin available = tradeAmount.add(securityDeposit).add(securityDeposit);
Coin totalAmount = buyerAmount.add(sellerAmount).add(arbitratorAmount);
if (totalAmount.compareTo(available) > 0) {
new Popup<>().warning("Amount entered exceeds available amount of " + available.toFriendlyString() + ".\n" +
"We adjust this input field to the max possible value.")
.show();
if (inputTextField == buyerPayoutAmountInputTextField) {
buyerAmount = available.subtract(sellerAmount).subtract(arbitratorAmount);
inputTextField.setText(formatter.formatCoin(buyerAmount));
} else if (inputTextField == sellerPayoutAmountInputTextField) {
sellerAmount = available.subtract(buyerAmount).subtract(arbitratorAmount);
inputTextField.setText(formatter.formatCoin(sellerAmount));
} else if (inputTextField == arbitratorPayoutAmountInputTextField) {
arbitratorAmount = available.subtract(sellerAmount).subtract(buyerAmount);
inputTextField.setText(formatter.formatCoin(arbitratorAmount));
}
}
disputeResult.setBuyerPayoutAmount(buyerAmount);
disputeResult.setSellerPayoutAmount(sellerAmount);
disputeResult.setArbitratorPayoutAmount(arbitratorAmount);
if (buyerAmount.compareTo(sellerAmount) == 0)
disputeResult.setWinner(DisputeResult.Winner.STALE_MATE);
else if (buyerAmount.compareTo(sellerAmount) > 0)
disputeResult.setWinner(DisputeResult.Winner.BUYER);
else
disputeResult.setWinner(DisputeResult.Winner.SELLER);
}
private void addFeeControls() {
Label splitFeeLabel = addLabel(gridPane, ++rowIndex, "Arbitration fee:", 10);
GridPane.setValignment(splitFeeLabel, VPos.TOP);
loserPaysFeeRadioButton = new RadioButton("Loser pays arbitration fee");
splitFeeRadioButton = new RadioButton("Split arbitration fee");
waiveFeeRadioButton = new RadioButton("Waive arbitration fee");
HBox feeRadioButtonPane = new HBox();
feeRadioButtonPane.setSpacing(20);
feeRadioButtonPane.getChildren().addAll(loserPaysFeeRadioButton, splitFeeRadioButton, waiveFeeRadioButton);
GridPane.setRowIndex(feeRadioButtonPane, rowIndex);
GridPane.setColumnIndex(feeRadioButtonPane, 1);
GridPane.setMargin(feeRadioButtonPane, new Insets(10, 0, 10, 0));
gridPane.getChildren().add(feeRadioButtonPane);
feeToggleGroup = new ToggleGroup();
loserPaysFeeRadioButton.setToggleGroup(feeToggleGroup);
splitFeeRadioButton.setToggleGroup(feeToggleGroup);
waiveFeeRadioButton.setToggleGroup(feeToggleGroup);
feeToggleSelectionListener = (observable, oldValue, newValue) -> {
if (newValue == loserPaysFeeRadioButton)
disputeResult.setDisputeFeePolicy(DisputeResult.DisputeFeePolicy.LOSER);
else if (newValue == splitFeeRadioButton)
disputeResult.setDisputeFeePolicy(DisputeResult.DisputeFeePolicy.SPLIT);
else if (newValue == waiveFeeRadioButton)
disputeResult.setDisputeFeePolicy(DisputeResult.DisputeFeePolicy.WAIVE);
};
feeToggleGroup.selectedToggleProperty().addListener(feeToggleSelectionListener);
if (dispute.isSupportTicket())
feeToggleGroup.selectToggle(waiveFeeRadioButton);
}
private void addPayoutAmountTextFields() {
buyerPayoutAmountInputTextField = addLabelInputTextField(gridPane, ++rowIndex, "Buyer's payout amount:").second;
buyerPayoutAmountInputTextField.setEditable(false);
sellerPayoutAmountInputTextField = addLabelInputTextField(gridPane, ++rowIndex, "Seller's payout amount:").second;
sellerPayoutAmountInputTextField.setEditable(false);
arbitratorPayoutAmountInputTextField = addLabelInputTextField(gridPane, ++rowIndex, "Arbitrator's payout amount:").second;
arbitratorPayoutAmountInputTextField.setEditable(false);
isLoserPublisherCheckBox = addLabelCheckBox(gridPane, ++rowIndex, "Use loser as publisher:").second;
}
private void setFeeRadioButtonState() {
switch (disputeResult.getDisputeFeePolicy()) {
case LOSER:
feeToggleGroup.selectToggle(loserPaysFeeRadioButton);
break;
case SPLIT:
feeToggleGroup.selectToggle(splitFeeRadioButton);
break;
case WAIVE:
feeToggleGroup.selectToggle(waiveFeeRadioButton);
break;
}
}
private void addReasonControls() {
Label label = addLabel(gridPane, ++rowIndex, "Reason of dispute:", 10);
GridPane.setValignment(label, VPos.TOP);
reasonWasBugRadioButton = new RadioButton("Bug");
reasonWasUsabilityIssueRadioButton = new RadioButton("Usability");
reasonProtocolViolationRadioButton = new RadioButton("Protocol violation");
reasonNoReplyRadioButton = new RadioButton("No reply");
reasonWasScamRadioButton = new RadioButton("Scam");
reasonWasOtherRadioButton = new RadioButton("Other");
HBox feeRadioButtonPane = new HBox();
feeRadioButtonPane.setSpacing(20);
feeRadioButtonPane.getChildren().addAll(reasonWasBugRadioButton, reasonWasUsabilityIssueRadioButton,
reasonProtocolViolationRadioButton, reasonNoReplyRadioButton,
reasonWasScamRadioButton, reasonWasOtherRadioButton);
GridPane.setRowIndex(feeRadioButtonPane, rowIndex);
GridPane.setColumnIndex(feeRadioButtonPane, 1);
GridPane.setMargin(feeRadioButtonPane, new Insets(10, 0, 10, 0));
gridPane.getChildren().add(feeRadioButtonPane);
reasonToggleGroup = new ToggleGroup();
reasonWasBugRadioButton.setToggleGroup(reasonToggleGroup);
reasonWasUsabilityIssueRadioButton.setToggleGroup(reasonToggleGroup);
reasonProtocolViolationRadioButton.setToggleGroup(reasonToggleGroup);
reasonNoReplyRadioButton.setToggleGroup(reasonToggleGroup);
reasonWasScamRadioButton.setToggleGroup(reasonToggleGroup);
reasonWasOtherRadioButton.setToggleGroup(reasonToggleGroup);
reasonToggleSelectionListener = (observable, oldValue, newValue) -> {
if (newValue == reasonWasBugRadioButton)
disputeResult.setReason(DisputeResult.Reason.BUG);
else if (newValue == reasonWasUsabilityIssueRadioButton)
disputeResult.setReason(DisputeResult.Reason.USABILITY);
else if (newValue == reasonProtocolViolationRadioButton)
disputeResult.setReason(DisputeResult.Reason.PROTOCOL_VIOLATION);
else if (newValue == reasonNoReplyRadioButton)
disputeResult.setReason(DisputeResult.Reason.NO_REPLY);
else if (newValue == reasonWasScamRadioButton)
disputeResult.setReason(DisputeResult.Reason.SCAM);
else if (newValue == reasonWasOtherRadioButton)
disputeResult.setReason(DisputeResult.Reason.OTHER);
};
reasonToggleGroup.selectedToggleProperty().addListener(reasonToggleSelectionListener);
}
private void setReasonRadioButtonState() {
if (disputeResult.getReason() != null) {
switch (disputeResult.getReason()) {
case BUG:
reasonToggleGroup.selectToggle(reasonWasBugRadioButton);
break;
case USABILITY:
reasonToggleGroup.selectToggle(reasonWasUsabilityIssueRadioButton);
break;
case PROTOCOL_VIOLATION:
reasonToggleGroup.selectToggle(reasonProtocolViolationRadioButton);
break;
case NO_REPLY:
reasonToggleGroup.selectToggle(reasonNoReplyRadioButton);
break;
case SCAM:
reasonToggleGroup.selectToggle(reasonWasScamRadioButton);
break;
case OTHER:
reasonToggleGroup.selectToggle(reasonWasOtherRadioButton);
break;
}
}
}
private void addSummaryNotes() {
Label label = addLabel(gridPane, ++rowIndex, "Summary notes:", 0);
GridPane.setValignment(label, VPos.TOP);
summaryNotesTextArea = new TextArea();
summaryNotesTextArea.setPromptText("Add summary notes");
summaryNotesTextArea.setWrapText(true);
summaryNotesTextArea.setPrefHeight(50);
summaryNotesTextArea.textProperty().bindBidirectional(disputeResult.summaryNotesProperty());
GridPane.setRowIndex(summaryNotesTextArea, rowIndex);
GridPane.setColumnIndex(summaryNotesTextArea, 1);
gridPane.getChildren().add(summaryNotesTextArea);
}
private void addButtons(Contract contract) {
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++rowIndex, "Close ticket", "Cancel");
Button closeTicketButton = tuple.first;
closeTicketButton.disableProperty().bind(Bindings.createBooleanBinding(
() -> tradeAmountToggleGroup.getSelectedToggle() == null
|| summaryNotesTextArea.getText() == null
|| summaryNotesTextArea.getText().length() == 0
|| !isPayoutAmountValid(),
tradeAmountToggleGroup.selectedToggleProperty(),
summaryNotesTextArea.textProperty(),
buyerPayoutAmountInputTextField.textProperty(),
sellerPayoutAmountInputTextField.textProperty(),
arbitratorPayoutAmountInputTextField.textProperty()));
Button cancelButton = tuple.second;
final Dispute finalPeersDispute = peersDisputeOptional.get();
closeTicketButton.setOnAction(e -> {
if (dispute.getDepositTxSerialized() != null) {
try {
AddressEntry arbitratorAddressEntry = walletService.getOrCreateAddressEntry(AddressEntry.Context.ARBITRATOR);
disputeResult.setArbitratorAddressAsString(arbitratorAddressEntry.getAddressString());
disputeResult.setArbitratorPubKey(arbitratorAddressEntry.getPubKey());
/* byte[] depositTxSerialized,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
Coin arbitratorPayoutAmount,
String buyerAddressString,
String sellerAddressString,
AddressEntry arbitratorAddressEntry,
byte[] buyerPubKey,
byte[] sellerPubKey,
byte[] arbitratorPubKey)
*/
byte[] arbitratorSignature = tradeWalletService.arbitratorSignsDisputedPayoutTx(
dispute.getDepositTxSerialized(),
disputeResult.getBuyerPayoutAmount(),
disputeResult.getSellerPayoutAmount(),
disputeResult.getArbitratorPayoutAmount(),
contract.getBuyerPayoutAddressString(),
contract.getSellerPayoutAddressString(),
arbitratorAddressEntry.getAddressString(),
arbitratorAddressEntry.getKeyPair(),
contract.getBuyerMultiSigPubKey(),
contract.getSellerMultiSigPubKey(),
arbitratorAddressEntry.getPubKey()
);
disputeResult.setArbitratorSignature(arbitratorSignature);
closeTicketButton.disableProperty().unbind();
dispute.setDisputeResult(disputeResult);
disputeResult.setLoserIsPublisher(isLoserPublisherCheckBox.isSelected());
disputeResult.setCloseDate(new Date());
String text = "Ticket closed on " + formatter.formatDateTime(disputeResult.getCloseDate()) +
"\n\nSummary:" +
"\n" + role + " delivered tamper proof evidence: " + formatter.booleanToYesNo(disputeResult.tamperProofEvidenceProperty().get()) +
"\n" + role + " did ID verification: " + formatter.booleanToYesNo(disputeResult.idVerificationProperty().get()) +
"\n" + role + " did screencast or video: " + formatter.booleanToYesNo(disputeResult.screenCastProperty().get()) +
"\nPayout amount for BTC buyer: " + formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()) +
"\nPayout amount for BTC seller: " + formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()) +
"\nArbitrator's dispute fee: " + formatter.formatCoinWithCode(disputeResult.getArbitratorPayoutAmount()) +
"\n\nSummary notes:\n" + disputeResult.summaryNotesProperty().get();
dispute.setIsClosed(true);
disputeManager.sendDisputeResultMessage(disputeResult, dispute, text);
if (!finalPeersDispute.isClosed())
UserThread.runAfter(() ->
new Popup().attention("You need to close also the trading peers ticket!").show(),
Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS);
hide();
finalizeDisputeHandlerOptional.ifPresent(finalizeDisputeHandler -> finalizeDisputeHandler.run());
} catch (AddressFormatException | TransactionVerificationException e2) {
e2.printStackTrace();
}
} else {
log.warn("dispute.getDepositTxSerialized is null");
}
});
cancelButton.setOnAction(e -> {
dispute.setDisputeResult(disputeResult);
hide();
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Controller
///////////////////////////////////////////////////////////////////////////////////////////
private void applyPayoutAmounts(DisputeResult.DisputeFeePolicy feePayment, Toggle selectedTradeAmountToggle) {
if (selectedTradeAmountToggle != customRadioButton) {
calculatePayoutAmounts(feePayment);
if (selectedTradeAmountToggle != null) {
applyPayoutAmountsToDisputeResult(selectedTradeAmountToggle);
applyTradeAmountRadioButtonStates();
}
}
}
private void calculatePayoutAmounts(DisputeResult.DisputeFeePolicy feePayment) {
Contract contract = dispute.getContract();
Coin refund = FeePolicy.getSecurityDeposit(dispute.getContract().offer);
Coin winnerRefund;
Coin loserRefund;
switch (feePayment) {
case SPLIT:
winnerRefund = refund.divide(2L);
loserRefund = winnerRefund;
arbitratorPayoutAmount = refund;
break;
case WAIVE:
winnerRefund = refund;
loserRefund = refund;
arbitratorPayoutAmount = Coin.ZERO;
break;
case LOSER:
default:
winnerRefund = refund;
loserRefund = Coin.ZERO;
arbitratorPayoutAmount = refund;
break;
}
winnerPayoutAmount = contract.getTradeAmount().add(winnerRefund);
loserPayoutAmount = loserRefund;
stalematePayoutAmount = contract.getTradeAmount().divide(2L).add(winnerRefund);
}
private void applyPayoutAmountsToDisputeResult(Toggle selectedTradeAmountToggle) {
if (selectedTradeAmountToggle == buyerIsWinnerRadioButton) {
disputeResult.setBuyerPayoutAmount(winnerPayoutAmount);
disputeResult.setSellerPayoutAmount(loserPayoutAmount);
disputeResult.setWinner(DisputeResult.Winner.BUYER);
buyerPayoutAmountInputTextField.setText(formatter.formatCoin(winnerPayoutAmount));
sellerPayoutAmountInputTextField.setText(formatter.formatCoin(loserPayoutAmount));
} else if (selectedTradeAmountToggle == sellerIsWinnerRadioButton) {
disputeResult.setBuyerPayoutAmount(loserPayoutAmount);
disputeResult.setSellerPayoutAmount(winnerPayoutAmount);
disputeResult.setWinner(DisputeResult.Winner.SELLER);
buyerPayoutAmountInputTextField.setText(formatter.formatCoin(loserPayoutAmount));
sellerPayoutAmountInputTextField.setText(formatter.formatCoin(winnerPayoutAmount));
} else if (selectedTradeAmountToggle == shareRadioButton) {
disputeResult.setBuyerPayoutAmount(stalematePayoutAmount);
disputeResult.setSellerPayoutAmount(stalematePayoutAmount);
disputeResult.setWinner(DisputeResult.Winner.STALE_MATE);
buyerPayoutAmountInputTextField.setText(formatter.formatCoin(stalematePayoutAmount));
sellerPayoutAmountInputTextField.setText(formatter.formatCoin(stalematePayoutAmount));
}
disputeResult.setArbitratorPayoutAmount(arbitratorPayoutAmount);
arbitratorPayoutAmountInputTextField.setText(formatter.formatCoin(arbitratorPayoutAmount));
}
private void applyTradeAmountRadioButtonStates() {
Coin buyerPayoutAmount = disputeResult.getBuyerPayoutAmount();
Coin sellerPayoutAmount = disputeResult.getSellerPayoutAmount();
buyerPayoutAmountInputTextField.setText(formatter.formatCoin(buyerPayoutAmount));
sellerPayoutAmountInputTextField.setText(formatter.formatCoin(sellerPayoutAmount));
arbitratorPayoutAmountInputTextField.setText(formatter.formatCoin(disputeResult.getArbitratorPayoutAmount()));
if (buyerPayoutAmount.equals(winnerPayoutAmount) && sellerPayoutAmount.equals(loserPayoutAmount)) {
buyerIsWinnerRadioButton.setSelected(true);
} else if (sellerPayoutAmount.equals(winnerPayoutAmount) && buyerPayoutAmount.equals(loserPayoutAmount)) {
sellerIsWinnerRadioButton.setSelected(true);
} else if (sellerPayoutAmount.equals(buyerPayoutAmount)) {
shareRadioButton.setSelected(true);
} else {
customRadioButton.setSelected(true);
}
}
}