diff --git a/api/src/main/java/com/rappytv/betterfriends/api/FriendHelper.java b/api/src/main/java/com/rappytv/betterfriends/api/FriendHelper.java new file mode 100644 index 0000000..036943f --- /dev/null +++ b/api/src/main/java/com/rappytv/betterfriends/api/FriendHelper.java @@ -0,0 +1,20 @@ +package com.rappytv.betterfriends.api; + +import java.util.UUID; +import net.labymod.api.labyconnect.protocol.model.friend.Friend; +import net.labymod.api.mojang.GameProfile; +import net.labymod.api.reference.annotation.Referenceable; +import org.jetbrains.annotations.Nullable; + +@Referenceable +public interface FriendHelper { + + @Nullable + default Friend getFriend(GameProfile profile) { + return this.getFriend(profile.getUniqueId()); + } + + @Nullable + Friend getFriend(UUID uuid); + +} diff --git a/build.gradle.kts b/build.gradle.kts index ce7ad63..4e9c89b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { val versions = providers.gradleProperty("net.labymod.minecraft-versions").get().split(";") group = "org.example" -version = providers.environmentVariable("VERSION").getOrElse("1.0.0") +version = providers.environmentVariable("VERSION").getOrElse("1.0.1") labyMod { defaultPackageName = "com.rappytv.betterfriends" diff --git a/core/src/main/java/com/rappytv/betterfriends/BetterFriendsAddon.java b/core/src/main/java/com/rappytv/betterfriends/BetterFriendsAddon.java index 4a64d06..8fbc046 100644 --- a/core/src/main/java/com/rappytv/betterfriends/BetterFriendsAddon.java +++ b/core/src/main/java/com/rappytv/betterfriends/BetterFriendsAddon.java @@ -1,5 +1,6 @@ package com.rappytv.betterfriends; +import com.rappytv.betterfriends.api.generated.ReferenceStorage; import com.rappytv.betterfriends.command.BetterFriendsCommand; import com.rappytv.betterfriends.config.BetterFriendsConfig; import com.rappytv.betterfriends.interactions.FriendNoteEditorBullet; @@ -19,6 +20,7 @@ import com.rappytv.betterfriends.ui.hud.UnreadChatCountWidget; import com.rappytv.betterfriends.ui.tags.FriendNoteNameTag; import com.rappytv.betterfriends.ui.tags.FriendPinIconTag; +import com.rappytv.betterfriends.utils.GroupHelper; import net.labymod.api.Laby; import net.labymod.api.addon.LabyAddon; import net.labymod.api.client.component.Component; @@ -30,17 +32,29 @@ import net.labymod.api.client.entity.player.tag.PositionType; import net.labymod.api.client.gui.hud.binding.category.HudWidgetCategory; import net.labymod.api.models.addon.annotation.AddonMain; +import net.labymod.api.revision.SimpleRevision; +import net.labymod.api.util.version.SemanticVersion; @AddonMain public class BetterFriendsAddon extends LabyAddon { - private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacyAmpersand(); + private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.legacyAmpersand(); private static BetterFriendsAddon instance; + @Override + protected void preConfigurationLoad() { + Laby.references().revisionRegistry().register(new SimpleRevision( + "betterfriends", + new SemanticVersion(1, 0, 1), + "2025-12-10" + )); + } + @Override protected void enable() { instance = this; this.registerSettingCategory(); + GroupHelper.registerGroupIds(); this.registerCommand(new BetterFriendsCommand()); @@ -75,7 +89,7 @@ protected void enable() { "VoiceTag", "betterfriends_pin_icon", PositionType.RIGHT_TO_NAME, - new FriendPinIconTag(this) + new FriendPinIconTag() ); Laby.references().badgeRegistry().registerBefore( "VoiceBadge", @@ -90,6 +104,10 @@ protected Class configurationClass() { return BetterFriendsConfig.class; } + public static ReferenceStorage references() { + return instance.referenceStorageAccessor(); + } + public static TextComponent getPrefix() { return Component.empty() .append(Component.text( @@ -102,6 +120,6 @@ public static TextComponent getPrefix() { } public LegacyComponentSerializer getSerializer() { - return this.serializer; + return SERIALIZER; } } diff --git a/core/src/main/java/com/rappytv/betterfriends/command/BetterFriendsCommand.java b/core/src/main/java/com/rappytv/betterfriends/command/BetterFriendsCommand.java index 5e12afe..0401b9b 100644 --- a/core/src/main/java/com/rappytv/betterfriends/command/BetterFriendsCommand.java +++ b/core/src/main/java/com/rappytv/betterfriends/command/BetterFriendsCommand.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.UUID; import com.rappytv.betterfriends.listeners.LabyChatReceiveListener; -import com.rappytv.betterfriends.utils.NameHelper; +import com.rappytv.betterfriends.utils.GroupHelper; import net.labymod.api.Laby; import net.labymod.api.client.chat.command.Command; import net.labymod.api.client.chat.command.SubCommand; @@ -99,7 +99,7 @@ public boolean execute(String prefix, String[] arguments) { .append(Component.translatable( this.getTranslationKey("success.accept"), NamedTextColor.GRAY, - NameHelper.getColoredName(request.getName(), request.gameUser()) + GroupHelper.getColoredName(request.getName(), request.gameUser()) )) ); return true; @@ -160,7 +160,7 @@ public boolean execute(String prefix, String[] arguments) { .append(Component.translatable( this.getTranslationKey("success.decline"), NamedTextColor.GRAY, - NameHelper.getColoredName(request.getName(), request.gameUser()) + GroupHelper.getColoredName(request.getName(), request.gameUser()) )) ); return true; diff --git a/core/src/main/java/com/rappytv/betterfriends/config/BetterFriendsConfig.java b/core/src/main/java/com/rappytv/betterfriends/config/BetterFriendsConfig.java index 4d7d52d..5f860e2 100644 --- a/core/src/main/java/com/rappytv/betterfriends/config/BetterFriendsConfig.java +++ b/core/src/main/java/com/rappytv/betterfriends/config/BetterFriendsConfig.java @@ -2,17 +2,23 @@ import com.rappytv.betterfriends.config.subconfig.FriendNoteTagConfig; import com.rappytv.betterfriends.config.subconfig.PinIconConfig; +import com.rappytv.betterfriends.ui.activities.config.FriendlistActivity; +import com.rappytv.betterfriends.ui.widgets.FriendlistFriendWidget; import net.labymod.api.addon.AddonConfig; +import net.labymod.api.client.gui.screen.activity.Activity; +import net.labymod.api.client.gui.screen.widget.widgets.activity.settings.ActivitySettingWidget.ActivitySetting; import net.labymod.api.client.gui.screen.widget.widgets.input.SwitchWidget.SwitchSetting; import net.labymod.api.client.gui.screen.widget.widgets.input.TextFieldWidget.TextFieldSetting; import net.labymod.api.client.gui.screen.widget.widgets.input.color.ColorPickerWidget.ColorPickerSetting; import net.labymod.api.client.gui.screen.widget.widgets.input.dropdown.DropdownWidget.DropdownSetting; +import net.labymod.api.configuration.loader.annotation.IntroducedIn; import net.labymod.api.configuration.loader.annotation.SpriteSlot; import net.labymod.api.configuration.loader.annotation.SpriteTexture; import net.labymod.api.configuration.loader.property.ConfigProperty; import net.labymod.api.configuration.settings.annotation.SettingRequires; import net.labymod.api.configuration.settings.annotation.SettingSection; import net.labymod.api.util.Color; +import net.labymod.api.util.MethodOrder; @SpriteTexture("settings.png") public class BetterFriendsConfig extends AddonConfig { @@ -21,13 +27,23 @@ public class BetterFriendsConfig extends AddonConfig { @SpriteSlot @SwitchSetting private final ConfigProperty enabled = new ConfigProperty<>(true); + @SpriteSlot(x = 1) @ColorPickerSetting private final ConfigProperty prefixColor = new ConfigProperty<>(Color.ofRGB(255, 102, 0)); + + @MethodOrder(after = "prefixColor") + @ActivitySetting + public Activity advancedFriendlist() { + return new FriendlistActivity<>(FriendlistFriendWidget::new); + } + @SpriteSlot(size = 8, x = 4) private final PinIconConfig pinIconConfig = new PinIconConfig(); + @SpriteSlot(x = 3) private final FriendNoteTagConfig friendNoteTagConfig = new FriendNoteTagConfig(); + @SpriteSlot(x = 4) @TextFieldSetting private final ConfigProperty friendPrefix = new ConfigProperty<>("&aⒻ"); @@ -36,31 +52,43 @@ public class BetterFriendsConfig extends AddonConfig { @SpriteSlot(x = 5) @SwitchSetting private final ConfigProperty friendRequestNotifications = new ConfigProperty<>(true); + @SettingRequires("friendRequestNotifications") @SpriteSlot(x = 6) @DropdownSetting private final ConfigProperty automaticFriendRequestReaction = new ConfigProperty<>( FriendRequestReaction.NONE); + @SpriteSlot(x = 7) @SwitchSetting private final ConfigProperty friendServerSwitchNotifications = new ConfigProperty<>(true); + @SpriteSlot(y = 1) @SwitchSetting private final ConfigProperty friendStatusUpdateNotifications = new ConfigProperty<>(true); + @SpriteSlot(x = 1, y = 1) @SwitchSetting private final ConfigProperty friendRemovalNotifications = new ConfigProperty<>(true); + @SpriteSlot(x = 2, y = 1) @SwitchSetting private final ConfigProperty friendRequestRemovalNotifications = new ConfigProperty<>(true); + @SpriteSlot(x = 3, y = 1) @SwitchSetting private final ConfigProperty labyChatMessageNotifications = new ConfigProperty<>(true); + @SettingRequires("labyChatMessageNotifications") @SpriteSlot(x = 4, y = 1) @SwitchSetting private final ConfigProperty ownLabyChatMessages = new ConfigProperty<>(true); + @SettingRequires("labyChatMessageNotifications") + @IntroducedIn(namespace = "betterfriends", value = "1.0.1") + @SwitchSetting + private final ConfigProperty showInteractionButtons = new ConfigProperty<>(true); + @SettingSection(value = "interactions", center = true) // @SpriteSlot(x = 5, y = 1) // @SwitchSetting @@ -78,15 +106,18 @@ public class BetterFriendsConfig extends AddonConfig { public ConfigProperty enabled() { return this.enabled; } + public ConfigProperty prefixColor() { return this.prefixColor; } + public PinIconConfig pinIconConfig() { return this.pinIconConfig; } public FriendNoteTagConfig friendNoteTagConfig() { return this.friendNoteTagConfig; } + public ConfigProperty friendPrefix() { return this.friendPrefix; } @@ -94,34 +125,46 @@ public ConfigProperty friendPrefix() { public ConfigProperty friendRequestNotifications() { return this.friendRequestNotifications; } + public ConfigProperty automaticFriendRequestReaction() { return this.automaticFriendRequestReaction; } + public ConfigProperty friendServerSwitchNotifications() { return this.friendServerSwitchNotifications; } + public ConfigProperty friendStatusUpdateNotifications() { return this.friendStatusUpdateNotifications; } + public ConfigProperty friendRemovalNotifications() { return this.friendRemovalNotifications; } + public ConfigProperty friendRequestRemovalNotifications() { return this.friendRequestRemovalNotifications; } + public ConfigProperty labyChatMessageNotifications() { return this.labyChatMessageNotifications; } + public ConfigProperty ownLabyChatMessages() { return this.ownLabyChatMessages; } + public ConfigProperty showInteractionButtons() { + return this.showInteractionButtons; + } + // public ConfigProperty noteEditorBullet() { // return this.noteEditorBullet; // } public ConfigProperty togglePinBullet() { return this.togglePinBullet; } + public ConfigProperty restartWhenMuted() { return this.restartWhenMuted; } diff --git a/core/src/main/java/com/rappytv/betterfriends/listeners/ChatReceiveListener.java b/core/src/main/java/com/rappytv/betterfriends/listeners/ChatReceiveListener.java index 15c440d..31951c6 100644 --- a/core/src/main/java/com/rappytv/betterfriends/listeners/ChatReceiveListener.java +++ b/core/src/main/java/com/rappytv/betterfriends/listeners/ChatReceiveListener.java @@ -47,8 +47,10 @@ public void onChatReceive(ChatReceiveEvent receiveEvent) { } receiveEvent.setMessage( - this.addon.getSerializer() - .deserialize(this.addon.configuration().friendPrefix().get()) + Component.empty() + .append(this.addon.getSerializer().deserialize( + this.addon.configuration().friendPrefix().get() + )) .append(Component.space()) .append(message) ); diff --git a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRemoveListener.java b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRemoveListener.java index 24e6fe9..a849989 100644 --- a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRemoveListener.java +++ b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRemoveListener.java @@ -1,7 +1,7 @@ package com.rappytv.betterfriends.listeners; import com.rappytv.betterfriends.BetterFriendsAddon; -import com.rappytv.betterfriends.utils.NameHelper; +import com.rappytv.betterfriends.utils.GroupHelper; import net.labymod.api.client.component.Component; import net.labymod.api.client.gui.icon.Icon; import net.labymod.api.event.Subscribe; @@ -25,7 +25,7 @@ public void onFriendRemove(LabyConnectFriendRemoveEvent event) { .title(Component.translatable("betterfriends.notifications.friendRemoval.title")) .text(Component.translatable( "betterfriends.notifications.friendRemoval.description", - NameHelper.getColoredName(event.friend()) + GroupHelper.getColoredName(event.friend()) )) .icon(Icon.head(event.friend().getUniqueId(), true)) .duration(15000) diff --git a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRequestReceiveListener.java b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRequestReceiveListener.java index db2ac5f..066d751 100644 --- a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRequestReceiveListener.java +++ b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRequestReceiveListener.java @@ -1,7 +1,7 @@ package com.rappytv.betterfriends.listeners; import com.rappytv.betterfriends.BetterFriendsAddon; -import com.rappytv.betterfriends.utils.NameHelper; +import com.rappytv.betterfriends.utils.GroupHelper; import net.labymod.api.Laby; import net.labymod.api.client.component.Component; import net.labymod.api.client.component.event.ClickEvent; @@ -25,7 +25,7 @@ public void onFriendRequestReceive(LabyConnectIncomingFriendRequestAddEvent even return; } IncomingFriendRequest request = event.request(); - Component sender = NameHelper.getColoredName(request.getName(), request.gameUser()) + Component sender = GroupHelper.getColoredName(request.getName(), request.gameUser()) .hoverEvent(HoverEvent.showText(Component.translatable( "betterfriends.general.labynet", NamedTextColor.DARK_PURPLE diff --git a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRequestRemoveListener.java b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRequestRemoveListener.java index 43efb4d..65b385c 100644 --- a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRequestRemoveListener.java +++ b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendRequestRemoveListener.java @@ -1,7 +1,7 @@ package com.rappytv.betterfriends.listeners; import com.rappytv.betterfriends.BetterFriendsAddon; -import com.rappytv.betterfriends.utils.NameHelper; +import com.rappytv.betterfriends.utils.GroupHelper; import net.labymod.api.client.component.Component; import net.labymod.api.client.gui.icon.Icon; import net.labymod.api.event.Subscribe; @@ -34,7 +34,7 @@ public void onFriendRequestRemove(LabyConnectOutgoingFriendRequestRemoveEvent ev .title(Component.translatable("betterfriends.notifications.friendRequestRemoval.title")) .text(Component.translatable( "betterfriends.notifications.friendRequestRemoval.description", - NameHelper.getColoredName(event.request().getName(), event.request().gameUser()) + GroupHelper.getColoredName(event.request().getName(), event.request().gameUser()) )) .icon(Icon.head(event.request().getUniqueId(), true)) .duration(15000) diff --git a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendServerStateListener.java b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendServerStateListener.java index 05b3e41..a7418f5 100644 --- a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendServerStateListener.java +++ b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendServerStateListener.java @@ -1,7 +1,7 @@ package com.rappytv.betterfriends.listeners; import com.rappytv.betterfriends.BetterFriendsAddon; -import com.rappytv.betterfriends.utils.NameHelper; +import com.rappytv.betterfriends.utils.GroupHelper; import net.labymod.api.client.component.Component; import net.labymod.api.client.component.event.ClickEvent; import net.labymod.api.client.component.event.HoverEvent; @@ -68,7 +68,7 @@ public void onServerUpdate(LabyConnectFriendServerEvent event) { this.addon.displayMessage( Component.empty() .append(BetterFriendsAddon.getPrefix()) - .append(NameHelper.getColoredName(friend)) + .append(GroupHelper.getColoredName(friend)) .append(Component.space()) .append(text) .color(NamedTextColor.GRAY) diff --git a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendStatusUpdateListener.java b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendStatusUpdateListener.java index c2e74a8..064e7b5 100644 --- a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendStatusUpdateListener.java +++ b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendStatusUpdateListener.java @@ -1,7 +1,7 @@ package com.rappytv.betterfriends.listeners; import com.rappytv.betterfriends.BetterFriendsAddon; -import com.rappytv.betterfriends.utils.NameHelper; +import com.rappytv.betterfriends.utils.GroupHelper; import net.labymod.api.client.component.Component; import net.labymod.api.client.component.format.NamedTextColor; import net.labymod.api.event.Subscribe; @@ -39,7 +39,7 @@ public void onStatusUpdate(LabyConnectFriendStatusEvent event) { .append(BetterFriendsAddon.getPrefix()) .append(Component.translatable( "betterfriends.notifications.statusUpdate.message", - NameHelper.getColoredName(event.friend()), + GroupHelper.getColoredName(event.friend()), stateComponent )) .color(NamedTextColor.GRAY) diff --git a/core/src/main/java/com/rappytv/betterfriends/listeners/LabyChatReceiveListener.java b/core/src/main/java/com/rappytv/betterfriends/listeners/LabyChatReceiveListener.java index 0d7b77f..85d0936 100644 --- a/core/src/main/java/com/rappytv/betterfriends/listeners/LabyChatReceiveListener.java +++ b/core/src/main/java/com/rappytv/betterfriends/listeners/LabyChatReceiveListener.java @@ -1,21 +1,20 @@ package com.rappytv.betterfriends.listeners; import com.rappytv.betterfriends.BetterFriendsAddon; -import com.rappytv.betterfriends.utils.NameHelper; +import com.rappytv.betterfriends.utils.GroupHelper; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; import net.labymod.api.Laby; import net.labymod.api.client.component.Component; import net.labymod.api.client.component.event.ClickEvent; import net.labymod.api.client.component.event.HoverEvent; import net.labymod.api.client.component.format.NamedTextColor; import net.labymod.api.event.Subscribe; - import net.labymod.api.event.labymod.labyconnect.session.chat.LabyConnectChatMessageEvent; import net.labymod.api.labyconnect.LabyConnectSession; import net.labymod.api.labyconnect.protocol.model.User; import net.labymod.api.labyconnect.protocol.model.chat.TextChatMessage; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; public class LabyChatReceiveListener { @@ -51,9 +50,9 @@ public void onChatReceive(LabyConnectChatMessageEvent event) { Component component = Component.empty() .append(BetterFriendsAddon.getPrefix()) - .append(NameHelper.getColoredName(sender.getName(), sender.gameUser())) + .append(GroupHelper.getColoredName(sender.getName(), sender.gameUser())) .append(Component.text(" → ", NamedTextColor.DARK_GRAY)) - .append(NameHelper.getColoredName(receiver.getName(), receiver.gameUser())) + .append(GroupHelper.getColoredName(receiver.getName(), receiver.gameUser())) .append(Component.text(" » ", NamedTextColor.DARK_GRAY)) .append(Component.text(message.getMessage(), NamedTextColor.WHITE)); @@ -68,7 +67,8 @@ public void onChatReceive(LabyConnectChatMessageEvent event) { )); } - if(receiver.getName().equals(Laby.labyAPI().getName())) { + if (receiver.getName().equals(Laby.labyAPI().getName()) + && this.addon.configuration().showInteractionButtons().get()) { component .append(Component .text(" ✔", NamedTextColor.GREEN) diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/activities/config/FriendlistActivity.java b/core/src/main/java/com/rappytv/betterfriends/ui/activities/config/FriendlistActivity.java new file mode 100644 index 0000000..d0ef0b6 --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/activities/config/FriendlistActivity.java @@ -0,0 +1,218 @@ +package com.rappytv.betterfriends.ui.activities.config; + +import com.rappytv.betterfriends.ui.widgets.FriendWidget; +import com.rappytv.betterfriends.utils.GroupHelper; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; +import net.labymod.api.Laby; +import net.labymod.api.client.component.Component; +import net.labymod.api.client.gui.screen.Parent; +import net.labymod.api.client.gui.screen.activity.AutoActivity; +import net.labymod.api.client.gui.screen.activity.Link; +import net.labymod.api.client.gui.screen.activity.Links; +import net.labymod.api.client.gui.screen.activity.types.SimpleActivity; +import net.labymod.api.client.gui.screen.widget.widgets.ComponentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.input.TextFieldWidget; +import net.labymod.api.client.gui.screen.widget.widgets.input.dropdown.DropdownWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.FlexibleContentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.ScrollWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.HorizontalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.VerticalListWidget; +import net.labymod.api.event.Phase; +import net.labymod.api.event.Subscribe; +import net.labymod.api.event.labymod.labyconnect.session.friend.LabyConnectFriendAddEvent; +import net.labymod.api.event.labymod.labyconnect.session.friend.LabyConnectFriendRemoveEvent; +import net.labymod.api.event.labymod.labyconnect.session.login.LabyConnectFriendAddBulkEvent; +import net.labymod.api.event.labymod.user.UserUpdateDataEvent; +import net.labymod.api.labyconnect.LabyConnectSession; +import net.labymod.api.labyconnect.protocol.model.User; +import net.labymod.api.labyconnect.protocol.model.chat.ChatMessage; +import net.labymod.api.labyconnect.protocol.model.friend.Friend; + +@Links({@Link("friend_list.lss"), @Link("friend.lss")}) +@AutoActivity +public class FriendlistActivity extends SimpleActivity { + + private final Function friendWidgetConstructor; + private final VerticalListWidget entries = new VerticalListWidget<>() + .addId("friends"); + private final ComponentWidget error = ComponentWidget.empty().addId("error"); + private ScrollWidget scroll; + private String filterQuery = ""; + private SortingStrategy sortingStrategy = SortingStrategy.ONLINE_STATUS; + + public FriendlistActivity(Function friendWidgetConstructor) { + this.friendWidgetConstructor = friendWidgetConstructor; + } + + @Override + public void initialize(Parent parent) { + super.initialize(parent); + + FlexibleContentWidget container = new FlexibleContentWidget().addId("container"); + + HorizontalListWidget filterSettings = new HorizontalListWidget() + .addId("filter-settings"); + + TextFieldWidget searchField = new TextFieldWidget(); + searchField.addId("search-field"); + searchField.placeholder(Component.translatable("labymod.ui.textfield.search")); + searchField.setText(this.filterQuery); + searchField.updateListener(this::applyFriendsFilter); + + DropdownWidget sortingDropdown = new DropdownWidget<>(); + for (SortingStrategy sortingStrategy : SortingStrategy.values()) { + sortingDropdown.add(sortingStrategy); + } + sortingDropdown.setSelected(this.sortingStrategy); + sortingDropdown.setTranslationKeyPrefix("betterfriends.settings.advancedFriendlist.sorting"); + sortingDropdown.setChangeListener(this::applySortingStrategy); + + filterSettings.addEntry(searchField); + filterSettings.addEntry(sortingDropdown); + + this.scroll = new ScrollWidget(this.entries); + this.entries.setComparator((f1, f2) -> { + if (!(f1 instanceof FriendWidget friendWidget1 && f2 instanceof FriendWidget friendWidget2)) { + return 0; + } + Friend friend1 = friendWidget1.getFriend(); + Friend friend2 = friendWidget2.getFriend(); + int comparePin = Boolean.compare(friend2.isPinned(), friend1.isPinned()); + if (comparePin != 0) { + return comparePin; + } + int selectedComparator = sortingDropdown.getSelected().compare(friend1, friend2); + if (selectedComparator != 0) { + return selectedComparator; + } + ChatMessage message1 = friend1.chat().getLastMessage(); + ChatMessage message2 = friend2.chat().getLastMessage(); + if (message1 != null || message2 != null) { + int compareLastMessage = Long.compare( + message2 != null ? message2.getTimestamp() : 0L, + message1 != null ? message1.getTimestamp() : 0L + ); + if (compareLastMessage != 0) { + return compareLastMessage; + } + } + return Long.compare(friend2.getLastOnline(), friend1.getLastOnline()); + }); + this.initializeFriendlist(false); + + container.addContent(filterSettings); + container.addContent(this.error); + container.addFlexibleContent(this.scroll); + this.document.addChild(container); + } + + private void initializeFriendlist(boolean initialized) { + this.entries.getChildren().clear(); + LabyConnectSession session = Laby.references().labyConnect().getSession(); + if (session == null || !session.isAuthenticated()) { + this.handleError("betterfriends.errors.notConnected"); + return; + } + if (session.getFriends().isEmpty()) { + this.handleError("betterfriends.errors.emptyList"); + return; + } + this.error.setVisible(false); + this.scroll.setVisible(true); + + List children = new ArrayList<>(); + + for (Friend friend : session.getFriends()) { + if (this.isUserInFilter(friend)) { + children.add(this.friendWidgetConstructor.apply(friend)); + } + } + + if (initialized) { + this.entries.addChildrenInitialized(children, true); + } else { + this.entries.addChildren(children, true); + } + } + + private void handleError(String translatable) { + this.error.setComponent(Component.translatable(translatable)); + this.error.setVisible(true); + this.scroll.setVisible(false); + } + + private void applyFriendsFilter(String query) { + this.filterQuery = query; + this.initializeFriendlist(true); + } + + private void applySortingStrategy(SortingStrategy strategy) { + this.sortingStrategy = strategy; + this.initializeFriendlist(true); + } + + private boolean hasFilter() { + return this.filterQuery != null && !this.filterQuery.isEmpty(); + } + + private boolean isUserInFilter(User user) { + return !this.hasFilter() || user.getName().toLowerCase() + .contains(this.filterQuery.toLowerCase()); + } + + @Subscribe + public void onUserUpdateData(final UserUpdateDataEvent event) { + if (event.phase() == Phase.POST) { + this.labyAPI.minecraft().executeOnRenderThread(() -> + FriendlistActivity.this.entries.reInitializeChildrenIf( + FriendWidget.class, + widget -> widget.getFriend().getUniqueId().equals( + event.gameUser().getUniqueId() + ) + ) + ); + } + } + + @Subscribe + public void onLabyConnectFriendAdd(LabyConnectFriendAddEvent event) { + this.entries.addChildAsync(this.friendWidgetConstructor.apply(event.friend())); + } + + @Subscribe + public void onLabyConnectFriendAddBulk(LabyConnectFriendAddBulkEvent event) { + for (Friend friend : event.getFriends()) { + this.entries.addChildAsync(this.friendWidgetConstructor.apply(friend)); + } + } + + @Subscribe + public void onLabyConnectFriendRemove(LabyConnectFriendRemoveEvent event) { + this.entries.removeChildIf( + FriendWidget.class, + widget -> widget.getFriend().getUniqueId().equals(event.friend().getUniqueId()) + ); + } + + private enum SortingStrategy { + ONLINE_STATUS((a, b) -> Boolean.compare(b.isOnline(), a.isOnline())), + ROLE(Comparator.comparingInt(a -> + GroupHelper.getGroupIndex(a.gameUser().visibleGroup().getIdentifier()) + )), + A_TO_Z((a, b) -> a.getName().compareToIgnoreCase(b.getName())), + Z_TO_A((a, b) -> b.getName().compareToIgnoreCase(a.getName())); + + private final Comparator sortingFunction; + + SortingStrategy(Comparator comparator) { + this.sortingFunction = comparator; + } + + public int compare(Friend f1, Friend f2) { + return this.sortingFunction.compare(f1, f2); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/badge/FriendPinBadge.java b/core/src/main/java/com/rappytv/betterfriends/ui/badge/FriendPinBadge.java index 5afe4d9..0267336 100644 --- a/core/src/main/java/com/rappytv/betterfriends/ui/badge/FriendPinBadge.java +++ b/core/src/main/java/com/rappytv/betterfriends/ui/badge/FriendPinBadge.java @@ -1,12 +1,10 @@ package com.rappytv.betterfriends.ui.badge; import com.rappytv.betterfriends.BetterFriendsAddon; -import net.labymod.api.Laby; import net.labymod.api.Textures; import net.labymod.api.client.entity.player.badge.renderer.BadgeRenderer; +import net.labymod.api.client.gui.screen.ScreenContext; import net.labymod.api.client.network.NetworkPlayerInfo; -import net.labymod.api.client.render.matrix.Stack; -import net.labymod.api.labyconnect.LabyConnectSession; import net.labymod.api.labyconnect.protocol.model.friend.Friend; public class FriendPinBadge extends BadgeRenderer { @@ -18,8 +16,8 @@ public FriendPinBadge(BetterFriendsAddon addon) { } @Override - public void render(Stack stack, float x, float y, NetworkPlayerInfo player) { - Textures.SpriteCommon.PIN.render(stack, x + 2.0F, y + 0.5F, 6f); + public void render(ScreenContext context, float x, float y, NetworkPlayerInfo player) { + Textures.SpriteCommon.PIN.render(context.stack(), x + 2.0F, y + 0.5F, 6f); } @Override @@ -28,13 +26,8 @@ protected boolean isVisible(NetworkPlayerInfo player) { || !this.addon.configuration().pinIconConfig().pinBadge().get()) { return false; } + Friend friend = BetterFriendsAddon.references().friendHelper().getFriend(player.profile()); - LabyConnectSession session = Laby.references().labyConnect().getSession(); - if (session == null || !session.isAuthenticated()) { - return false; - } - - Friend friend = session.getFriend(player.profile().getUniqueId()); return friend != null && friend.isPinned(); } } diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsFriendSnapshot.java b/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsFriendSnapshot.java new file mode 100644 index 0000000..5e289ca --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsFriendSnapshot.java @@ -0,0 +1,30 @@ +package com.rappytv.betterfriends.ui.snapshot; + +import com.rappytv.betterfriends.BetterFriendsAddon; +import com.rappytv.betterfriends.config.BetterFriendsConfig; +import net.labymod.api.client.entity.player.Player; +import net.labymod.api.laby3d.renderer.snapshot.AbstractLabySnapshot; +import net.labymod.api.laby3d.renderer.snapshot.Extras; +import net.labymod.api.labyconnect.protocol.model.friend.Friend; +import org.jetbrains.annotations.Nullable; + +public class BetterFriendsFriendSnapshot extends AbstractLabySnapshot { + + private final Friend friend; + private final BetterFriendsConfig config; + + public BetterFriendsFriendSnapshot(Player player, Extras extras, BetterFriendsAddon addon) { + super(extras); + this.friend = BetterFriendsAddon.references().friendHelper().getFriend(player.profile()); + this.config = addon.configuration(); + } + + @Nullable + public Friend friend() { + return this.friend; + } + + public BetterFriendsConfig config() { + return this.config; + } +} diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsFriendSnapshotProcessor.java b/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsFriendSnapshotProcessor.java new file mode 100644 index 0000000..b74bba5 --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsFriendSnapshotProcessor.java @@ -0,0 +1,26 @@ +package com.rappytv.betterfriends.ui.snapshot; + +import net.labymod.api.client.entity.Entity; +import net.labymod.api.client.entity.player.Player; +import net.labymod.api.client.render.state.entity.EntitySnapshotProcessor; +import net.labymod.api.client.render.state.entity.EntitySnapshotRegistry; +import net.labymod.api.laby3d.renderer.snapshot.ExtrasWriter; +import net.labymod.api.service.annotation.AutoService; + +@AutoService(EntitySnapshotProcessor.class) +public class BetterFriendsFriendSnapshotProcessor extends EntitySnapshotProcessor { + + public BetterFriendsFriendSnapshotProcessor(EntitySnapshotRegistry registry) { + super(registry); + } + + @Override + public boolean supports(Entity entity) { + return entity instanceof Player; + } + + @Override + public void process(Player player, float partialTicks, ExtrasWriter entityWriter) { + this.registry().captureSnapshot(entityWriter, BetterFriendsKeys.FRIEND, player); + } +} diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsKeys.java b/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsKeys.java new file mode 100644 index 0000000..c41fd0a --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsKeys.java @@ -0,0 +1,9 @@ +package com.rappytv.betterfriends.ui.snapshot; + +import net.labymod.api.laby3d.renderer.snapshot.ExtraKey; + +public class BetterFriendsKeys { + + public static final ExtraKey FRIEND = ExtraKey.of( + "better_friends_friend", BetterFriendsFriendSnapshot.class); +} diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsSnapshotFactory.java b/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsSnapshotFactory.java new file mode 100644 index 0000000..92aeba7 --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/snapshot/BetterFriendsSnapshotFactory.java @@ -0,0 +1,24 @@ +package com.rappytv.betterfriends.ui.snapshot; + +import com.rappytv.betterfriends.BetterFriendsAddon; +import net.labymod.api.client.entity.player.Player; +import net.labymod.api.laby3d.renderer.snapshot.Extras; +import net.labymod.api.laby3d.renderer.snapshot.LabySnapshotFactory; +import net.labymod.api.service.annotation.AutoService; + +@AutoService(LabySnapshotFactory.class) +public class BetterFriendsSnapshotFactory extends + LabySnapshotFactory { + + private final BetterFriendsAddon addon; + + public BetterFriendsSnapshotFactory(BetterFriendsAddon addon) { + super(BetterFriendsKeys.FRIEND); + this.addon = addon; + } + + @Override + protected BetterFriendsFriendSnapshot create(Player player, Extras extras) { + return new BetterFriendsFriendSnapshot(player, extras, this.addon); + } +} diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/tags/FriendNoteNameTag.java b/core/src/main/java/com/rappytv/betterfriends/ui/tags/FriendNoteNameTag.java index 704a5cb..881fe5b 100644 --- a/core/src/main/java/com/rappytv/betterfriends/ui/tags/FriendNoteNameTag.java +++ b/core/src/main/java/com/rappytv/betterfriends/ui/tags/FriendNoteNameTag.java @@ -1,20 +1,19 @@ package com.rappytv.betterfriends.ui.tags; import com.rappytv.betterfriends.BetterFriendsAddon; -import java.awt.*; -import net.labymod.api.Laby; -import net.labymod.api.client.entity.player.Player; +import com.rappytv.betterfriends.ui.snapshot.BetterFriendsFriendSnapshot; +import com.rappytv.betterfriends.ui.snapshot.BetterFriendsKeys; +import java.util.Collections; +import java.util.List; +import net.labymod.api.client.component.Component; import net.labymod.api.client.entity.player.tag.PositionType; -import net.labymod.api.client.entity.player.tag.tags.NameTag; -import net.labymod.api.client.entity.player.tag.tags.NameTagBackground; -import net.labymod.api.client.render.font.RenderableComponent; -import net.labymod.api.labyconnect.LabyConnectSession; +import net.labymod.api.client.entity.player.tag.tags.ComponentNameTag; +import net.labymod.api.client.render.state.entity.EntitySnapshot; import net.labymod.api.labyconnect.protocol.model.friend.Friend; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; -public class FriendNoteNameTag extends NameTag { +public class FriendNoteNameTag extends ComponentNameTag { - private static final int gray = new Color(0, 0, 0, 70).getRGB(); private final BetterFriendsAddon addon; private final PositionType position; @@ -24,57 +23,50 @@ public FriendNoteNameTag(BetterFriendsAddon addon, PositionType position) { } @Override - protected @Nullable RenderableComponent getRenderableComponent() { - if (this.entity == null || !(this.entity instanceof Player)) { - return null; + protected @NotNull List buildComponents(EntitySnapshot snapshot) { + if (this.snapshot.isDiscrete() + || this.snapshot.isInvisible() + || !this.snapshot.has(BetterFriendsKeys.FRIEND)) { + return super.buildComponents(snapshot); } + BetterFriendsFriendSnapshot friendSnapshot = snapshot.get(BetterFriendsKeys.FRIEND); - LabyConnectSession session = Laby.references().labyConnect().getSession(); - if (session == null || !session.isAuthenticated()) { - return null; + boolean condition = friendSnapshot.config().enabled().get() + && friendSnapshot.config().friendNoteTagConfig().enabled().get() + && friendSnapshot.config().friendNoteTagConfig().position().get() == this.position; + + if (!condition) { + return super.buildComponents(snapshot); } - Friend friend = session.getFriend(this.entity.getUniqueId()); + Friend friend = friendSnapshot.friend(); if (friend == null) { - return null; + return super.buildComponents(snapshot); } - String note = friend.getNote(); if (note != null && !note.isBlank()) { - return RenderableComponent.of(this.addon.getSerializer().deserialize(note)); - } else { - String defaultTag = this.addon.configuration().friendNoteTagConfig().defaultTag().get(); - if (defaultTag.isBlank()) { - return null; - } - return RenderableComponent.of(this.addon.getSerializer().deserialize(defaultTag)); + return Collections.singletonList(this.addon.getSerializer().deserialize(note)); } + String defaultTag = friendSnapshot.config().friendNoteTagConfig().defaultTag().get(); + if (defaultTag.isBlank()) { + return super.buildComponents(snapshot); + } + return Collections.singletonList(this.addon.getSerializer().deserialize(defaultTag)); } @Override - public float getScale() { - return (float) this.addon.configuration().friendNoteTagConfig().size().get() / 10; - } - - @Override - protected NameTagBackground getCustomBackground() { - boolean enabled = !this.addon.configuration().friendNoteTagConfig().hideBackground().get(); - NameTagBackground background = super.getCustomBackground(); - - if (background == null) { - background = NameTagBackground.custom(enabled, gray); + protected int getBackgroundColor(EntitySnapshot snapshot) { + BetterFriendsFriendSnapshot friendSnapshot = snapshot.get(BetterFriendsKeys.FRIEND); + if (friendSnapshot == null) { + return super.getBackgroundColor(snapshot); } - - background.setEnabled(enabled); - return background; + return friendSnapshot.config().friendNoteTagConfig().hideBackground().get() + ? 0 + : super.getBackgroundColor(snapshot); } @Override - public boolean isVisible() { - return this.addon.configuration().enabled().get() - && this.addon.configuration().friendNoteTagConfig().enabled().get() - && this.addon.configuration().friendNoteTagConfig().position().get() == this.position - && !this.entity.isCrouching() - && super.isVisible(); + public float getScale() { + return (float) this.addon.configuration().friendNoteTagConfig().size().get() / 10; } } diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/tags/FriendPinIconTag.java b/core/src/main/java/com/rappytv/betterfriends/ui/tags/FriendPinIconTag.java index 0e64ade..cf90ee9 100644 --- a/core/src/main/java/com/rappytv/betterfriends/ui/tags/FriendPinIconTag.java +++ b/core/src/main/java/com/rappytv/betterfriends/ui/tags/FriendPinIconTag.java @@ -1,44 +1,31 @@ package com.rappytv.betterfriends.ui.tags; -import com.rappytv.betterfriends.BetterFriendsAddon; -import net.labymod.api.Laby; +import com.rappytv.betterfriends.ui.snapshot.BetterFriendsFriendSnapshot; +import com.rappytv.betterfriends.ui.snapshot.BetterFriendsKeys; import net.labymod.api.Textures; -import net.labymod.api.client.entity.player.Player; import net.labymod.api.client.entity.player.tag.tags.IconTag; -import net.labymod.api.client.gui.icon.Icon; -import net.labymod.api.labyconnect.LabyConnectSession; import net.labymod.api.labyconnect.protocol.model.friend.Friend; public class FriendPinIconTag extends IconTag { - private final BetterFriendsAddon addon; - - public FriendPinIconTag(BetterFriendsAddon addon) { - super(8); - this.addon = addon; - } - - @Override - public Icon getIcon() { - return Textures.SpriteCommon.PIN; + public FriendPinIconTag() { + super(Textures.SpriteCommon.PIN, 8); } @Override public boolean isVisible() { - if (!this.addon.configuration().enabled().get() - || !this.addon.configuration().pinIconConfig().pinIcon().get()) { - return false; - } - if (this.entity == null || !(this.entity instanceof Player)) { + if (!this.snapshot.has(BetterFriendsKeys.FRIEND)) { return false; } - - LabyConnectSession session = Laby.references().labyConnect().getSession(); - if (session == null || !session.isAuthenticated()) { - return false; - } - - Friend friend = session.getFriend(this.entity.getUniqueId()); - return friend != null && friend.isPinned() && super.isVisible(); + BetterFriendsFriendSnapshot friendSnapshot = this.snapshot.get(BetterFriendsKeys.FRIEND); + + Friend friend = friendSnapshot.friend(); + return super.isVisible() + && !this.snapshot.isDiscrete() + && !this.snapshot.isInvisible() + && friendSnapshot.config().enabled().get() + && friendSnapshot.config().pinIconConfig().pinIcon().get() + && friend != null + && friend.isPinned(); } } diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/widgets/FriendWidget.java b/core/src/main/java/com/rappytv/betterfriends/ui/widgets/FriendWidget.java new file mode 100644 index 0000000..e36f2c7 --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/widgets/FriendWidget.java @@ -0,0 +1,102 @@ +package com.rappytv.betterfriends.ui.widgets; + +import java.util.List; +import net.labymod.api.Laby; +import net.labymod.api.Textures.SpriteCommon; +import net.labymod.api.client.component.Component; +import net.labymod.api.client.component.format.NamedTextColor; +import net.labymod.api.client.component.format.TextColor; +import net.labymod.api.client.gui.icon.Icon; +import net.labymod.api.client.gui.lss.property.annotation.AutoWidget; +import net.labymod.api.client.gui.screen.Parent; +import net.labymod.api.client.gui.screen.widget.widgets.ComponentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.input.ButtonWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.HorizontalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.renderer.IconWidget; +import net.labymod.api.labyconnect.protocol.model.UserStatus; +import net.labymod.api.labyconnect.protocol.model.friend.Friend; + +@AutoWidget +public abstract class FriendWidget extends HorizontalListWidget { + + protected final Friend friend; + + public FriendWidget(Friend friend) { + this.friend = friend; + } + + @Override + public void initialize(Parent parent) { + super.initialize(parent); + + UserStatus userStatus = this.friend.userStatus(); + IconWidget indicatorWidget = new IconWidget(SpriteCommon.STATUS_INDICATOR); + indicatorWidget.addId("indicator"); + indicatorWidget.color().set(userStatus.getColor().getValue()); + Component statusComponent = Component.translatable( + userStatus.getLocalTranslationKey(), + userStatus.textColor() + ); + + if (userStatus == UserStatus.OFFLINE) { + statusComponent = Component.translatable( + "betterfriends.notifications.statusUpdate.offline", + NamedTextColor.DARK_GRAY + ); + } + indicatorWidget.setHoverComponent(statusComponent); + + IconWidget headWidget = new IconWidget(Icon.head(this.friend.getUniqueId())) + .addId("player-head"); + headWidget.setHoverComponent(Component.translatable("betterfriends.general.labynet")); + headWidget.setPressable(() -> + Laby.references().chatExecutor().openUrl("https://laby.net/@" + this.friend.getUniqueId()) + ); + + ComponentWidget usernameWidget = ComponentWidget + .component(Component.text( + this.friend.getName(), + TextColor.color(this.friend.gameUser().visibleGroup().getColor().getRGB()) + )) + .addId("username"); + + usernameWidget.setHoverComponent(Component.translatable("betterfriends.general.copyUuid")); + usernameWidget.setPressable(() -> + Laby.references().chatExecutor().copyToClipboard(this.friend.getUniqueId().toString()) + ); + + this.addEntry(indicatorWidget); + this.addEntry(headWidget); + this.addEntry(usernameWidget); + + if (this.friend.isPinned()) { + IconWidget pinIconWidget = new IconWidget(SpriteCommon.PIN) + .addId("pin-icon"); + this.addEntry(pinIconWidget); + } + + List buttons = this.getButtons(); + + if(!buttons.isEmpty()) { + HorizontalListWidget buttonWrapper = new HorizontalListWidget().addId("buttons"); + + for(ButtonWidget button : buttons) { + buttonWrapper.addEntry(button); + } + + this.addEntry(buttonWrapper); + } + } + + public Friend getFriend() { + return this.friend; + } + + // Without this, the comparator just doesn't work for whatever reason + @Override + public int getSortingValue() { + return 1; + } + + public abstract List getButtons(); +} diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/widgets/FriendlistFriendWidget.java b/core/src/main/java/com/rappytv/betterfriends/ui/widgets/FriendlistFriendWidget.java new file mode 100644 index 0000000..649e95c --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/widgets/FriendlistFriendWidget.java @@ -0,0 +1,112 @@ +package com.rappytv.betterfriends.ui.widgets; + +import java.util.List; +import net.labymod.api.Laby; +import net.labymod.api.Textures; +import net.labymod.api.Textures.SpriteCommon; +import net.labymod.api.client.component.Component; +import net.labymod.api.client.component.format.NamedTextColor; +import net.labymod.api.client.gui.lss.property.annotation.AutoWidget; +import net.labymod.api.client.gui.screen.key.Key; +import net.labymod.api.client.gui.screen.widget.widgets.input.ButtonWidget; +import net.labymod.api.event.Subscribe; +import net.labymod.api.event.client.input.KeyEvent; +import net.labymod.api.labyconnect.protocol.model.friend.Friend; + +@AutoWidget +public class FriendlistFriendWidget extends FriendWidget { + + private static final Component X = Component.text("✘", NamedTextColor.RED); + private boolean skipConfirmation = false; + private boolean confirmRemoval = false; + + public FriendlistFriendWidget(Friend friend) { + super(friend); + Laby.labyAPI().eventBus().registerListener(this); + } + + @Override + public List getButtons() { + if(this.confirmRemoval) { + ButtonWidget removeConfirmationButton = ButtonWidget.icon(SpriteCommon.GREEN_CHECKED, () -> { + this.friend.remove(); + this.confirmRemoval = false; + this.reInitialize(); + }); + removeConfirmationButton.setHoverComponent(Component.translatable( + "betterfriends.settings.advancedFriendlist.removal.confirm", + NamedTextColor.GREEN + )); + ButtonWidget cancelButton = ButtonWidget.component(X, () -> { + this.confirmRemoval = false; + this.reInitialize(); + }); + cancelButton.setHoverComponent(Component.translatable( + "betterfriends.settings.advancedFriendlist.removal.cancel", + NamedTextColor.RED + )); + return List.of(removeConfirmationButton, cancelButton); + } + ButtonWidget pinButton = ButtonWidget.icon(Textures.SpriteCommon.PIN, () -> { + if (this.friend.isPinned()) { + this.friend.unpin(); + } else { + this.friend.pin(); + } + this.reInitialize(); + }).addId("pin-button"); + pinButton.setHoverComponent( + Component.translatable( + this.friend.isPinned() + ? "labymod.activity.labyconnect.chat.action.unpin" + : "labymod.activity.labyconnect.chat.action.pin" + ) + ); + ButtonWidget noteButton = ButtonWidget.icon( + SpriteCommon.PAINT, + this.friend::openNoteEditor + ).addId("note-button"); + noteButton.setHoverComponent(Component.translatable( + "labymod.activity.labyconnect.chat.action.note" + )); + ButtonWidget removeButton = ButtonWidget.component(X, () -> { + if (this.skipConfirmation) { + this.friend.remove(); + } else { + this.confirmRemoval = true; + this.reInitialize(); + } + }).addId("remove-button"); + removeButton.setHoverComponent( + Component + .translatable( + "betterfriends.settings.advancedFriendlist.removal.label", + NamedTextColor.RED + ) + .append(Component.newline()) + .append(Component.translatable( + "betterfriends.settings.advancedFriendlist.removal.skipConfirmation", + NamedTextColor.DARK_GRAY + )) + ); + return List.of(pinButton, noteButton, removeButton); + } + + @Subscribe + public void onKeyDown(KeyEvent event) { + if (event.key() != Key.L_SHIFT) { + return; + } + if (event.state() == KeyEvent.State.PRESS) { + this.skipConfirmation = true; + } else if (event.state() == KeyEvent.State.UNPRESSED) { + this.skipConfirmation = false; + } + } + + @Override + public void destroy() { + super.destroy(); + Laby.labyAPI().eventBus().unregisterListener(this); + } +} diff --git a/core/src/main/java/com/rappytv/betterfriends/utils/DefaultFriendHelper.java b/core/src/main/java/com/rappytv/betterfriends/utils/DefaultFriendHelper.java new file mode 100644 index 0000000..89d0558 --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/utils/DefaultFriendHelper.java @@ -0,0 +1,26 @@ +package com.rappytv.betterfriends.utils; + +import com.rappytv.betterfriends.api.FriendHelper; +import java.util.UUID; +import javax.inject.Singleton; +import net.labymod.api.Laby; +import net.labymod.api.labyconnect.LabyConnectSession; +import net.labymod.api.labyconnect.protocol.model.friend.Friend; +import net.labymod.api.models.Implements; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FriendHelper.class) +public class DefaultFriendHelper implements FriendHelper { + + @Override + @Nullable + public Friend getFriend(UUID uuid) { + LabyConnectSession session = Laby.references().labyConnect().getSession(); + if (session == null || !session.isAuthenticated()) { + return null; + } + + return session.getFriend(uuid); + } +} diff --git a/core/src/main/java/com/rappytv/betterfriends/utils/GroupHelper.java b/core/src/main/java/com/rappytv/betterfriends/utils/GroupHelper.java new file mode 100644 index 0000000..6f5aa2e --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/utils/GroupHelper.java @@ -0,0 +1,52 @@ +package com.rappytv.betterfriends.utils; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.util.ArrayList; +import java.util.List; +import net.labymod.api.Constants.LegacyUrls; +import net.labymod.api.client.component.Component; +import net.labymod.api.labyconnect.protocol.model.friend.Friend; +import net.labymod.api.user.GameUser; +import net.labymod.api.user.group.Group; +import net.labymod.api.util.GsonUtil; +import net.labymod.api.util.io.web.request.Request; + +public class GroupHelper { + + private static final List groupIds = new ArrayList<>(); + + public static void registerGroupIds() { + Request.ofGson(JsonElement.class) + .url(LegacyUrls.GROUPS) + .execute(response -> { + if (response.hasException()) { + return; + } + JsonElement element = response.get(); + if (!element.isJsonObject()) { + return; + } + JsonObject groupContainer = element.getAsJsonObject(); + + for(JsonElement groupElement : groupContainer.getAsJsonArray("groups")) { + Group group = GsonUtil.DEFAULT_GSON.fromJson(groupElement, Group.class); + group.initialize(); + groupIds.add(group.getIdentifier()); + } + }); + } + + public static int getGroupIndex(int identifier) { + int index = groupIds.indexOf(identifier); + return index != -1 ? index : groupIds.size(); + } + + public static Component getColoredName(Friend friend) { + return getColoredName(friend.getName(), friend.gameUser()); + } + + public static Component getColoredName(String name, GameUser user) { + return Component.text(name, user.visibleGroup().getTextColor()); + } +} diff --git a/core/src/main/java/com/rappytv/betterfriends/utils/NameHelper.java b/core/src/main/java/com/rappytv/betterfriends/utils/NameHelper.java deleted file mode 100644 index d68389d..0000000 --- a/core/src/main/java/com/rappytv/betterfriends/utils/NameHelper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.rappytv.betterfriends.utils; - -import net.labymod.api.client.component.Component; -import net.labymod.api.labyconnect.protocol.model.friend.Friend; -import net.labymod.api.user.GameUser; - -public class NameHelper { - - public static Component getColoredName(Friend friend) { - return getColoredName(friend.getName(), friend.gameUser()); - } - - public static Component getColoredName(String name, GameUser user) { - return Component.text(name, user.visibleGroup().getTextColor()); - } -} diff --git a/core/src/main/resources/assets/betterfriends/i18n/en_us.json b/core/src/main/resources/assets/betterfriends/i18n/en_us.json index 6c2d601..3ff6e5c 100644 --- a/core/src/main/resources/assets/betterfriends/i18n/en_us.json +++ b/core/src/main/resources/assets/betterfriends/i18n/en_us.json @@ -22,6 +22,21 @@ "name": "Prefix color", "description": "This option changes the color theme of the \"BF »\" prefix which is prepended to addon messages." }, + "advancedFriendlist": { + "name": "Advanced friend list", + "removal": { + "label": "Remove friend", + "skipConfirmation": "(Hold shift to skip confirmation)", + "confirm": "Confirm removal", + "cancel": "Cancel removal" + }, + "sorting": { + "onlineStatus": "Online status", + "role": "LabyMod role", + "aToZ": "A-Z", + "zToA": "Z-A" + } + }, "pinIconConfig": { "name": "Friend pin icon", "pinIcon": { @@ -95,6 +110,10 @@ "name": "Own LabyChat Messages", "description": "If you also want to see your own sent messages in chat." }, + "showInteractionButtons": { + "name": "Show interaction buttons", + "description": "If you want to have LabyChat messages to have interactable buttons" + }, "noteEditorBullet": { "name": "Show friend note editor bullet", "description": "Toggles the visibility of the interaction bullet which can open the note editor of a friend." @@ -133,10 +152,12 @@ } }, "general": { - "labynet": "Open on laby.net" + "labynet": "Open on laby.net", + "copyUuid": "Click to copy UUID" }, "errors": { - "notConnected": "You're not connected to the LabyChat!" + "notConnected": "You're not connected to the LabyChat!", + "emptyList": "Your friend list is empty!" }, "command": { "usage": "Usage: %s", diff --git a/core/src/main/resources/assets/betterfriends/themes/vanilla/lss/friend_list.lss b/core/src/main/resources/assets/betterfriends/themes/vanilla/lss/friend_list.lss new file mode 100644 index 0000000..5eed99a --- /dev/null +++ b/core/src/main/resources/assets/betterfriends/themes/vanilla/lss/friend_list.lss @@ -0,0 +1,96 @@ +.container { + height: 100%; + width: 100%; + + .filter-settings { + width: 99%; + height: 20; + + TextField { + width: 60%; + } + + Dropdown { + width: 40%; + } + } + + Scroll { + width: 100%; + height: 100%; + + .friends { + margin-top: 5; + width: 100%; + height: fit-content; + } + + Scrollbar { + left: 0; + top: 0; + width: 5; + height: 100%; + margin-left: 5; + } + } + + .error { + font-size: 0.8; + text-color: red; + width: fit-content; + margin-top: 100; + alignment-x: center; + alignment-y: center; + } +} + +FriendlistFriend { + width: 100%; + height: 20; + padding: 1; + + .player-head { + margin-left: 3; + width: 16; + height: width; + } + + .indicator { + width: 8; + height: width; + } + + .username { + max-width: 80%; + width: fit-content; + scale-to-fit: true; + max-lines: 1; + margin-left: 2; + top: 2; + } + + .pin-icon { + margin-left: 3; + width: 8; + height: width; + } + + .buttons { + height: fit-content; + width: fit-content; + alignment: right; + + Button { + width: 20; + height: width; + padding: 0; + } + + .pin-button { + .button-icon { + width: 50%; + height: width; + } + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 6168956..9243d49 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ org.gradle.jvmargs=-Xmx4096m -net.labymod.minecraft-versions=1.8.9;1.12.2;1.16.5;1.17.1;1.18.2;1.19.2;1.19.3;1.19.4;1.20.1;1.20.2;1.20.4;1.20.5;1.20.6;1.21;1.21.1;1.21.3;1.21.4 \ No newline at end of file +net.labymod.minecraft-versions=1.8.9;1.12.2;1.16.5;1.17.1;1.18.2;1.19.2;1.19.3;1.19.4;1.20.1;1.20.2;1.20.4;1.20.5;1.20.6;1.21;1.21.1;1.21.3;1.21.4;1.21.5;1.21.8;1.21.10 \ No newline at end of file