diff --git a/core/src/main/java/com/rappytv/betterfriends/BetterFriendsAddon.java b/core/src/main/java/com/rappytv/betterfriends/BetterFriendsAddon.java index 4a64d06..ff86bcb 100644 --- a/core/src/main/java/com/rappytv/betterfriends/BetterFriendsAddon.java +++ b/core/src/main/java/com/rappytv/betterfriends/BetterFriendsAddon.java @@ -19,6 +19,9 @@ 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.FriendshipExpirationHandler; +import com.rappytv.betterfriends.utils.GroupHelper; +import com.rappytv.betterfriends.utils.VoiceChatHelper; import net.labymod.api.Laby; import net.labymod.api.addon.LabyAddon; import net.labymod.api.client.component.Component; @@ -36,15 +39,20 @@ public class BetterFriendsAddon extends LabyAddon { private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacyAmpersand(); private static BetterFriendsAddon instance; + private final VoiceChatHelper voiceChatHelper = new VoiceChatHelper(); + private FriendshipExpirationHandler expirationHandler; @Override protected void enable() { instance = this; this.registerSettingCategory(); + GroupHelper.registerGroupIds(); + this.expirationHandler = new FriendshipExpirationHandler(this); this.registerCommand(new BetterFriendsCommand()); this.registerListener(new ChatReceiveListener(this)); + this.registerListener(this.expirationHandler); this.registerListener(new FriendAddListener(this)); this.registerListener(new FriendRemoveListener(this)); this.registerListener(new FriendRequestReceiveListener(this)); @@ -101,6 +109,18 @@ public static TextComponent getPrefix() { .append(Component.space()); } + public static BetterFriendsAddon getInstance() { + return instance; + } + + public FriendshipExpirationHandler getExpirationHandler() { + return this.expirationHandler; + } + + public VoiceChatHelper getVoiceChatHelper() { + return this.voiceChatHelper; + } + public LegacyComponentSerializer getSerializer() { return this.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..afeebd0 100644 --- a/core/src/main/java/com/rappytv/betterfriends/config/BetterFriendsConfig.java +++ b/core/src/main/java/com/rappytv/betterfriends/config/BetterFriendsConfig.java @@ -1,8 +1,13 @@ package com.rappytv.betterfriends.config; import com.rappytv.betterfriends.config.subconfig.FriendNoteTagConfig; +import com.rappytv.betterfriends.config.subconfig.FriendlistExpiryConfig; 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; @@ -13,6 +18,7 @@ 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 { @@ -24,6 +30,15 @@ public class BetterFriendsConfig extends AddonConfig { @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); + } + + private final FriendlistExpiryConfig expiryConfig = new FriendlistExpiryConfig(); + @SpriteSlot(size = 8, x = 4) private final PinIconConfig pinIconConfig = new PinIconConfig(); @SpriteSlot(x = 3) @@ -81,6 +96,9 @@ public ConfigProperty enabled() { public ConfigProperty prefixColor() { return this.prefixColor; } + public FriendlistExpiryConfig expiryConfig() { + return this.expiryConfig; + } public PinIconConfig pinIconConfig() { return this.pinIconConfig; } diff --git a/core/src/main/java/com/rappytv/betterfriends/config/subconfig/FriendlistExpiryConfig.java b/core/src/main/java/com/rappytv/betterfriends/config/subconfig/FriendlistExpiryConfig.java new file mode 100644 index 0000000..1a5db3b --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/config/subconfig/FriendlistExpiryConfig.java @@ -0,0 +1,49 @@ +package com.rappytv.betterfriends.config.subconfig; + +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.configuration.loader.Config; +import net.labymod.api.configuration.loader.annotation.Exclude; +import net.labymod.api.configuration.loader.property.ConfigProperty; +import net.labymod.api.util.MethodOrder; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class FriendlistExpiryConfig extends Config { + + @Exclude + private final Map expirations = new HashMap<>(); + + @SwitchSetting + private final ConfigProperty sendNotifications = new ConfigProperty<>(true); + + @SwitchSetting + private final ConfigProperty notifyOnExpiration = new ConfigProperty<>(true); + + @MethodOrder(after = "notifyOnExpiration") + @ActivitySetting + public Activity manageExpirationDates() { + return null; + } + + public void addExpiration(UUID uuid, long expiration) { + this.expirations.put(uuid, expiration); + } + + public void removeExpiration(UUID uuid) { + this.expirations.remove(uuid); + } + + public Map getExpirations() { + return this.expirations; + } + + public ConfigProperty sendNotifications() { + return this.sendNotifications; + } + public ConfigProperty notifyOnExpiration() { + return this.notifyOnExpiration; + } +} diff --git a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendAddListener.java b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendAddListener.java index aac122b..d37d067 100644 --- a/core/src/main/java/com/rappytv/betterfriends/listeners/FriendAddListener.java +++ b/core/src/main/java/com/rappytv/betterfriends/listeners/FriendAddListener.java @@ -1,12 +1,14 @@ package com.rappytv.betterfriends.listeners; import com.rappytv.betterfriends.BetterFriendsAddon; -import net.labymod.addons.voicechat.api.audio.stream.AudioStreamState; -import net.labymod.addons.voicechat.api.client.VoiceConnector; -import net.labymod.addons.voicechat.core.VoiceChatAddon; +import com.rappytv.betterfriends.ui.activities.FriendlistExpirationActivity; import net.labymod.api.Laby; +import net.labymod.api.client.component.Component; +import net.labymod.api.client.gui.icon.Icon; import net.labymod.api.event.Subscribe; import net.labymod.api.event.labymod.labyconnect.session.friend.LabyConnectFriendAddEvent; +import net.labymod.api.notification.Notification; +import net.labymod.api.notification.Notification.NotificationButton; public class FriendAddListener { @@ -18,28 +20,28 @@ public FriendAddListener(BetterFriendsAddon addon) { @Subscribe public void onFriendAdd(LabyConnectFriendAddEvent event) { - if (!this.addon.configuration().restartWhenMuted().get()) { - return; + if(this.addon.configuration().expiryConfig().sendNotifications().get()) { + Notification.builder() + .title(Component.translatable("betterfriends.notifications.friendshipExpiration.set.title")) + .text(Component.translatable("betterfriends.notifications.friendshipExpiration.set.description")) + .icon(Icon.head(event.friend().getUniqueId(), true)) + .addButton(NotificationButton.of( + Component.translatable("betterfriends.notifications.friendshipExpiration.set.button"), + () -> Laby.labyAPI().minecraft().minecraftWindow().displayScreen( + new FriendlistExpirationActivity(event.friend()) + ) + )) + .duration(20000) + .buildAndPush(); } - if (!Laby.labyAPI().addonService().isEnabled("voicechat")) { + if (!this.addon.configuration().restartWhenMuted().get()) { return; } - - VoiceConnector client = VoiceChatAddon.INSTANCE.client(); - if (!client.isAuthenticated() || !this.isSelfMuted()) { + if (!this.addon.getVoiceChatHelper().isEnabled()) { return; } - client.disconnect(); - client.connect(); + this.addon.getVoiceChatHelper().reconnect(); } - - private boolean isSelfMuted() { - return VoiceChatAddon.INSTANCE - .referenceStorage() - .audioStreamRegistry() - .getState(Laby.labyAPI().getUniqueId(), false) == AudioStreamState.INPUT_GLOBAL_MUTED; - } - } 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..760fde3 100644 --- a/core/src/main/java/com/rappytv/betterfriends/listeners/LabyChatReceiveListener.java +++ b/core/src/main/java/com/rappytv/betterfriends/listeners/LabyChatReceiveListener.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; @@ -51,9 +51,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)); diff --git a/core/src/main/java/com/rappytv/betterfriends/ui/activities/FriendlistExpirationActivity.java b/core/src/main/java/com/rappytv/betterfriends/ui/activities/FriendlistExpirationActivity.java new file mode 100644 index 0000000..5cc6410 --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/activities/FriendlistExpirationActivity.java @@ -0,0 +1,148 @@ +package com.rappytv.betterfriends.ui.activities; + +import com.rappytv.betterfriends.BetterFriendsAddon; +import com.rappytv.betterfriends.utils.FriendshipExpirationHandler; +import com.rappytv.betterfriends.utils.FriendshipExpirationHandler.FriendshipExpirationType; +import com.rappytv.betterfriends.utils.VoiceChatHelper; +import net.labymod.api.Laby; +import net.labymod.api.client.component.Component; +import net.labymod.api.client.component.format.NamedTextColor; +import net.labymod.api.client.gui.icon.Icon; +import net.labymod.api.client.gui.screen.Parent; +import net.labymod.api.client.gui.screen.ScreenInstance; +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.types.SimpleActivity; +import net.labymod.api.client.gui.screen.widget.Widget; +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.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.list.HorizontalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.VerticalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.renderer.IconWidget; +import net.labymod.api.labyconnect.protocol.model.friend.Friend; +import net.labymod.api.util.I18n; + +@Link("expiration.lss") +@AutoActivity +public class FriendlistExpirationActivity extends SimpleActivity { + + private final FriendshipExpirationHandler handler; + private final VoiceChatHelper voiceChatHelper; + private final Friend friend; + private TextFieldWidget customExpirationField; + + public FriendlistExpirationActivity(Friend friend) { + this.handler = BetterFriendsAddon.getInstance().getExpirationHandler(); + this.voiceChatHelper = BetterFriendsAddon.getInstance().getVoiceChatHelper(); + this.friend = friend; + } + + @Override + public void initialize(Parent parent) { + super.initialize(parent); + + FlexibleContentWidget window = new FlexibleContentWidget() + .addId("window"); + + HorizontalListWidget profileWrapper = new HorizontalListWidget() + .addId("header"); + IconWidget headIcon = new IconWidget(Icon.head(this.friend.getUniqueId())).addId("head"); + ComponentWidget nameComponent = ComponentWidget.i18n( + "betterfriends.settings.advancedFriendlist.expiration.title", + this.friend.getName() + ).addId("username"); + VerticalListWidget content = new VerticalListWidget<>().addId("content"); + + profileWrapper.addEntry(headIcon); + profileWrapper.addEntry(nameComponent); + + DropdownWidget expirationTypeDropdown = new DropdownWidget<>(); + if(Laby.labyAPI().minecraft().isIngame()) { + expirationTypeDropdown.add(FriendshipExpirationType.ON_SERVER_LEAVE); + } + if(this.voiceChatHelper.isEnabled()) { + long expiration = this.voiceChatHelper.getMuteExpiration(this.friend.getUniqueId()); + if(expiration != -1) { + expirationTypeDropdown.add(FriendshipExpirationType.ON_MUTE_EXPIRATION); + } + } + expirationTypeDropdown.add(FriendshipExpirationType.CUSTOM_DATE); + expirationTypeDropdown.setSelected(expirationTypeDropdown.entries().getFirst()); + expirationTypeDropdown.setTranslationKeyPrefix("betterfriends.settings.advancedFriendlist.expiration.types"); + + this.customExpirationField = new TextFieldWidget(); + this.customExpirationField.placeholder(Component.text(I18n.translate( + "betterfriends.settings.advancedFriendlist.expiration.forExample" + ) + " 6h, 2d, 4w, 1y")); + this.customExpirationField.setVisible(expirationTypeDropdown.getSelected() == FriendshipExpirationType.CUSTOM_DATE); + + ButtonWidget saveButton = ButtonWidget.i18n( + "betterfriends.settings.advancedFriendlist.expiration.button", () -> { + switch (expirationTypeDropdown.getSelected()) { + case ON_SERVER_LEAVE -> + this.handler.scheduleServerLeaveExpiration(this.friend.getUniqueId()); + case ON_MUTE_EXPIRATION -> + this.handler.scheduleVoiceUnmuteExpiration(this.friend.getUniqueId()); + case CUSTOM_DATE -> { + long duration = getDuration(this.customExpirationField.getText()); + this.handler.scheduleCustomExpiration( + this.friend.getUniqueId(), + System.currentTimeMillis() + duration + ); + } + } + Laby.references().chatExecutor().displayClientMessage( + Component.empty() + .append(BetterFriendsAddon.getPrefix()) + .append(Component.translatable( + "betterfriends.settings.advancedFriendlist.expiration.success", + NamedTextColor.GREEN + )) + ); + Laby.labyAPI().minecraft().minecraftWindow().displayScreen((ScreenInstance) null); + }); + Runnable updateButtonVisibility = () -> saveButton.setEnabled( + expirationTypeDropdown.getSelected() != FriendshipExpirationType.CUSTOM_DATE + || getDuration(this.customExpirationField.getText()) > 0 + ); + expirationTypeDropdown.setChangeListener((type) -> { + this.customExpirationField.setVisible(type == FriendshipExpirationType.CUSTOM_DATE); + updateButtonVisibility.run(); + }); + this.customExpirationField.updateListener((text) -> updateButtonVisibility.run()); + updateButtonVisibility.run(); + + window.addContent(profileWrapper); + window.addContent(content); + + content.addChild(expirationTypeDropdown); + content.addChild(this.customExpirationField); + content.addChild(saveButton); + + this.document.addChild(window); + } + + public static long getDuration(String timeArg) { + String format; + long duration; + try { + format = timeArg.substring(timeArg.length() - 1); + duration = Integer.parseInt(timeArg.substring(0, timeArg.length() - 1)); + } catch (NumberFormatException | IndexOutOfBoundsException e) { + return -1; + } + + return switch (format) { + case "s" -> duration * 1000; + case "m" -> duration * 1000 * 60; + case "h" -> duration * 1000 * 60 * 60; + case "d" -> duration * 1000 * 60 * 60 * 24; + case "w" -> duration * 1000 * 60 * 60 * 24 * 7; + case "y" -> duration * 1000 * 60 * 60 * 24 * 7 * 52; + default -> -1; + }; + } +} 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..34a9764 --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/activities/config/FriendlistActivity.java @@ -0,0 +1,248 @@ +package com.rappytv.betterfriends.ui.activities.config; + +import com.rappytv.betterfriends.ui.widgets.FriendWidget; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; +import com.rappytv.betterfriends.utils.GroupHelper; +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.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.LabyConnectStateUpdateEvent; +import net.labymod.api.event.labymod.labyconnect.session.friend.LabyConnectFriendAddEvent; +import net.labymod.api.event.labymod.labyconnect.session.friend.LabyConnectFriendPinUpdateEvent; +import net.labymod.api.event.labymod.labyconnect.session.friend.LabyConnectFriendRemoveEvent; +import net.labymod.api.event.labymod.labyconnect.session.friend.LabyConnectFriendStatusEvent; +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; + +@Link("friend_list.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<>(); + int addedOfflineFriends = 0; + + for (Friend friend : session.getFriends()) { + boolean offline = !friend.isOnline(); + if ((!offline || addedOfflineFriends < 100 || friend.isPinned()) && this.isUserInFilter( + friend)) { + children.add(this.friendWidgetConstructor.apply(friend)); + if (offline) { + addedOfflineFriends++; + } + } + } + + 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()) + ); + } + + @Subscribe + public void onLabyConnectStateUpdate(LabyConnectStateUpdateEvent event) { + this.initializeFriendlist(true); + } + + @Subscribe + public void onLabyConnectStateUpdate(LabyConnectFriendPinUpdateEvent event) { + this.entries.reInitializeChildrenIf( + FriendWidget.class, + widget -> widget.getFriend().getUniqueId().equals(event.friend().getUniqueId()) + ); + } + + @Subscribe + public void onLabyConnectFriendStatus(LabyConnectFriendStatusEvent event) { + this.entries.reInitializeChildrenIf( + FriendWidget.class, + widget -> widget.getFriend().getUniqueId().equals(event.friend().getUniqueId()) + ); + } + + private enum SortingStrategy { + ONLINE_STATUS((a, b) -> Boolean.compare(b.isOnline(), a.isOnline())), + ROLE((a, b) -> Integer.compare( + GroupHelper.getGroupIndex(a.gameUser().visibleGroup().getIdentifier()), + GroupHelper.getGroupIndex(b.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 BiFunction sortingFunction; + + SortingStrategy(BiFunction sortingFunction) { + this.sortingFunction = sortingFunction; + } + + public int compare(Friend f1, Friend f2) { + return this.sortingFunction.apply(f1, f2); + } + } +} \ No newline at end of file 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..5e8602a --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/widgets/FriendWidget.java @@ -0,0 +1,104 @@ +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.activity.Link; +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; + +@Link("friend.lss") +@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().getRGB()); + 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..91b9c69 --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/ui/widgets/FriendlistFriendWidget.java @@ -0,0 +1,120 @@ +package com.rappytv.betterfriends.ui.widgets; + +import java.util.List; +import com.rappytv.betterfriends.ui.activities.FriendlistExpirationActivity; +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; + }); + 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(); + } + }).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 expirationButton = ButtonWidget.icon( + SpriteCommon.QUESTION_MARK, // TODO: Add real icon + () -> Laby.labyAPI().minecraft().minecraftWindow().displayScreen( + new FriendlistExpirationActivity(this.friend) + ) + ).addId("expiration-button"); + expirationButton.setHoverComponent(Component.translatable( + "betterfriends.settings.advancedFriendlist.expiration.label" + )); + 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, expirationButton, 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/FriendshipExpirationHandler.java b/core/src/main/java/com/rappytv/betterfriends/utils/FriendshipExpirationHandler.java new file mode 100644 index 0000000..aec8b3e --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/utils/FriendshipExpirationHandler.java @@ -0,0 +1,139 @@ +package com.rappytv.betterfriends.utils; + +import com.rappytv.betterfriends.BetterFriendsAddon; +import com.rappytv.betterfriends.config.subconfig.FriendlistExpiryConfig; +import net.labymod.api.Laby; +import net.labymod.api.client.component.Component; +import net.labymod.api.client.component.format.NamedTextColor; +import net.labymod.api.client.gui.icon.Icon; +import net.labymod.api.event.Subscribe; +import net.labymod.api.event.client.network.server.ServerDisconnectEvent; +import net.labymod.api.event.labymod.labyconnect.LabyConnectStateUpdateEvent; +import net.labymod.api.labyconnect.LabyConnectSession; +import net.labymod.api.labyconnect.protocol.LabyConnectState; +import net.labymod.api.notification.Notification; +import net.labymod.api.util.I18n; +import net.labymod.api.util.concurrent.task.Task; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class FriendshipExpirationHandler { + + private final Set serverLeaveExpirations = new HashSet<>(); + private final BetterFriendsAddon addon; + private final FriendlistExpiryConfig config; + + public FriendshipExpirationHandler(BetterFriendsAddon addon) { + this.addon = addon; + this.config = addon.configuration().expiryConfig(); + + Task.builder(this::removeExpiredFriendships).repeat(1, TimeUnit.MINUTES).build().execute(); + } + + public void removeExpiredFriendships() { + List toRemove = new ArrayList<>(); + for (UUID uuid : this.config.getExpirations().keySet()) { + if(this.config.getExpirations().get(uuid) > System.currentTimeMillis()) { + continue; + } + if(!this.removeFriend(uuid)) { + continue; + } + toRemove.add(uuid); + if(!this.config.notifyOnExpiration().get()) { + continue; + } + Laby.labyAPI().labyNetController().loadNameByUniqueId(uuid, (name) -> + Notification.builder() + .title(Component.translatable("betterfriends.notifications.friendshipExpiration.expired.title")) + .text(Component.translatable( + "betterfriends.notifications.friendshipExpiration.expired.description", + Component.text( + Objects.requireNonNull(name.getOrDefault(I18n.translate( + "betterfriends.notifications.friendshipExpiration.expired.unknown" + ))), + NamedTextColor.AQUA + ) + )) + .icon(Icon.head(uuid, true)) + .duration(20000) + .buildAndPush()); + } + toRemove.forEach(this.config::removeExpiration); + } + + public void scheduleCustomExpiration(UUID uuid, long expiration) { + this.config.addExpiration(uuid, expiration); + } + + public void scheduleVoiceUnmuteExpiration(UUID uuid) { + long expiration = this.addon.getVoiceChatHelper().getMuteExpiration(uuid); + if(expiration != -1) { + this.config.addExpiration(uuid, expiration); + } + } + + public void scheduleServerLeaveExpiration(UUID uuid) { + this.serverLeaveExpirations.add(uuid); + } + + @Subscribe + public void onLabyChatConnect(LabyConnectStateUpdateEvent event) { + if(event.state() == LabyConnectState.PLAY) { + this.removeExpiredFriendships(); + } + } + + @Subscribe + public void onServerLeave(ServerDisconnectEvent event) { + List toRemove = new ArrayList<>(); + for(UUID uuid : this.serverLeaveExpirations) { + if(!this.removeFriend(uuid)) { + continue; + } + toRemove.add(uuid); + if(!this.config.notifyOnExpiration().get()) { + continue; + } + Laby.labyAPI().labyNetController().loadNameByUniqueId(uuid, (name) -> + Notification.builder() + .title(Component.translatable("betterfriends.notifications.friendshipExpiration.expired.title")) + .text(Component.translatable( + "betterfriends.notifications.friendshipExpiration.expired.disconnectDescription", + Component.text( + Objects.requireNonNull(name.getOrDefault(I18n.translate( + "betterfriends.notifications.friendshipExpiration.expired.unknown" + ))), + NamedTextColor.AQUA + ) + )) + .icon(Icon.head(uuid, true)) + .duration(20000) + .buildAndPush()); + } + toRemove.forEach(this.serverLeaveExpirations::remove); + } + + @SuppressWarnings("all") + private boolean removeFriend(UUID uuid) { + LabyConnectSession session = Laby.labyAPI().labyConnect().getSession(); + if(session == null || !session.isAuthenticated()) { + return false; + } + if(!Laby.labyAPI().labyModLoader().isAddonDevelopmentEnvironment()) { + session.removeFriend(uuid); + } + return true; + } + + public enum FriendshipExpirationType { + ON_SERVER_LEAVE, + ON_MUTE_EXPIRATION, + CUSTOM_DATE + } +} 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..0a10e09 --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/utils/GroupHelper.java @@ -0,0 +1,53 @@ +package com.rappytv.betterfriends.utils; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +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; +import java.util.ArrayList; +import java.util.List; + +public class GroupHelper { + + private static final List groupIds = new ArrayList<>(); + + @SuppressWarnings("deprecation") + 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/java/com/rappytv/betterfriends/utils/VoiceChatHelper.java b/core/src/main/java/com/rappytv/betterfriends/utils/VoiceChatHelper.java new file mode 100644 index 0000000..47e603c --- /dev/null +++ b/core/src/main/java/com/rappytv/betterfriends/utils/VoiceChatHelper.java @@ -0,0 +1,46 @@ +package com.rappytv.betterfriends.utils; + +import net.labymod.addons.voicechat.api.audio.stream.AudioStreamState; +import net.labymod.addons.voicechat.api.client.VoiceConnector; +import net.labymod.addons.voicechat.api.client.user.VoiceUser; +import net.labymod.addons.voicechat.core.VoiceChatAddon; +import net.labymod.api.Laby; +import javax.inject.Singleton; +import java.util.UUID; + +@Singleton +public class VoiceChatHelper { + + public boolean isEnabled() { + return Laby.labyAPI().addonService().isEnabled("voicechat"); + } + + public void reconnect() { + VoiceConnector client = VoiceChatAddon.INSTANCE.client(); + if (!client.isAuthenticated() || !this.isSelfMuted()) { + return; + } + + client.disconnect(); + client.connect(); + } + + public long getMuteExpiration(UUID uuid) { + VoiceUser user = VoiceChatAddon.INSTANCE + .referenceStorage() + .voiceUserRegistry().get(uuid); + + if (user == null) { + return -1; + } + + return user.isMuted() ? user.getMute().getTimeEnd() : -1; + } + + private boolean isSelfMuted() { + return VoiceChatAddon.INSTANCE + .referenceStorage() + .audioStreamRegistry() + .getClientState(false) == AudioStreamState.INPUT_GLOBAL_MUTED; + } +} 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..1e55dc4 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,45 @@ "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", + "sorting": { + "onlineStatus": "Online status", + "role": "LabyMod role", + "aToZ": "A-Z", + "zToA": "Z-A" + }, + "expiration": { + "label": "Add expiration date", + "title": "Set expiration date for %s", + "forExample": "e. g.", + "button": "Set expiration", + "types": { + "onServerLeave": "On disconnect", + "onMuteExpiration": "When VoiceMute expires", + "customDate": "Custom date" + }, + "success": "The expiration date was successfully set! You can cancel the expiration in the addon settings." + }, + "removal": { + "label": "Remove friend", + "skipConfirmation": "(Hold shift to skip confirmation)", + "confirm": "Confirm removal", + "cancel": "Cancel removal" + } + }, + "expiryConfig": { + "name": "Temporary friendships", + "sendNotifications": { + "name": "Ask if frienship should be temporary when adding new friend" + }, + "notifyOnExpiration": { + "name": "Notify when temporary friendship expires" + }, + "manageExpirationDates": { + "name": "Manage expiring friendships" + } + }, "pinIconConfig": { "name": "Friend pin icon", "pinIcon": { @@ -133,10 +172,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", @@ -183,6 +224,19 @@ "title": "Request removed", "description": "%s has declined your friend request. (Or you removed it)" }, + "friendshipExpiration": { + "set": { + "title": "New friend added", + "description": "Do you want to set an expiration date for the friendship?", + "button": "Add expiration date" + }, + "expired": { + "title": "Friendship expired!", + "description": "Your friendship with %s has expired.", + "disconnectDescription": "Your friendship with %s has expired because you left the server.", + "unknown": "Unknown" + } + }, "statusUpdate": { "message": "%1$s is now %2$s!", "offline": "offline" diff --git a/core/src/main/resources/assets/betterfriends/themes/vanilla/lss/expiration.lss b/core/src/main/resources/assets/betterfriends/themes/vanilla/lss/expiration.lss new file mode 100644 index 0000000..daf33cb --- /dev/null +++ b/core/src/main/resources/assets/betterfriends/themes/vanilla/lss/expiration.lss @@ -0,0 +1,51 @@ +.window { + top: 50%; + left: 50%; + width: 300; + height: 180; + alignment-x: center; + alignment-y: center; + + .header { + left: 50%; + width: 100%; + height: 20; + padding: 0 2 0 2; + space-between-entries: 3; + + .head { + width: 16; + height: width; + alignment: center; + } + + .username { + alignment: center; + } + } + + .content { + width: 100%; + height: 160; + alignment-x: center; + alignment-y: center; + overwrite-width: false; + + Dropdown { + width: 112; + margin-top: 20; + alignment-x: center; + } + + TextField { + width: 100; + margin-top: 10; + alignment-x: center; + } + + Button { + margin-top: 10; + alignment-x: center; + } + } +} \ No newline at end of file diff --git a/core/src/main/resources/assets/betterfriends/themes/vanilla/lss/friend.lss b/core/src/main/resources/assets/betterfriends/themes/vanilla/lss/friend.lss new file mode 100644 index 0000000..06ddd72 --- /dev/null +++ b/core/src/main/resources/assets/betterfriends/themes/vanilla/lss/friend.lss @@ -0,0 +1,50 @@ +Friend, 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/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..a0de43b --- /dev/null +++ b/core/src/main/resources/assets/betterfriends/themes/vanilla/lss/friend_list.lss @@ -0,0 +1,45 @@ +.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; + } + } \ No newline at end of file