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

Add new mediator selection version #3273

Original file line number Diff line number Diff line change
Expand Up @@ -19,51 +19,24 @@

import bisq.chat.ChatChannel;
import bisq.chat.ChatMessage;
import bisq.desktop.components.controls.BisqMenuItem;
import bisq.desktop.main.content.chat.message_container.list.ChatMessageListItem;
import bisq.desktop.main.content.chat.message_container.list.ChatMessagesListController;
import javafx.geometry.Pos;
import javafx.scene.layout.HBox;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import bisq.desktop.main.content.chat.message_container.list.message_box.MessageDeliveryStatusBox;

public class MyProtocolLogMessageBox extends PeerProtocolLogMessageBox {
private final Subscription shouldShowTryAgainPin, messageDeliveryStatusNodePin;
private final MessageDeliveryStatusBox messageDeliveryStatusBox;

public MyProtocolLogMessageBox(ChatMessageListItem<? extends ChatMessage, ? extends ChatChannel<? extends ChatMessage>> item,
ChatMessagesListController controller) {
super(item);

BisqMenuItem tryAgainMenuItem = item.getTryAgainMenuItem();
HBox deliveryStateHBox = new HBox();
deliveryStateHBox.setAlignment(Pos.CENTER);
HBox messageStatusHbox = new HBox(5, tryAgainMenuItem, deliveryStateHBox);
messageStatusHbox.setAlignment(Pos.CENTER);
messageDeliveryStatusBox = new MessageDeliveryStatusBox(item, controller);

messageDeliveryStatusNodePin = EasyBind.subscribe(item.getMessageDeliveryStatusNode(), node -> {
deliveryStateHBox.setManaged(node != null);
deliveryStateHBox.setVisible(node != null);
if (node != null) {
deliveryStateHBox.getChildren().setAll(node);
}
});

shouldShowTryAgainPin = EasyBind.subscribe(item.getShouldShowTryAgain(), showTryAgain -> {
tryAgainMenuItem.setVisible(showTryAgain);
tryAgainMenuItem.setManaged(showTryAgain);
if (showTryAgain) {
tryAgainMenuItem.setOnAction(e -> controller.onResendMessage(item.getMessageId()));
} else {
tryAgainMenuItem.setOnAction(null);
}
});

dateTimeHBox.getChildren().add(0, messageStatusHbox);
dateTimeHBox.getChildren().add(0, messageDeliveryStatusBox);
}

@Override
public void dispose() {
shouldShowTryAgainPin.unsubscribe();
messageDeliveryStatusNodePin.unsubscribe();
messageDeliveryStatusBox.dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ public void takeOffer(Runnable onCancelHandler) {
onCancelHandler.run();
return;
}
Optional<UserProfile> mediator = mediationRequestService.selectMediator(bisqEasyOffer.getMakersUserProfileId(), takerIdentity.getId());
Optional<UserProfile> mediator = mediationRequestService.selectMediator(bisqEasyOffer.getMakersUserProfileId(),
takerIdentity.getId(),
bisqEasyOffer.getId());
if (!DevMode.isDevMode() && mediator.isEmpty()) {
new Popup().warning(Res.get("bisqEasy.takeOffer.noMediatorAvailable.warning"))
.closeButtonText(Res.get("action.cancel"))
Expand Down Expand Up @@ -204,11 +206,11 @@ private void doTakeOffer(BisqEasyOffer bisqEasyOffer, UserIdentity takerIdentity
log.info("Selected mediator for trade {}: {}", bisqEasyTrade.getShortId(), mediator.map(UserProfile::getUserName).orElse("N/A"));
model.setBisqEasyTrade(bisqEasyTrade);
errorMessagePin = bisqEasyTrade.errorMessageObservable().addObserver(errorMessage -> {
if (errorMessage != null) {
UIThread.run(() -> new Popup().error(Res.get("bisqEasy.openTrades.failed.popup",
errorMessage,
StringUtils.truncate(bisqEasyTrade.getErrorStackTrace(), 500)))
.show());
if (errorMessage != null) {
UIThread.run(() -> new Popup().error(Res.get("bisqEasy.openTrades.failed.popup",
errorMessage,
StringUtils.truncate(bisqEasyTrade.getErrorStackTrace(), 500)))
.show());
}
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,9 @@ public void takeOffer() {
// If taker is banned we don't need to show them a popup
return;
}
Optional<UserProfile> mediator = mediationRequestService.selectMediator(bisqEasyOffer.getMakersUserProfileId(), takerIdentity.getId());
Optional<UserProfile> mediator = mediationRequestService.selectMediator(bisqEasyOffer.getMakersUserProfileId(),
takerIdentity.getId(),
bisqEasyOffer.getId());
if (!DevMode.isDevMode() && mediator.isEmpty()) {
new Popup().warning(Res.get("bisqEasy.takeOffer.noMediatorAvailable.warning"))
.closeButtonText(Res.get("action.cancel"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@
import bisq.chat.Citation;
import bisq.chat.bisq_easy.BisqEasyOfferMessage;
import bisq.chat.bisq_easy.offerbook.BisqEasyOfferbookMessage;
import bisq.chat.bisq_easy.open_trades.BisqEasyOpenTradeMessage;
import bisq.chat.priv.PrivateChatMessage;
import bisq.chat.pub.PublicChatChannel;
import bisq.chat.reactions.ChatMessageReaction;
import bisq.chat.reactions.Reaction;
import bisq.common.currency.Market;
import bisq.common.data.Pair;
import bisq.common.data.Triple;
import bisq.common.locale.LanguageRepository;
import bisq.common.observable.Observable;
import bisq.common.observable.Pin;
Expand All @@ -50,6 +52,7 @@
import bisq.i18n.Res;
import bisq.network.NetworkService;
import bisq.network.identity.NetworkId;
import bisq.network.p2p.services.confidential.ack.AckRequestingMessage;
import bisq.network.p2p.services.confidential.ack.MessageDeliveryStatus;
import bisq.network.p2p.services.confidential.resend.ResendMessageService;
import bisq.offer.Direction;
Expand All @@ -73,8 +76,6 @@
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import lombok.EqualsAndHashCode;
import lombok.Getter;
Expand Down Expand Up @@ -108,17 +109,14 @@ public final class ChatMessageListItem<M extends ChatMessage, C extends ChatChan
private final ReputationScore reputationScore;
private final ReputationScoreDisplay reputationScoreDisplay = new ReputationScoreDisplay();
private final boolean offerAlreadyTaken;
@Nullable
private String messageId;
private final MarketPriceService marketPriceService;
private final UserIdentityService userIdentityService;
private final BooleanProperty showHighlighted = new SimpleBooleanProperty();

// Delivery status
private final Set<Pin> mapPins = new HashSet<>();
private final Set<Pin> statusPins = new HashSet<>();
private final BooleanProperty shouldShowTryAgain = new SimpleBooleanProperty();
private final SimpleObjectProperty<Node> messageDeliveryStatusNode = new SimpleObjectProperty<>();
private final SimpleObjectProperty<Map<String, Triple<MessageDeliveryStatus, String, Boolean>>> messageDeliveryStatusByPeerProfileId = new SimpleObjectProperty<>();
private final Optional<ResendMessageService> resendMessageService;
private ImageView successfulDeliveryIcon, connectingDeliveryIcon, pendingDeliveryIcon, addedToMailboxIcon, failedDeliveryIcon;
private BisqMenuItem tryAgainMenuItem;
Expand Down Expand Up @@ -349,9 +347,30 @@ private void initializeDeliveryStatusIcons() {
private void addSubscriptionToMessageDeliveryStatus(NetworkService networkService) {
mapPins.add(networkService.getMessageDeliveryStatusByMessageId().addObserver(new HashMapObserver<>() {
@Override
public void put(String messageId, Observable<MessageDeliveryStatus> value) {
if (messageId.equals(chatMessage.getId())) {
updateMessageStatus(messageId, value);
public void put(String ackRequestingMessageId, Observable<MessageDeliveryStatus> value) {
if (chatMessage instanceof AckRequestingMessage ackRequestingMessage) {
String messageId = ackRequestingMessageId;
String chatMessageId = ackRequestingMessage.getAckRequestingMessageId();
String peersProfileId = null;
String separator = BisqEasyOpenTradeMessage.ACK_REQUESTING_MESSAGE_ID_SEPARATOR;
if (chatMessage instanceof BisqEasyOpenTradeMessage bisqEasyOpenTradeMessage) {
// In case of a bisqEasyOpenTradeMessage we use the message id and receiver id separated with a '_'.
// This allows us to handle the ACK messages separately to know when the message was received by
// both the peer and the mediator (in case of mediation).
if (messageId.contains(separator)) {
String[] parts = messageId.split(separator);
messageId = parts[0];
peersProfileId = parts[1];
}
if (chatMessageId.contains(separator)) {
String[] parts = chatMessageId.split(separator);
chatMessageId = parts[0];
}
}

if (messageId.equals(chatMessageId)) {
updateMessageStatus(ackRequestingMessageId, value, peersProfileId);
}
}
}

Expand All @@ -370,39 +389,20 @@ public void clear() {
}));
}

private void updateMessageStatus(String messageId, Observable<MessageDeliveryStatus> value) {
private void updateMessageStatus(String ackRequestingMessageId,
Observable<MessageDeliveryStatus> value,
@Nullable String peersProfileId) {
// Delay to avoid ConcurrentModificationException
UIThread.runOnNextRenderFrame(() -> statusPins.add(value.addObserver(status -> UIThread.run(() -> {
ChatMessageListItem.this.messageId = messageId;
boolean shouldShowTryAgain = false;
if (status != null) {
Label statusLabel = new Label();
statusLabel.setTooltip(new BisqTooltip(Res.get("chat.message.deliveryState." + status.name())));
switch (status) {
// Successful delivery
case ACK_RECEIVED:
case MAILBOX_MSG_RECEIVED:
statusLabel.setGraphic(successfulDeliveryIcon);
break;
// Pending delivery
case CONNECTING:
statusLabel.setGraphic(connectingDeliveryIcon);
break;
case SENT:
case TRY_ADD_TO_MAILBOX:
statusLabel.setGraphic(pendingDeliveryIcon);
break;
case ADDED_TO_MAILBOX:
statusLabel.setGraphic(addedToMailboxIcon);
break;
case FAILED:
statusLabel.setGraphic(failedDeliveryIcon);
shouldShowTryAgain = resendMessageService.map(service -> service.canManuallyResendMessage(messageId)).orElse(false);
break;
}
messageDeliveryStatusNode.set(statusLabel);
Map<String, Triple<MessageDeliveryStatus, String, Boolean>> map = messageDeliveryStatusByPeerProfileId.get();
if (map == null) {
map = new HashMap<>();
}
this.shouldShowTryAgain.set(shouldShowTryAgain);
boolean canManuallyResendMessage = status == MessageDeliveryStatus.FAILED &&
resendMessageService.map(service -> service.canManuallyResendMessage(ackRequestingMessageId)).orElse(false);
map.put(peersProfileId, new Triple<>(status, ackRequestingMessageId, canManuallyResendMessage));
messageDeliveryStatusByPeerProfileId.set(null); // trigger update by setting it to null
messageDeliveryStatusByPeerProfileId.set(map);
}))));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* 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 bisq.desktop.main.content.chat.message_container.list.message_box;

import bisq.chat.ChatChannel;
import bisq.chat.ChatMessage;
import bisq.common.data.Triple;
import bisq.desktop.common.utils.ImageUtil;
import bisq.desktop.components.controls.BisqMenuItem;
import bisq.desktop.components.controls.BisqTooltip;
import bisq.desktop.main.content.chat.message_container.list.ChatMessageListItem;
import bisq.desktop.main.content.chat.message_container.list.ChatMessagesListController;
import bisq.i18n.Res;
import bisq.network.p2p.services.confidential.ack.MessageDeliveryStatus;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;

import java.util.HashMap;
import java.util.Map;

public class MessageDeliveryStatusBox extends HBox {
private final Subscription messageDeliveryStatusNodePin;
private final Map<String, Triple<BisqMenuItem, Label, BisqTooltip>> deliveryStateTripleByProfileId = new HashMap<>();

public MessageDeliveryStatusBox(ChatMessageListItem<? extends ChatMessage, ? extends ChatChannel<? extends ChatMessage>> item,
ChatMessagesListController controller) {
super(7.5);
setAlignment(Pos.CENTER);

messageDeliveryStatusNodePin = EasyBind.subscribe(item.getMessageDeliveryStatusByPeerProfileId(), map -> {
setManaged(map != null);
setVisible(map != null);
if (map == null) {
return;
}
map.forEach((peersProfileId, triple) -> {
MessageDeliveryStatus status = triple.getFirst();
String ackRequestingMessageId = triple.getSecond();
boolean canManuallyResendMessage = triple.getThird();

Triple<BisqMenuItem, Label, BisqTooltip> deliveryStateTriple = deliveryStateTripleByProfileId.get(peersProfileId);
BisqMenuItem tryAgainMenuItem;
Label icon;
BisqTooltip tooltip;
if (deliveryStateTriple == null) {
tryAgainMenuItem = new BisqMenuItem("try-again-grey", "try-again-white");
tryAgainMenuItem.useIconOnly(22);
tryAgainMenuItem.setTooltip(new BisqTooltip(Res.get("chat.message.resendMessage")));

icon = new Label();
tooltip = new BisqTooltip();
icon.setTooltip(tooltip);

HBox hBox = new HBox(1, tryAgainMenuItem, icon);
hBox.setAlignment(Pos.CENTER);
getChildren().add(hBox);

deliveryStateTripleByProfileId.put(peersProfileId, new Triple<>(tryAgainMenuItem, icon, tooltip));
} else {
tryAgainMenuItem = deliveryStateTriple.getFirst();
icon = deliveryStateTriple.getSecond();
tooltip = deliveryStateTriple.getThird();
}

tryAgainMenuItem.setVisible(canManuallyResendMessage);
tryAgainMenuItem.setManaged(canManuallyResendMessage);
if (canManuallyResendMessage) {
tryAgainMenuItem.setOnAction(e -> controller.onResendMessage(ackRequestingMessageId));
} else {
tryAgainMenuItem.setOnAction(null);
}


String deliveryState = Res.get("chat.message.deliveryState." + status.name());
if (map.size() > 1) {
String userName = controller.getUserName(peersProfileId);
tooltip.setText(Res.get("chat.message.deliveryState.multiplePeers", userName, deliveryState));
} else {
tooltip.setText(deliveryState);
}

switch (status) {
// Successful delivery
case ACK_RECEIVED:
case MAILBOX_MSG_RECEIVED:
icon.setGraphic(ImageUtil.getImageViewById("received-check-grey"));
break;
// Pending delivery
case CONNECTING:
icon.setGraphic(ImageUtil.getImageViewById("connecting-grey"));
break;
case SENT:
case TRY_ADD_TO_MAILBOX:
icon.setGraphic(ImageUtil.getImageViewById("sent-message-grey"));
break;
case ADDED_TO_MAILBOX:
icon.setGraphic(ImageUtil.getImageViewById("mailbox-grey"));
break;
case FAILED:
icon.setGraphic(ImageUtil.getImageViewById("undelivered-message-yellow"));
break;
}
});
});
}

public void dispose() {
messageDeliveryStatusNodePin.unsubscribe();
deliveryStateTripleByProfileId.values().forEach(triple ->
triple.getFirst().setOnAction(null));
}
}
Loading
Loading