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 jump to quoted message functionality #3261

Merged
merged 11 commits into from
Mar 8, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ public Optional<Citation> getCitation() {
}

String truncated = StringUtils.truncate(text, Citation.MAX_TEXT_LENGTH);
return Optional.of(new Citation(userProfile.getId(), truncated));
String chatMessageId = controller.model.chatMessageId;
return Optional.of(new Citation(userProfile.getId(), truncated, Optional.of(chatMessageId)));
}

private static class Controller implements bisq.desktop.common.view.Controller {
Expand All @@ -88,6 +89,7 @@ private Controller(ServiceProvider serviceProvider) {
}

private void reply(ChatMessage chatMessage) {
model.chatMessageId = chatMessage.getId();
userProfileService.findUserProfile(chatMessage.getAuthorUserProfileId()).ifPresent(author -> {
model.author = author;
model.userName.set(author.getUserName());
Expand All @@ -114,11 +116,13 @@ public void onDeactivate() {
}

private static class Model implements bisq.desktop.common.view.Model {
static final double CAT_HASH_IMAGE_SIZE = 25;
private static final double CAT_HASH_IMAGE_SIZE = 25;

private final BooleanProperty visible = new SimpleBooleanProperty();
private final StringProperty citation = new SimpleStringProperty("");
private final ObjectProperty<Image> catHashImage = new SimpleObjectProperty<>();
private final StringProperty userName = new SimpleStringProperty();
private String chatMessageId;
private UserProfile author;

private Model() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ private MessageBox createMessage(ChatMessageListItem<? extends ChatMessage, ? ex
}
}

if (item.isChatRulesWarningMessage()) {
return new ChatRulesWarningMessageBox(item, controller);
}

if (item.isMyMessage()) {
if (item.isProtocolLogMessage()) {
return new MyProtocolLogMessageBox(item, controller);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ public boolean isLeaveChatMessage() {
return chatMessage.getChatMessageType() == LEAVE;
}

public boolean isChatRulesWarningMessage() {
return chatMessage.getChatMessageType() == CHAT_RULES_WARNING;
}

public String getSupportedLanguageCodes(BisqEasyOfferbookMessage chatMessage) {
String result = getSupportedLanguageCodes(chatMessage, ", ", LanguageRepository::getDisplayLanguage);
return result.isEmpty() ? "" : Res.get("chat.message.supportedLanguages") + " " + StringUtils.truncate(result, 100);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import bisq.chat.two_party.TwoPartyPrivateChatMessage;
import bisq.common.observable.Pin;
import bisq.common.observable.collection.CollectionObserver;
import bisq.common.util.StringUtils;
import bisq.desktop.ServiceProvider;
import bisq.desktop.common.threading.UIScheduler;
import bisq.desktop.common.threading.UIThread;
Expand Down Expand Up @@ -535,6 +536,22 @@ public boolean canResendMessage(String messageId) {
return resendMessageService.map(service -> service.canManuallyResendMessage(messageId)).orElse(false);
}

public void onLearnMoreAboutChatRules() {
Navigation.navigateTo(NavigationTarget.CHAT_RULES);
}

public void onClickQuoteMessage(Optional<String> chatMessageId) {
chatMessageId.ifPresent(messageId -> {
model.getChatMessages().forEach(item -> {
boolean shouldHighlightMessage = item.getChatMessage().getId().equals(messageId);
item.getShowHighlighted().set(shouldHighlightMessage);
if (shouldHighlightMessage) {
view.scrollToChatMessage(item);
}
});
});
}


/* --------------------------------------------------------------------- */
// Scrolling
Expand Down Expand Up @@ -641,6 +658,8 @@ private <M extends ChatMessage, C extends ChatChannel<M>> Pin bindChatMessages(C
resendMessageService,
authorizedBondedRolesService))
.collect(Collectors.toSet()));
addChatRulesWarningMessageListItemInPrivateChats(channel);

model.getChatMessageIds().clear();
model.getChatMessageIds().addAll(model.getChatMessages().stream()
.map(e -> e.getChatMessage().getId())
Expand Down Expand Up @@ -746,4 +765,65 @@ private void deleteChatMessageReaction(ChatMessageReaction messageReaction, User
.deleteChatMessageReaction((BisqEasyOfferbookMessageReaction) messageReaction, userIdentity.getNetworkIdWithKeyPair());
}
}

private <M extends ChatMessage, C extends ChatChannel<M>> void addChatRulesWarningMessageListItemInPrivateChats(C channel) {
if (channel instanceof TwoPartyPrivateChatChannel twoPartyPrivateChatChannel) {
TwoPartyPrivateChatMessage twoPartyPrivateChatMessage = createChatRulesWarningMessage(twoPartyPrivateChatChannel);
model.getChatMessages().add(createChatMessageListItem(twoPartyPrivateChatMessage, twoPartyPrivateChatChannel));
} else if (channel instanceof BisqEasyOpenTradeChannel bisqEasyOpenTradeChannel) {
BisqEasyOpenTradeMessage bisqEasyOpenTradeMessage = createChatRulesWarningMessage(bisqEasyOpenTradeChannel);
model.getChatMessages().add(createChatMessageListItem(bisqEasyOpenTradeMessage, bisqEasyOpenTradeChannel));
}
}

private TwoPartyPrivateChatMessage createChatRulesWarningMessage(TwoPartyPrivateChatChannel channel) {
UserProfile receiverUserProfile = channel.getMyUserIdentity().getUserProfile();
UserProfile senderUserProfile = channel.getPeer();
String text = Res.get("chat.private.chatRulesWarningMessage.text");
return new TwoPartyPrivateChatMessage(StringUtils.createUid(),
channel.getChatChannelDomain(),
channel.getId(),
senderUserProfile,
receiverUserProfile.getId(),
receiverUserProfile.getNetworkId(),
text,
Optional.empty(),
0L,
false,
ChatMessageType.CHAT_RULES_WARNING,
new HashSet<>());
}

private BisqEasyOpenTradeMessage createChatRulesWarningMessage(BisqEasyOpenTradeChannel channel) {
UserProfile receiverUserProfile = channel.getMyUserIdentity().getUserProfile();
UserProfile senderUserProfile = channel.getPeer();
String text = Res.get("chat.private.chatRulesWarningMessage.text");
return new BisqEasyOpenTradeMessage(channel.getTradeId(),
StringUtils.createUid(),
channel.getId(),
senderUserProfile,
receiverUserProfile.getId(),
receiverUserProfile.getNetworkId(),
text,
Optional.empty(),
0L,
false,
channel.getMediator(),
ChatMessageType.CHAT_RULES_WARNING,
Optional.empty(),
new HashSet<>());
}

private <M extends ChatMessage, C extends ChatChannel<M>> ChatMessageListItem<M, C> createChatMessageListItem(M message, C channel) {
return new ChatMessageListItem<>(message,
channel,
marketPriceService,
userProfileService,
reputationService,
bisqEasyTradeService,
userIdentityService,
networkService,
Optional.empty(),
authorizedBondedRolesService);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
/*
* 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;

import bisq.chat.ChatChannel;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
/*
* 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;

import bisq.chat.ChatChannel;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ public void dispose() {
if (messageBgHBox != null) {
messageBgHBox.setEffect(null);
}

quotedMessageVBox.setOnMouseClicked(null);
}

private void showDateTimeAndActionsMenu(boolean shouldShow) {
Expand Down Expand Up @@ -303,11 +305,14 @@ private void handleQuoteMessageBox() {
userName.getStyleClass().add("font-medium");
userName.setStyle("-fx-text-fill: -bisq-mid-grey-30");
quotedMessageVBox.getChildren().setAll(userName, quotedMessageField);
quotedMessageVBox.setOnMouseClicked(e -> controller.onClickQuoteMessage(citation.getChatMessageId()));
quotedMessageVBox.getStyleClass().add("hand-cursor");
}
} else {
quotedMessageVBox.getChildren().clear();
quotedMessageVBox.setVisible(false);
quotedMessageVBox.setManaged(false);
quotedMessageVBox.setOnMouseClicked(null);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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.desktop.common.utils.ImageUtil;
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 javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;

public final class ChatRulesWarningMessageBox extends MessageBox {
private final Hyperlink learnMoreLink;

public ChatRulesWarningMessageBox(
ChatMessageListItem<? extends ChatMessage, ? extends ChatChannel<? extends ChatMessage>> item,
ChatMessagesListController controller) {

Label warningHeadline = new Label(Res.get("chat.private.chatRulesWarningMessage.headline"), ImageUtil.getImageViewById("undelivered-message-grey"));
warningHeadline.getStyleClass().addAll("text-fill-grey-dimmed", "normal-text");
warningHeadline.setGraphicTextGap(10);
warningHeadline.setPadding(new Insets(0, 0, 10, 0));

String decoded = Res.decode(item.getMessage());
Label message = new Label(decoded);
message.getStyleClass().addAll("text-fill-grey-dimmed", "font-light", "medium-text");
message.setAlignment(Pos.CENTER);
message.setWrapText(true);

learnMoreLink = new Hyperlink(Res.get("chat.private.chatRulesWarningMessage.learnMore"));
learnMoreLink.getStyleClass().addAll("text-fill-green", "font-light", "medium-text");
learnMoreLink.setOnAction(e -> controller.onLearnMoreAboutChatRules());

VBox messageBg = new VBox();
messageBg.setSpacing(5);
messageBg.getChildren().addAll(warningHeadline, message, learnMoreLink);
messageBg.setFillWidth(true);
messageBg.setAlignment(Pos.CENTER_LEFT);
messageBg.getStyleClass().add("system-message-background");
HBox.setHgrow(messageBg, Priority.ALWAYS);

setFillWidth(true);
HBox.setHgrow(this, Priority.ALWAYS);
setPadding(new Insets(0));

VBox contentVBox = new VBox(messageBg);
contentVBox.setMaxWidth(CHAT_BOX_MAX_WIDTH);
contentVBox.setPadding(new Insets(0, 70, 0, 70));
getChildren().setAll(contentVBox);
setAlignment(Pos.CENTER);
}

@Override
public void dispose() {
learnMoreLink.setOnAction(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public ChatRulesView(ChatRulesModel model, ChatRulesController controller) {

root.setAlignment(Pos.TOP_LEFT);
root.setPrefWidth(OverlayModel.WIDTH);
root.setPrefHeight(470);
root.setPrefHeight(505);

root.setPadding(new Insets(30, 60, 30, 60));

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/desktop/src/main/resources/css/chat.css
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@

.system-message-background {
-fx-background-color: rgba(36, 36, 36, 0.4); /* -bisq-dark-grey-30 */
-fx-padding: 25 0 25 0;
-fx-padding: 25 25 25 25;
}

.system-message-background .leave-chat-message,
Expand Down
3 changes: 2 additions & 1 deletion chat/src/main/java/bisq/chat/ChatMessageType.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public enum ChatMessageType implements ProtoEnum {
TEXT,
LEAVE,
TAKE_BISQ_EASY_OFFER,
PROTOCOL_LOG_MESSAGE;
PROTOCOL_LOG_MESSAGE,
CHAT_RULES_WARNING;

@Override
public bisq.chat.protobuf.ChatMessageType toProtoEnum() {
Expand Down
22 changes: 17 additions & 5 deletions chat/src/main/java/bisq/chat/Citation.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@

package bisq.chat;

import bisq.common.annotation.ExcludeForHash;
import bisq.common.proto.NetworkProto;
import bisq.common.validation.NetworkDataValidation;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;

import java.util.Optional;

@Getter
@ToString
@EqualsAndHashCode
Expand All @@ -32,9 +35,15 @@ public final class Citation implements NetworkProto {
private final String authorUserProfileId;
private final String text;

public Citation(String authorUserProfileId, String text) {
// Added with v2.1.7
@EqualsAndHashCode.Exclude
@ExcludeForHash
private final Optional<String> chatMessageId;

public Citation(String authorUserProfileId, String text, Optional<String> chatMessageId) {
this.authorUserProfileId = authorUserProfileId;
this.text = text;
this.chatMessageId = chatMessageId;

verify();
}
Expand All @@ -47,9 +56,11 @@ public void verify() {

@Override
public bisq.chat.protobuf.Citation.Builder getBuilder(boolean serializeForHash) {
return bisq.chat.protobuf.Citation.newBuilder()
bisq.chat.protobuf.Citation.Builder builder = bisq.chat.protobuf.Citation.newBuilder()
.setAuthorUserProfileId(authorUserProfileId)
.setText(text);
chatMessageId.ifPresent(builder::setChatMessageId);
return builder;
}

@Override
Expand All @@ -59,11 +70,12 @@ public bisq.chat.protobuf.Citation toProto(boolean serializeForHash) {

public static Citation fromProto(bisq.chat.protobuf.Citation proto) {
return new Citation(proto.getAuthorUserProfileId(),
proto.getText());
proto.getText(),
proto.hasChatMessageId() ? Optional.of(proto.getChatMessageId()) : Optional.empty());
}

public boolean isValid() {
return authorUserProfileId != null && !authorUserProfileId.isEmpty() &&
text != null && !text.isEmpty();
return authorUserProfileId != null && !authorUserProfileId.isEmpty()
&& text != null && !text.isEmpty();
}
}
Loading
Loading