mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-09-19 04:24:57 -04:00
update chat views from upstream, support sending logs
Co-authored-by: jmacxx <47253594+jmacxx@users.noreply.github.com>
This commit is contained in:
parent
833cdb3b84
commit
1647a582f5
23 changed files with 1691 additions and 206 deletions
|
@ -17,40 +17,46 @@
|
|||
|
||||
package haveno.desktop.components;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import haveno.desktop.main.overlays.editor.PeerInfoWithTagEditor;
|
||||
import haveno.desktop.util.DisplayUtils;
|
||||
|
||||
import haveno.core.alert.PrivateNotificationManager;
|
||||
import haveno.core.locale.Res;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.user.Preferences;
|
||||
import haveno.desktop.main.overlays.editor.PeerInfoWithTagEditor;
|
||||
import haveno.desktop.util.DisplayUtils;
|
||||
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import javafx.geometry.Point2D;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import lombok.Setter;
|
||||
|
||||
import javafx.geometry.Point2D;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class PeerInfoIcon extends Group {
|
||||
public interface notify {
|
||||
void avatarTagUpdated();
|
||||
}
|
||||
|
||||
@Setter
|
||||
private notify callback;
|
||||
protected Preferences preferences;
|
||||
protected final String fullAddress;
|
||||
protected String tooltipText;
|
||||
|
@ -59,10 +65,12 @@ public class PeerInfoIcon extends Group {
|
|||
protected Pane tagPane;
|
||||
protected Pane numTradesPane;
|
||||
protected int numTrades = 0;
|
||||
private final StringProperty tag;
|
||||
|
||||
public PeerInfoIcon(NodeAddress nodeAddress, Preferences preferences) {
|
||||
this.preferences = preferences;
|
||||
this.fullAddress = nodeAddress != null ? nodeAddress.getFullAddress() : "";
|
||||
this.tag = new SimpleStringProperty("");
|
||||
}
|
||||
|
||||
protected void createAvatar(Color ringColor) {
|
||||
|
@ -162,23 +170,24 @@ public class PeerInfoIcon extends Group {
|
|||
Res.get("peerInfo.unknownAge") :
|
||||
null;
|
||||
|
||||
setOnMouseClicked(e -> new PeerInfoWithTagEditor(privateNotificationManager, trade, offer, preferences, useDevPrivilegeKeys)
|
||||
.fullAddress(fullAddress)
|
||||
.numTrades(numTrades)
|
||||
.accountAge(accountAgeFormatted)
|
||||
.signAge(signAgeFormatted)
|
||||
.accountAgeInfo(peersAccountAgeInfo)
|
||||
.signAgeInfo(peersSignAgeInfo)
|
||||
.accountSigningState(accountSigningState)
|
||||
.position(localToScene(new Point2D(0, 0)))
|
||||
.onSave(newTag -> {
|
||||
preferences.setTagForPeer(fullAddress, newTag);
|
||||
updatePeerInfoIcon();
|
||||
if (callback != null) {
|
||||
callback.avatarTagUpdated();
|
||||
}
|
||||
})
|
||||
.show());
|
||||
setOnMouseClicked(e -> {
|
||||
if (e.getButton().equals(MouseButton.PRIMARY)) {
|
||||
new PeerInfoWithTagEditor(privateNotificationManager, trade, offer, preferences, useDevPrivilegeKeys)
|
||||
.fullAddress(fullAddress)
|
||||
.numTrades(numTrades)
|
||||
.accountAge(accountAgeFormatted)
|
||||
.signAge(signAgeFormatted)
|
||||
.accountAgeInfo(peersAccountAgeInfo)
|
||||
.signAgeInfo(peersSignAgeInfo)
|
||||
.accountSigningState(accountSigningState)
|
||||
.position(localToScene(new Point2D(0, 0)))
|
||||
.onSave(newTag -> {
|
||||
preferences.setTagForPeer(fullAddress, newTag);
|
||||
tag.set(newTag);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected double getScaleFactor() {
|
||||
|
@ -192,20 +201,6 @@ public class PeerInfoIcon extends Group {
|
|||
}
|
||||
|
||||
protected void updatePeerInfoIcon() {
|
||||
String tag;
|
||||
Map<String, String> peerTagMap = preferences.getPeerTagMap();
|
||||
if (peerTagMap.containsKey(fullAddress)) {
|
||||
tag = peerTagMap.get(fullAddress);
|
||||
final String text = !tag.isEmpty() ? Res.get("peerInfoIcon.tooltip", tooltipText, tag) : tooltipText;
|
||||
Tooltip.install(this, new Tooltip(text));
|
||||
} else {
|
||||
tag = "";
|
||||
Tooltip.install(this, new Tooltip(tooltipText));
|
||||
}
|
||||
|
||||
if (!tag.isEmpty())
|
||||
tagLabel.setText(tag.substring(0, 1));
|
||||
|
||||
if (numTrades > 0) {
|
||||
numTradesLabel.setText(numTrades > 99 ? "*" : String.valueOf(numTrades));
|
||||
|
||||
|
@ -216,9 +211,27 @@ public class PeerInfoIcon extends Group {
|
|||
numTradesLabel.relocate(scaleFactor * 5, scaleFactor * 1);
|
||||
}
|
||||
}
|
||||
|
||||
numTradesPane.setVisible(numTrades > 0);
|
||||
|
||||
tagPane.setVisible(!tag.isEmpty());
|
||||
refreshTag();
|
||||
}
|
||||
|
||||
protected void refreshTag() {
|
||||
Map<String, String> peerTagMap = preferences.getPeerTagMap();
|
||||
if (peerTagMap.containsKey(fullAddress)) {
|
||||
tag.set(peerTagMap.get(fullAddress));
|
||||
}
|
||||
|
||||
Tooltip.install(this, new Tooltip(!tag.get().isEmpty() ?
|
||||
Res.get("peerInfoIcon.tooltip", tooltipText, tag.get()) : tooltipText));
|
||||
|
||||
if (!tag.get().isEmpty()) {
|
||||
tagLabel.setText(tag.get().substring(0, 1));
|
||||
}
|
||||
tagPane.setVisible(!tag.get().isEmpty());
|
||||
}
|
||||
|
||||
protected StringProperty tagProperty() {
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,8 +40,4 @@ public class PeerInfoIconDispute extends PeerInfoIcon {
|
|||
addMouseListener(numTrades, null, null, null, preferences, false,
|
||||
false, accountAge, 0L, null, null, null);
|
||||
}
|
||||
|
||||
public void refreshTag() {
|
||||
updatePeerInfoIcon();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 haveno.desktop.components;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class PeerInfoIconMap extends HashMap<String, PeerInfoIcon> implements ChangeListener<String> {
|
||||
|
||||
@Override
|
||||
public PeerInfoIcon put(String key, PeerInfoIcon icon) {
|
||||
icon.tagProperty().addListener(this);
|
||||
return super.put(key, icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends String> o, String oldVal, String newVal) {
|
||||
log.info("Updating avatar tags, the avatar map size is {}", size());
|
||||
forEach((key, icon) -> {
|
||||
// We update all avatars, as some could be sharing the same tag.
|
||||
// We also temporarily remove listeners to prevent firing of
|
||||
// events while each icon's tagProperty is being reset.
|
||||
icon.tagProperty().removeListener(this);
|
||||
icon.refreshTag();
|
||||
icon.tagProperty().addListener(this);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* 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 haveno.desktop.main.overlays.windows;
|
||||
|
||||
import haveno.desktop.components.AutoTooltipButton;
|
||||
import haveno.desktop.main.overlays.Overlay;
|
||||
import haveno.desktop.main.portfolio.pendingtrades.steps.TradeWizardItem;
|
||||
import haveno.desktop.main.portfolio.pendingtrades.steps.buyer.BuyerStep1View;
|
||||
import haveno.desktop.main.portfolio.pendingtrades.steps.buyer.BuyerStep2View;
|
||||
import haveno.desktop.main.portfolio.pendingtrades.steps.buyer.BuyerStep3View;
|
||||
import haveno.desktop.util.Layout;
|
||||
|
||||
import haveno.core.locale.Res;
|
||||
import haveno.core.support.dispute.arbitration.ArbitrationManager;
|
||||
import haveno.core.support.dispute.mediation.FileTransferSender;
|
||||
import haveno.core.support.dispute.mediation.FileTransferSession;
|
||||
|
||||
import haveno.common.UserThread;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static haveno.desktop.util.FormBuilder.addMultilineLabel;
|
||||
|
||||
@Slf4j
|
||||
public class SendLogFilesWindow extends Overlay<SendLogFilesWindow> implements FileTransferSession.FtpCallback {
|
||||
|
||||
private final String tradeId;
|
||||
private final int traderId;
|
||||
private final ArbitrationManager arbitrationManager;
|
||||
private Label statusLabel;
|
||||
private Button sendButton, stopButton;
|
||||
private final DoubleProperty ftpProgress = new SimpleDoubleProperty(-1);
|
||||
TradeWizardItem step1, step2, step3;
|
||||
private FileTransferSender fileTransferSender;
|
||||
|
||||
public SendLogFilesWindow(String tradeId, int traderId,
|
||||
ArbitrationManager arbitrationManager) {
|
||||
this.tradeId = tradeId;
|
||||
this.traderId = traderId;
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
type = Type.Attention;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
headLine = Res.get("support.sendLogs.title");
|
||||
width = 668;
|
||||
createGridPane();
|
||||
addHeadLine();
|
||||
addContent();
|
||||
addButtons();
|
||||
applyStyles();
|
||||
display();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createGridPane() {
|
||||
gridPane = new GridPane();
|
||||
gridPane.setHgap(5);
|
||||
gridPane.setVgap(5);
|
||||
gridPane.setPadding(new Insets(64, 64, 64, 64));
|
||||
gridPane.setPrefWidth(width);
|
||||
}
|
||||
|
||||
void addWizardsToGridPane(TradeWizardItem tradeWizardItem) {
|
||||
GridPane.setRowIndex(tradeWizardItem, rowIndex++);
|
||||
GridPane.setColumnIndex(tradeWizardItem, 0);
|
||||
GridPane.setHalignment(tradeWizardItem, HPos.LEFT);
|
||||
gridPane.getChildren().add(tradeWizardItem);
|
||||
}
|
||||
|
||||
void addLineSeparatorToGridPane() {
|
||||
final Separator separator = new Separator(Orientation.VERTICAL);
|
||||
separator.setMinHeight(22);
|
||||
GridPane.setMargin(separator, new Insets(0, 0, 0, 13));
|
||||
GridPane.setHalignment(separator, HPos.LEFT);
|
||||
GridPane.setRowIndex(separator, rowIndex++);
|
||||
gridPane.getChildren().add(separator);
|
||||
}
|
||||
|
||||
void addRegionToGridPane() {
|
||||
final Region region = new Region();
|
||||
region.setMinHeight(22);
|
||||
GridPane.setMargin(region, new Insets(0, 0, 0, 13));
|
||||
GridPane.setRowIndex(region, rowIndex++);
|
||||
gridPane.getChildren().add(region);
|
||||
}
|
||||
|
||||
private void addContent() {
|
||||
this.hideCloseButton = true;
|
||||
|
||||
addMultilineLabel(gridPane, ++rowIndex, Res.get("support.sendLogs.backgroundInfo"), 0);
|
||||
addRegionToGridPane();
|
||||
|
||||
step1 = new TradeWizardItem(BuyerStep1View.class, Res.get("support.sendLogs.step1"), "1");
|
||||
step2 = new TradeWizardItem(BuyerStep2View.class, Res.get("support.sendLogs.step2"), "2");
|
||||
step3 = new TradeWizardItem(BuyerStep3View.class, Res.get("support.sendLogs.step3"), "3");
|
||||
|
||||
addRegionToGridPane();
|
||||
addRegionToGridPane();
|
||||
addWizardsToGridPane(step1);
|
||||
addLineSeparatorToGridPane();
|
||||
addWizardsToGridPane(step2);
|
||||
addLineSeparatorToGridPane();
|
||||
addWizardsToGridPane(step3);
|
||||
addRegionToGridPane();
|
||||
|
||||
ProgressBar progressBar = new ProgressBar();
|
||||
progressBar.setMinHeight(19);
|
||||
progressBar.setMaxHeight(19);
|
||||
progressBar.setPrefWidth(9305);
|
||||
progressBar.setVisible(false);
|
||||
progressBar.progressProperty().bind(ftpProgress);
|
||||
gridPane.add(progressBar, 0, ++rowIndex);
|
||||
|
||||
statusLabel = addMultilineLabel(gridPane, ++rowIndex, "", -Layout.FLOATING_LABEL_DISTANCE);
|
||||
statusLabel.getStyleClass().add("sub-info");
|
||||
addRegionToGridPane();
|
||||
|
||||
sendButton = new AutoTooltipButton(Res.get("support.sendLogs.send"));
|
||||
stopButton = new AutoTooltipButton(Res.get("support.sendLogs.cancel"));
|
||||
stopButton.setDisable(true);
|
||||
closeButton = new AutoTooltipButton(Res.get("shared.close"));
|
||||
sendButton.setOnAction(e -> {
|
||||
try {
|
||||
progressBar.setVisible(true);
|
||||
if (fileTransferSender == null) {
|
||||
setActiveStep(1);
|
||||
statusLabel.setText(Res.get("support.sendLogs.init"));
|
||||
fileTransferSender = arbitrationManager.initLogUpload(this, tradeId, traderId);
|
||||
UserThread.runAfter(() -> {
|
||||
fileTransferSender.createZipFileToSend();
|
||||
setActiveStep(2);
|
||||
UserThread.runAfter(() -> {
|
||||
setActiveStep(3);
|
||||
try {
|
||||
fileTransferSender.initSend();
|
||||
} catch (IOException ioe) {
|
||||
log.error(ioe.toString());
|
||||
statusLabel.setText(ioe.toString());
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}, 1);
|
||||
}, 1);
|
||||
sendButton.setDisable(true);
|
||||
stopButton.setDisable(false);
|
||||
} else {
|
||||
// resend the latest block in the event of a timeout
|
||||
statusLabel.setText(Res.get("support.sendLogs.retry"));
|
||||
fileTransferSender.retrySend();
|
||||
sendButton.setDisable(true);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
log.error(ex.toString());
|
||||
statusLabel.setText(ex.toString());
|
||||
ex.printStackTrace();
|
||||
}
|
||||
});
|
||||
stopButton.setOnAction(e -> {
|
||||
if (fileTransferSender != null) {
|
||||
fileTransferSender.resetSession();
|
||||
statusLabel.setText(Res.get("support.sendLogs.stopped"));
|
||||
stopButton.setDisable(true);
|
||||
}
|
||||
});
|
||||
closeButton.setOnAction(e -> {
|
||||
hide();
|
||||
closeHandlerOptional.ifPresent(Runnable::run);
|
||||
});
|
||||
HBox hBox = new HBox();
|
||||
hBox.setSpacing(10);
|
||||
hBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
GridPane.setRowIndex(hBox, ++rowIndex);
|
||||
GridPane.setColumnSpan(hBox, 2);
|
||||
GridPane.setColumnIndex(hBox, 0);
|
||||
hBox.getChildren().addAll(sendButton, stopButton, closeButton);
|
||||
gridPane.getChildren().add(hBox);
|
||||
GridPane.setMargin(hBox, new Insets(10, 0, 0, 0));
|
||||
}
|
||||
|
||||
void setActiveStep(int step) {
|
||||
if (step < 1) {
|
||||
step1.setDisabled();
|
||||
step2.setDisabled();
|
||||
step3.setDisabled();
|
||||
} else if (step == 1) {
|
||||
step1.setActive();
|
||||
} else if (step == 2) {
|
||||
step1.setCompleted();
|
||||
step2.setActive();
|
||||
} else if (step == 3) {
|
||||
step2.setCompleted();
|
||||
step3.setActive();
|
||||
} else {
|
||||
step3.setCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFtpProgress(double progressPct) {
|
||||
UserThread.execute(() -> {
|
||||
if (progressPct > 0.0) {
|
||||
statusLabel.setText(String.format(Res.get("support.sendLogs.progress"), progressPct * 100));
|
||||
sendButton.setDisable(true);
|
||||
}
|
||||
ftpProgress.set(progressPct);
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onFtpComplete(FileTransferSession session) {
|
||||
UserThread.execute(() -> {
|
||||
setActiveStep(4); // all finished
|
||||
statusLabel.setText(Res.get("support.sendLogs.finished"));
|
||||
stopButton.setDisable(true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFtpTimeout(String statusMsg, FileTransferSession session) {
|
||||
UserThread.execute(() -> {
|
||||
statusLabel.setText(statusMsg + "\r\n" + Res.get("support.sendLogs.command"));
|
||||
sendButton.setDisable(false);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -443,7 +443,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
|
|||
model.dataModel.getTradeManager().requestPersistence();
|
||||
tradeIdOfOpenChat = trade.getId();
|
||||
|
||||
ChatView chatView = new ChatView(traderChatManager, formatter, Res.get("offerbook.trader"));
|
||||
ChatView chatView = new ChatView(traderChatManager, Res.get("offerbook.trader"));
|
||||
chatView.setAllowAttachments(false);
|
||||
chatView.setDisplayHeader(false);
|
||||
chatView.initialize();
|
||||
|
|
|
@ -17,35 +17,36 @@
|
|||
|
||||
package haveno.desktop.main.shared;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import haveno.common.Timer;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.util.Utilities;
|
||||
import haveno.desktop.components.AutoTooltipButton;
|
||||
import haveno.desktop.components.AutoTooltipLabel;
|
||||
import haveno.desktop.components.HavenoTextArea;
|
||||
import haveno.desktop.components.BusyAnimation;
|
||||
import haveno.desktop.components.TableGroupHeadline;
|
||||
import haveno.desktop.main.overlays.notifications.Notification;
|
||||
import haveno.desktop.main.overlays.popups.Popup;
|
||||
import haveno.desktop.util.DisplayUtils;
|
||||
import haveno.desktop.util.GUIUtil;
|
||||
|
||||
import haveno.core.locale.Res;
|
||||
import haveno.core.support.SupportManager;
|
||||
import haveno.core.support.SupportSession;
|
||||
import haveno.core.support.dispute.Attachment;
|
||||
import haveno.core.support.messages.ChatMessage;
|
||||
import haveno.core.util.coin.CoinFormatter;
|
||||
import haveno.desktop.components.AutoTooltipButton;
|
||||
import haveno.desktop.components.AutoTooltipLabel;
|
||||
import haveno.desktop.components.BusyAnimation;
|
||||
import haveno.desktop.components.HavenoTextArea;
|
||||
import haveno.desktop.components.TableGroupHeadline;
|
||||
import haveno.desktop.components.TextFieldWithIcon;
|
||||
import haveno.desktop.main.overlays.popups.Popup;
|
||||
import haveno.desktop.util.DisplayUtils;
|
||||
import haveno.desktop.util.GUIUtil;
|
||||
|
||||
import haveno.network.p2p.network.Connection;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
|
||||
import haveno.common.Timer;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.util.Utilities;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||
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;
|
||||
|
@ -61,22 +62,31 @@ import javafx.scene.layout.Pane;
|
|||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.util.Callback;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
||||
import javafx.event.EventHandler;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
|
@ -84,8 +94,14 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Slf4j
|
||||
public class ChatView extends AnchorPane {
|
||||
public static final Logger log = LoggerFactory.getLogger(TextFieldWithIcon.class);
|
||||
|
||||
// UI
|
||||
private TextArea inputTextArea;
|
||||
|
@ -98,7 +114,7 @@ public class ChatView extends AnchorPane {
|
|||
|
||||
// Options
|
||||
@Getter
|
||||
Button extraButton;
|
||||
Node extraButton;
|
||||
@Getter
|
||||
private ReadOnlyDoubleProperty widthProperty;
|
||||
@Setter
|
||||
|
@ -112,18 +128,16 @@ public class ChatView extends AnchorPane {
|
|||
private ListChangeListener<ChatMessage> disputeDirectMessageListListener;
|
||||
private Subscription inputTextAreaTextSubscription;
|
||||
private final List<Attachment> tempAttachments = new ArrayList<>();
|
||||
private ChangeListener<Boolean> storedInMailboxPropertyListener, arrivedPropertyListener;
|
||||
private ChangeListener<Boolean> storedInMailboxPropertyListener, acknowledgedPropertyListener;
|
||||
private ChangeListener<String> sendMessageErrorPropertyListener;
|
||||
|
||||
protected final CoinFormatter formatter;
|
||||
private EventHandler<KeyEvent> keyEventEventHandler;
|
||||
private SupportManager supportManager;
|
||||
private Optional<SupportSession> optionalSupportSession = Optional.empty();
|
||||
private String counterpartyName;
|
||||
|
||||
public ChatView(SupportManager supportManager, CoinFormatter formatter, String counterpartyName) {
|
||||
public ChatView(SupportManager supportManager, String counterpartyName) {
|
||||
this.supportManager = supportManager;
|
||||
this.formatter = formatter;
|
||||
this.counterpartyName = counterpartyName;
|
||||
allowAttachments = true;
|
||||
displayHeader = true;
|
||||
|
@ -157,7 +171,7 @@ public class ChatView extends AnchorPane {
|
|||
}
|
||||
|
||||
public void display(SupportSession supportSession,
|
||||
@Nullable Button extraButton,
|
||||
@Nullable Node extraButton,
|
||||
ReadOnlyDoubleProperty widthProperty) {
|
||||
optionalSupportSession = Optional.of(supportSession);
|
||||
removeListenersOnSessionChange();
|
||||
|
@ -201,6 +215,10 @@ public class ChatView extends AnchorPane {
|
|||
|
||||
Button uploadButton = new AutoTooltipButton(Res.get("support.addAttachments"));
|
||||
uploadButton.setOnAction(e -> onRequestUpload());
|
||||
Button clipboardButton = new AutoTooltipButton(Res.get("shared.copyToClipboard"));
|
||||
clipboardButton.setOnAction(e -> copyChatMessagesToClipboard(clipboardButton));
|
||||
uploadButton.setStyle("-fx-pref-width: 125; -fx-padding: 3 3 3 3;");
|
||||
clipboardButton.setStyle("-fx-pref-width: 125; -fx-padding: 3 3 3 3;");
|
||||
|
||||
sendMsgInfoLabel = new AutoTooltipLabel();
|
||||
sendMsgInfoLabel.setVisible(false);
|
||||
|
@ -216,12 +234,11 @@ public class ChatView extends AnchorPane {
|
|||
HBox buttonBox = new HBox();
|
||||
buttonBox.setSpacing(10);
|
||||
if (allowAttachments)
|
||||
buttonBox.getChildren().addAll(sendButton, uploadButton, sendMsgBusyAnimation, sendMsgInfoLabel);
|
||||
buttonBox.getChildren().addAll(sendButton, uploadButton, clipboardButton, sendMsgBusyAnimation, sendMsgInfoLabel);
|
||||
else
|
||||
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);
|
||||
|
@ -329,7 +346,7 @@ public class ChatView extends AnchorPane {
|
|||
bg.setId("message-bubble-green");
|
||||
messageLabel.getStyleClass().add("my-message");
|
||||
copyIcon.getStyleClass().add("my-message");
|
||||
message.addChangeListener(() -> updateMsgState(message));
|
||||
message.addWeakMessageStateListener(() -> updateMsgState(message));
|
||||
updateMsgState(message);
|
||||
} else if (isMyMsg) {
|
||||
headerLabel.getStyleClass().add("my-message-header");
|
||||
|
@ -350,7 +367,7 @@ public class ChatView extends AnchorPane {
|
|||
};
|
||||
|
||||
sendMsgBusyAnimation.isRunningProperty().addListener(sendMsgBusyAnimationListener);
|
||||
message.addChangeListener(() -> updateMsgState(message));
|
||||
message.addWeakMessageStateListener(() -> updateMsgState(message));
|
||||
updateMsgState(message);
|
||||
} else {
|
||||
headerLabel.getStyleClass().add("message-header");
|
||||
|
@ -401,13 +418,13 @@ public class ChatView extends AnchorPane {
|
|||
String metaData = DisplayUtils.formatDateTime(new Date(message.getDate()));
|
||||
if (!message.isSystemMessage())
|
||||
metaData = (isMyMsg ? "Sent " : "Received ") + metaData
|
||||
+ (isMyMsg ? "" : " from " + counterpartyName);
|
||||
+ (isMyMsg ? "" : " from " + counterpartyName);
|
||||
headerLabel.setText(metaData);
|
||||
messageLabel.setText(message.getMessage());
|
||||
attachmentsBox.getChildren().clear();
|
||||
if (allowAttachments &&
|
||||
message.getAttachments() != null &&
|
||||
!message.getAttachments().isEmpty()) {
|
||||
message.getAttachments().size() > 0) {
|
||||
AnchorPane.setBottomAnchor(messageLabel, bottomBorder + attachmentsBoxHeight + 10);
|
||||
attachmentsBox.getChildren().add(new AutoTooltipLabel(Res.get("support.attachments") + " ") {{
|
||||
setPadding(new Insets(0, 0, 3, 0));
|
||||
|
@ -466,6 +483,10 @@ public class ChatView extends AnchorPane {
|
|||
visible = true;
|
||||
icon = AwesomeIcon.OK_SIGN;
|
||||
text = Res.get("support.acknowledged");
|
||||
} else if (message.storedInMailboxProperty().get()) {
|
||||
visible = true;
|
||||
icon = AwesomeIcon.ENVELOPE;
|
||||
text = Res.get("support.savedInMailbox");
|
||||
} else if (message.ackErrorProperty().get() != null) {
|
||||
visible = true;
|
||||
icon = AwesomeIcon.EXCLAMATION_SIGN;
|
||||
|
@ -474,17 +495,13 @@ public class ChatView extends AnchorPane {
|
|||
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");
|
||||
icon = AwesomeIcon.MAIL_REPLY;
|
||||
text = Res.get("support.transient");
|
||||
} else {
|
||||
visible = false;
|
||||
log.debug("updateMsgState called but no msg state available. message={}", message);
|
||||
}
|
||||
|
||||
|
||||
statusHBox.setVisible(visible);
|
||||
if (visible) {
|
||||
AwesomeDude.setIcon(statusIcon, icon, "14");
|
||||
|
@ -529,7 +546,7 @@ public class ChatView extends AnchorPane {
|
|||
int maxMsgSize = Connection.getPermittedMessageSize();
|
||||
int maxSizeInKB = maxMsgSize / 1024;
|
||||
fileChooser.setTitle(Res.get("support.openFile", maxSizeInKB));
|
||||
/* if (Utilities.isUnix())
|
||||
/* if (Utilities.isUnix())
|
||||
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));*/
|
||||
File result = fileChooser.showOpenDialog(getScene().getWindow());
|
||||
if (result != null) {
|
||||
|
@ -561,13 +578,51 @@ 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 copyChatMessagesToClipboard(Button sourceBtn) {
|
||||
optionalSupportSession.ifPresent(session -> {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
chatMessages.forEach(i -> {
|
||||
String metaData = DisplayUtils.formatDateTime(new Date(i.getDate()));
|
||||
metaData = metaData + (i.isSystemMessage() ? " (System message)" :
|
||||
(i.isSenderIsTrader() ? " (from Trader)" : " (from Agent)"));
|
||||
stringBuilder.append(metaData).append("\n").append(i.getMessage()).append("\n\n");
|
||||
});
|
||||
Utilities.copyToClipboard(stringBuilder.toString());
|
||||
new Notification()
|
||||
.notification(Res.get("shared.copiedToClipboard"))
|
||||
.hideCloseButton()
|
||||
.autoClose()
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
private void onOpenAttachment(Attachment attachment) {
|
||||
if (!allowAttachments)
|
||||
return;
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(Res.get("support.save"));
|
||||
fileChooser.setInitialFileName(attachment.getFileName());
|
||||
/* if (Utilities.isUnix())
|
||||
/* if (Utilities.isUnix())
|
||||
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));*/
|
||||
File file = fileChooser.showSaveDialog(getScene().getWindow());
|
||||
if (file != null) {
|
||||
|
@ -582,7 +637,7 @@ public class ChatView extends AnchorPane {
|
|||
|
||||
private void onSendMessage(String inputText) {
|
||||
if (chatMessage != null) {
|
||||
chatMessage.arrivedProperty().removeListener(arrivedPropertyListener);
|
||||
chatMessage.acknowledgedProperty().removeListener(acknowledgedPropertyListener);
|
||||
chatMessage.storedInMailboxProperty().removeListener(storedInMailboxPropertyListener);
|
||||
chatMessage.sendMessageErrorProperty().removeListener(sendMessageErrorPropertyListener);
|
||||
}
|
||||
|
@ -594,6 +649,8 @@ public class ChatView extends AnchorPane {
|
|||
inputTextArea.setDisable(true);
|
||||
inputTextArea.clear();
|
||||
|
||||
chatMessage.startAckTimer();
|
||||
|
||||
Timer timer = UserThread.runAfter(() -> {
|
||||
sendMsgInfoLabel.setVisible(true);
|
||||
sendMsgInfoLabel.setManaged(true);
|
||||
|
@ -602,8 +659,9 @@ public class ChatView extends AnchorPane {
|
|||
sendMsgBusyAnimation.play();
|
||||
}, 500, TimeUnit.MILLISECONDS);
|
||||
|
||||
arrivedPropertyListener = (observable, oldValue, newValue) -> {
|
||||
acknowledgedPropertyListener = (observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
sendMsgInfoLabel.setVisible(false);
|
||||
hideSendMsgInfo(timer);
|
||||
}
|
||||
};
|
||||
|
@ -624,7 +682,7 @@ public class ChatView extends AnchorPane {
|
|||
}
|
||||
};
|
||||
if (chatMessage != null) {
|
||||
chatMessage.arrivedProperty().addListener(arrivedPropertyListener);
|
||||
chatMessage.acknowledgedProperty().addListener(acknowledgedPropertyListener);
|
||||
chatMessage.storedInMailboxProperty().addListener(storedInMailboxPropertyListener);
|
||||
chatMessage.sendMessageErrorProperty().addListener(sendMessageErrorPropertyListener);
|
||||
}
|
||||
|
@ -697,15 +755,12 @@ public class ChatView extends AnchorPane {
|
|||
}
|
||||
|
||||
private void removeListenersOnSessionChange() {
|
||||
if (chatMessages != null) {
|
||||
if (disputeDirectMessageListListener != null) chatMessages.removeListener(disputeDirectMessageListListener);
|
||||
chatMessages.forEach(ChatMessage::removeChangeListener);
|
||||
}
|
||||
if (chatMessages != null && disputeDirectMessageListListener != null)
|
||||
chatMessages.removeListener(disputeDirectMessageListListener);
|
||||
|
||||
if (chatMessage != null) {
|
||||
chatMessage.removeChangeListener();
|
||||
if (arrivedPropertyListener != null)
|
||||
chatMessage.arrivedProperty().removeListener(arrivedPropertyListener);
|
||||
if (acknowledgedPropertyListener != null)
|
||||
chatMessage.arrivedProperty().removeListener(acknowledgedPropertyListener);
|
||||
if (storedInMailboxPropertyListener != null)
|
||||
chatMessage.storedInMailboxProperty().removeListener(storedInMailboxPropertyListener);
|
||||
}
|
||||
|
@ -722,4 +777,4 @@ public class ChatView extends AnchorPane {
|
|||
inputTextAreaTextSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
* This file is part of haveno.
|
||||
*
|
||||
* 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
|
||||
|
@ -12,56 +12,68 @@
|
|||
* 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/>.
|
||||
* along with haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.desktop.main.support.dispute;
|
||||
|
||||
import haveno.common.UserThread;
|
||||
import haveno.desktop.components.AutoTooltipButton;
|
||||
import haveno.desktop.main.MainView;
|
||||
import haveno.desktop.main.shared.ChatView;
|
||||
import haveno.desktop.util.CssTheme;
|
||||
import haveno.desktop.util.DisplayUtils;
|
||||
|
||||
import haveno.core.locale.Res;
|
||||
import haveno.core.support.dispute.Dispute;
|
||||
import haveno.core.support.dispute.DisputeList;
|
||||
import haveno.core.support.dispute.DisputeManager;
|
||||
import haveno.core.support.dispute.DisputeSession;
|
||||
import haveno.core.support.messages.ChatMessage;
|
||||
import haveno.core.user.Preferences;
|
||||
import haveno.core.util.coin.CoinFormatter;
|
||||
import haveno.desktop.components.AutoTooltipButton;
|
||||
import haveno.desktop.main.MainView;
|
||||
import haveno.desktop.main.shared.ChatView;
|
||||
import haveno.desktop.util.CssTheme;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import haveno.common.UserThread;
|
||||
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
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;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class DisputeChatPopup {
|
||||
public interface ChatCallback {
|
||||
void onCloseDisputeFromChatWindow(Dispute dispute);
|
||||
void onSendLogsFromChatWindow(Dispute dispute);
|
||||
}
|
||||
|
||||
private Stage chatPopupStage;
|
||||
protected final DisputeManager<? extends DisputeList<Dispute>> disputeManager;
|
||||
protected final CoinFormatter formatter;
|
||||
protected final Preferences preferences;
|
||||
private ChatCallback chatCallback;
|
||||
private final ChatCallback chatCallback;
|
||||
private double chatPopupStageXPosition = -1;
|
||||
private double chatPopupStageYPosition = -1;
|
||||
private ChangeListener<Number> xPositionListener;
|
||||
private ChangeListener<Number> yPositionListener;
|
||||
@Getter private Dispute selectedDispute;
|
||||
|
||||
DisputeChatPopup(DisputeManager<? extends DisputeList<Dispute>> disputeManager,
|
||||
CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
ChatCallback chatCallback) {
|
||||
CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
ChatCallback chatCallback) {
|
||||
this.disputeManager = disputeManager;
|
||||
this.formatter = formatter;
|
||||
this.preferences = preferences;
|
||||
|
@ -84,7 +96,7 @@ public class DisputeChatPopup {
|
|||
selectedDispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
|
||||
disputeManager.requestPersistence();
|
||||
|
||||
ChatView chatView = new ChatView(disputeManager, formatter, counterpartyName);
|
||||
ChatView chatView = new ChatView(disputeManager, counterpartyName);
|
||||
chatView.setAllowAttachments(true);
|
||||
chatView.setDisplayHeader(false);
|
||||
chatView.initialize();
|
||||
|
@ -96,12 +108,27 @@ public class DisputeChatPopup {
|
|||
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));
|
||||
if (selectedDispute.isClosed()) {
|
||||
chatView.display(concreteDisputeSession, null, pane.widthProperty());
|
||||
} 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 {
|
||||
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));
|
||||
setChatUploadEnabledState(menuItem1);
|
||||
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.display(concreteDisputeSession, closeDisputeButton, pane.widthProperty());
|
||||
chatView.activate();
|
||||
chatView.scrollToBottom();
|
||||
chatPopupStage = new Stage();
|
||||
|
@ -132,9 +159,9 @@ public class DisputeChatPopup {
|
|||
chatPopupStage.setOpacity(0);
|
||||
chatPopupStage.show();
|
||||
|
||||
xPositionListener = (observable, oldValue, newValue) -> chatPopupStageXPosition = (double) newValue;
|
||||
ChangeListener<Number> xPositionListener = (observable, oldValue, newValue) -> chatPopupStageXPosition = (double) newValue;
|
||||
chatPopupStage.xProperty().addListener(xPositionListener);
|
||||
yPositionListener = (observable, oldValue, newValue) -> chatPopupStageYPosition = (double) newValue;
|
||||
ChangeListener<Number> yPositionListener = (observable, oldValue, newValue) -> chatPopupStageYPosition = (double) newValue;
|
||||
chatPopupStage.yProperty().addListener(yPositionListener);
|
||||
|
||||
if (chatPopupStageXPosition == -1) {
|
||||
|
@ -149,8 +176,33 @@ public class DisputeChatPopup {
|
|||
|
||||
// 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(() -> {
|
||||
if (chatPopupStage != null) chatPopupStage.setOpacity(1);
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setChatUploadEnabledState(MenuItem menuItem) {
|
||||
disputeManager.findTrade(selectedDispute).ifPresentOrElse(t -> {
|
||||
menuItem.setDisable(t.getChatMessages().size() == 0);
|
||||
}, () -> {
|
||||
menuItem.setDisable(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ import haveno.core.support.dispute.DisputeManager;
|
|||
import haveno.core.support.dispute.DisputeResult;
|
||||
import haveno.core.support.dispute.DisputeSession;
|
||||
import haveno.core.support.dispute.agent.DisputeAgentLookupMap;
|
||||
import haveno.core.support.dispute.arbitration.ArbitrationManager;
|
||||
import haveno.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import haveno.core.support.dispute.mediation.MediationManager;
|
||||
import haveno.core.support.messages.ChatMessage;
|
||||
|
@ -67,9 +68,12 @@ import haveno.desktop.components.AutoTooltipLabel;
|
|||
import haveno.desktop.components.AutoTooltipTableColumn;
|
||||
import haveno.desktop.components.HyperlinkWithIcon;
|
||||
import haveno.desktop.components.InputTextField;
|
||||
import haveno.desktop.components.PeerInfoIconDispute;
|
||||
import haveno.desktop.components.PeerInfoIconMap;
|
||||
import haveno.desktop.main.overlays.popups.Popup;
|
||||
import haveno.desktop.main.overlays.windows.ContractWindow;
|
||||
import haveno.desktop.main.overlays.windows.DisputeSummaryWindow;
|
||||
import haveno.desktop.main.overlays.windows.SendLogFilesWindow;
|
||||
import haveno.desktop.main.overlays.windows.SendPrivateNotificationWindow;
|
||||
import haveno.desktop.main.overlays.windows.TradeDetailsWindow;
|
||||
import haveno.desktop.main.overlays.windows.VerifyDisputeResultSignatureWindow;
|
||||
|
@ -119,7 +123,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
import static haveno.desktop.util.FormBuilder.getIconForLabel;
|
||||
import static haveno.desktop.util.FormBuilder.getRegularIconButton;
|
||||
|
||||
public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
||||
public abstract class DisputeView extends ActivatableView<VBox, Void> implements DisputeChatPopup.ChatCallback {
|
||||
public enum FilterResult {
|
||||
NO_MATCH("No Match"),
|
||||
NO_FILTER("No filter text"),
|
||||
|
@ -181,6 +185,8 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
private Map<String, Button> chatButtonByDispute = new HashMap<>();
|
||||
private Map<String, JFXBadge> chatBadgeByDispute = new HashMap<>();
|
||||
private Map<String, JFXBadge> newBadgeByDispute = new HashMap<>();
|
||||
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
|
||||
private final PeerInfoIconMap avatarMap = new PeerInfoIconMap();
|
||||
protected DisputeChatPopup chatPopup;
|
||||
|
||||
|
||||
|
@ -212,8 +218,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.useDevPrivilegeKeys = useDevPrivilegeKeys;
|
||||
DisputeChatPopup.ChatCallback chatCallback = this::handleOnProcessDispute;
|
||||
chatPopup = new DisputeChatPopup(disputeManager, formatter, preferences, chatCallback);
|
||||
chatPopup = new DisputeChatPopup(disputeManager, formatter, preferences, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -223,6 +228,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
HBox.setHgrow(label, Priority.NEVER);
|
||||
|
||||
filterTextField = new InputTextField();
|
||||
filterTextField.setPromptText(Res.get("support.filter.prompt"));
|
||||
Tooltip tooltip = new Tooltip();
|
||||
tooltip.setShowDelay(Duration.millis(100));
|
||||
tooltip.setShowDuration(Duration.seconds(10));
|
||||
|
@ -382,7 +388,9 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
ObservableList<ChatMessage> chatMessages = dispute.getChatMessages();
|
||||
// If last message is not a result message we re-open as we might have received a new message from the
|
||||
// trader/mediator/arbitrator who has reopened the case
|
||||
if (!chatMessages.isEmpty() && !chatMessages.get(chatMessages.size() - 1).isResultMessage(dispute)) {
|
||||
if (!chatMessages.isEmpty() &&
|
||||
!chatMessages.get(chatMessages.size() - 1).isResultMessage(dispute) &&
|
||||
dispute.unreadMessageCount(senderFlag()) > 0) {
|
||||
onSelectDispute(dispute);
|
||||
reOpenDispute();
|
||||
}
|
||||
|
@ -428,7 +436,8 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
|
||||
// For open filter we do not want to continue further as json data would cause a match
|
||||
if (filter.equalsIgnoreCase("open")) {
|
||||
return !dispute.isClosed() ? FilterResult.OPEN_DISPUTES : FilterResult.NO_MATCH;
|
||||
return !dispute.isClosed() || dispute.unreadMessageCount(senderFlag()) > 0 ?
|
||||
FilterResult.OPEN_DISPUTES : FilterResult.NO_MATCH;
|
||||
}
|
||||
|
||||
if (dispute.getTradeId().toLowerCase().contains(filter)) {
|
||||
|
@ -1083,7 +1092,8 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
private TableColumn<Dispute, Dispute> getDateColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("shared.date")) {
|
||||
{
|
||||
setMinWidth(180);
|
||||
setMinWidth(100);
|
||||
setPrefWidth(150);
|
||||
}
|
||||
};
|
||||
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
|
||||
|
@ -1109,7 +1119,8 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
private TableColumn<Dispute, Dispute> getTradeIdColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("shared.tradeId")) {
|
||||
{
|
||||
setMinWidth(110);
|
||||
setMinWidth(50);
|
||||
setPrefWidth(100);
|
||||
}
|
||||
};
|
||||
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
|
||||
|
@ -1167,10 +1178,14 @@ 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 (item != null && !empty) {
|
||||
setText(getBuyerOnionAddressColumnLabel(item));
|
||||
else
|
||||
PeerInfoIconDispute peerInfoIconDispute = createAvatar(tableRowProperty().get().getIndex(), item, true);
|
||||
setGraphic(peerInfoIconDispute);
|
||||
} else {
|
||||
setText("");
|
||||
setText(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1193,10 +1208,14 @@ 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 (item != null && !empty) {
|
||||
setText(getSellerOnionAddressColumnLabel(item));
|
||||
else
|
||||
PeerInfoIconDispute peerInfoIconDispute = createAvatar(tableRowProperty().get().getIndex(), item, false);
|
||||
setGraphic(peerInfoIconDispute);
|
||||
} else {
|
||||
setText("");
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1314,8 +1333,8 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
return;
|
||||
}
|
||||
|
||||
String keyBaseUserName = DisputeAgentLookupMap.getMatrixUserName(agentNodeAddress.getFullAddress());
|
||||
setText(keyBaseUserName);
|
||||
String MatrixUserName = DisputeAgentLookupMap.getMatrixUserName(agentNodeAddress.getFullAddress());
|
||||
setText(MatrixUserName);
|
||||
} else {
|
||||
setText("");
|
||||
}
|
||||
|
@ -1448,4 +1467,36 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
return (disputeManager instanceof MediationManager) ? Res.get("shared.mediator") : Res.get("shared.refundAgent");
|
||||
}
|
||||
}
|
||||
|
||||
private PeerInfoIconDispute createAvatar(Integer tableRowId, Dispute dispute, boolean isBuyer) {
|
||||
NodeAddress nodeAddress = isBuyer ? dispute.getContract().getBuyerNodeAddress() : dispute.getContract().getSellerNodeAddress();
|
||||
String key = tableRowId + nodeAddress.getHostNameWithoutPostFix() + (isBuyer ? "BUYER" : "SELLER");
|
||||
Long accountAge = isBuyer ?
|
||||
accountAgeWitnessService.getAccountAge(dispute.getBuyerPaymentAccountPayload(), dispute.getContract().getBuyerPubKeyRing()) :
|
||||
accountAgeWitnessService.getAccountAge(dispute.getSellerPaymentAccountPayload(), dispute.getContract().getSellerPubKeyRing());
|
||||
PeerInfoIconDispute peerInfoIcon = new PeerInfoIconDispute(
|
||||
nodeAddress,
|
||||
disputeManager.getNrOfDisputes(isBuyer, dispute.getContract()),
|
||||
accountAge,
|
||||
preferences);
|
||||
avatarMap.put(key, peerInfoIcon); // TODO
|
||||
return peerInfoIcon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseDisputeFromChatWindow(Dispute dispute) {
|
||||
if (dispute.getDisputeState() == Dispute.State.NEW || dispute.getDisputeState() == Dispute.State.OPEN) {
|
||||
handleOnProcessDispute(dispute);
|
||||
} else {
|
||||
closeDisputeFromButton();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendLogsFromChatWindow(Dispute dispute) {
|
||||
if (!(disputeManager instanceof ArbitrationManager))
|
||||
return;
|
||||
ArbitrationManager arbitrationManager = (ArbitrationManager) disputeManager;
|
||||
new SendLogFilesWindow(dispute.getTradeId(), dispute.getTraderId(), arbitrationManager).show();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue