Dispute subsystem enhancements.

Dispute result show payout suggestion and penalty.
Feature allowing trader chat upload to dispute agents.
This commit is contained in:
jmacxx 2022-03-03 11:06:51 -06:00
parent e7f1bf2d7d
commit 7e802e5e28
No known key found for this signature in database
GPG Key ID: 155297BABFE94A1B
5 changed files with 120 additions and 28 deletions

View File

@ -17,6 +17,7 @@
package bisq.core.support.dispute;
import bisq.core.locale.Res;
import bisq.core.support.messages.ChatMessage;
import bisq.common.proto.ProtoUtil;
@ -68,14 +69,30 @@ public final class DisputeResult implements NetworkPayload {
}
public enum PayoutSuggestion {
UNKNOWN,
BUYER_GETS_TRADE_AMOUNT,
BUYER_GETS_TRADE_AMOUNT_PLUS_COMPENSATION,
BUYER_GETS_TRADE_AMOUNT_MINUS_PENALTY,
SELLER_GETS_TRADE_AMOUNT,
SELLER_GETS_TRADE_AMOUNT_PLUS_COMPENSATION,
SELLER_GETS_TRADE_AMOUNT_MINUS_PENALTY,
CUSTOM_PAYOUT
UNKNOWN("shared.na", null),
BUYER_GETS_TRADE_AMOUNT("disputeSummaryWindow.payout.getsTradeAmount", "shared.buyer"),
BUYER_GETS_TRADE_AMOUNT_PLUS_COMPENSATION("disputeSummaryWindow.payout.getsCompensation", "shared.buyer"),
BUYER_GETS_TRADE_AMOUNT_MINUS_PENALTY("disputeSummaryWindow.payout.getsPenalty", "shared.buyer"),
SELLER_GETS_TRADE_AMOUNT("disputeSummaryWindow.payout.getsTradeAmount", "shared.seller"),
SELLER_GETS_TRADE_AMOUNT_PLUS_COMPENSATION("disputeSummaryWindow.payout.getsCompensation", "shared.seller"),
SELLER_GETS_TRADE_AMOUNT_MINUS_PENALTY("disputeSummaryWindow.payout.getsPenalty", "shared.seller"),
CUSTOM_PAYOUT("disputeSummaryWindow.payout.custom", null);
private String suggestionKey;
@Nullable private String buyerSellerKey;
PayoutSuggestion(String suggestionKey, @Nullable String buyerSellerKey) {
this.suggestionKey = suggestionKey;
this.buyerSellerKey = buyerSellerKey;
}
public String toString() {
if (buyerSellerKey == null) {
return Res.get(suggestionKey);
} else {
return Res.get(suggestionKey, Res.get(buyerSellerKey));
}
}
}
private final String tradeId;
@ -259,6 +276,17 @@ public final class DisputeResult implements NetworkPayload {
return new Date(closeDate);
}
public String getPayoutSuggestionText() {
if (payoutSuggestion.equals(PayoutSuggestion.BUYER_GETS_TRADE_AMOUNT_MINUS_PENALTY)
|| payoutSuggestion.equals(PayoutSuggestion.BUYER_GETS_TRADE_AMOUNT_PLUS_COMPENSATION)
|| payoutSuggestion.equals(PayoutSuggestion.SELLER_GETS_TRADE_AMOUNT_MINUS_PENALTY)
|| payoutSuggestion.equals(PayoutSuggestion.SELLER_GETS_TRADE_AMOUNT_PLUS_COMPENSATION)) {
return payoutSuggestion + " " + payoutAdjustmentPercent + "%";
}
return payoutSuggestion.toString();
}
@Override
public String toString() {
return "DisputeResult{" +

View File

@ -1223,14 +1223,16 @@ support.state=State
support.chat=Chat
support.closed=Closed
support.open=Open
support.moreButton=MORE...
support.sendLogFiles=Send Log Files
support.uploadTraderChat=Upload Trader Chat
support.process=Process
support.buyerOfferer=BTC buyer/Maker
support.sellerOfferer=BTC seller/Maker
support.buyerTaker=BTC buyer/Taker
support.sellerTaker=BTC seller/Taker
support.sendLogs.title=Send Log Files
support.sendLogs.backgroundInfo=When you experience a bug, mediators and support staff will often request copies of the your log files to diagnose the issue.\n\n\ \
support.sendLogs.backgroundInfo=When you experience a bug, mediators and support staff will often request copies of the your log files to diagnose the issue.\n\n\
Upon pressing 'Send', your log files will be compressed and transmitted directly to the mediator.
support.sendLogs.step1=Create Zip Archive of Log Files
support.sendLogs.step2=Connection Request to Mediator
@ -2726,11 +2728,12 @@ disputeSummaryWindow.close.msg=Ticket closed on {0}\n\
Summary:\n\
Trade ID: {3}\n\
Currency: {4}\n\
Trade amount: {5}\n\
Payout amount for BTC buyer: {6}\n\
Payout amount for BTC seller: {7}\n\n\
Reason for dispute: {8}\n\n\
Summary notes:\n{9}\n
Reason for dispute: {5}\n\
Payout suggestion: {6}\n\
Trade amount: {7}\n\
Payout amount for BTC buyer: {8}\n\
Payout amount for BTC seller: {9}\n\n\
Summary notes:\n{10}\n
# Do no change any line break or order of tokens as the structure is used for signature verification
disputeSummaryWindow.close.msgWithSig={0}{1}{2}{3}

View File

@ -334,13 +334,20 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
}
private void addTradeAmountPayoutControls() {
buyerGetsTradeAmountRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.payout.getsTradeAmount", Res.get("shared.buyer")));
buyerGetsCompensationRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.payout.getsCompensation", Res.get("shared.buyer")));
buyerGetsTradeAmountMinusPenaltyRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.payout.getsPenalty", Res.get("shared.buyer")));
sellerGetsTradeAmountRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.payout.getsTradeAmount", Res.get("shared.seller")));
sellerGetsCompensationRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.payout.getsCompensation", Res.get("shared.seller")));
sellerGetsTradeAmountMinusPenaltyRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.payout.getsPenalty", Res.get("shared.seller")));
customRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.payout.custom"));
buyerGetsTradeAmountRadioButton = new AutoTooltipRadioButton(
DisputeResult.PayoutSuggestion.BUYER_GETS_TRADE_AMOUNT.toString());
buyerGetsCompensationRadioButton = new AutoTooltipRadioButton(
DisputeResult.PayoutSuggestion.BUYER_GETS_TRADE_AMOUNT_PLUS_COMPENSATION.toString());
buyerGetsTradeAmountMinusPenaltyRadioButton = new AutoTooltipRadioButton(
DisputeResult.PayoutSuggestion.BUYER_GETS_TRADE_AMOUNT_MINUS_PENALTY.toString());
sellerGetsTradeAmountRadioButton = new AutoTooltipRadioButton(
DisputeResult.PayoutSuggestion.SELLER_GETS_TRADE_AMOUNT.toString());
sellerGetsCompensationRadioButton = new AutoTooltipRadioButton(
DisputeResult.PayoutSuggestion.SELLER_GETS_TRADE_AMOUNT_PLUS_COMPENSATION.toString());
sellerGetsTradeAmountMinusPenaltyRadioButton = new AutoTooltipRadioButton(
DisputeResult.PayoutSuggestion.SELLER_GETS_TRADE_AMOUNT_MINUS_PENALTY.toString());
customRadioButton = new AutoTooltipRadioButton(
DisputeResult.PayoutSuggestion.CUSTOM_PAYOUT.toString());
VBox radioButtonPane = new VBox();
radioButtonPane.setSpacing(10);
@ -877,10 +884,11 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
agentNodeAddress,
dispute.getShortTradeId(),
currencyCode,
Res.get("disputeSummaryWindow.reason." + reason.name()),
disputeResult.getPayoutSuggestionText(),
amount,
formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()),
formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()),
Res.get("disputeSummaryWindow.reason." + reason.name()),
disputeResult.summaryNotesProperty().get()
);

View File

@ -45,6 +45,7 @@ import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.stage.FileChooser;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
@ -112,7 +113,7 @@ public class ChatView extends AnchorPane {
// Options
@Getter
Button extraButton;
Node extraButton;
@Getter
private ReadOnlyDoubleProperty widthProperty;
@Setter
@ -169,7 +170,7 @@ public class ChatView extends AnchorPane {
}
public void display(SupportSession supportSession,
@Nullable Button extraButton,
@Nullable Node extraButton,
ReadOnlyDoubleProperty widthProperty) {
optionalSupportSession = Optional.of(supportSession);
removeListenersOnSessionChange();
@ -233,7 +234,6 @@ public class ChatView extends AnchorPane {
buttonBox.getChildren().addAll(sendButton, sendMsgBusyAnimation, sendMsgInfoLabel);
if (extraButton != null) {
extraButton.setDefaultButton(true);
Pane spacer = new Pane();
HBox.setHgrow(spacer, Priority.ALWAYS);
buttonBox.getChildren().addAll(spacer, extraButton);
@ -571,6 +571,26 @@ public class ChatView extends AnchorPane {
}
}
public void onAttachText(String textAttachment, String name) {
if (!allowAttachments)
return;
try {
byte[] filesAsBytes = textAttachment.getBytes("UTF8");
int size = filesAsBytes.length;
int maxMsgSize = Connection.getPermittedMessageSize();
int maxSizeInKB = maxMsgSize / 1024;
if (size > maxMsgSize) {
new Popup().warning(Res.get("support.attachmentTooLarge", (size / 1024), maxSizeInKB)).show();
} else {
tempAttachments.add(new Attachment(name, filesAsBytes));
inputTextArea.setText(inputTextArea.getText() + "\n[" + Res.get("support.attachment") + " " + name + "]");
}
} catch (Exception e) {
log.error(e.toString());
e.printStackTrace();
}
}
private void onOpenAttachment(Attachment attachment) {
if (!allowAttachments)
return;

View File

@ -21,12 +21,14 @@ import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.main.MainView;
import bisq.desktop.main.shared.ChatView;
import bisq.desktop.util.CssTheme;
import bisq.desktop.util.DisplayUtils;
import bisq.core.locale.Res;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeList;
import bisq.core.support.dispute.DisputeManager;
import bisq.core.support.dispute.DisputeSession;
import bisq.core.support.messages.ChatMessage;
import bisq.core.user.Preferences;
import bisq.core.util.coin.CoinFormatter;
@ -39,6 +41,8 @@ import javafx.stage.Window;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
@ -46,6 +50,9 @@ import javafx.scene.layout.StackPane;
import javafx.beans.value.ChangeListener;
import java.util.Date;
import java.util.List;
import lombok.Getter;
public class DisputeChatPopup {
@ -106,12 +113,19 @@ public class DisputeChatPopup {
} else {
if (disputeManager.isAgent(selectedDispute)) {
Button closeDisputeButton = new AutoTooltipButton(Res.get("support.closeTicket"));
closeDisputeButton.setDefaultButton(true);
closeDisputeButton.setOnAction(e -> chatCallback.onCloseDisputeFromChatWindow(selectedDispute));
chatView.display(concreteDisputeSession, closeDisputeButton, pane.widthProperty());
} else {
Button sendLogsButton = new AutoTooltipButton(Res.get("support.sendLogFiles"));
sendLogsButton.setOnAction(e -> chatCallback.onSendLogsFromChatWindow(selectedDispute));
chatView.display(concreteDisputeSession, sendLogsButton, pane.widthProperty());
MenuButton menuButton = new MenuButton(Res.get("support.moreButton"));
MenuItem menuItem1 = new MenuItem(Res.get("support.uploadTraderChat"));
MenuItem menuItem2 = new MenuItem(Res.get("support.sendLogFiles"));
menuItem1.setOnAction(e -> doTextAttachment(chatView));
menuItem2.setOnAction(e -> chatCallback.onSendLogsFromChatWindow(selectedDispute));
menuButton.getItems().addAll(menuItem1, menuItem2);
menuButton.getStyleClass().add("jfx-button");
menuButton.setStyle("-fx-padding: 0 10 0 10;");
chatView.display(concreteDisputeSession, menuButton, pane.widthProperty());
}
}
chatView.activate();
@ -163,4 +177,23 @@ public class DisputeChatPopup {
// and after a short moment in the correct position
UserThread.execute(() -> chatPopupStage.setOpacity(1));
}
private void doTextAttachment(ChatView chatView) {
disputeManager.findTrade(selectedDispute).ifPresent(t -> {
List<ChatMessage> chatMessages = t.getChatMessages();
if (chatMessages.size() > 0) {
StringBuilder stringBuilder = new StringBuilder();
chatMessages.forEach(i -> {
boolean isMyMsg = i.isSenderIsTrader();
String metaData = DisplayUtils.formatDateTime(new Date(i.getDate()));
if (!i.isSystemMessage())
metaData = (isMyMsg ? "Sent " : "Received ") + metaData
+ (isMyMsg ? "" : " from Trader");
stringBuilder.append(metaData).append("\n").append(i.getMessage()).append("\n\n");
});
String fileName = selectedDispute.getShortTradeId() + "_" + selectedDispute.getRoleStringForLogFile() + "_TraderChat.txt";
chatView.onAttachText(stringBuilder.toString(), fileName);
}
});
}
}