Skip to content

Commit

Permalink
Merge pull request #2409 from axpoems/imp-selection-marker
Browse files Browse the repository at this point in the history
Align reactions display components
  • Loading branch information
djing-chan authored Jul 14, 2024
2 parents a560649 + 14c49cb commit 8f68755
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 124 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 @@ -116,8 +116,9 @@ public final class ChatMessageListItem<M extends ChatMessage, C extends ChatChan
private BisqMenuItem tryAgainMenuItem;

// Reactions
private Optional<Pin> userReactionsPin = Optional.empty();
private final Pin userIdentityPin;
private final HashMap<Reaction, ReactionItem> userReactions = new HashMap<>();
private Optional<Pin> userReactionsPin = Optional.empty();

public ChatMessageListItem(M chatMessage,
C chatChannel,
Expand Down Expand Up @@ -172,7 +173,8 @@ public ChatMessageListItem(M chatMessage,
lastSeen = senderUserProfile.map(userProfileService::getLastSeen).orElse(-1L);
lastSeenAsString = TimeFormatter.formatAge(lastSeen);

// TODO: Release all the listeners when destroying this object
userIdentityPin = userIdentityService.getSelectedUserIdentityObservable().addObserver(userIdentity -> UIThread.run(this::onUserIdentity));

createAndAddSubscriptionToUserReactions(userProfileService);
initializeDeliveryStatusIcons();
addSubscriptionToMessageDeliveryStatus(networkService);
Expand All @@ -196,6 +198,7 @@ public void dispose() {
mapPins.forEach(Pin::unbind);
statusPins.forEach(Pin::unbind);
userReactionsPin.ifPresent(Pin::unbind);
userIdentityPin.unbind();
}

public boolean hasTradeChatOffer() {
Expand Down Expand Up @@ -380,7 +383,8 @@ private void createAndAddSubscriptionToUserReactions(UserProfileService userProf
}

// Create all the ReactionItems
Arrays.stream(Reaction.values()).forEach(reaction -> userReactions.put(reaction, new ReactionItem(reaction)));
UserProfile selectedUserProfile = userIdentityService.getSelectedUserIdentity().getUserProfile();
Arrays.stream(Reaction.values()).forEach(reaction -> userReactions.put(reaction, new ReactionItem(reaction, selectedUserProfile)));

// Subscribe to changes
userReactionsPin = Optional.ofNullable(chatMessage.getChatMessageReactions().addObserver(new CollectionObserver<>() {
Expand All @@ -391,7 +395,7 @@ public void add(ChatMessageReaction element) {
Optional<UserProfile> userProfile = userProfileService.findUserProfile(element.getUserProfileId());
userProfile.ifPresent(profile -> {
if (!userProfileService.isChatUserIgnored(profile)) {
userReactions.get(reaction).addUser(element, profile, isMyUser(profile));
userReactions.get(reaction).addUser(element, profile);
}
});
}
Expand All @@ -403,7 +407,7 @@ public void remove(Object element) {
Reaction reaction = getReactionFromOrdinal(chatMessageReaction.getReactionId());
if (userReactions.containsKey(reaction)) {
Optional<UserProfile> userProfile = userProfileService.findUserProfile(chatMessageReaction.getUserProfileId());
userProfile.ifPresent(profile -> userReactions.get(reaction).removeUser(profile, isMyUser(profile)));
userProfile.ifPresent(profile -> userReactions.get(reaction).removeUser(profile));
}
}

Expand All @@ -419,8 +423,8 @@ private static Reaction getReactionFromOrdinal(int ordinal) {
return Reaction.values()[ordinal];
}

private boolean isMyUser(UserProfile profile) {
UserProfile myProfile = userIdentityService.getSelectedUserIdentity().getUserProfile();
return myProfile.equals(profile);
private void onUserIdentity() {
UserProfile selectedUserProfile = userIdentityService.getSelectedUserIdentity().getUserProfile();
userReactions.forEach((key, value) -> value.setSelectedUserProfile(selectedUserProfile));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,32 @@ public class ReactionItem {
private long firstAdded;
private final SimpleIntegerProperty count = new SimpleIntegerProperty(0);
private final SimpleBooleanProperty selected = new SimpleBooleanProperty(false);
Set<UserProfile> users = new HashSet<>();
private final Set<UserProfile> users = new HashSet<>();
private UserProfile selectedUserProfile;

ReactionItem(Reaction reaction) {
ReactionItem(Reaction reaction, UserProfile selectedUserProfile) {
this.reaction = reaction;
this.selectedUserProfile = selectedUserProfile;
this.iconId = reaction.toString().replace("_", "").toLowerCase();
}

void addUser(ChatMessageReaction chatMessageReaction, UserProfile userProfile, boolean isMyUser) {
public boolean hasActiveReactions() {
return !users.isEmpty();
}

public String getCountAsString() {
long count = users.size();
if (count < 2) {
return "";
}
return count < 100 ? String.valueOf(count) : "+99";
}

public static Comparator<ReactionItem> firstAddedComparator() {
return Comparator.comparingLong(ReactionItem::getFirstAdded);
}

void addUser(ChatMessageReaction chatMessageReaction, UserProfile userProfile) {
if (hasReactionBeenRemoved(chatMessageReaction)) {
return;
}
Expand All @@ -54,35 +72,28 @@ void addUser(ChatMessageReaction chatMessageReaction, UserProfile userProfile, b
firstAdded = chatMessageReaction.getDate();
}

selected.set(isMyUser);
users.add(userProfile);
count.set(users.size());
updateSelected();
}

void removeUser(UserProfile userProfile, boolean isMyUser) {
selected.set(!isMyUser);
void removeUser(UserProfile userProfile) {
users.remove(userProfile);
count.set(users.size());
updateSelected();
}

public boolean hasActiveReactions() {
return !users.isEmpty();
}

public String getCountAsString() {
long count = users.size();
if (count == 1) {
return "";
}
return count < 100 ? String.valueOf(count) : "+99";
void setSelectedUserProfile(UserProfile selectedUserProfile) {
this.selectedUserProfile = selectedUserProfile;
updateSelected();
}

private boolean hasReactionBeenRemoved(ChatMessageReaction chatMessageReaction) {
return (chatMessageReaction instanceof PrivateChatMessageReaction
&& ((PrivateChatMessageReaction) chatMessageReaction).isRemoved());
}

public static Comparator<ReactionItem> firstAddedComparator() {
return Comparator.comparingLong(ReactionItem::getFirstAdded);
private void updateSelected() {
selected.set(getUsers().contains(selectedUserProfile));
}
}
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);
}
}
}
Loading

0 comments on commit 8f68755

Please sign in to comment.