Skip to content

Commit

Permalink
Add new react menu box
Browse files Browse the repository at this point in the history
  • Loading branch information
axpoems committed Jul 14, 2024
1 parent bb6a1cb commit 8512a63
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class DrawerMenu extends HBox {
private static final String SLIDE_LEFT_CSS_STYLE = "slide-left";

private final Button menuButton = new Button();
private final HBox itemsHBox = new HBox();
protected final HBox itemsHBox = new HBox();
private final ImageView defaultIcon, hoverIcon, activeIcon;
@Getter
private final BooleanProperty isMenuShowing = new SimpleBooleanProperty(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@
import bisq.desktop.common.utils.ImageUtil;
import bisq.desktop.components.controls.BisqMenuItem;
import bisq.desktop.components.controls.BisqTooltip;
import bisq.desktop.components.controls.DrawerMenu;
import bisq.desktop.components.controls.DropdownMenu;
import bisq.desktop.main.content.chat.message_container.list.ChatMessageListItem;
import bisq.desktop.main.content.chat.message_container.list.ChatMessagesListController;
import bisq.desktop.main.content.chat.message_container.list.reactions_box.ActiveReactionsDisplayBox;
import bisq.desktop.main.content.chat.message_container.list.reactions_box.ReactMenuBox;
import bisq.desktop.main.content.chat.message_container.list.reactions_box.ToggleReaction;
import bisq.desktop.main.content.components.UserProfileIcon;
import bisq.i18n.Res;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
Expand All @@ -43,12 +42,10 @@
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
Expand All @@ -59,7 +56,7 @@ public abstract class BubbleMessageBox extends MessageBox {
protected static final double CHAT_MESSAGE_BOX_MAX_WIDTH = 630; // TODO: it should be 510 because of reactions on min size
protected static final double OFFER_MESSAGE_USER_ICON_SIZE = 70;
protected static final Insets ACTION_ITEMS_MARGIN = new Insets(2, 0, -2, 0);
private static final List<Reaction> REACTIONS = Arrays.asList(Reaction.THUMBS_UP, Reaction.THUMBS_DOWN, Reaction.HAPPY,
private static final List<Reaction> REACTIONS_ORDER = Arrays.asList(Reaction.THUMBS_UP, Reaction.THUMBS_DOWN, Reaction.HAPPY,
Reaction.LAUGH, Reaction.HEART, Reaction.PARTY);

private final Subscription showHighlightedPin;
Expand All @@ -68,12 +65,11 @@ public abstract class BubbleMessageBox extends MessageBox {
protected final ChatMessagesListController controller;
protected final UserProfileIcon userProfileIcon = new UserProfileIcon(60);
protected final HBox actionsHBox = new HBox(5);
protected final ActiveReactionsDisplayBox activeReactionsDisplayHBox;
protected final VBox quotedMessageVBox, contentVBox;
protected final BisqMenuItem deleteAction = new BisqMenuItem("delete-t-grey", "delete-t-red");
protected DrawerMenu reactMenu;
private Subscription reactMenuPin;
protected List<ReactionMenuItem> reactionMenuItems = new ArrayList<>();
protected ActiveReactionsDisplayBox activeReactionsDisplayHBox;
protected ReactMenuBox reactMenuBox;
protected Label supportedLanguages, userName, dateTime, message;
protected HBox userNameAndDateHBox, messageBgHBox, messageHBox;
protected VBox userProfileIconVbox;
Expand All @@ -89,11 +85,11 @@ public BubbleMessageBox(ChatMessageListItem<? extends ChatMessage, ? extends Cha

setUpUserNameAndDateTime();
setUpUserProfileIcon();
setUpReactions();
setUpActions();
addActionsHandlers();
addOnMouseEventHandlers();

activeReactionsDisplayHBox = createAndGetActiveReactionsDisplayBox();
supportedLanguages = createAndGetSupportedLanguagesLabel();
quotedMessageVBox = createAndGetQuotedMessageVBox();
handleQuoteMessageBox();
Expand All @@ -117,8 +113,6 @@ public BubbleMessageBox(ChatMessageListItem<? extends ChatMessage, ? extends Cha
messageBgHBox.getStyleClass().remove(HIGHLIGHTED_MESSAGE_BG_STYLE_CLASS);
}
});

reactionMenuItems.forEach(this::updateIsReactionSelected);
}

protected void setUpUserNameAndDateTime() {
Expand All @@ -144,24 +138,37 @@ private void setUpUserProfileIcon() {
});
}

protected void setUpActions() {
copyAction = new BisqMenuItem("copy-grey", "copy-white");
copyAction.useIconOnly();
copyAction.setTooltip(Res.get("action.copyToClipboard"));
reactMenu = createAndGetReactMenu();
setUpReactMenu();
actionsHBox.setVisible(false);
reactMenuPin = EasyBind.subscribe(reactMenu.getIsMenuShowing(), isShowing -> {
private void setUpReactions() {
// Active Reactions Display
ToggleReaction toggleReactionDisplayMenuFunction = reactionItem ->
controller.onReactMessage(item.getChatMessage(), reactionItem.getReaction(), item.getChatChannel());
activeReactionsDisplayHBox = new ActiveReactionsDisplayBox(item.getUserReactions().values(), toggleReactionDisplayMenuFunction);

// React Menu
ToggleReaction toggleReactionReactMenuFunction = reactionItem -> {
controller.onReactMessage(item.getChatMessage(), reactionItem.getReaction(), item.getChatChannel());
reactMenuBox.hideMenu();
};
reactMenuBox = new ReactMenuBox(item.getUserReactions(), REACTIONS_ORDER, toggleReactionReactMenuFunction,
"react-grey", "react-white", "react-green");
reactMenuBox.setTooltip(Res.get("action.react"));
reactMenuBox.setVisible(item.getChatMessage().canShowReactions());
reactMenuBox.setManaged(item.getChatMessage().canShowReactions());

reactMenuPin = EasyBind.subscribe(reactMenuBox.getIsMenuShowing(), isShowing -> {
if (!isShowing && !isHover()) {
showDateTimeAndActionsMenu(false);
}
});
}

reactMenu.setVisible(item.getChatMessage().canShowReactions());
reactMenu.setManaged(item.getChatMessage().canShowReactions());

protected void setUpActions() {
copyAction = new BisqMenuItem("copy-grey", "copy-white");
copyAction.useIconOnly();
copyAction.setTooltip(Res.get("action.copyToClipboard"));
actionsHBox.setVisible(false);
HBox.setMargin(copyAction, ACTION_ITEMS_MARGIN);
HBox.setMargin(reactMenu, ACTION_ITEMS_MARGIN);
HBox.setMargin(reactMenuBox, ACTION_ITEMS_MARGIN);
}

protected void addActionsHandlers() {
Expand All @@ -177,36 +184,28 @@ public void cleanup() {
setOnMouseEntered(null);
setOnMouseExited(null);

reactionMenuItems.forEach(menuItem -> menuItem.setOnAction(null));

showHighlightedPin.unsubscribe();
reactMenuPin.unsubscribe();

activeReactionsDisplayHBox.dispose();
reactMenuBox.dispose();
}

private void showDateTimeAndActionsMenu(boolean shouldShow) {
if (shouldShow) {
if ((moreActionsMenu != null && moreActionsMenu.getIsMenuShowing().get()) || reactMenu.getIsMenuShowing().get()) {
if ((moreActionsMenu != null && moreActionsMenu.getIsMenuShowing().get()) || reactMenuBox.getIsMenuShowing().get()) {
return;
}
dateTime.setVisible(true);
actionsHBox.setVisible(true);
} else {
if ((moreActionsMenu == null || !moreActionsMenu.getIsMenuShowing().get()) && !reactMenu.getIsMenuShowing().get()) {
if ((moreActionsMenu == null || !moreActionsMenu.getIsMenuShowing().get()) && !reactMenuBox.getIsMenuShowing().get()) {
dateTime.setVisible(false);
actionsHBox.setVisible(false);
}
}
}

private ActiveReactionsDisplayBox createAndGetActiveReactionsDisplayBox() {
ToggleReaction toggleReactionFunction = reactionItem -> {
controller.onReactMessage(item.getChatMessage(), reactionItem.getReaction(), item.getChatChannel());
};
return new ActiveReactionsDisplayBox(item.getUserReactions().values(), toggleReactionFunction);
}

private Label createAndGetSupportedLanguagesLabel() {
Label label = new Label();
if (item.isBisqEasyPublicChatMessageWithOffer()) {
Expand Down Expand Up @@ -285,49 +284,4 @@ protected static void onCopyMessage(ChatMessage chatMessage) {
protected static void onCopyMessage(String chatMessageText) {
ClipboardUtil.copyToClipboard(chatMessageText);
}

private DrawerMenu createAndGetReactMenu() {
DrawerMenu drawerMenu = new DrawerMenu("react-grey", "react-white", "react-green");
drawerMenu.setTooltip(Res.get("action.react"));
drawerMenu.getStyleClass().add("react-menu");
return drawerMenu;
}

private void setUpReactMenu() {
REACTIONS.forEach(reaction -> {
String iconId = reaction.toString().replace("_", "").toLowerCase();
ReactionMenuItem reactionMenuItem = new ReactionMenuItem(iconId, reaction);
reactionMenuItem.setOnAction(e -> toggleReaction(reactionMenuItem));
reactionMenuItems.add(reactionMenuItem);
});
}

private void toggleReaction(ReactionMenuItem reactionMenuItem) {
controller.onReactMessage(item.getChatMessage(), reactionMenuItem.getReaction(), item.getChatChannel());
reactMenu.hideMenu();
updateIsReactionSelected(reactionMenuItem);
}

private void updateIsReactionSelected(ReactionMenuItem reactionMenuItem) {
reactionMenuItem.setIsReactionSelected(item.hasAddedReaction(reactionMenuItem.getReaction()));
}

@Getter
public static final class ReactionMenuItem extends BisqMenuItem {
private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");

private final Reaction reaction;

public ReactionMenuItem(String iconId, Reaction reaction) {
super(iconId, iconId);

this.reaction = reaction;
useIconOnly(24);
getStyleClass().add("reaction-menu-item");
}

public void setIsReactionSelected(boolean isSelected) {
pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, isSelected);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;

import java.util.Collections;

public final class MyTextMessageBox extends BubbleMessageBox {
private final static String EDITED_POST_FIX = " " + Res.get("chat.message.wasEdited");

Expand All @@ -65,7 +63,7 @@ public MyTextMessageBox(ChatMessageListItem<? extends ChatMessage, ? extends Cha
message.maxWidthProperty().bind(list.widthProperty().subtract(140));
userProfileIcon.setSize(30);
userProfileIconVbox.setAlignment(Pos.TOP_LEFT);
actionsHBox.getChildren().setAll(Spacer.fillHBox(), reactMenu, editAction, copyAction, deleteAction);
actionsHBox.getChildren().setAll(Spacer.fillHBox(), reactMenuBox, editAction, copyAction, deleteAction);
HBox.setMargin(messageVBox, new Insets(0, -15, 0, 0));
HBox.setMargin(userProfileIconVbox, new Insets(7.5, 0, -5, 5));
HBox.setMargin(editInputField, new Insets(6, -10, -25, 0));
Expand Down Expand Up @@ -113,9 +111,8 @@ protected void setUpUserNameAndDateTime() {
protected void setUpActions() {
super.setUpActions();

reactMenu.setSlideToTheLeft();
Collections.reverse(reactionMenuItems);
reactMenu.addItems(reactionMenuItems);
reactMenuBox.setSlideToTheLeft();
reactMenuBox.reverseReactionsDisplayOrder();
editAction = new BisqMenuItem("edit-grey", "edit-white");
editAction.useIconOnly();
editAction.setTooltip(Res.get("action.edit"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public PeerTextMessageBox(ChatMessageListItem<? extends ChatMessage, ? extends C
setUpPeerMessage();
setMargin(userNameAndDateHBox, new Insets(-5, 0, -5, 10));
messageHBox.getChildren().setAll(messageBgHBox, activeReactionsDisplayHBox, Spacer.fillHBox());
actionsHBox.getChildren().setAll(replyAction, openPrivateChatAction, copyAction, reactMenu, moreActionsMenu, Spacer.fillHBox());
actionsHBox.getChildren().setAll(replyAction, openPrivateChatAction, copyAction, reactMenuBox, moreActionsMenu, Spacer.fillHBox());

contentVBox.getChildren().setAll(userNameAndDateHBox, messageHBox, actionsHBox);
}
Expand All @@ -65,7 +65,6 @@ protected void setUpUserNameAndDateTime() {
protected void setUpActions() {
super.setUpActions();

reactMenu.addItems(reactionMenuItems);
replyAction = new BisqMenuItem("reply-grey", "reply-white");
replyAction.useIconOnly();
replyAction.setTooltip(Res.get("chat.message.reply"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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.reactions_box;

import bisq.chat.reactions.Reaction;
import bisq.desktop.components.controls.BisqMenuItem;
import bisq.desktop.components.controls.DrawerMenu;
import bisq.desktop.main.content.chat.message_container.list.ReactionItem;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import lombok.Getter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ReactMenuBox extends DrawerMenu {
private final Map<ReactionItem, ChangeListener<Boolean>> itemSelectedChangeListener = new HashMap<>();

public ReactMenuBox(HashMap<Reaction, ReactionItem> reactionItems, List<Reaction> orderedReactions, ToggleReaction toggleReaction,
String defaultIconId, String hoverIconId, String activeIconId) {
super(defaultIconId, hoverIconId, activeIconId);

List<ReactionMenuItem> reactionMenuItems = new ArrayList<>();
orderedReactions.forEach(reaction -> {
if (reactionItems.containsKey(reaction)) {
ReactionItem reactionItem = reactionItems.get(reaction);
ReactionMenuItem reactionMenuItem = new ReactionMenuItem(reactionItem, toggleReaction);
reactionMenuItems.add(reactionMenuItem);
addItemListener(reactionItem, reactionMenuItem);
}
});
addItems(reactionMenuItems);

getStyleClass().add("react-menu-box");
}

public void dispose() {
removeItemListeners();
}

public void reverseReactionsDisplayOrder() {
ObservableList<Node> items = itemsHBox.getChildren();
FXCollections.reverse(items);
}

private void addItemListener(ReactionItem reactionItem, ReactionMenuItem reactionMenuItem) {
ChangeListener<Boolean> booleanChangeListener = (obs, oldValue, newValue) -> reactionMenuItem.setIsReactionSelected(newValue);;
reactionItem.getSelected().addListener(booleanChangeListener);
itemSelectedChangeListener.put(reactionItem, booleanChangeListener);
}

private void removeItemListeners() {
itemSelectedChangeListener.forEach((item, changeListener) -> item.getSelected().removeListener(changeListener));
}

@Getter
private static final class ReactionMenuItem extends BisqMenuItem {
private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");

private ReactionItem reactionItem;
private ToggleReaction toggleReaction;

private ReactionMenuItem(ReactionItem reactionItem, ToggleReaction toggleReaction) {
this(reactionItem.getIconId());

this.reactionItem = reactionItem;
this.toggleReaction = toggleReaction;
useIconOnly(24);
getStyleClass().add("reaction-menu-item");
setOnAction(e -> toggleReaction.execute(reactionItem));
setIsReactionSelected(reactionItem.getSelected().get());
}

private ReactionMenuItem(String iconId) {
super(iconId, iconId);
}

private void setIsReactionSelected(boolean isSelected) {
pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, isSelected);
}
}
}
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 @@ -155,7 +155,7 @@
-fx-font-family: "IBM Plex Sans Light";
}

.chat-message-content-box .react-menu .drawer-menu-items .bisq-menu-item:hover {
.chat-message-content-box .react-menu-box .drawer-menu-items .bisq-menu-item:hover {
-fx-background-color: -bisq-dark-grey-50 !important;
}

Expand Down

0 comments on commit 8512a63

Please sign in to comment.