Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Traders chat #3165

Merged
merged 35 commits into from Aug 30, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b5b126c
Refactor Chat out of TraderDisputeView
sqrrm May 23, 2019
390fc44
Send acks when capability found
sqrrm May 25, 2019
b1362a9
Separate most of Chat and arbitration
sqrrm May 25, 2019
1a25678
Move close ticket button creation to arbitration
sqrrm May 25, 2019
2d77f32
Handle Chat message subscription inside Chat
sqrrm May 25, 2019
1b1dce1
Add ChatManager
sqrrm May 30, 2019
b93e7ba
Add trader chat
sqrrm May 25, 2019
3b87045
Make attachments optional
sqrrm Jun 3, 2019
fb71c32
Test trade chat UI
sqrrm Jun 3, 2019
0217c3d
Add comments on BSQ trade fee calculation
sqrrm Jun 5, 2019
2375880
Change message box aid
sqrrm Jun 5, 2019
13f2651
Add basic chat UI
sqrrm Jun 5, 2019
329572c
Merge branch 'master_upstream' into sq-add-trader-chat
chimp1984 Aug 29, 2019
5d338e6
Fix merge issues
chimp1984 Aug 29, 2019
cb67376
Update string
chimp1984 Aug 29, 2019
8e3a351
Add comments, cleanup, fix wrong param name
chimp1984 Aug 29, 2019
2189cba
Show chat in popup instead of tab (WIP)
chimp1984 Aug 29, 2019
bfb111d
Add close handler to popup
chimp1984 Aug 29, 2019
fa43f03
Merge branch 'master_upstream' into sq-add-trader-chat
chimp1984 Aug 29, 2019
29e957a
Apply changes from master
chimp1984 Aug 29, 2019
855cb4f
Add missing allServicesInitialized check
chimp1984 Aug 29, 2019
e0c1a85
Add wasDisplayed field to chatMsg
chimp1984 Aug 29, 2019
81a93b5
Add onAllServicesInitialized call, rename method
chimp1984 Aug 29, 2019
a2cd808
Add missing setWasDisplayed
chimp1984 Aug 29, 2019
6304c9e
Add badge for num new chat msg
chimp1984 Aug 29, 2019
5c1999f
Fix wrong pubkey
chimp1984 Aug 29, 2019
663d859
Add chat rules system msg; cleanups
chimp1984 Aug 29, 2019
5e267fa
Handle nullables
chimp1984 Aug 29, 2019
964ca64
Fix positioning of badge
ripcurlx Aug 30, 2019
8edd261
Use button instead of label to fix tooltip font size, use material de…
ripcurlx Aug 30, 2019
fa30720
Update core/src/main/resources/i18n/displayStrings.properties
chimp1984 Aug 30, 2019
16d3bc5
Update core/src/main/resources/i18n/displayStrings.properties
chimp1984 Aug 30, 2019
c615780
Update tradeChat.rules
chimp1984 Aug 30, 2019
57ec0db
Persist chat position. Improve listener handling.
chimp1984 Aug 30, 2019
50baa9b
Close chat window if dispute gets closed
chimp1984 Aug 30, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 23 additions & 6 deletions core/src/main/java/bisq/core/trade/TradeChatSession.java
Expand Up @@ -19,6 +19,7 @@

import bisq.core.arbitration.messages.DisputeCommunicationMessage;
import bisq.core.arbitration.messages.DisputeMessage;
import bisq.core.arbitration.messages.DisputeResultMessage;
import bisq.core.chat.ChatManager;
import bisq.core.chat.ChatSession;

Expand All @@ -31,6 +32,7 @@

import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

import org.slf4j.Logger;
Expand All @@ -44,12 +46,18 @@
public class TradeChatSession extends ChatSession {
private static final Logger log = LoggerFactory.getLogger(TradeChatSession.class);

public interface DisputeStateListener {
void onDisputeClosed(String tradeId);
}

@Nullable
private Trade trade;
private boolean isClient;
private boolean isBuyer;
private TradeManager tradeManager;
private ChatManager chatManager;
// Needed to avoid ConcurrentModificationException as we remove a listener at the handler call
private List<DisputeStateListener> disputeStateListeners = new CopyOnWriteArrayList<>();

public TradeChatSession(@Nullable Trade trade,
boolean isClient,
Expand All @@ -64,6 +72,14 @@ public TradeChatSession(@Nullable Trade trade,
this.chatManager = chatManager;
}

public void addDisputeStateListener(DisputeStateListener disputeStateListener) {
disputeStateListeners.add(disputeStateListener);
}

public void removeDisputeStateListener(DisputeStateListener disputeStateListener) {
disputeStateListeners.remove(disputeStateListener);
}

@Override
public boolean isClient() {
return isClient;
Expand Down Expand Up @@ -138,14 +154,15 @@ public void dispatchMessage(DisputeMessage message) {
log.info("Received {} with tradeId {} and uid {}",
message.getClass().getSimpleName(), message.getTradeId(), message.getUid());
if (message instanceof DisputeCommunicationMessage) {
if (((DisputeCommunicationMessage) message).getType() != DisputeCommunicationMessage.Type.TRADE) {
log.debug("Ignore non trade type communication message");
return;
if (((DisputeCommunicationMessage) message).getType() == DisputeCommunicationMessage.Type.TRADE) {
chatManager.onDisputeDirectMessage((DisputeCommunicationMessage) message);
}
chatManager.onDisputeDirectMessage((DisputeCommunicationMessage) message);
} else {
log.warn("Unsupported message at dispatchMessage.\nmessage=" + message);
// We ignore dispute messages
} else if (message instanceof DisputeResultMessage) {
// We notify about dispute closed state
disputeStateListeners.forEach(e -> e.onDisputeClosed(message.getTradeId()));
}
// We ignore all other non DisputeCommunicationMessages
}

@Override
Expand Down
Expand Up @@ -78,6 +78,7 @@
import org.fxmisc.easybind.Subscription;

import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;

import javafx.event.EventHandler;

Expand Down Expand Up @@ -113,6 +114,15 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
private ListChangeListener<PendingTradesListItem> tradesListChangeListener;
private Map<String, Long> newChatMessagesByTradeMap = new HashMap<>();
private String tradeIdOfOpenChat;
private double chatPopupStageXPosition = -1;
private double chatPopupStageYPosition = -1;
private ChangeListener<Number> xPositionListener;
private ChangeListener<Number> yPositionListener;

private Map<String, Button> buttonByTrade = new HashMap<>();
private Map<String, JFXBadge> badgeByTrade = new HashMap<>();
private Map<String, ListChangeListener<DisputeCommunicationMessage>> listenerByTrade = new HashMap<>();
private TradeChatSession.DisputeStateListener disputeStateListener;


///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -338,19 +348,26 @@ private void openChat(Trade trade) {
tradeChat.initialize();

AnchorPane pane = new AnchorPane(tradeChat);
pane.setPrefSize(700, 500);
pane.setPrefSize(760, 500);
AnchorPane.setLeftAnchor(tradeChat, 10d);
AnchorPane.setRightAnchor(tradeChat, 10d);
AnchorPane.setTopAnchor(tradeChat, -20d);
AnchorPane.setBottomAnchor(tradeChat, 10d);

boolean isTaker = !model.dataModel.isMaker(trade.getOffer());
boolean isBuyer = model.dataModel.isBuyer();
tradeChat.display(new TradeChatSession(trade, isTaker, isBuyer,
model.dataModel.tradeManager,
model.dataModel.tradeManager.getChatManager()),
null,
pane.widthProperty());
TradeChatSession chatSession = new TradeChatSession(trade, isTaker, isBuyer,
model.dataModel.tradeManager,
model.dataModel.tradeManager.getChatManager());

disputeStateListener = tradeId -> {
if (trade.getId().equals(tradeId)) {
chatPopupStage.hide();
}
};
chatSession.addDisputeStateListener(disputeStateListener);

tradeChat.display(chatSession, null, pane.widthProperty());

tradeChat.activate();
tradeChat.scrollToBottom();
Expand All @@ -368,6 +385,16 @@ private void openChat(Trade trade) {
trade.getCommunicationMessages().forEach(m -> m.setWasDisplayed(true));
trade.persist();
tradeIdOfOpenChat = null;

if (xPositionListener != null) {
chatPopupStage.xProperty().removeListener(xPositionListener);
}
if (yPositionListener != null) {
chatPopupStage.xProperty().removeListener(yPositionListener);
}
if (disputeStateListener != null) {
chatSession.removeDisputeStateListener(disputeStateListener);
}
});

Scene scene = new Scene(pane);
Expand All @@ -385,10 +412,20 @@ private void openChat(Trade trade) {
chatPopupStage.setOpacity(0);
chatPopupStage.show();

Window rootSceneWindow = rootScene.getWindow();
double titleBarHeight = rootSceneWindow.getHeight() - rootScene.getHeight();
chatPopupStage.setX(Math.round(rootSceneWindow.getX() + (owner.getWidth() - chatPopupStage.getWidth() / 4 * 3)));
chatPopupStage.setY(Math.round(rootSceneWindow.getY() + titleBarHeight + (owner.getHeight() - chatPopupStage.getHeight() / 4 * 3)));
xPositionListener = (observable, oldValue, newValue) -> chatPopupStageXPosition = (double) newValue;
chatPopupStage.xProperty().addListener(xPositionListener);
yPositionListener = (observable, oldValue, newValue) -> chatPopupStageYPosition = (double) newValue;
chatPopupStage.yProperty().addListener(yPositionListener);

if (chatPopupStageXPosition == -1) {
Window rootSceneWindow = rootScene.getWindow();
double titleBarHeight = rootSceneWindow.getHeight() - rootScene.getHeight();
chatPopupStage.setX(Math.round(rootSceneWindow.getX() + (owner.getWidth() - chatPopupStage.getWidth() / 4 * 3)));
chatPopupStage.setY(Math.round(rootSceneWindow.getY() + titleBarHeight + (owner.getHeight() - chatPopupStage.getHeight() / 4 * 3)));
} else {
chatPopupStage.setX(chatPopupStageXPosition);
chatPopupStage.setY(chatPopupStageYPosition);
}

// Delay display to next render frame to avoid that the popup is first quickly displayed in default position
// and after a short moment in the correct position
Expand Down Expand Up @@ -647,58 +684,75 @@ private TableColumn<PendingTradesListItem, PendingTradesListItem> setChatColumnC
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
return new TableCell<>() {
Button button;

@Override
public void updateItem(final PendingTradesListItem newItem, boolean empty) {
super.updateItem(newItem, empty);

if (!empty && newItem != null) {
Trade trade = newItem.getTrade();

if (button == null) {
String id = trade.getId();

// We use maps for each trade to avoid multiple listener registrations when
// switching views. With current implementation we avoid that but we do not
// remove listeners when a trade is removed (completed) but that has no consequences
// as we will not receive any message anyway from a closed trade. Supporting it
// more correctly would require more effort and managing listener deactivation at
// screen switches (currently we get the update called if we have selected another
// view.
Button button;
if (!buttonByTrade.containsKey(id)) {
button = FormBuilder.getIconButton(MaterialDesignIcon.COMMENT_MULTIPLE_OUTLINE);
buttonByTrade.put(id, button);
button.setTooltip(new Tooltip(Res.get("tradeChat.openChat")));
} else {
button = buttonByTrade.get(id);
}

JFXBadge numNewMsg = new JFXBadge(button);
numNewMsg.setPosition(Pos.TOP_RIGHT);
JFXBadge badge;
if (!badgeByTrade.containsKey(id)) {
badge = new JFXBadge(button);
badgeByTrade.put(id, badge);
badge.setPosition(Pos.TOP_RIGHT);
} else {
badge = badgeByTrade.get(id);
}

button.setOnAction(e -> {
openChat(trade);
update(trade, numNewMsg);
update(trade, badge);
});

ListChangeListener<DisputeCommunicationMessage> listener = c -> update(trade, numNewMsg);
trade.getCommunicationMessages().addListener(listener);
if (!listenerByTrade.containsKey(id)) {
ListChangeListener<DisputeCommunicationMessage> listener = c -> update(trade, badge);
listenerByTrade.put(id, listener);
trade.getCommunicationMessages().addListener(listener);
}

update(trade, numNewMsg);
update(trade, badge);

setGraphic(numNewMsg);
setGraphic(badge);
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}

private void update(Trade trade, JFXBadge numNewMsg) {
private void update(Trade trade, JFXBadge badge) {
if (!trade.getId().equals(tradeIdOfOpenChat)) {
updateNewChatMessagesByTradeMap();
long num = newChatMessagesByTradeMap.get(trade.getId());
if (num > 0) {
numNewMsg.setText(String.valueOf(num));
numNewMsg.setEnabled(true);
badge.setText(String.valueOf(num));
badge.setEnabled(true);
} else {
numNewMsg.setText("");
numNewMsg.setEnabled(false);
badge.setText("");
badge.setEnabled(false);
}
} else {
numNewMsg.setText("");
numNewMsg.setEnabled(false);
badge.setText("");
badge.setEnabled(false);
}
numNewMsg.refreshBadge();
badge.refreshBadge();
}
};
}
Expand Down