From 7fb4a38cf42e3e0d6bf64256d2779d9415e41029 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Mon, 3 Jul 2023 22:08:28 -0400 Subject: [PATCH 01/27] feat: Alert groups --- .../runelite/watchdog/AlertManager.java | 71 ++++++++-- .../runelite/watchdog/EventHandler.java | 48 +++---- .../runelite/watchdog/FlashOverlay.java | 1 - .../runelite/watchdog/TriggerType.java | 1 + .../adamk33n3r/runelite/watchdog/Util.java | 23 ++++ .../runelite/watchdog/WatchdogPanel.java | 130 +++++------------- .../runelite/watchdog/alerts/Alert.java | 29 +++- .../runelite/watchdog/alerts/AlertGroup.java | 19 +++ .../watchdog/alerts/SpawnedAlert.java | 1 + .../runelite/watchdog/ui/AlertListItem.java | 18 ++- .../watchdog/ui/alerts/AlertGroupPanel.java | 55 ++++++++ .../ui/alerts/GameMessageAlertPanel.java | 19 +++ .../ui/alerts/InventoryAlertPanel.java | 27 ++++ .../alerts/NotificationFiredAlertPanel.java | 18 +++ .../ui/alerts/SoundFiredAlertPanel.java | 24 ++++ .../watchdog/ui/alerts/SpawnedAlertPanel.java | 20 +++ .../ui/alerts/StatChangedAlertPanel.java | 21 +++ .../watchdog/ui/alerts/XPDropAlertPanel.java | 21 +++ .../panels/NotificationPanel.java | 3 - .../panels/ScreenFlashNotificationPanel.java | 2 - .../watchdog/ui/panels/AlertListPanel.java | 24 ++++ .../watchdog/ui/panels/AlertPanel.java | 126 +++++++++-------- .../ui/panels/NotificationsPanel.java | 1 - .../watchdog/ui/panels/PanelUtils.java | 53 ++++--- .../runelite/watchdog/version.properties | 12 +- 25 files changed, 538 insertions(+), 229 deletions(-) create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/alerts/AlertGroup.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/GameMessageAlertPanel.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/InventoryAlertPanel.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/NotificationFiredAlertPanel.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/SoundFiredAlertPanel.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/SpawnedAlertPanel.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/StatChangedAlertPanel.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/XPDropAlertPanel.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java index c4b2cd6..1ed83a8 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java @@ -1,7 +1,29 @@ package com.adamk33n3r.runelite.watchdog; -import com.adamk33n3r.runelite.watchdog.alerts.*; -import com.adamk33n3r.runelite.watchdog.notifications.*; +import com.adamk33n3r.runelite.watchdog.alerts.Alert; +import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; +import com.adamk33n3r.runelite.watchdog.alerts.ChatAlert; +import com.adamk33n3r.runelite.watchdog.alerts.FlashMode; +import com.adamk33n3r.runelite.watchdog.alerts.InventoryAlert; +import com.adamk33n3r.runelite.watchdog.alerts.NotificationFiredAlert; +import com.adamk33n3r.runelite.watchdog.alerts.RegexMatcher; +import com.adamk33n3r.runelite.watchdog.alerts.SoundFiredAlert; +import com.adamk33n3r.runelite.watchdog.alerts.SpawnedAlert; +import com.adamk33n3r.runelite.watchdog.alerts.StatChangedAlert; +import com.adamk33n3r.runelite.watchdog.alerts.StatDrainAlert; +import com.adamk33n3r.runelite.watchdog.alerts.XPDropAlert; +import com.adamk33n3r.runelite.watchdog.notifications.GameMessage; +import com.adamk33n3r.runelite.watchdog.notifications.IAudioNotification; +import com.adamk33n3r.runelite.watchdog.notifications.INotification; +import com.adamk33n3r.runelite.watchdog.notifications.Notification; +import com.adamk33n3r.runelite.watchdog.notifications.NotificationEvent; +import com.adamk33n3r.runelite.watchdog.notifications.Overhead; +import com.adamk33n3r.runelite.watchdog.notifications.Overlay; +import com.adamk33n3r.runelite.watchdog.notifications.ScreenFlash; +import com.adamk33n3r.runelite.watchdog.notifications.Sound; +import com.adamk33n3r.runelite.watchdog.notifications.SoundEffect; +import com.adamk33n3r.runelite.watchdog.notifications.TextToSpeech; +import com.adamk33n3r.runelite.watchdog.notifications.TrayNotification; import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; import net.runelite.client.config.ConfigManager; @@ -66,7 +88,8 @@ private void init() { .registerSubtype(XPDropAlert.class) .registerSubtype(SoundFiredAlert.class) .registerSubtype(SpawnedAlert.class) - .registerSubtype(InventoryAlert.class); + .registerSubtype(InventoryAlert.class) + .registerSubtype(AlertGroup.class); // Add new notification types here final RuntimeTypeAdapterFactory notificationTypeFactory = RuntimeTypeAdapterFactory.of(Notification.class) .registerSubtype(TrayNotification.class) @@ -85,6 +108,26 @@ private void init() { .create(); } + public Stream getAllEnabledAlerts() { + return this.getAllAlerts().filter(Alert::isEnabled); + } + public Stream getAllEnabledAlertsOfType(Class type) { + return this.getAllEnabledAlerts() + .filter(type::isInstance) + .map(type::cast); + } + public Stream getAllAlerts() { + return this.getAllAlertsHelper(this.alerts.stream()); + } + private Stream getAllAlertsHelper(Stream alerts) { + return alerts.flatMap(alert -> { + if (alert instanceof AlertGroup) { + return getAllAlertsHelper(((AlertGroup) alert).getAlerts().stream()); + } + return Stream.of(alert); + }); + } + public void addAlert(Alert alert) { this.alerts.add(alert); this.saveAlerts(); @@ -99,11 +142,11 @@ public void removeAlert(Alert alert) { SwingUtilities.invokeLater(this.watchdogPanel::rebuild); } - public void cloneAlert(Alert alert) { + public Alert cloneAlert(Alert alert) { String json = this.gson.toJson(alert, ALERT_TYPE); Alert clonedAlert = this.gson.fromJson(json, ALERT_TYPE); clonedAlert.setName(clonedAlert.getName() + " Clone"); - this.addAlert(clonedAlert); + return clonedAlert; } public void moveAlertTo(Alert alert, int pos) { @@ -182,14 +225,18 @@ public boolean importAlerts(String json, boolean append, boolean checkRegex) { this.saveAlerts(); } + Util.setParentsOnAlerts(this.alerts); + // Inject dependencies - for (Alert alert : this.alerts) { - WatchdogPlugin.getInstance().getInjector().injectMembers(alert); - for (INotification notification : alert.getNotifications()) { - WatchdogPlugin.getInstance().getInjector().injectMembers(notification); - notification.setAlert(alert); - } - } + this.getAllAlerts() + .filter(alert -> !(alert instanceof AlertGroup)) + .forEach(alert -> { + WatchdogPlugin.getInstance().getInjector().injectMembers(alert); + for (INotification notification : alert.getNotifications()) { + WatchdogPlugin.getInstance().getInjector().injectMembers(notification); + notification.setAlert(alert); + } + }); SwingUtilities.invokeLater(this.watchdogPanel::rebuild); return true; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/EventHandler.java b/src/main/java/com/adamk33n3r/runelite/watchdog/EventHandler.java index c2c32e5..08935c3 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/EventHandler.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/EventHandler.java @@ -1,6 +1,7 @@ package com.adamk33n3r.runelite.watchdog; import com.adamk33n3r.runelite.watchdog.alerts.Alert; +import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; import com.adamk33n3r.runelite.watchdog.alerts.ChatAlert; import com.adamk33n3r.runelite.watchdog.alerts.InventoryAlert; import com.adamk33n3r.runelite.watchdog.alerts.NotificationFiredAlert; @@ -56,12 +57,15 @@ import java.awt.TrayIcon; import java.time.Instant; import java.util.Arrays; +import java.util.Comparator; import java.util.EnumMap; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; import static com.adamk33n3r.runelite.watchdog.alerts.SpawnedAlert.SpawnedDespawned.DESPAWNED; import static com.adamk33n3r.runelite.watchdog.alerts.SpawnedAlert.SpawnedDespawned.SPAWNED; @@ -133,10 +137,7 @@ public void onChatMessage(ChatMessage chatMessage) { } String unformattedMessage = Text.removeFormattingTags(chatMessage.getMessage()); - this.alertManager.getAlerts().stream() - .filter(alert -> alert instanceof ChatAlert) - .map(alert -> (ChatAlert) alert) -// .filter(chatAlert -> chatAlert.getChatMessageType() == chatMessage.getType()) + this.alertManager.getAllEnabledAlertsOfType(ChatAlert.class) .forEach(chatAlert -> { String[] groups = this.matchPattern(chatAlert, unformattedMessage); if (groups == null) return; @@ -154,9 +155,7 @@ public void onNotificationFired(NotificationFired notificationFired) { return; } - this.alertManager.getAlerts().stream() - .filter(alert -> alert instanceof NotificationFiredAlert) - .map(alert -> (NotificationFiredAlert) alert) + this.alertManager.getAllEnabledAlertsOfType(NotificationFiredAlert.class) .forEach(notificationFiredAlert -> { String[] groups = this.matchPattern(notificationFiredAlert, notificationFired.getMessage()); if (groups == null) return; @@ -190,9 +189,7 @@ private void handleStatChanged(StatChanged statChanged) { return; } - this.alertManager.getAlerts().stream() - .filter(alert -> alert instanceof StatChangedAlert) - .map(alert -> (StatChangedAlert) alert) + this.alertManager.getAllEnabledAlertsOfType(StatChangedAlert.class) .filter(alert -> { boolean isSkill = alert.getSkill() == statChanged.getSkill(); if (!isSkill) { @@ -218,9 +215,7 @@ private void handleXPDrop(StatChanged statChanged) { return; } - this.alertManager.getAlerts().stream() - .filter(alert -> alert instanceof XPDropAlert) - .map(alert -> (XPDropAlert) alert) + this.alertManager.getAllEnabledAlertsOfType(XPDropAlert.class) .filter(alert -> { boolean isSkill = alert.getSkill() == statChanged.getSkill(); int gainedXP = statChanged.getXp() - previousXP; @@ -242,9 +237,7 @@ private void onAreaSoundEffectPlayed(AreaSoundEffectPlayed areaSoundEffectPlayed } private void handleSoundEffectPlayed(int soundID) { - this.alertManager.getAlerts().stream() - .filter(alert -> alert instanceof SoundFiredAlert) - .map(alert -> (SoundFiredAlert) alert) + this.alertManager.getAllEnabledAlertsOfType(SoundFiredAlert.class) .filter(soundFiredAlert -> soundID == soundFiredAlert.getSoundID()) .forEach(alert -> this.fireAlert(alert, "" + soundID)); } @@ -256,9 +249,7 @@ private void onItemContainerChanged(ItemContainerChanged itemContainerChanged) { // Ignore everything but inventory if (itemContainerChanged.getItemContainer().getId() != InventoryID.INVENTORY.getId()) return; - this.alertManager.getAlerts().stream() - .filter(alert -> alert instanceof InventoryAlert) - .map(alert -> (InventoryAlert) alert) + this.alertManager.getAllEnabledAlertsOfType(InventoryAlert.class) .forEach(inventoryAlert -> { Item[] items = itemContainerChanged.getItemContainer().getItems(); long itemCount = Arrays.stream(items).filter(item -> item.getId() > -1).count(); @@ -362,9 +353,7 @@ private void onTileObjectSpawned(TileObject tileObject, SpawnedAlert.SpawnedDesp private void onSpawned(String name, SpawnedAlert.SpawnedDespawned mode, SpawnedAlert.SpawnedType type) { String unformattedName = Text.removeFormattingTags(name); - this.alertManager.getAlerts().stream() - .filter(alert -> alert instanceof SpawnedAlert) - .map(alert -> (SpawnedAlert) alert) + this.alertManager.getAllEnabledAlertsOfType(SpawnedAlert.class) .filter(spawnedAlert -> spawnedAlert.getSpawnedDespawned() == mode) .filter(spawnedAlert -> spawnedAlert.getSpawnedType() == type) .forEach(spawnedAlert -> { @@ -396,12 +385,23 @@ private void fireAlert(Alert alert, String[] triggerValues) { // Don't fire if it is disabled if (!alert.isEnabled()) return; + List ancestors = alert.getAncestors(); + // Don't fire if any of the ancestors are disabled + if (ancestors != null && !ancestors.stream().allMatch(Alert::isEnabled)) { + return; + } + + Alert alertToDebounceWith = ancestors == null ? alert : Stream.concat(ancestors.stream(), Stream.of(alert)) + .filter(ancestor -> ancestor.getDebounceTime() > 0) + .max(Comparator.comparingInt(Alert::getDebounceTime)) + .orElse(alert); + // If the alert hasn't been fired yet, or has been enough time, set the last trigger time to now and fire. - if (!this.lastTriggered.containsKey(alert) || Instant.now().compareTo(this.lastTriggered.get(alert).plusMillis(alert.getDebounceTime())) >= 0) { + if (!this.lastTriggered.containsKey(alertToDebounceWith) || Instant.now().compareTo(this.lastTriggered.get(alertToDebounceWith).plusMillis(alertToDebounceWith.getDebounceTime())) >= 0) { SwingUtilities.invokeLater(() -> { this.historyPanelProvider.get().addEntry(alert, triggerValues); }); - this.lastTriggered.put(alert, Instant.now()); + this.lastTriggered.put(alertToDebounceWith, Instant.now()); alert.getNotifications().forEach(notification -> notification.fire(triggerValues)); } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/FlashOverlay.java b/src/main/java/com/adamk33n3r/runelite/watchdog/FlashOverlay.java index 2bd92ab..a57077c 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/FlashOverlay.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/FlashOverlay.java @@ -5,7 +5,6 @@ import net.runelite.api.Client; import net.runelite.api.Constants; -import net.runelite.client.config.FlashNotification; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/TriggerType.java b/src/main/java/com/adamk33n3r/runelite/watchdog/TriggerType.java index 651a0b3..801d879 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/TriggerType.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/TriggerType.java @@ -8,6 +8,7 @@ @Getter @AllArgsConstructor public enum TriggerType { + ALERT_GROUP("Alert Group", "Group alerts together", AlertGroup.class), GAME_MESSAGE("Game Message", "Game messages sent in chat", ChatAlert.class), STAT_CHANGED("Stat Changed", "Stat changes like boosts or drains", StatChangedAlert.class), XP_DROP("XP Drop", "Get an xp drop", XPDropAlert.class), diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java b/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java index 27fbd29..e6f633b 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java @@ -1,6 +1,29 @@ package com.adamk33n3r.runelite.watchdog; +import com.adamk33n3r.runelite.watchdog.alerts.Alert; +import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; + +import java.util.List; + public class Util { + public static void setParentsOnAlerts(List alerts) { + for (Alert alert : alerts) { + if (alert instanceof AlertGroup) { + AlertGroup group = (AlertGroup) alert; + setParentsOnAlertsHelper(group); + } + } + } + + private static void setParentsOnAlertsHelper(AlertGroup parent) { + for (Alert childAlert : parent.getAlerts()) { + childAlert.setParent(parent); + if (childAlert instanceof AlertGroup) { + setParentsOnAlertsHelper((AlertGroup) childAlert); + } + } + } + public static T defaultArg(T thing, T defaultValue) { if (thing instanceof String) { String string = (String) thing; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java index 3cd90ec..e016634 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java @@ -3,12 +3,11 @@ import com.adamk33n3r.runelite.watchdog.alerts.*; import com.adamk33n3r.runelite.watchdog.ui.AlertListItem; import com.adamk33n3r.runelite.watchdog.ui.ImportExportDialog; -import com.adamk33n3r.runelite.watchdog.ui.dropdownbutton.DropDownButtonFactory; -import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; +import com.adamk33n3r.runelite.watchdog.ui.alerts.*; +import com.adamk33n3r.runelite.watchdog.ui.panels.AlertListPanel; import com.adamk33n3r.runelite.watchdog.ui.panels.HistoryPanel; import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; -import net.runelite.api.Skill; import net.runelite.client.plugins.config.ConfigPlugin; import net.runelite.client.plugins.info.InfoPanel; import net.runelite.client.plugins.timetracking.TimeTrackingPlugin; @@ -20,7 +19,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.text.WordUtils; import javax.inject.Inject; import javax.inject.Named; @@ -28,9 +26,7 @@ import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; -import javax.swing.JMenuItem; import javax.swing.JPanel; -import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; @@ -41,16 +37,12 @@ import java.awt.FlowLayout; import java.awt.Font; import java.awt.GridLayout; -import java.awt.event.ActionListener; import java.awt.image.BufferedImage; -import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; @Slf4j public class WatchdogPanel extends PluginPanel { - @Inject - @Named("watchdog.wikiPage.soundIDs") - private String SOUND_ID_WIKI_PAGE; - @Inject @Named("watchdog.helpURL") private String HELP_URL; @@ -127,8 +119,9 @@ public void rebuild() { title.setFont(title.getFont().deriveFont(Font.BOLD)); title.setHorizontalAlignment(JLabel.LEFT); title.setForeground(Color.WHITE); + title.setToolTipText("Watchdog v" + PLUGIN_VERSION); titlePanel.add(title); - JLabel version = new JLabel("v"+PLUGIN_VERSION); + JLabel version = new JLabel("v" + PLUGIN_VERSION); version.setFont(version.getFont().deriveFont(10f)); version.setBorder(new EmptyBorder(5, 0, 0, 0)); titlePanel.add(version); @@ -159,27 +152,30 @@ public void rebuild() { JButton historyButton = PanelUtils.createActionButton(HISTORY_ICON, HISTORY_ICON_HOVER, "History", (btn, modifiers) -> { this.muxer.pushState(this.historyPanelProvider.get()); +// System.out.println(this.alertManager.getAllEnabledAlertsOfType(InventoryAlert.class).count()); +// this.alertManager.getAllEnabledAlertsOfType(InventoryAlert.class) +// .forEach(alert -> { +// List ancestors = alert.getAncestors(); +// if (ancestors != null) { +// String isEnabled = ancestors.stream().allMatch(Alert::isEnabled) ? "Enabled: " : "Disabled: "; +// System.out.print(isEnabled); +// String ancestorStr = ancestors.stream().map(ancestor -> ancestor.getName() + (ancestor.isEnabled() ? "*" : "")).collect(Collectors.joining(" -> ")); +// System.out.print(ancestorStr); +// System.out.print(" -> "); +// } else { +// System.out.print(alert.isEnabled() ? "Enabled: " : "Disabled: "); +// } +// System.out.print(alert.getName()); +// System.out.println(); +// }); }); actionButtons.add(historyButton); - JPopupMenu popupMenu = new JPopupMenu(); - ActionListener actionListener = e -> { - JMenuItem menuItem = (JMenuItem) e.getSource(); - TriggerType tType = (TriggerType) menuItem.getClientProperty(TriggerType.class); - this.createAlert(tType); - }; - - Arrays.stream(TriggerType.values()) - .forEach(tType -> { - JMenuItem c = new JMenuItem(WordUtils.capitalizeFully(tType.name().replaceAll("_", " "))); - c.setToolTipText(tType.getTooltip()); - c.putClientProperty(TriggerType.class, tType); - c.addActionListener(actionListener); - popupMenu.add(c); - }); - JButton addDropDownButton = DropDownButtonFactory.createDropDownButton(ADD_ICON, popupMenu); - addDropDownButton.setToolTipText("Create New Alert"); - actionButtons.add(addDropDownButton); + JButton alertDropDownButton = PanelUtils.createAlertDropDownButton(createdAlert -> { + this.alertManager.addAlert(createdAlert); + this.openAlert(createdAlert); + }); + actionButtons.add(alertDropDownButton); topPanel.add(actionButtons, BorderLayout.EAST); @@ -192,13 +188,8 @@ public void rebuild() { // log.debug("drag listener: " + alertListItem.getAlert().getName() + " to " + pos); alertManager.moveAlertTo(alertListItem.getAlert(), pos); }); - JPanel alertPanel = new JPanel(new BorderLayout()); - alertPanel.add(dragAndDropReorderPane, BorderLayout.NORTH); - for (Alert alert : this.alertManager.getAlerts()) { - AlertListItem alertListItem = new AlertListItem(this, this.alertManager, alert, dragAndDropReorderPane); - dragAndDropReorderPane.add(alertListItem); - } + AlertListPanel alertPanel = new AlertListPanel(this.alertManager.getAlerts(), dragAndDropReorderPane, this::rebuild); this.add(new JScrollPane(alertPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); JPanel importExportGroup = new JPanel(new GridLayout(1, 2, 5, 0)); @@ -231,70 +222,23 @@ public void openAlert(Alert alert) { } } - private void createAlert(TriggerType triggerType) { - Alert createdAlert = WatchdogPlugin.getInstance().getInjector().getInstance(triggerType.getImplClass()); - this.alertManager.addAlert(createdAlert); - this.openAlert(createdAlert); - } - private PluginPanel createPluginPanel(Alert alert) { if (alert instanceof ChatAlert) { - ChatAlert chatAlert = (ChatAlert) alert; - return AlertPanel.create(this.muxer, alert) - .addAlertDefaults(alert) - .addRegexMatcher(chatAlert, "Enter the message to trigger on...", "The message to trigger on. Supports glob (*)") - .addLabel("Note: Will not trigger on
player chat messages
") - .build(); + return new GameMessageAlertPanel(this, (ChatAlert) alert); } else if (alert instanceof NotificationFiredAlert) { - NotificationFiredAlert notificationFiredAlert = (NotificationFiredAlert) alert; - return AlertPanel.create(this.muxer, alert) - .addAlertDefaults(alert) - .addRegexMatcher(notificationFiredAlert, "Enter the message to trigger on...", "The message to trigger on. Supports glob (*)") - .build(); + return new NotificationFiredAlertPanel(this, (NotificationFiredAlert) alert); } else if (alert instanceof StatChangedAlert) { - StatChangedAlert statChangedAlert = (StatChangedAlert) alert; - return AlertPanel.create(this.muxer, alert) - .addAlertDefaults(alert) - .addSelect("Skill", "The skill to track", Skill.class, statChangedAlert.getSkill(), statChangedAlert::setSkill) - .addSpinner("Changed Amount", "The difference in level to trigger the alert. Can be positive for boost and negative for drain", statChangedAlert.getChangedAmount(), statChangedAlert::setChangedAmount) - .build(); + return new StatChangedAlertPanel(this, (StatChangedAlert) alert); } else if (alert instanceof XPDropAlert) { - XPDropAlert xpDropAlert = (XPDropAlert) alert; - return AlertPanel.create(this.muxer, alert) - .addAlertDefaults(alert) - .addSelect("Skill", "The skill to track", Skill.class, xpDropAlert.getSkill(), xpDropAlert::setSkill) - .addSpinner("Gained Amount", "How much xp needed to trigger this alert", xpDropAlert.getGainedAmount(), xpDropAlert::setGainedAmount) - .build(); + return new XPDropAlertPanel(this, (XPDropAlert) alert); } else if (alert instanceof SoundFiredAlert) { - SoundFiredAlert soundFiredAlert = (SoundFiredAlert) alert; - return AlertPanel.create(this.muxer, alert) - .addAlertDefaults(alert) - .addRichTextPane("Go to this wiki page to get a list
of sound ids") - .addSpinner("Sound ID", "The ID of the sound", soundFiredAlert.getSoundID(), soundFiredAlert::setSoundID, 0, 99999, 1) - .build(); + return new SoundFiredAlertPanel(this, (SoundFiredAlert) alert); } else if (alert instanceof SpawnedAlert) { - SpawnedAlert spawnedAlert = (SpawnedAlert) alert; - return AlertPanel.create(this.muxer, alert) - .addAlertDefaults(alert) - .addSelect("Spawned/Despawned", "Spawned or Despawned", SpawnedAlert.SpawnedDespawned.class, spawnedAlert.getSpawnedDespawned(), spawnedAlert::setSpawnedDespawned) - .addSelect("Type", "The type of object to trigger on", SpawnedAlert.SpawnedType.class, spawnedAlert.getSpawnedType(), spawnedAlert::setSpawnedType) - .addRegexMatcher(spawnedAlert, "Enter the object to trigger on...", "The name to trigger on. Supports glob (*)") - .build(); + return new SpawnedAlertPanel(this, (SpawnedAlert) alert); } else if (alert instanceof InventoryAlert) { - InventoryAlert inventoryAlert = (InventoryAlert) alert; - return AlertPanel.create(this.muxer, alert) - .addAlertDefaults(alert) - .addSelect("Type", "Type", InventoryAlert.InventoryAlertType.class, inventoryAlert.getInventoryAlertType(), (val) -> { - inventoryAlert.setInventoryAlertType(val); - this.muxer.popState(); - this.openAlert(alert); - }) - .addIf( - panel -> panel.addRegexMatcher(inventoryAlert, "Enter the name of the item to trigger on...", "The name to trigger on. Supports glob (*)") - .addSpinner("Quantity", "The quantity of item to trigger on, use 0 for every time", inventoryAlert.getItemQuantity(), inventoryAlert::setItemQuantity), - () -> inventoryAlert.getInventoryAlertType() == InventoryAlert.InventoryAlertType.ITEM - ) - .build(); + return new InventoryAlertPanel(this, (InventoryAlert) alert); + } else if (alert instanceof AlertGroup) { + return new AlertGroupPanel(this, (AlertGroup) alert); } return null; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java index 5f2f5ac..6e77b01 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java @@ -3,25 +3,27 @@ import com.adamk33n3r.runelite.watchdog.TriggerType; import com.adamk33n3r.runelite.watchdog.notifications.Notification; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @Getter +@Setter public abstract class Alert { - @Setter private boolean enabled = true; - - @Setter private String name; - - @Setter private int debounceTime; - private final List notifications = new ArrayList<>(); + @Nullable + private transient AlertGroup parent; + + @Setter(AccessLevel.PROTECTED) + private List notifications = new ArrayList<>(); public Alert(String name) { this.name = name; @@ -73,4 +75,19 @@ public void moveNotificationDown(Notification notification) { this.notifications.remove(notification); this.notifications.add(newIdx, notification); } + + @Nullable + public List getAncestors() { + if (this.parent == null) { + return null; + } + + ArrayList ancestors = new ArrayList<>(); + AlertGroup alertGroup = this.parent; + do { + ancestors.add(0, alertGroup); + } while ((alertGroup = alertGroup.getParent()) != null); + + return ancestors; + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/AlertGroup.java b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/AlertGroup.java new file mode 100644 index 0000000..2abddb2 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/AlertGroup.java @@ -0,0 +1,19 @@ +package com.adamk33n3r.runelite.watchdog.alerts; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +public class AlertGroup extends Alert { + private List alerts = new ArrayList<>(); + + public AlertGroup() { + super("New Alert Group"); + // So that we don't serialize the empty array + this.setNotifications(null); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/SpawnedAlert.java b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/SpawnedAlert.java index dc33619..feb2b09 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/SpawnedAlert.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/SpawnedAlert.java @@ -1,6 +1,7 @@ package com.adamk33n3r.runelite.watchdog.alerts; import com.adamk33n3r.runelite.watchdog.Displayable; + import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java index 5262371..ff74abc 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java @@ -22,6 +22,7 @@ import javax.swing.border.EmptyBorder; import java.awt.BorderLayout; import java.awt.Dimension; +import java.util.List; public class AlertListItem extends JPanel { public static final ImageIcon DELETE_ICON_HOVER; @@ -39,20 +40,20 @@ public class AlertListItem extends JPanel { @Getter private final Alert alert; - public AlertListItem(WatchdogPanel panel, AlertManager alertManager, Alert alert, JComponent parent) { + public AlertListItem(WatchdogPanel panel, AlertManager alertManager, Alert alert, List parentList, JComponent parent, Runnable onChange) { this.alert = alert; - this.setLayout(new BorderLayout(5, 0)); + + MouseDragEventForwarder mouseDragEventForwarder = new MouseDragEventForwarder(parent); + this.setBorder(new EmptyBorder(PADDING, 0, PADDING, 0)); this.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, ROW_HEIGHT + PADDING * 2)); - JPanel frontGroup = new JPanel(new DynamicGridLayout(1, 0, 3, 0)); JButton dragHandle = new JButton(DRAG_VERT); SwingUtil.removeButtonDecorations(dragHandle); dragHandle.setPreferredSize(new Dimension(8, 16)); - MouseDragEventForwarder mouseDragEventForwarder = new MouseDragEventForwarder(parent); dragHandle.addMouseListener(mouseDragEventForwarder); dragHandle.addMouseMotionListener(mouseDragEventForwarder); frontGroup.add(dragHandle); @@ -79,13 +80,18 @@ public AlertListItem(WatchdogPanel panel, AlertManager alertManager, Alert alert this.add(actionButtons, BorderLayout.LINE_END); actionButtons.add(PanelUtils.createActionButton(CLONE_ICON, CLONE_ICON, "Clone Alert", (btn, modifiers) -> { - alertManager.cloneAlert(alert); + Alert cloned = alertManager.cloneAlert(alert); + parentList.add(cloned); + alertManager.saveAlerts(); + onChange.run(); })); final JButton deleteButton = PanelUtils.createActionButton(DELETE_ICON, DELETE_ICON, "Delete Alert", (btn, modifiers) -> { int result = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete the " + alert.getName() + " alert?", "Delete?", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE); if (result == JOptionPane.YES_OPTION) { - alertManager.removeAlert(alert); + parentList.remove(alert); + alertManager.saveAlerts(); + onChange.run(); } }); actionButtons.add(deleteButton); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java new file mode 100644 index 0000000..b7f3515 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java @@ -0,0 +1,55 @@ +package com.adamk33n3r.runelite.watchdog.ui.alerts; + +import com.adamk33n3r.runelite.watchdog.AlertManager; +import com.adamk33n3r.runelite.watchdog.WatchdogPanel; +import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; +import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; +import com.adamk33n3r.runelite.watchdog.ui.AlertListItem; +import com.adamk33n3r.runelite.watchdog.ui.HorizontalRuleBorder; +import com.adamk33n3r.runelite.watchdog.ui.panels.AlertListPanel; +import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; +import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; + +import net.runelite.client.ui.DynamicGridLayout; +import net.runelite.client.ui.components.DragAndDropReorderPane; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import java.awt.BorderLayout; + +public class AlertGroupPanel extends AlertPanel { + public AlertGroupPanel(WatchdogPanel watchdogPanel, AlertGroup alert) { + super(watchdogPanel, alert); + } + + @Override + protected void build() { + AlertManager alertManager = WatchdogPlugin.getInstance().getAlertManager(); + this.addAlertDefaults(); + JPanel buttonPanel = new JPanel(new BorderLayout()); + buttonPanel.add(new JLabel("Alerts"), BorderLayout.WEST); + + JButton alertDropDownButton = PanelUtils.createAlertDropDownButton(createdAlert -> { + this.alert.getAlerts().add(createdAlert); + alertManager.saveAlerts(); + this.rebuild(); + this.watchdogPanel.openAlert(createdAlert); + }); + + buttonPanel.add(alertDropDownButton, BorderLayout.EAST); + JPanel subGroupPanel = new JPanel(new DynamicGridLayout(0, 1, 3, 3)); + subGroupPanel.setBorder(new HorizontalRuleBorder(10)); + subGroupPanel.add(buttonPanel); + this.addSubPanel(subGroupPanel); + DragAndDropReorderPane dragAndDropReorderPane = new DragAndDropReorderPane(); + dragAndDropReorderPane.addDragListener((c) -> { + int pos = dragAndDropReorderPane.getPosition(c); + AlertListItem alertListItem = (AlertListItem) c; + this.alert.getAlerts().remove(alertListItem.getAlert()); + this.alert.getAlerts().add(pos, alertListItem.getAlert()); + alertManager.saveAlerts(); + }); + subGroupPanel.add(new AlertListPanel(this.alert.getAlerts(), dragAndDropReorderPane, this::rebuild)); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/GameMessageAlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/GameMessageAlertPanel.java new file mode 100644 index 0000000..0850ab6 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/GameMessageAlertPanel.java @@ -0,0 +1,19 @@ +package com.adamk33n3r.runelite.watchdog.ui.alerts; + +import com.adamk33n3r.runelite.watchdog.WatchdogPanel; +import com.adamk33n3r.runelite.watchdog.alerts.ChatAlert; +import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; + +public class GameMessageAlertPanel extends AlertPanel { + public GameMessageAlertPanel(WatchdogPanel watchdogPanel, ChatAlert alert) { + super(watchdogPanel, alert); + } + + @Override + protected void build() { + this.addAlertDefaults() + .addRegexMatcher(this.alert, "Enter the message to trigger on...", "The message to trigger on. Supports glob (*)") + .addLabel("Note: Will not trigger on
player chat messages
") + .addNotifications(); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/InventoryAlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/InventoryAlertPanel.java new file mode 100644 index 0000000..8d951f6 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/InventoryAlertPanel.java @@ -0,0 +1,27 @@ +package com.adamk33n3r.runelite.watchdog.ui.alerts; + +import com.adamk33n3r.runelite.watchdog.WatchdogPanel; +import com.adamk33n3r.runelite.watchdog.alerts.InventoryAlert; +import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; + +public class InventoryAlertPanel extends AlertPanel { + public InventoryAlertPanel(WatchdogPanel watchdogPanel, InventoryAlert alert) { + super(watchdogPanel, alert); + } + + @Override + protected void build() { + this.addAlertDefaults() + .addSelect("Type", "Type", InventoryAlert.InventoryAlertType.class, this.alert.getInventoryAlertType(), (val) -> { + this.alert.setInventoryAlertType(val); + this.muxer.popState(); + watchdogPanel.openAlert(alert); + }) + .addIf( + panel -> panel.addRegexMatcher(this.alert, "Enter the name of the item to trigger on...", "The name to trigger on. Supports glob (*)") + .addSpinner("Quantity", "The quantity of item to trigger on, use 0 for every time", this.alert.getItemQuantity(), this.alert::setItemQuantity), + () -> this.alert.getInventoryAlertType() == InventoryAlert.InventoryAlertType.ITEM + ) + .addNotifications(); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/NotificationFiredAlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/NotificationFiredAlertPanel.java new file mode 100644 index 0000000..1934e66 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/NotificationFiredAlertPanel.java @@ -0,0 +1,18 @@ +package com.adamk33n3r.runelite.watchdog.ui.alerts; + +import com.adamk33n3r.runelite.watchdog.WatchdogPanel; +import com.adamk33n3r.runelite.watchdog.alerts.NotificationFiredAlert; +import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; + +public class NotificationFiredAlertPanel extends AlertPanel { + public NotificationFiredAlertPanel(WatchdogPanel watchdogPanel, NotificationFiredAlert alert) { + super(watchdogPanel, alert); + } + + @Override + protected void build() { + this.addAlertDefaults() + .addRegexMatcher(this.alert, "Enter the message to trigger on...", "The message to trigger on. Supports glob (*)") + .addNotifications(); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/SoundFiredAlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/SoundFiredAlertPanel.java new file mode 100644 index 0000000..a33b520 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/SoundFiredAlertPanel.java @@ -0,0 +1,24 @@ +package com.adamk33n3r.runelite.watchdog.ui.alerts; + +import com.adamk33n3r.runelite.watchdog.WatchdogPanel; +import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; +import com.adamk33n3r.runelite.watchdog.alerts.SoundFiredAlert; +import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; + +import com.google.inject.Key; +import com.google.inject.name.Names; + +public class SoundFiredAlertPanel extends AlertPanel { + public SoundFiredAlertPanel(WatchdogPanel watchdogPanel, SoundFiredAlert alert) { + super(watchdogPanel, alert); + } + + @Override + protected void build() { + String wikiPage = WatchdogPlugin.getInstance().getInjector().getInstance(Key.get(String.class, Names.named("watchdog.wikiPage.soundIDs"))); + this.addAlertDefaults() + .addRichTextPane("Go to this wiki page to get a list
of sound ids") + .addSpinner("Sound ID", "The ID of the sound", this.alert.getSoundID(), this.alert::setSoundID, 0, 99999, 1) + .addNotifications(); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/SpawnedAlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/SpawnedAlertPanel.java new file mode 100644 index 0000000..cbeda3a --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/SpawnedAlertPanel.java @@ -0,0 +1,20 @@ +package com.adamk33n3r.runelite.watchdog.ui.alerts; + +import com.adamk33n3r.runelite.watchdog.WatchdogPanel; +import com.adamk33n3r.runelite.watchdog.alerts.SpawnedAlert; +import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; + +public class SpawnedAlertPanel extends AlertPanel { + public SpawnedAlertPanel(WatchdogPanel watchdogPanel, SpawnedAlert alert) { + super(watchdogPanel, alert); + } + + @Override + protected void build() { + this.addAlertDefaults() + .addSelect("Spawned/Despawned", "Spawned or Despawned", SpawnedAlert.SpawnedDespawned.class, this.alert.getSpawnedDespawned(), this.alert::setSpawnedDespawned) + .addSelect("Type", "The type of object to trigger on", SpawnedAlert.SpawnedType.class, this.alert.getSpawnedType(), this.alert::setSpawnedType) + .addRegexMatcher(this.alert, "Enter the object to trigger on...", "The name to trigger on. Supports glob (*)") + .addNotifications(); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/StatChangedAlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/StatChangedAlertPanel.java new file mode 100644 index 0000000..0dffac2 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/StatChangedAlertPanel.java @@ -0,0 +1,21 @@ +package com.adamk33n3r.runelite.watchdog.ui.alerts; + +import com.adamk33n3r.runelite.watchdog.WatchdogPanel; +import com.adamk33n3r.runelite.watchdog.alerts.StatChangedAlert; +import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; + +import net.runelite.api.Skill; + +public class StatChangedAlertPanel extends AlertPanel { + public StatChangedAlertPanel(WatchdogPanel watchdogPanel, StatChangedAlert alert) { + super(watchdogPanel, alert); + } + + @Override + protected void build() { + this.addAlertDefaults() + .addSelect("Skill", "The skill to track", Skill.class, this.alert.getSkill(), this.alert::setSkill) + .addSpinner("Changed Amount", "The difference in level to trigger the alert. Can be positive for boost and negative for drain", this.alert.getChangedAmount(), this.alert::setChangedAmount) + .addNotifications(); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/XPDropAlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/XPDropAlertPanel.java new file mode 100644 index 0000000..dd1b126 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/XPDropAlertPanel.java @@ -0,0 +1,21 @@ +package com.adamk33n3r.runelite.watchdog.ui.alerts; + +import com.adamk33n3r.runelite.watchdog.WatchdogPanel; +import com.adamk33n3r.runelite.watchdog.alerts.XPDropAlert; +import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; + +import net.runelite.api.Skill; + +public class XPDropAlertPanel extends AlertPanel { + public XPDropAlertPanel(WatchdogPanel watchdogPanel, XPDropAlert alert) { + super(watchdogPanel, alert); + } + + @Override + protected void build() { + this.addAlertDefaults() + .addSelect("Skill", "The skill to track", Skill.class, this.alert.getSkill(), this.alert::setSkill) + .addSpinner("Gained Amount", "How much xp needed to trigger this alert", this.alert.getGainedAmount(), this.alert::setGainedAmount) + .addNotifications(); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java index 0cc0ab8..ef2d4d5 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java @@ -85,9 +85,6 @@ public NotificationPanel(Notification notification, NotificationsPanel parentPan nameWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR); nameWrapper.setBorder(NAME_BOTTOM_BORDER); - nameWrapper.setBorder(new CompoundBorder( - BorderFactory.createMatteBorder(0, 0, 1, 0, ColorScheme.DARK_GRAY_COLOR), - BorderFactory.createMatteBorder(5, 10, 5, 0, ColorScheme.DARKER_GRAY_COLOR))); NotificationType notificationType = notification.getType(); JLabel nameLabel = new JLabel(notificationType.getName()); nameLabel.setToolTipText(notificationType.getTooltip()); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java index 751eb40..0835c2d 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java @@ -5,14 +5,12 @@ import com.adamk33n3r.runelite.watchdog.ui.panels.NotificationsPanel; import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; -import net.runelite.client.config.FlashNotification; import net.runelite.client.ui.components.ColorJButton; import net.runelite.client.ui.components.colorpicker.ColorPickerManager; import javax.swing.DefaultListCellRenderer; import javax.swing.JComboBox; import javax.swing.JSpinner; -import java.util.Arrays; public class ScreenFlashNotificationPanel extends NotificationPanel { public ScreenFlashNotificationPanel(ScreenFlash screenFlash, NotificationsPanel parentPanel, ColorPickerManager colorPickerManager, Runnable onChangeListener, PanelUtils.OnRemove onRemove) { diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java new file mode 100644 index 0000000..ed95821 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java @@ -0,0 +1,24 @@ +package com.adamk33n3r.runelite.watchdog.ui.panels; + +import com.adamk33n3r.runelite.watchdog.AlertManager; +import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; +import com.adamk33n3r.runelite.watchdog.alerts.Alert; +import com.adamk33n3r.runelite.watchdog.ui.AlertListItem; + +import net.runelite.client.ui.components.DragAndDropReorderPane; + +import javax.swing.JPanel; +import java.awt.BorderLayout; +import java.util.List; + +public class AlertListPanel extends JPanel { + public AlertListPanel(List alerts, DragAndDropReorderPane dragAndDropReorderPane, Runnable onChange) { + this.setLayout(new BorderLayout()); + AlertManager alertManager = WatchdogPlugin.getInstance().getAlertManager(); + alerts.forEach((subAlert) -> { + AlertListItem alertListItem = new AlertListItem(WatchdogPlugin.getInstance().getPanel(), alertManager, subAlert, alerts, dragAndDropReorderPane, onChange); + dragAndDropReorderPane.add(alertListItem); + }); + this.add(dragAndDropReorderPane, BorderLayout.NORTH); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java index 2960d3e..fa73b50 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java @@ -3,8 +3,10 @@ import com.adamk33n3r.runelite.watchdog.AlertManager; import com.adamk33n3r.runelite.watchdog.Displayable; import com.adamk33n3r.runelite.watchdog.TriggerType; +import com.adamk33n3r.runelite.watchdog.WatchdogPanel; import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; import com.adamk33n3r.runelite.watchdog.alerts.Alert; +import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; import com.adamk33n3r.runelite.watchdog.alerts.RegexMatcher; import com.adamk33n3r.runelite.watchdog.ui.HorizontalRuleBorder; import com.adamk33n3r.runelite.watchdog.ui.ImportExportDialog; @@ -32,24 +34,24 @@ import java.util.function.Consumer; import java.util.function.Supplier; -import static com.adamk33n3r.runelite.watchdog.AlertManager.ALERT_LIST_TYPE; import static com.adamk33n3r.runelite.watchdog.ui.notifications.panels.NotificationPanel.TEST_ICON; import static com.adamk33n3r.runelite.watchdog.ui.notifications.panels.NotificationPanel.TEST_ICON_HOVER; @Slf4j -public class AlertPanel extends PluginPanel { +public abstract class AlertPanel extends PluginPanel { private final ScrollablePanel container; - private final MultiplexingPluginPanel muxer; - private final Alert alert; + protected final WatchdogPanel watchdogPanel; + protected final MultiplexingPluginPanel muxer; + protected final T alert; private final JPanel wrapper; private final JScrollPane scroll; private final AlertManager alertManager; - static final ImageIcon BACK_ICON; - static final ImageIcon BACK_ICON_HOVER; - static final ImageIcon EXPORT_ICON; - static final ImageIcon EXPORT_ICON_HOVER; + public static final ImageIcon BACK_ICON; + public static final ImageIcon BACK_ICON_HOVER; + public static final ImageIcon EXPORT_ICON; + public static final ImageIcon EXPORT_ICON_HOVER; public static final ImageIcon REGEX_ICON; public static final ImageIcon REGEX_ICON_HOVER; public static final ImageIcon REGEX_SELECTED_ICON; @@ -72,10 +74,11 @@ public class AlertPanel extends PluginPanel { REGEX_SELECTED_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(regexIconSelected, -80)); } - private AlertPanel(MultiplexingPluginPanel muxer, Alert alert) { + public AlertPanel(WatchdogPanel watchdogPanel, T alert) { super(false); - this.muxer = muxer; + this.watchdogPanel = watchdogPanel; + this.muxer = watchdogPanel.getMuxer(); this.alert = alert; this.alertManager = WatchdogPlugin.getInstance().getAlertManager(); @@ -115,17 +118,19 @@ private AlertPanel(MultiplexingPluginPanel muxer, Alert alert) { ); rightButtons.add(exportAlertBtn); - JButton testAlert = PanelUtils.createActionButton( - TEST_ICON, - TEST_ICON_HOVER, - "Test the whole alert", - (btn, modifiers) -> { - String[] triggerValues = {"1", "2", "3", "4", "5"}; - WatchdogPlugin.getInstance().getPanel().getHistoryPanelProvider().get().addEntry(alert, triggerValues); - alert.getNotifications().forEach(notification -> notification.fireForced(triggerValues)); - } - ); - rightButtons.add(testAlert); + if (!(alert instanceof AlertGroup)) { + JButton testAlert = PanelUtils.createActionButton( + TEST_ICON, + TEST_ICON_HOVER, + "Test the whole alert", + (btn, modifiers) -> { + String[] triggerValues = {"1", "2", "3", "4", "5"}; + WatchdogPlugin.getInstance().getPanel().getHistoryPanelProvider().get().addEntry(alert, triggerValues); + alert.getNotifications().forEach(notification -> notification.fireForced(triggerValues)); + } + ); + rightButtons.add(testAlert); + } ToggleButton toggleButton = new ToggleButton(); toggleButton.setSelected(alert.isEnabled()); @@ -155,17 +160,13 @@ private AlertPanel(MultiplexingPluginPanel muxer, Alert alert) { this.add(wrapper, BorderLayout.CENTER); } - public static AlertPanel create(MultiplexingPluginPanel muxer, Alert alert) { - return new AlertPanel(muxer, alert); - } - - public AlertPanel addLabel(String label) { + public AlertPanel addLabel(String label) { JLabel labelComp = new JLabel(label); this.container.add(labelComp); return this; } - public AlertPanel addRichTextPane(String text) { + public AlertPanel addRichTextPane(String text) { JRichTextPane richTextPane = new JRichTextPane(); richTextPane.setContentType("text/html"); richTextPane.setText(text); @@ -174,7 +175,7 @@ public AlertPanel addRichTextPane(String text) { return this; } - public AlertPanel addTextField(String placeholder, String tooltip, String initialValue, Consumer saveAction) { + public AlertPanel addTextField(String placeholder, String tooltip, String initialValue, Consumer saveAction) { PlaceholderTextField textField = new PlaceholderTextField(initialValue); textField.setPlaceholder(placeholder); textField.setToolTipText(tooltip); @@ -194,7 +195,7 @@ public void focusLost(FocusEvent e) { return this; } - public AlertPanel addTextArea(String placeholder, String tooltip, String initialValue, Consumer saveAction) { + public AlertPanel addTextArea(String placeholder, String tooltip, String initialValue, Consumer saveAction) { JTextArea textArea = PanelUtils.createTextArea(placeholder, tooltip, initialValue, val -> { saveAction.accept(val); this.alertManager.saveAlerts(); @@ -203,11 +204,11 @@ public AlertPanel addTextArea(String placeholder, String tooltip, String initial return this; } - public AlertPanel addSpinner(String name, String tooltip, int initialValue, Consumer saveAction) { + public AlertPanel addSpinner(String name, String tooltip, int initialValue, Consumer saveAction) { return this.addSpinner(name, tooltip, initialValue, saveAction, -99, 99, 1); } - public AlertPanel addSpinner(String name, String tooltip, int initialValue, Consumer saveAction, int min, int max, int step) { + public AlertPanel addSpinner(String name, String tooltip, int initialValue, Consumer saveAction, int min, int max, int step) { JSpinner spinner = PanelUtils.createSpinner(initialValue, min, max, step, val -> { saveAction.accept(val); this.alertManager.saveAlerts(); @@ -216,8 +217,8 @@ public AlertPanel addSpinner(String name, String tooltip, int initialValue, Cons return this; } - public > AlertPanel addSelect(String name, String tooltip, Class enumType, T initialValue, Consumer saveAction) { - JComboBox select = new JComboBox<>(enumType.getEnumConstants()); + public > AlertPanel addSelect(String name, String tooltip, Class enumType, E initialValue, Consumer saveAction) { + JComboBox select = new JComboBox<>(enumType.getEnumConstants()); select.setSelectedItem(initialValue); select.setRenderer((list, value, index, isSelected, cellHasFocus) -> { if (value instanceof Displayable) { @@ -236,7 +237,7 @@ public > AlertPanel addSelect(String name, String tooltip, Cla return this; } - public AlertPanel addCheckbox(String name, String tooltip, boolean initialValue, Consumer saveAction) { + public AlertPanel addCheckbox(String name, String tooltip, boolean initialValue, Consumer saveAction) { JCheckBox checkbox = PanelUtils.createCheckbox(name, tooltip, initialValue, val -> { saveAction.accept(val); this.alertManager.saveAlerts(); @@ -245,11 +246,11 @@ public AlertPanel addCheckbox(String name, String tooltip, boolean initialValue, return this; } - public AlertPanel addInputGroupWithSuffix(JComponent mainComponent, JComponent suffix) { + public AlertPanel addInputGroupWithSuffix(JComponent mainComponent, JComponent suffix) { return this.addInputGroup(mainComponent, null, Collections.singletonList(suffix)); } - public AlertPanel addInputGroup(JComponent mainComponent, List prefixes, List suffixes) { + public AlertPanel addInputGroup(JComponent mainComponent, List prefixes, List suffixes) { InputGroup textFieldGroup = new InputGroup(mainComponent) .addPrefixes(prefixes) .addSuffixes(suffixes); @@ -257,40 +258,27 @@ public AlertPanel addInputGroup(JComponent mainComponent, List prefi return this; } - public PluginPanel build() { - NotificationsPanel notificationPanel = new NotificationsPanel(this.alert); - WatchdogPlugin.getInstance().getInjector().injectMembers(notificationPanel); - notificationPanel.setBorder(new HorizontalRuleBorder(10)); - this.container.add(notificationPanel); - // I don't know why but sometimes the scroll pane is starting scrolled down 1 element, and we have to wait a tick to reset it -// SwingUtilities.invokeLater(() -> { -// this.scroll.getVerticalScrollBar();//.setValue(0); -// }); - - return this; - } - - public AlertPanel addAlertDefaults(Alert alert) { - return this.addTextField("Enter the alert name...", "Name of Alert", alert.getName(), alert::setName) + public AlertPanel addAlertDefaults() { + return this.addTextField("Enter the alert name...", "Name of Alert", this.alert.getName(), this.alert::setName) .addSpinner( "Debounce Time (ms)", "How long to wait before allowing this alert to trigger again in milliseconds", - alert.getDebounceTime(), - alert::setDebounceTime, + this.alert.getDebounceTime(), + this.alert::setDebounceTime, 0, 8640000, // 6 hours - max time a player can be logged in 100 ); } - public AlertPanel addIf(Consumer panel, Supplier ifFunc) { + public AlertPanel addIf(Consumer> panel, Supplier ifFunc) { if (ifFunc.get()) { panel.accept(this); } return this; } - public AlertPanel addRegexMatcher(RegexMatcher regexMatcher, String placeholder, String tooltip) { + public AlertPanel addRegexMatcher(RegexMatcher regexMatcher, String placeholder, String tooltip) { return this.addInputGroupWithSuffix( PanelUtils.createTextArea(placeholder, tooltip, regexMatcher.getPattern(), msg -> { if (!PanelUtils.isPatternValid(this, msg, regexMatcher.isRegexEnabled())) @@ -313,4 +301,32 @@ public AlertPanel addRegexMatcher(RegexMatcher regexMatcher, String placeholder, ) ); } + + public AlertPanel addNotifications() { + NotificationsPanel notificationPanel = new NotificationsPanel(this.alert); + WatchdogPlugin.getInstance().getInjector().injectMembers(notificationPanel); + notificationPanel.setBorder(new HorizontalRuleBorder(10)); + this.container.add(notificationPanel); + + return this; + } + + public AlertPanel addSubPanel(JPanel sub) { + this.container.add(sub); + + return this; + } + + protected abstract void build(); + protected void rebuild() { + this.container.removeAll(); + this.build(); + this.revalidate(); + this.repaint(); + } + + @Override + public void onActivate() { + this.rebuild(); + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java index 1e9a00e..67f684c 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java @@ -14,7 +14,6 @@ import com.adamk33n3r.runelite.watchdog.notifications.SoundEffect; import com.adamk33n3r.runelite.watchdog.notifications.TextToSpeech; import com.adamk33n3r.runelite.watchdog.notifications.TrayNotification; -import com.adamk33n3r.runelite.watchdog.ui.AlertListItem; import com.adamk33n3r.runelite.watchdog.ui.dropdownbutton.DropDownButtonFactory; import com.adamk33n3r.runelite.watchdog.ui.notifications.panels.MessageNotificationPanel; import com.adamk33n3r.runelite.watchdog.ui.notifications.panels.NotificationPanel; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java index a616a87..213315f 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java @@ -1,7 +1,11 @@ package com.adamk33n3r.runelite.watchdog.ui.panels; +import com.adamk33n3r.runelite.watchdog.TriggerType; import com.adamk33n3r.runelite.watchdog.Util; +import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; +import com.adamk33n3r.runelite.watchdog.alerts.Alert; import com.adamk33n3r.runelite.watchdog.ui.PlaceholderTextArea; +import com.adamk33n3r.runelite.watchdog.ui.dropdownbutton.DropDownButtonFactory; import net.runelite.client.ui.DynamicGridLayout; import net.runelite.client.ui.components.ColorJButton; @@ -11,27 +15,9 @@ import net.runelite.client.util.ImageUtil; import net.runelite.client.util.SwingUtil; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JFileChooser; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.JTextArea; -import javax.swing.JTextField; -import javax.swing.SpinnerNumberModel; -import javax.swing.SwingUtilities; +import javax.swing.*; import javax.swing.filechooser.FileFilter; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.GridLayout; -import java.awt.Insets; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; @@ -46,6 +32,8 @@ import java.util.regex.PatternSyntaxException; import java.util.stream.Collectors; +import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.ADD_ICON; + public class PanelUtils { private static final ImageIcon FOLDER_ICON; static { @@ -254,4 +242,29 @@ public static boolean isPatternValid(Component parent, String pattern, boolean i return false; } } + + public static JButton createAlertDropDownButton(Consumer onCreate) { + ActionListener actionListener = e -> { + JMenuItem menuItem = (JMenuItem) e.getSource(); + TriggerType tType = (TriggerType) menuItem.getClientProperty(TriggerType.class); + Alert createdAlert = WatchdogPlugin.getInstance().getInjector().getInstance(tType.getImplClass()); + onCreate.accept(createdAlert); + }; + + JPopupMenu popupMenu = new JPopupMenu(); + popupMenu.add(new JMenuItem(TriggerType.ALERT_GROUP.getName())); + popupMenu.addSeparator(); + Arrays.stream(TriggerType.values()) + .filter(tType -> tType != TriggerType.ALERT_GROUP) + .forEach(tType -> { + JMenuItem c = new JMenuItem(tType.getName()); + c.setToolTipText(tType.getTooltip()); + c.putClientProperty(TriggerType.class, tType); + c.addActionListener(actionListener); + popupMenu.add(c); + }); + JButton addDropDownButton = DropDownButtonFactory.createDropDownButton(ADD_ICON, popupMenu); + addDropDownButton.setToolTipText("Create New Alert"); + return addDropDownButton; + } } diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 732b03b..12fc3be 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,6 +1,6 @@ -#Sun Jul 02 14:38:39 EDT 2023 -VERSION_BUILD=2238 -VERSION_PHASE=release -VERSION_MAJOR=2 -VERSION_MINOR=13 -VERSION_PATCH=1 +#Mon Jul 03 22:07:03 EDT 2023 +VERSION_BUILD=2323 +VERSION_PHASE=alpha +VERSION_MAJOR=3 +VERSION_MINOR=0 +VERSION_PATCH=0 From 5ac0991ab24ae1d115e779a390a3b0d075486b4b Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Mon, 3 Jul 2023 23:49:42 -0400 Subject: [PATCH 02/27] feat: Import alerts into group --- .../runelite/watchdog/AlertManager.java | 60 ++++++++++-------- .../runelite/watchdog/WatchdogPanel.java | 5 +- .../watchdog/ui/ImportExportDialog.java | 5 +- .../watchdog/ui/panels/AlertPanel.java | 55 ++++++++++------ .../watchdog/ui/panels/export_icon.png | Bin 366 -> 0 bytes .../runelite/watchdog/version.properties | 4 +- 6 files changed, 79 insertions(+), 50 deletions(-) delete mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/ui/panels/export_icon.png diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java index 1ed83a8..2a61c6b 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java @@ -111,18 +111,21 @@ private void init() { public Stream getAllEnabledAlerts() { return this.getAllAlerts().filter(Alert::isEnabled); } + public Stream getAllEnabledAlertsOfType(Class type) { return this.getAllEnabledAlerts() .filter(type::isInstance) .map(type::cast); } + public Stream getAllAlerts() { - return this.getAllAlertsHelper(this.alerts.stream()); + return this.getAllAlertsFrom(this.alerts.stream()); } - private Stream getAllAlertsHelper(Stream alerts) { + + public Stream getAllAlertsFrom(Stream alerts) { return alerts.flatMap(alert -> { if (alert instanceof AlertGroup) { - return getAllAlertsHelper(((AlertGroup) alert).getAlerts().stream()); + return getAllAlertsFrom(((AlertGroup) alert).getAlerts().stream()); } return Stream.of(alert); }); @@ -195,40 +198,43 @@ public void moveAlertDown(Alert alert) { public void loadAlerts() { final String json = this.configManager.getConfiguration(WatchdogConfig.CONFIG_GROUP_NAME, WatchdogConfig.ALERTS); - this.importAlerts(json, false, false); + this.importAlerts(json, this.alerts, false, false); this.handleUpgrades(); } - public boolean importAlerts(String json, boolean append, boolean checkRegex) { - if (!Strings.isNullOrEmpty(json)) { - if (!append) { - this.alerts.clear(); - } - List importedAlerts = this.gson.fromJson(json, ALERT_LIST_TYPE); - Supplier> alertStream = () -> importedAlerts.stream().filter(Objects::nonNull); - - // Validate regex properties - if (checkRegex && !alertStream.get().allMatch(alert -> { - if (alert instanceof RegexMatcher) { - RegexMatcher matcher = (RegexMatcher) alert; - return PanelUtils.isPatternValid(this.watchdogPanel, matcher.getPattern(), matcher.isRegexEnabled()); - } + public boolean importAlerts(String json, List alerts, boolean append, boolean checkRegex) { + if (Strings.isNullOrEmpty(json)) { + return false; + } - return true; - })) { - return false; - } + if (!append) { + alerts.clear(); + } - alertStream.get().forEach(this.alerts::add); + List importedAlerts = this.gson.fromJson(json, ALERT_LIST_TYPE); + Supplier> alertStream = () -> importedAlerts.stream().filter(Objects::nonNull); - // Save immediately to save new properties - this.saveAlerts(); + // Validate regex properties + if (checkRegex && !alertStream.get().allMatch(alert -> { + if (alert instanceof RegexMatcher) { + RegexMatcher matcher = (RegexMatcher) alert; + return PanelUtils.isPatternValid(this.watchdogPanel, matcher.getPattern(), matcher.isRegexEnabled()); + } + + return true; + })) { + return false; } - Util.setParentsOnAlerts(this.alerts); + alertStream.get().forEach(alerts::add); + + // Save immediately to save new properties + this.saveAlerts(); + + Util.setParentsOnAlerts(alerts); // Inject dependencies - this.getAllAlerts() + this.getAllAlertsFrom(alertStream.get()) .filter(alert -> !(alert instanceof AlertGroup)) .forEach(alert -> { WatchdogPlugin.getInstance().getInjector().injectMembers(alert); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java index e016634..b76a9d6 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java @@ -196,7 +196,10 @@ public void rebuild() { JButton importButton = new JButton("Import", IMPORT_ICON); importButton.setHorizontalTextPosition(SwingConstants.LEFT); importButton.addActionListener(ev -> { - ImportExportDialog importExportDialog = new ImportExportDialog(SwingUtilities.getWindowAncestor(this)); + ImportExportDialog importExportDialog = new ImportExportDialog( + SwingUtilities.getWindowAncestor(this), + (json, append) -> WatchdogPlugin.getInstance().getAlertManager().importAlerts(json, this.alertManager.getAlerts(), append, true) + ); importExportDialog.setVisible(true); }); importExportGroup.add(importButton); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java index 94adb0a..fab8465 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java @@ -20,11 +20,12 @@ import java.awt.datatransfer.StringSelection; import java.awt.event.ActionListener; import java.util.function.Function; +import java.util.function.BiFunction; @Slf4j public class ImportExportDialog extends JDialog { // Import - public ImportExportDialog(Component parent) { + public ImportExportDialog(Component parent, BiFunction onImport) { this.setTitle("Import"); this.setSize(500, 250); this.setLocationRelativeTo(parent); @@ -48,7 +49,7 @@ public ImportExportDialog(Component parent) { } String json = textArea.getText(); try { - if (WatchdogPlugin.getInstance().getAlertManager().importAlerts(json, append, true)) { + if (onImport.apply(json, append)) { this.setVisible(false); } } catch (Exception ex) { diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java index fa73b50..b4a86fa 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java @@ -34,6 +34,8 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.IMPORT_ICON; +import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.EXPORT_ICON; import static com.adamk33n3r.runelite.watchdog.ui.notifications.panels.NotificationPanel.TEST_ICON; import static com.adamk33n3r.runelite.watchdog.ui.notifications.panels.NotificationPanel.TEST_ICON_HOVER; @@ -50,7 +52,7 @@ public abstract class AlertPanel extends PluginPanel { public static final ImageIcon BACK_ICON; public static final ImageIcon BACK_ICON_HOVER; - public static final ImageIcon EXPORT_ICON; + public static final ImageIcon IMPORT_ICON_HOVER; public static final ImageIcon EXPORT_ICON_HOVER; public static final ImageIcon REGEX_ICON; public static final ImageIcon REGEX_ICON_HOVER; @@ -62,9 +64,8 @@ public abstract class AlertPanel extends PluginPanel { BACK_ICON = new ImageIcon(backIcon); BACK_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(backIcon, -120)); - final BufferedImage exportIcon = ImageUtil.loadImageResource(AlertPanel.class, "export_icon.png"); - EXPORT_ICON = new ImageIcon(exportIcon); - EXPORT_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(exportIcon, -120)); + IMPORT_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(IMPORT_ICON.getImage(), -120)); + EXPORT_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(EXPORT_ICON.getImage(), -120)); final BufferedImage regexIcon = ImageUtil.loadImageResource(AlertPanel.class, "regex_icon.png"); final BufferedImage regexIconSelected = ImageUtil.loadImageResource(AlertPanel.class, "regex_icon_selected.png"); @@ -104,6 +105,38 @@ public AlertPanel(WatchdogPanel watchdogPanel, T alert) { JPanel rightButtons = new JPanel(new GridLayout(1, 0)); + if (alert instanceof AlertGroup) { + JButton importAlertBtn = PanelUtils.createActionButton( + IMPORT_ICON, + IMPORT_ICON_HOVER, + "Import alert into this group", + (btn, modifiers) -> { + ImportExportDialog importExportDialog = new ImportExportDialog( + SwingUtilities.getWindowAncestor(this), + (json, append) -> { + boolean result = WatchdogPlugin.getInstance().getAlertManager().importAlerts(json, ((AlertGroup) alert).getAlerts(), append, true); + this.rebuild(); + return result; + } + ); + importExportDialog.setVisible(true); + } + ); + rightButtons.add(importAlertBtn); + } else { + JButton testAlert = PanelUtils.createActionButton( + TEST_ICON, + TEST_ICON_HOVER, + "Test the whole alert", + (btn, modifiers) -> { + String[] triggerValues = {"1", "2", "3", "4", "5"}; + WatchdogPlugin.getInstance().getPanel().getHistoryPanelProvider().get().addEntry(alert, triggerValues); + alert.getNotifications().forEach(notification -> notification.fireForced(triggerValues)); + } + ); + rightButtons.add(testAlert); + } + JButton exportAlertBtn = PanelUtils.createActionButton( EXPORT_ICON, EXPORT_ICON_HOVER, @@ -118,20 +151,6 @@ public AlertPanel(WatchdogPanel watchdogPanel, T alert) { ); rightButtons.add(exportAlertBtn); - if (!(alert instanceof AlertGroup)) { - JButton testAlert = PanelUtils.createActionButton( - TEST_ICON, - TEST_ICON_HOVER, - "Test the whole alert", - (btn, modifiers) -> { - String[] triggerValues = {"1", "2", "3", "4", "5"}; - WatchdogPlugin.getInstance().getPanel().getHistoryPanelProvider().get().addEntry(alert, triggerValues); - alert.getNotifications().forEach(notification -> notification.fireForced(triggerValues)); - } - ); - rightButtons.add(testAlert); - } - ToggleButton toggleButton = new ToggleButton(); toggleButton.setSelected(alert.isEnabled()); toggleButton.addItemListener(i -> { diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/ui/panels/export_icon.png b/src/main/resources/com/adamk33n3r/runelite/watchdog/ui/panels/export_icon.png deleted file mode 100644 index cba67e895abc6c24c18d12988c82f09ae4ef9bda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 366 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc4a}Y{jv*HQODFI3a&{DO%U4p1;GT8p)EzV5 z8pe&Aw|dVv->}I;{fC6-b8n8OGR+!>XjfNh&(#~|~&@sdf_4i9(kZcqMmq(gm8 z?PiVc-9Oq+SM=%y9^ZGYR{HOGzmle8Y2D|tzb|s0D=LZmz_z|%73Yrw!ZjU!1?h{Y zq*)w^%~^C!ZN=`FPYZwO+dMMo3(&tBm;FsBTg2=+_o2_}G7H6juRpz3ruFR4rVoFZ zwimC9Fe_+%`pIk$7scIEs_UgY_H!w%iI~EBZNaKKhHkd`J@-Dn28Aetr>mdK II;Vst0FP~wu>b%7 diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 12fc3be..dd45f11 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Mon Jul 03 22:07:03 EDT 2023 -VERSION_BUILD=2323 +#Mon Jul 03 23:48:36 EDT 2023 +VERSION_BUILD=2330 VERSION_PHASE=alpha VERSION_MAJOR=3 VERSION_MINOR=0 From a3c1541faad13c5f4b4b44b0bf5ff21575c6a9f1 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Fri, 14 Jul 2023 01:26:46 -0400 Subject: [PATCH 03/27] Bring some of the notification layout to the alert item Started alert hub panel work --- build.gradle | 3 +- .../adamk33n3r/runelite/watchdog/Icons.java | 30 +++ .../runelite/watchdog/WatchdogPanel.java | 41 +++- .../runelite/watchdog/WatchdogProperties.java | 5 + .../watchdog/hub/AlertHubCategory.java | 18 ++ .../runelite/watchdog/hub/AlertHubClient.java | 97 +++++++++ .../runelite/watchdog/hub/AlertHubItem.java | 22 +++ .../runelite/watchdog/hub/AlertHubPanel.java | 184 ++++++++++++++++++ .../runelite/watchdog/hub/AlertManifest.java | 44 +++++ .../watchdog/ui/AlertListItemNew.java | 121 ++++++++++++ .../watchdog/ui/alerts/AlertGroupPanel.java | 3 +- .../watchdog/ui/panels/AlertListPanel.java | 10 +- .../watchdog/ui/panels/HistoryPanel.java | 27 ++- .../runelite/watchdog/copy_icon.png | Bin 0 -> 193 bytes .../runelite/watchdog/drag_handle.png | Bin 0 -> 183 bytes .../runelite/watchdog/drag_handle2.png | Bin 0 -> 150 bytes .../runelite/watchdog/mdi_database-export.png | Bin 0 -> 375 bytes .../runelite/watchdog/mdi_database-import.png | Bin 0 -> 392 bytes .../runelite/watchdog/mdi_delete-outline.png | Bin 0 -> 248 bytes .../runelite/watchdog/mdi_delete.png | Bin 0 -> 212 bytes .../watchdog/mdi_download-box-outline.png | Bin 0 -> 330 bytes .../watchdog/mdi_download-outline.png | Bin 0 -> 272 bytes .../runelite/watchdog/mdi_download.png | Bin 0 -> 250 bytes .../runelite/watchdog/mdi_pencil-outline.png | Bin 0 -> 315 bytes .../runelite/watchdog/mdi_pencil.png | Bin 0 -> 293 bytes .../runelite/watchdog/version.properties | 4 +- 26 files changed, 582 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/Icons.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/copy_icon.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/drag_handle.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/drag_handle2.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_database-export.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_database-import.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_delete-outline.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_delete.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_download-box-outline.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_download-outline.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_download.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_pencil-outline.png create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_pencil.png diff --git a/build.gradle b/build.gradle index 9e5cc9c..0837e63 100644 --- a/build.gradle +++ b/build.gradle @@ -51,9 +51,10 @@ versionProps['VERSION_BUILD'] = build.toString() versionProps['VERSION_PHASE'] = phase versionProps.store(versionPropsFile.newWriter(), null) -version = major+'.'+minor+'.'+patch+'.'+build +version = major+'.'+minor+'.'+patch if (!phase.empty) version = version+'-'+phase +version = version+'+'+build sourceCompatibility = '1.8' tasks.withType(JavaCompile) { diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/Icons.java b/src/main/java/com/adamk33n3r/runelite/watchdog/Icons.java new file mode 100644 index 0000000..7b310b4 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/Icons.java @@ -0,0 +1,30 @@ +package com.adamk33n3r.runelite.watchdog; + +import net.runelite.client.plugins.config.ConfigPlugin; +import net.runelite.client.util.ImageUtil; + +import javax.swing.ImageIcon; + +public abstract class Icons { + public static final ImageIcon EDIT_ICON; + public static final ImageIcon EXPORT_ICON; + public static final ImageIcon IMPORT_ICON; + public static final ImageIcon DELETE_ICON = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_delete.png")); + public static final ImageIcon DELETE_ICON_OUTLINE; + public static final ImageIcon DOWNLOAD_ICON; + public static final ImageIcon DOWNLOAD_BOX_ICON; + public static final ImageIcon DOWNLOAD_OUTLINE_ICON; + public static final ImageIcon EDIT_OUTLINE_ICON; + public static final ImageIcon CLONE_ICON = new ImageIcon(ImageUtil.loadImageResource(ConfigPlugin.class, "mdi_content-duplicate.png")); + + static { + EXPORT_ICON = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_database-export.png")); + IMPORT_ICON = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_database-import.png")); + DELETE_ICON_OUTLINE = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_delete-outline.png")); + DOWNLOAD_ICON = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_download.png")); + DOWNLOAD_BOX_ICON = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_download-box-outline.png")); + DOWNLOAD_OUTLINE_ICON = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_download-outline.png")); + EDIT_ICON = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_pencil.png")); + EDIT_OUTLINE_ICON = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_pencil-outline.png")); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java index b76a9d6..795f154 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java @@ -1,12 +1,13 @@ package com.adamk33n3r.runelite.watchdog; import com.adamk33n3r.runelite.watchdog.alerts.*; -import com.adamk33n3r.runelite.watchdog.ui.AlertListItem; +import com.adamk33n3r.runelite.watchdog.ui.AlertListItemNew; import com.adamk33n3r.runelite.watchdog.ui.ImportExportDialog; import com.adamk33n3r.runelite.watchdog.ui.alerts.*; import com.adamk33n3r.runelite.watchdog.ui.panels.AlertListPanel; import com.adamk33n3r.runelite.watchdog.ui.panels.HistoryPanel; import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; +import com.adamk33n3r.runelite.watchdog.hub.AlertHubPanel; import net.runelite.client.plugins.config.ConfigPlugin; import net.runelite.client.plugins.info.InfoPanel; @@ -38,8 +39,6 @@ import java.awt.Font; import java.awt.GridLayout; import java.awt.image.BufferedImage; -import java.util.List; -import java.util.stream.Collectors; @Slf4j public class WatchdogPanel extends PluginPanel { @@ -59,6 +58,14 @@ public class WatchdogPanel extends PluginPanel { @Named("watchdog.pluginVersion") private String PLUGIN_VERSION; + @Inject + @Named("watchdog.pluginVersionFull") + private String PLUGIN_VERSION_FULL; + + @Inject + @Named("VERSION_PHASE") + private String PLUGIN_VERSION_PHASE; + @Getter private final MultiplexingPluginPanel muxer = new MultiplexingPluginPanel(this); @@ -66,6 +73,9 @@ public class WatchdogPanel extends PluginPanel { @Inject private Provider historyPanelProvider; + @Inject + private Provider alertHubPanelProvider; + @Inject private AlertManager alertManager; @@ -119,9 +129,10 @@ public void rebuild() { title.setFont(title.getFont().deriveFont(Font.BOLD)); title.setHorizontalAlignment(JLabel.LEFT); title.setForeground(Color.WHITE); - title.setToolTipText("Watchdog v" + PLUGIN_VERSION); + boolean isPreRelease = !PLUGIN_VERSION_PHASE.equals("release") && !PLUGIN_VERSION_PHASE.isEmpty(); + title.setToolTipText("Watchdog v" + (isPreRelease ? PLUGIN_VERSION_FULL : PLUGIN_VERSION)); titlePanel.add(title); - JLabel version = new JLabel("v" + PLUGIN_VERSION); + JLabel version = new JLabel("v" + (isPreRelease ? PLUGIN_VERSION_FULL : PLUGIN_VERSION)); version.setFont(version.getFont().deriveFont(10f)); version.setBorder(new EmptyBorder(5, 0, 0, 0)); titlePanel.add(version); @@ -184,13 +195,16 @@ public void rebuild() { DragAndDropReorderPane dragAndDropReorderPane = new DragAndDropReorderPane(); dragAndDropReorderPane.addDragListener((c) -> { int pos = dragAndDropReorderPane.getPosition(c); - AlertListItem alertListItem = (AlertListItem) c; + AlertListItemNew alertListItem = (AlertListItemNew) c; // log.debug("drag listener: " + alertListItem.getAlert().getName() + " to " + pos); alertManager.moveAlertTo(alertListItem.getAlert(), pos); }); AlertListPanel alertPanel = new AlertListPanel(this.alertManager.getAlerts(), dragAndDropReorderPane, this::rebuild); - this.add(new JScrollPane(alertPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); +// this.add(alertPanel, BorderLayout.CENTER); + JScrollPane scroll = new JScrollPane(alertPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); +// scroll.setMaximumSize(new Dimension(PluginPanel.PANEL_WIDTH, 999)); + this.add(scroll, BorderLayout.CENTER); JPanel importExportGroup = new JPanel(new GridLayout(1, 2, 5, 0)); JButton importButton = new JButton("Import", IMPORT_ICON); @@ -210,7 +224,18 @@ public void rebuild() { importExportDialog.setVisible(true); }); importExportGroup.add(exportButton); - this.add(importExportGroup, BorderLayout.SOUTH); + + JPanel bottomPanel = new JPanel(new GridLayout(0, 1, 3, 3)); + bottomPanel.add(importExportGroup); + JButton hubButton = new JButton("Alert Hub", Icons.DOWNLOAD_ICON); + hubButton.setHorizontalTextPosition(SwingConstants.LEFT); + hubButton.addActionListener(ev -> { + AlertHubPanel alertHubPanel = this.alertHubPanelProvider.get(); +// alertHubPanel.reloadList(); + this.muxer.pushState(alertHubPanel); + }); + bottomPanel.add(hubButton); + this.add(bottomPanel, BorderLayout.SOUTH); // Need this for rebuild for some reason this.revalidate(); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogProperties.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogProperties.java index 589844f..d3fb8ed 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogProperties.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogProperties.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Objects; import java.util.Properties; public class WatchdogProperties { @@ -25,6 +26,10 @@ public class WatchdogProperties { properties.getProperty("VERSION_MINOR"), properties.getProperty("VERSION_PATCH")); properties.put("watchdog.pluginVersion", pluginVersion); + String phase = properties.getProperty("VERSION_PHASE"); + String build = properties.getProperty("VERSION_BUILD"); + String pluginVersionFull = String.format("%s-%s+%s", pluginVersion, phase, build); + properties.put("watchdog.pluginVersionFull", pluginVersionFull); } catch (IOException ex) { throw new RuntimeException(ex); } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java new file mode 100644 index 0000000..d2b7ab4 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java @@ -0,0 +1,18 @@ +package com.adamk33n3r.runelite.watchdog.hub; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum AlertHubCategory { + COMBAT("Combat", "Combat"), + SKILLING("Skilling", "Skilling"), + BOSSES("Bosses", "Bosses"), + DROPS("Drops", "Drops"), + AFK("AFK", "AFK"), + ; + + private final String name; + private final String tooltip; +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java new file mode 100644 index 0000000..296ba05 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java @@ -0,0 +1,97 @@ +package com.adamk33n3r.runelite.watchdog.hub; + +import com.google.gson.reflect.TypeToken; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import javax.imageio.ImageIO; +import javax.inject.Inject; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@Slf4j +public class AlertHubClient { + private final OkHttpClient cachingClient; + + @Inject + public AlertHubClient(OkHttpClient cachingClient) { + this.cachingClient = cachingClient; + } + + public List downloadManifest() throws IOException { + HttpUrl manifest = Objects.requireNonNull(HttpUrl.parse("https://raw.githubusercontent.com/melkypie/resource-packs")) + .newBuilder() + .addPathSegment("github-actions") + .addPathSegment("manifest.js") + .build(); + + return Arrays.asList(new AlertManifest( + "testAlert", + "284hfu43hhfiu24rf", + "Test Alert", + "This is a test alert on the hub", + "4", + "adamk33n3r", + AlertHubCategory.COMBAT, + Arrays.asList("afk", "combat"), + new URL("https://github.com/adamk33n3r/runelite-watchdog"), + "[]", + false + ), new AlertManifest( + "testAlert2", + "284hfu43hhfiu24rf", + "Test Alert 2", + "This is a test alert on the hub", + "4", + "adamk33n3r", + AlertHubCategory.SKILLING, + Arrays.asList("mining", "tts"), + new URL("https://github.com/adamk33n3r/runelite-watchdog"), + "[]", + false + )); +// try (Response res = cachingClient.newCall(new Request.Builder().url(manifest).build()).execute()) { +// if (res.code() != 200) { +// throw new IOException("Non-OK response code: " + res.code()); +// } +// +// String data = Objects.requireNonNull(res.body()).string(); +// +// return RuneLiteAPI.GSON.fromJson(data, new TypeToken>() {}.getType()); +// } + } + + public BufferedImage downloadIcon(AlertManifest alertManifest) throws IOException { + // TODO: Use defaults + if (!alertManifest.isHasIcon()) { + return null; + } + + HttpUrl url = Objects.requireNonNull(HttpUrl.parse("https://raw.githubusercontent.com/melkypie/resource-packs")) + .newBuilder() + .addPathSegment(alertManifest.getCommit()) + .addPathSegment("icon.png") + .build(); + + try (Response res = cachingClient.newCall(new Request.Builder().url(url).build()).execute()) { + if (res.code() != 200) { + throw new IOException("Non-OK response code: " + res.code()); + } + + byte[] bytes = Objects.requireNonNull(res.body()).bytes(); + synchronized (ImageIO.class) { + return ImageIO.read(new ByteArrayInputStream(bytes)); + } + } + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java new file mode 100644 index 0000000..f267863 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java @@ -0,0 +1,22 @@ +package com.adamk33n3r.runelite.watchdog.hub; + +import lombok.Getter; +import net.runelite.client.ui.DynamicGridLayout; + +import javax.swing.*; +import java.awt.*; + +@Getter +public class AlertHubItem extends JPanel { + private final AlertManifest manifest; + + public AlertHubItem(AlertManifest manifest) { + this.manifest = manifest; + + this.setLayout(new DynamicGridLayout(0, 1, 5, 5)); + this.add(new JLabel(this.manifest.toString())); + this.add(new JLabel(this.manifest.getAuthor())); + this.add(new JLabel(this.manifest.getDescription())); + this.add(new JLabel(this.manifest.getRepo().toString())); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java new file mode 100644 index 0000000..e5ec35c --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java @@ -0,0 +1,184 @@ +package com.adamk33n3r.runelite.watchdog.hub; + +import com.adamk33n3r.runelite.watchdog.ui.PlaceholderTextField; +import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout; +import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; +import com.adamk33n3r.runelite.watchdog.ui.panels.ScrollablePanel; + +import com.google.common.base.Splitter; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.DynamicGridLayout; +import net.runelite.client.ui.MultiplexingPluginPanel; +import net.runelite.client.ui.PluginPanel; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.ui.components.IconTextField; +import net.runelite.client.util.Text; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON; +import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON_HOVER; + +@Slf4j +//@Singleton +public class AlertHubPanel extends PluginPanel { + private final Provider muxer; + private final AlertHubClient alertHubClient; +// private final ScrollablePanel filteredAlerts; + private List alertHubItems = new ArrayList<>(); + private final IconTextField searchBar; + private final JPanel container; + private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings(); + + @Inject + public AlertHubPanel(Provider muxer, AlertHubClient alertHubClient) { + super(false); + this.muxer = muxer; + this.alertHubClient = alertHubClient; + +// this.setLayout(new BorderLayout()); +// +// JPanel topPanel = new JPanel(new BorderLayout()); +// topPanel.setBorder(new EmptyBorder(0, 0, 5, 0)); + JButton backButton = PanelUtils.createActionButton( + BACK_ICON, + BACK_ICON_HOVER, + "Back", + (btn, modifiers) -> this.muxer.get().popState() + ); + backButton.setPreferredSize(new Dimension(22, 16)); + backButton.setBorder(new EmptyBorder(0, 0, 0, 5)); +// topPanel.add(backButton, BorderLayout.WEST); +// PlaceholderTextField filterTextField = new PlaceholderTextField(); +// filterTextField.setPlaceholder("Filter"); +// filterTextField.getDocument().addDocumentListener(new DocumentListener() { +// @Override +// public void insertUpdate(DocumentEvent e) { +// updateFilter(filterTextField.getText()); +// } +// +// @Override +// public void removeUpdate(DocumentEvent e) { +// updateFilter(filterTextField.getText()); +// } +// +// @Override +// public void changedUpdate(DocumentEvent e) { +// updateFilter(filterTextField.getText()); +// } +// }); +// topPanel.add(filterTextField); +// this.add(topPanel, BorderLayout.NORTH); +// +// this.filteredAlerts = new ScrollablePanel(new StretchedStackedLayout(3, 3)); +// this.filteredAlerts.setBorder(new EmptyBorder(0, 10, 0, 10)); +// this.filteredAlerts.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT); +// this.filteredAlerts.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH); +// this.filteredAlerts.setScrollableBlockIncrement(ScrollablePanel.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10); +// JScrollPane scroll = new JScrollPane(this.filteredAlerts, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); +// +// this.add(scroll, BorderLayout.CENTER); + + GroupLayout layout = new GroupLayout(this); + this.setLayout(layout); + this.setBackground(ColorScheme.PROGRESS_ERROR_COLOR); + this.searchBar = new IconTextField(); + this.searchBar.setIcon(IconTextField.Icon.SEARCH); + this.searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR); + this.searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); + this.searchBar.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + updateFilter(searchBar.getText()); + } + + @Override + public void removeUpdate(DocumentEvent e) { + updateFilter(searchBar.getText()); + } + + @Override + public void changedUpdate(DocumentEvent e) { + updateFilter(searchBar.getText()); + } + }); + + this.container = new JPanel(new DynamicGridLayout(0, 1, 0, 5)); +// this.container.setMaximumSize(new Dimension(PANEL_WIDTH, 9999)); + this.container.setBackground(ColorScheme.GRAND_EXCHANGE_LIMIT); + this.container.setBorder(BorderFactory.createEmptyBorder(0, 7, 15, 7)); +// this.container.setAlignmentX(Component.LEFT_ALIGNMENT); + +// JPanel wrapper = new JPanel(new BorderLayout()); +// wrapper.add(this.container, BorderLayout.CENTER); + JScrollPane scrollPane = new JScrollPane(this.container, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setBackground(ColorScheme.GRAND_EXCHANGE_ALCH); + scrollPane.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999)); + + layout.setVerticalGroup(layout.createSequentialGroup() + .addGap(5) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(backButton, 24, 24, 24) + .addComponent(searchBar, 24, 24, 24)) + .addGap(10) + .addComponent(scrollPane) + ); + + layout.setHorizontalGroup(layout.createParallelGroup() + .addGroup(layout.createSequentialGroup() + .addGap(7) + .addComponent(backButton) + .addGap(3) + .addComponent(searchBar) + .addGap(7)) + .addComponent(scrollPane) + ); + +// this.revalidate(); + + reloadList(); + } + + public void reloadList() { + this.container.removeAll(); + + try { + List alertManifests = this.alertHubClient.downloadManifest(); + System.out.println(alertManifests.stream().map(AlertManifest::toString).collect(Collectors.joining(", "))); + this.reloadList(alertManifests); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void reloadList(List alertManifests) { + this.alertHubItems = alertManifests.stream().map(AlertHubItem::new).collect(Collectors.toList()); + this.updateFilter(this.searchBar.getText()); + } + + private void updateFilter(String search) { + this.container.removeAll(); + String upperSearch = search.toUpperCase(); + this.alertHubItems.stream().filter(alertHubItem -> { +// Alert alert = WatchdogPlugin.getInstance().getAlertManager().getGson().fromJson(alertHubItem.getManifest().getJson(), ALERT_LIST_TYPE); + AlertManifest manifest = alertHubItem.getManifest(); + return Text.matchesSearchTerms(SPLITTER.split(upperSearch), manifest.getKeywords()); + }).forEach(this.container::add); + this.revalidate(); + // Idk why I need to repaint sometimes and the PluginListPanel doesn't + this.repaint(); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java new file mode 100644 index 0000000..f16f943 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java @@ -0,0 +1,44 @@ +package com.adamk33n3r.runelite.watchdog.hub; + +import lombok.Data; + +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Data +public class AlertManifest { + private final String internalName; + private final String commit; + + private final String displayName; + private final String description; + private final String createdVersion; + private final String author; + private final AlertHubCategory category; + private final List tags; + private final URL repo; + private final String json; + private final boolean hasIcon; + + @Override + public String toString() + { + return displayName; + } + + public List getKeywords() { + Stream keywords = Stream.of( + this.getDisplayName(), + this.getInternalName(), + this.getAuthor() +// this.getCategory().getName() + ); + if (this.getTags() != null) { + return Stream.concat(keywords, this.getTags().stream()).collect(Collectors.toList()); + } + return keywords.collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java new file mode 100644 index 0000000..0c532c0 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java @@ -0,0 +1,121 @@ +package com.adamk33n3r.runelite.watchdog.ui; + +import com.adamk33n3r.runelite.watchdog.AlertManager; +import com.adamk33n3r.runelite.watchdog.Icons; +import com.adamk33n3r.runelite.watchdog.WatchdogPanel; +import com.adamk33n3r.runelite.watchdog.alerts.Alert; +import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; +import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; + +import net.runelite.client.plugins.screenmarkers.ScreenMarkerPlugin; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.DynamicGridLayout; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.MouseDragEventForwarder; +import net.runelite.client.util.ImageUtil; + +import lombok.Getter; + +import javax.swing.*; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.List; + +//import static com.adamk33n3r.runelite.watchdog.ui.AlertListItem.CLONE_ICON; +//import static com.adamk33n3r.runelite.watchdog.ui.AlertListItem.DELETE_ICON; + +public class AlertListItemNew extends JPanel { +// private static final ImageIcon EDIT_ICON; +// +// static { +// final BufferedImage editImg = ImageUtil.loadImageResource(ScreenMarkerPlugin.class, "border_color_icon.png"); +// EDIT_ICON = new ImageIcon(editImg); +// } + + private static final int ROW_HEIGHT = 30; + private static final int PADDING = 2; + + @Getter + private final Alert alert; + + public AlertListItemNew(WatchdogPanel panel, AlertManager alertManager, Alert alert, List parentList, JComponent parent, Runnable onChange) { + this.alert = alert; + this.setLayout(new BorderLayout(5, 0)); + this.setBorder(new EmptyBorder(PADDING, 0, PADDING, 0)); + this.setMaximumSize(new Dimension(PluginPanel.PANEL_WIDTH, 999)); + this.setAlignmentX(JPanel.LEFT_ALIGNMENT); +// this.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, ROW_HEIGHT + PADDING * 2)); + this.setBackground(ColorScheme.DARK_GRAY_COLOR); + MouseDragEventForwarder mouseDragEventForwarder = new MouseDragEventForwarder(parent); + + JPanel container = new JPanel(new StretchedStackedLayout(3, 3)); + container.setBackground(ColorScheme.DARKER_GRAY_COLOR); + JPanel topWrapper = new JPanel(new BorderLayout(3, 3)); + container.add(topWrapper); + + topWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR); + topWrapper.setBorder(new CompoundBorder( + BorderFactory.createMatteBorder(0, 0, 1, 0, ColorScheme.DARK_GRAY_COLOR), + BorderFactory.createMatteBorder(5, 10, 5, 0, ColorScheme.DARKER_GRAY_COLOR))); + + ToggleButton toggleButton = new ToggleButton(); + toggleButton.setSelected(alert.isEnabled()); + toggleButton.addItemListener(i -> { + alert.setEnabled(toggleButton.isSelected()); + alertManager.saveAlerts(); + }); +// toggleButton.setOpaque(false); + topWrapper.add(toggleButton, BorderLayout.WEST); + + JPanel nameWrapper = new JPanel(new DynamicGridLayout(1, 0, 2, 2)); + nameWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR); + topWrapper.add(nameWrapper, BorderLayout.CENTER); + + JLabel nameLabel = new JLabel(alert.getName()); + nameLabel.setToolTipText(alert.getName()); + nameWrapper.add(nameLabel); + + topWrapper.addMouseListener(mouseDragEventForwarder); + topWrapper.addMouseMotionListener(mouseDragEventForwarder); + nameWrapper.addMouseListener(mouseDragEventForwarder); + nameWrapper.addMouseMotionListener(mouseDragEventForwarder); + nameLabel.addMouseListener(mouseDragEventForwarder); + nameLabel.addMouseMotionListener(mouseDragEventForwarder); + + JPanel rightActions = new JPanel(new FlowLayout(FlowLayout.RIGHT, 3, 0)); + rightActions.setBorder(new EmptyBorder(4, 0, 0, 0)); + rightActions.setBackground(ColorScheme.DARKER_GRAY_COLOR); + topWrapper.add(rightActions, BorderLayout.EAST); + + rightActions.add(PanelUtils.createActionButton(Icons.EDIT_ICON, Icons.EDIT_ICON, "Edit Alert", (btn, modifiers) -> { + panel.openAlert(alert); + })); + + rightActions.add(PanelUtils.createActionButton(Icons.CLONE_ICON, Icons.CLONE_ICON, "Clone Alert", (btn, modifiers) -> { + alertManager.cloneAlert(alert); + })); + + final JButton deleteButton = PanelUtils.createActionButton(Icons.DELETE_ICON, Icons.DELETE_ICON, "Delete Alert", (btn, modifiers) -> { + int result = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete the " + alert.getName() + " alert?", "Delete?", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE); + if (result == JOptionPane.YES_OPTION) { + alertManager.removeAlert(alert); + } + }); + rightActions.add(deleteButton); + + JPanel settings = new JPanel(new StretchedStackedLayout(3, 3)); + settings.setBorder(new EmptyBorder(5, 10, 5, 10)); + settings.setBackground(ColorScheme.DARKER_GRAY_COLOR); + settings.add(new JLabel("settings place")); + JButton jButton = new JButton("open"); + jButton.addActionListener((ev) -> { + panel.openAlert(alert); + }); + settings.add(jButton); + container.add(settings); + + this.add(container); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java index b7f3515..89db233 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java @@ -5,6 +5,7 @@ import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; import com.adamk33n3r.runelite.watchdog.ui.AlertListItem; +import com.adamk33n3r.runelite.watchdog.ui.AlertListItemNew; import com.adamk33n3r.runelite.watchdog.ui.HorizontalRuleBorder; import com.adamk33n3r.runelite.watchdog.ui.panels.AlertListPanel; import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; @@ -45,7 +46,7 @@ protected void build() { DragAndDropReorderPane dragAndDropReorderPane = new DragAndDropReorderPane(); dragAndDropReorderPane.addDragListener((c) -> { int pos = dragAndDropReorderPane.getPosition(c); - AlertListItem alertListItem = (AlertListItem) c; + AlertListItemNew alertListItem = (AlertListItemNew) c; this.alert.getAlerts().remove(alertListItem.getAlert()); this.alert.getAlerts().add(pos, alertListItem.getAlert()); alertManager.saveAlerts(); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java index ed95821..b8e2f90 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java @@ -3,12 +3,12 @@ import com.adamk33n3r.runelite.watchdog.AlertManager; import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; import com.adamk33n3r.runelite.watchdog.alerts.Alert; -import com.adamk33n3r.runelite.watchdog.ui.AlertListItem; +import com.adamk33n3r.runelite.watchdog.ui.AlertListItemNew; import net.runelite.client.ui.components.DragAndDropReorderPane; -import javax.swing.JPanel; -import java.awt.BorderLayout; +import javax.swing.*; +import java.awt.*; import java.util.List; public class AlertListPanel extends JPanel { @@ -16,9 +16,9 @@ public AlertListPanel(List alerts, DragAndDropReorderPane dragAndDropReor this.setLayout(new BorderLayout()); AlertManager alertManager = WatchdogPlugin.getInstance().getAlertManager(); alerts.forEach((subAlert) -> { - AlertListItem alertListItem = new AlertListItem(WatchdogPlugin.getInstance().getPanel(), alertManager, subAlert, alerts, dragAndDropReorderPane, onChange); + AlertListItemNew alertListItem = new AlertListItemNew(WatchdogPlugin.getInstance().getPanel(), alertManager, subAlert, alerts, dragAndDropReorderPane, onChange); dragAndDropReorderPane.add(alertListItem); }); - this.add(dragAndDropReorderPane, BorderLayout.NORTH); + this.add(dragAndDropReorderPane, BorderLayout.CENTER); } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java index 616d5b3..5b6d544 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java @@ -5,10 +5,12 @@ import com.adamk33n3r.runelite.watchdog.ui.PlaceholderTextField; import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout; +import com.google.common.base.Splitter; import net.runelite.client.ui.MultiplexingPluginPanel; import net.runelite.client.ui.PluginPanel; import lombok.extern.slf4j.Slf4j; +import net.runelite.client.util.Text; import javax.inject.Inject; import javax.inject.Provider; @@ -22,7 +24,11 @@ import java.awt.BorderLayout; import java.awt.Dimension; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON; import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON_HOVER; @@ -35,6 +41,7 @@ public class HistoryPanel extends PluginPanel { private final List previousAlerts = new ArrayList<>(); private static final int MAX_HISTORY_ITEMS = 100; + private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings(); @Inject public HistoryPanel(Provider muxer) { @@ -97,23 +104,23 @@ public void addEntry(Alert alert, String[] triggerValues) { this.repaint(); } + // TODO: Abstract this out into a filterpanel type thing private void updateFilter(String search) { this.historyItems.removeAll(); String upperSearch = search.toUpperCase(); this.previousAlerts.stream().filter(historyEntryPanel -> { Alert alert = historyEntryPanel.getAlert(); - boolean alertName = alert.getName().toUpperCase().contains(upperSearch); - boolean typeName = alert.getType().getName().toUpperCase().contains(upperSearch); - boolean notifications = alert.getNotifications().stream().anyMatch(notification -> { - boolean notifName = notification.getType().getName().toUpperCase().contains(upperSearch); - boolean message = false; + Stream keywords = Stream.concat(Stream.of( + alert.getName(), + alert.getType().getName() + ), alert.getNotifications().stream().flatMap(notification -> { + Stream notificationType = Stream.of(notification.getType().getName()); if (notification instanceof IMessageNotification) { - message = ((IMessageNotification) notification).getMessage().toUpperCase().contains(upperSearch); + return Stream.concat(notificationType, Stream.of(((IMessageNotification) notification).getMessage())); } - - return notifName || message; - }); - return alertName || typeName || notifications; + return notificationType; + })).map(String::toUpperCase); + return Text.matchesSearchTerms(SPLITTER.split(upperSearch), keywords.collect(Collectors.toList())); }).forEach(this.historyItems::add); this.revalidate(); // Idk why I need to repaint sometimes and the PluginListPanel doesn't diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/copy_icon.png b/src/main/resources/com/adamk33n3r/runelite/watchdog/copy_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f7245bf92a0ad02f4084a4afb6d6cad39dcf6340 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%G}E0G|-o|Ns93nW2X+*8wS}k|4j}{|ryJ8+ZYEoCO|{#S9F5he4R}c>anM zprDzji(`mJaB_kRvjHQU0fXU$p9+F&m>i@|wR1QeQE1r46d)+Tti{02!KlKuPKG7H eM8v>|iNVj4VfLoQsy~1xFnGH9xvXPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0Srk*K~y+T#nZn| z!cZKB;g*pOII^I3KnxmO7#v72#DS2o5QEjYIk_^z;>0Crf^lVJX6fosZoa{iM{eEoB_P0+v#Ld>Bpej-2{F4j>AmS^lr4apdL za2dODj~up6pa*mOlmYUXp@WSHPEbUMbk1=M_3NvFT*I+nV+(cM;SfhKnZ!&^;o;VP z3vcl8jxLH&rxMaJtGadx1^8GZ9hFdm`t{U6Zm>~87GqpOE31?oKJWD8ub9Go7YhWq!m4az-+!ayd;yXK V*)dLS|3Cl$002ovPDHLkV1nD!nUMeh literal 0 HcmV?d00001 diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_database-import.png b/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_database-import.png new file mode 100644 index 0000000000000000000000000000000000000000..6e2d613113c6e170398686dfabe8c02b65930111 GIT binary patch literal 392 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc4YHmtjv*HQM=u+CGbPF#{}}AfE_?Vy=mi&c z&Racd#S`3?uPDFZ%~8d9@j;q%w_wMEJD0)~-m>~x-CE>)`0HQWd&T!Ok2muDd(d3o z{{MO9yZ<)R3u3R|RP}sc{(8-N2j*=T90ZShB=q?2Nooi+ad6--tZaxE);afpr})TH zuHf|Qf(Ch+g`KR6K6Sr;`gB!iu+5@wn?-Dr4@Fpu-LD)me;>6{u~&MJGQ0HJ0w&)@ zY5NkArQc3l@wxqiUrOLXSrZ58TN6`rj0^amN|oxIiM6QDQ05O#Ki`=4`;hLuf<7xR z569;Zn4M#i+T@(HPds8de?cd8$-?KNF$?)!{~MPcWOO-N`0~K|Nc&S+^ZGuqUO8YS k@|&x+e|~L!Z{KJBz4qRjjOog4z|dpxboFyt=akR{0KprcOaK4? literal 0 HcmV?d00001 diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_delete-outline.png b/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_delete-outline.png new file mode 100644 index 0000000000000000000000000000000000000000..bb467bbc1c8907639eb00fbafddd8f7dac4a535d GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!Cp@n#}Etu+aC%MVy%4*A^+=M# z8SW$9jWgss93LnP9B2Hfu29E%bNH9BY`8cF5%~|9y;_eYV@@RGH;Onz~AAhDKtfHf}sbGN;}ttU{Mi$)<^pt ndiWfYx*H{2|2jN(V`F1*U({vV#mVUnbQpuDtDnm{r-UW|3c*X4 literal 0 HcmV?d00001 diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_delete.png b/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..789f1a97dcde1a840745753355e6463518715033 GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|`aE46LoEE? zPB!Fg3J`F)@8SE#re*m9fg?9wJmoNpX$r33uzr)+!>LxoT6ac2=T4FN?=oX+Z6W8* zzbp*5s}tCtII!j%XOt~qdw0P1(pS?TvKJUqQ&ziON)-Fxb4%|U!=zi8f1R0c_c1ih zvv0V-{#dk5KZ!p_%+J!}RFd6_#>YG<40@CNRyX9&*ZwIje@@9l@W^bTS3s9Bc)I$z JtaD0e0s#GtOyvLo literal 0 HcmV?d00001 diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_download-box-outline.png b/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_download-box-outline.png new file mode 100644 index 0000000000000000000000000000000000000000..bdb93d37e733a69737d73410072006bba8c632c8 GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!TX*rjv*HQM<*L{9dZzG73NP|zjU4>&y5O( zV%dXndm4JT-inCq>InZJAeN;ZvGbq3wX~Y{#09PVax-(%($k$LSv{W=pYh^rc1|N} zigoE6=YxG8T7-8@-<@S-(e|%EEZ`)^t*t&=vn^GMmi%m6Jg9f z(RMmTEvxwS3SK!UvaPrKbI0)k+wTty{Tr5N=ZEwAX{F|NoPo{Djp( WcBz?roNy=5s|=p5elF{r5}E+9vV5ih literal 0 HcmV?d00001 diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_download-outline.png b/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_download-outline.png new file mode 100644 index 0000000000000000000000000000000000000000..a7925b805060a21e0546d64fd147dccb3fee882a GIT binary patch literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!KI!qjv*HQ$$$R;w`W#u;Pjfz`$3YSNXqCK z7st*8j2+U7K5rT(FkW9Ep}wF>Vg~caaD`*f(kI+VS72M|$Pi$%C{bGBAKQ`h4H04; zPaA(&hMeK*2)wemV8RAjfo78&_D8l3e?&VR*^*Zr6>vD@=kQ0iYhxB$qLb!D=8k&i zk8uY4+zYCL;&_iJ2!DJS>cGSPLhk}ok|Bq%dIjr|4@XQd?3~!h$nfCD!2(9PlV^Yq OX7F_Nb6Mw<&;$V7mt7_R literal 0 HcmV?d00001 diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_download.png b/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_download.png new file mode 100644 index 0000000000000000000000000000000000000000..43c3600aeaa00160c48b0d7bdead0aa82f979400 GIT binary patch literal 250 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!G2E{#}Etut&>jkHYfx`McphhQ72{Pp)(7eXMj~QZ2bVbF0{< z^w#|sjar3nZJ2hz_kUXQi3xn?J{V;0z88DYpqXdkxx-)Hs6N}!esxFjho8ZEio&&G s1qbITv`vhYyL2E*zkbId-xdi5hUW*Z@5}hT2Re`th)|#`H!;W}^>Q z_uP|SDe~C1J~1*#P+EATA?TF0%>S*MUt3E*HQMlM`o7KkOUu#~M2{DK@xJu^{`$`+ zxr=Trc&sL0duaBr51EH;`KQl2xuDf1EMQx~^M^$m-KtaMiX;{?Z5I~PsedfPs}Za6 zO{h8WfE4$OYmDnwC~urC@h;@v!D;#x8s0w^a7<0V&|R~$t>F5`=2Pq|*34G7c$^yf zX2Wq2E&f6$(X!`_vQyNKSuEnI{x?Oo=8L52jm^{l{mt)LB&{xMJdn+ecw44$rj JF6*2UngHateQf{$ literal 0 HcmV?d00001 diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_pencil.png b/src/main/resources/com/adamk33n3r/runelite/watchdog/mdi_pencil.png new file mode 100644 index 0000000000000000000000000000000000000000..8b443a7a1e406a4e9d5155eae6a66f2d42b92752 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!Cjs%jv*HQM=x7*9dZz9eRxrogOQt?J5Zox z@-F`H2Yfnr8hU$Iv^KRWybwF~pHI@l*z-eZ#RKI#Pv$iWT>5lxcl;&2_3_UqGjqK$ zoEN9()7(g2B_(&t;ucLK6UWA#)x8 literal 0 HcmV?d00001 diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index dd45f11..6fb463b 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Mon Jul 03 23:48:36 EDT 2023 -VERSION_BUILD=2330 +#Fri Jul 14 01:21:52 EDT 2023 +VERSION_BUILD=2491 VERSION_PHASE=alpha VERSION_MAJOR=3 VERSION_MINOR=0 From 71f0b1744f7403da02d89cfa3ff8964ee14324f4 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sat, 15 Jul 2023 02:01:23 -0400 Subject: [PATCH 04/27] Added expandable version of alert item panel --- .../runelite/watchdog/WatchdogPanel.java | 17 +-- .../runelite/watchdog/hub/AlertHubPanel.java | 33 +---- .../runelite/watchdog/hub/AlertManifest.java | 4 +- .../watchdog/ui/AlertListItemNew.java | 124 +++++++++++++----- .../runelite/watchdog/ui/SearchBar.java | 35 +++++ .../watchdog/ui/alerts/AlertGroupPanel.java | 4 +- .../watchdog/ui/panels/HistoryPanel.java | 27 +--- .../watchdog/ui/panels/PanelUtils.java | 6 +- .../runelite/watchdog/version.properties | 4 +- 9 files changed, 151 insertions(+), 103 deletions(-) create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/SearchBar.java diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java index 795f154..bbd8ad1 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java @@ -3,6 +3,7 @@ import com.adamk33n3r.runelite.watchdog.alerts.*; import com.adamk33n3r.runelite.watchdog.ui.AlertListItemNew; import com.adamk33n3r.runelite.watchdog.ui.ImportExportDialog; +import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout; import com.adamk33n3r.runelite.watchdog.ui.alerts.*; import com.adamk33n3r.runelite.watchdog.ui.panels.AlertListPanel; import com.adamk33n3r.runelite.watchdog.ui.panels.HistoryPanel; @@ -24,13 +25,7 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; +import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.BorderLayout; import java.awt.Color; @@ -120,7 +115,7 @@ public class WatchdogPanel extends PluginPanel { public void rebuild() { this.removeAll(); - this.setLayout(new BorderLayout(0, 5)); + this.setLayout(new BorderLayout(0, 0)); JPanel topPanel = new JPanel(new BorderLayout()); JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); @@ -201,9 +196,9 @@ public void rebuild() { }); AlertListPanel alertPanel = new AlertListPanel(this.alertManager.getAlerts(), dragAndDropReorderPane, this::rebuild); -// this.add(alertPanel, BorderLayout.CENTER); - JScrollPane scroll = new JScrollPane(alertPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); -// scroll.setMaximumSize(new Dimension(PluginPanel.PANEL_WIDTH, 999)); + JPanel wrapper = new JPanel(new StretchedStackedLayout(3, 3)); + wrapper.add(alertPanel); + JScrollPane scroll = new JScrollPane(wrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); this.add(scroll, BorderLayout.CENTER); JPanel importExportGroup = new JPanel(new GridLayout(1, 2, 5, 0)); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java index e5ec35c..16c8100 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java @@ -1,9 +1,7 @@ package com.adamk33n3r.runelite.watchdog.hub; -import com.adamk33n3r.runelite.watchdog.ui.PlaceholderTextField; -import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout; +import com.adamk33n3r.runelite.watchdog.ui.SearchBar; import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; -import com.adamk33n3r.runelite.watchdog.ui.panels.ScrollablePanel; import com.google.common.base.Splitter; import net.runelite.client.ui.ColorScheme; @@ -17,17 +15,13 @@ import javax.inject.Inject; import javax.inject.Provider; -import javax.inject.Singleton; import javax.swing.*; import javax.swing.border.EmptyBorder; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; import java.awt.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.Stream; import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON; import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON_HOVER; @@ -95,26 +89,7 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale GroupLayout layout = new GroupLayout(this); this.setLayout(layout); this.setBackground(ColorScheme.PROGRESS_ERROR_COLOR); - this.searchBar = new IconTextField(); - this.searchBar.setIcon(IconTextField.Icon.SEARCH); - this.searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR); - this.searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); - this.searchBar.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void insertUpdate(DocumentEvent e) { - updateFilter(searchBar.getText()); - } - - @Override - public void removeUpdate(DocumentEvent e) { - updateFilter(searchBar.getText()); - } - - @Override - public void changedUpdate(DocumentEvent e) { - updateFilter(searchBar.getText()); - } - }); + this.searchBar = new SearchBar(this::updateFilter); this.container = new JPanel(new DynamicGridLayout(0, 1, 0, 5)); // this.container.setMaximumSize(new Dimension(PANEL_WIDTH, 9999)); @@ -132,7 +107,7 @@ public void changedUpdate(DocumentEvent e) { .addGap(5) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(backButton, 24, 24, 24) - .addComponent(searchBar, 24, 24, 24)) + .addComponent(this.searchBar, 24, 24, 24)) .addGap(10) .addComponent(scrollPane) ); @@ -142,7 +117,7 @@ public void changedUpdate(DocumentEvent e) { .addGap(7) .addComponent(backButton) .addGap(3) - .addComponent(searchBar) + .addComponent(this.searchBar) .addGap(7)) .addComponent(scrollPane) ); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java index f16f943..a1caf62 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java @@ -37,8 +37,8 @@ public List getKeywords() { // this.getCategory().getName() ); if (this.getTags() != null) { - return Stream.concat(keywords, this.getTags().stream()).collect(Collectors.toList()); + return Stream.concat(keywords, this.getTags().stream()).map(String::toUpperCase).collect(Collectors.toList()); } - return keywords.collect(Collectors.toList()); + return keywords.map(String::toUpperCase).collect(Collectors.toList()); } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java index 0c532c0..d842143 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java @@ -7,7 +7,7 @@ import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; -import net.runelite.client.plugins.screenmarkers.ScreenMarkerPlugin; +import net.runelite.client.plugins.config.ConfigPlugin; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.DynamicGridLayout; import net.runelite.client.ui.PluginPanel; @@ -20,6 +20,8 @@ import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.util.List; @@ -33,89 +35,143 @@ public class AlertListItemNew extends JPanel { // final BufferedImage editImg = ImageUtil.loadImageResource(ScreenMarkerPlugin.class, "border_color_icon.png"); // EDIT_ICON = new ImageIcon(editImg); // } + private static final ImageIcon SECTION_EXPAND_ICON; + private static final ImageIcon SECTION_EXPAND_ICON_HOVER; + private static final ImageIcon SECTION_RETRACT_ICON; + private static final ImageIcon SECTION_RETRACT_ICON_HOVER; + + static { + BufferedImage sectionRetractIcon = ImageUtil.loadImageResource(ConfigPlugin.class, "/util/arrow_right.png"); + sectionRetractIcon = ImageUtil.luminanceOffset(sectionRetractIcon, -121); + SECTION_EXPAND_ICON = new ImageIcon(sectionRetractIcon); + SECTION_EXPAND_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(sectionRetractIcon, -100)); + final BufferedImage sectionExpandIcon = ImageUtil.rotateImage(sectionRetractIcon, Math.PI / 2); + SECTION_RETRACT_ICON = new ImageIcon(sectionExpandIcon); + SECTION_RETRACT_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(sectionExpandIcon, -100)); + } private static final int ROW_HEIGHT = 30; private static final int PADDING = 2; + private final MouseDragEventForwarder mouseDragEventForwarder; + private final AlertManager alertManager; + private final WatchdogPanel panel; + + private boolean collapsed = true; @Getter private final Alert alert; public AlertListItemNew(WatchdogPanel panel, AlertManager alertManager, Alert alert, List parentList, JComponent parent, Runnable onChange) { + this.panel = panel; this.alert = alert; + this.alertManager = alertManager; this.setLayout(new BorderLayout(5, 0)); this.setBorder(new EmptyBorder(PADDING, 0, PADDING, 0)); this.setMaximumSize(new Dimension(PluginPanel.PANEL_WIDTH, 999)); this.setAlignmentX(JPanel.LEFT_ALIGNMENT); // this.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, ROW_HEIGHT + PADDING * 2)); this.setBackground(ColorScheme.DARK_GRAY_COLOR); - MouseDragEventForwarder mouseDragEventForwarder = new MouseDragEventForwarder(parent); + this.mouseDragEventForwarder = new MouseDragEventForwarder(parent); + + this.rebuild(); + } + + public void rebuild() { + this.removeAll(); - JPanel container = new JPanel(new StretchedStackedLayout(3, 3)); + final JPanel container = new JPanel(new StretchedStackedLayout(3, 3)); container.setBackground(ColorScheme.DARKER_GRAY_COLOR); - JPanel topWrapper = new JPanel(new BorderLayout(3, 3)); + final JPanel topWrapper = new JPanel(new BorderLayout(3, 3)); container.add(topWrapper); topWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR); topWrapper.setBorder(new CompoundBorder( - BorderFactory.createMatteBorder(0, 0, 1, 0, ColorScheme.DARK_GRAY_COLOR), + BorderFactory.createMatteBorder(0, 0, this.collapsed ? 0 : 2, 0, ColorScheme.DARK_GRAY_COLOR), BorderFactory.createMatteBorder(5, 10, 5, 0, ColorScheme.DARKER_GRAY_COLOR))); - ToggleButton toggleButton = new ToggleButton(); - toggleButton.setSelected(alert.isEnabled()); + final ToggleButton toggleButton = new ToggleButton(); + toggleButton.setSelected(this.alert.isEnabled()); toggleButton.addItemListener(i -> { - alert.setEnabled(toggleButton.isSelected()); - alertManager.saveAlerts(); + this.alert.setEnabled(toggleButton.isSelected()); + this.alertManager.saveAlerts(); }); // toggleButton.setOpaque(false); topWrapper.add(toggleButton, BorderLayout.WEST); - JPanel nameWrapper = new JPanel(new DynamicGridLayout(1, 0, 2, 2)); + final JPanel nameWrapper = new JPanel(new DynamicGridLayout(1, 0, 2, 2)); nameWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR); topWrapper.add(nameWrapper, BorderLayout.CENTER); - JLabel nameLabel = new JLabel(alert.getName()); - nameLabel.setToolTipText(alert.getName()); + if (this.alert instanceof AlertGroup) { + final JButton collapseButton = PanelUtils.createActionButton( + this.collapsed ? SECTION_EXPAND_ICON : SECTION_RETRACT_ICON, + this.collapsed ? SECTION_EXPAND_ICON_HOVER : SECTION_RETRACT_ICON_HOVER, + this.collapsed ? "Expand" : "Collapse", + (btn, evt) -> { + this.collapsed = !this.collapsed; + this.rebuild(); + this.revalidate(); + } + ); + nameWrapper.add(collapseButton); + } + + final JLabel nameLabel = new JLabel(this.alert.getName()); + nameLabel.setToolTipText(this.alert.getName()); nameWrapper.add(nameLabel); - topWrapper.addMouseListener(mouseDragEventForwarder); - topWrapper.addMouseMotionListener(mouseDragEventForwarder); - nameWrapper.addMouseListener(mouseDragEventForwarder); - nameWrapper.addMouseMotionListener(mouseDragEventForwarder); - nameLabel.addMouseListener(mouseDragEventForwarder); - nameLabel.addMouseMotionListener(mouseDragEventForwarder); + topWrapper.addMouseListener(this.mouseDragEventForwarder); + topWrapper.addMouseMotionListener(this.mouseDragEventForwarder); + nameWrapper.addMouseListener(this.mouseDragEventForwarder); + nameWrapper.addMouseMotionListener(this.mouseDragEventForwarder); + nameLabel.addMouseListener(this.mouseDragEventForwarder); + nameLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + collapsed = !collapsed; + rebuild(); + revalidate(); + } + } + }); + nameLabel.addMouseMotionListener(this.mouseDragEventForwarder); - JPanel rightActions = new JPanel(new FlowLayout(FlowLayout.RIGHT, 3, 0)); + final JPanel rightActions = new JPanel(new FlowLayout(FlowLayout.RIGHT, 3, 0)); rightActions.setBorder(new EmptyBorder(4, 0, 0, 0)); rightActions.setBackground(ColorScheme.DARKER_GRAY_COLOR); topWrapper.add(rightActions, BorderLayout.EAST); rightActions.add(PanelUtils.createActionButton(Icons.EDIT_ICON, Icons.EDIT_ICON, "Edit Alert", (btn, modifiers) -> { - panel.openAlert(alert); + this.panel.openAlert(this.alert); })); rightActions.add(PanelUtils.createActionButton(Icons.CLONE_ICON, Icons.CLONE_ICON, "Clone Alert", (btn, modifiers) -> { - alertManager.cloneAlert(alert); + this.alertManager.cloneAlert(this.alert); })); final JButton deleteButton = PanelUtils.createActionButton(Icons.DELETE_ICON, Icons.DELETE_ICON, "Delete Alert", (btn, modifiers) -> { - int result = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete the " + alert.getName() + " alert?", "Delete?", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE); + int result = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete the " + this.alert.getName() + " alert?", "Delete?", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE); if (result == JOptionPane.YES_OPTION) { - alertManager.removeAlert(alert); + this.alertManager.removeAlert(this.alert); } }); rightActions.add(deleteButton); - JPanel settings = new JPanel(new StretchedStackedLayout(3, 3)); - settings.setBorder(new EmptyBorder(5, 10, 5, 10)); - settings.setBackground(ColorScheme.DARKER_GRAY_COLOR); - settings.add(new JLabel("settings place")); - JButton jButton = new JButton("open"); - jButton.addActionListener((ev) -> { - panel.openAlert(alert); - }); - settings.add(jButton); - container.add(settings); + if (this.alert instanceof AlertGroup && !this.collapsed) { + final JPanel settings = new JPanel(new StretchedStackedLayout(3, 3)); + settings.setBorder(new EmptyBorder(0, 10, 5, 10)); + settings.setBackground(ColorScheme.DARKER_GRAY_COLOR); + List subAlerts = ((AlertGroup) this.alert).getAlerts(); + for (Alert subAlert : subAlerts) { + settings.add(new JLabel(subAlert.getName())); + } + if (subAlerts.size() == 0) { + settings.add(new JLabel("No alerts in group")); + } + container.add(settings); + } - this.add(container); + this.add(container, BorderLayout.CENTER); } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/SearchBar.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/SearchBar.java new file mode 100644 index 0000000..ed26166 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/SearchBar.java @@ -0,0 +1,35 @@ +package com.adamk33n3r.runelite.watchdog.ui; + +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.components.IconTextField; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.util.Objects; +import java.util.function.Consumer; + +public class SearchBar extends IconTextField { + public SearchBar(Consumer onSearch) { + super(); + this.setIcon(new ImageIcon(Objects.requireNonNull(IconTextField.class.getResource(Icon.SEARCH.getFile())))); + this.setBackground(ColorScheme.DARKER_GRAY_COLOR); + this.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); + this.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + onSearch.accept(getText()); + } + + @Override + public void removeUpdate(DocumentEvent e) { + onSearch.accept(getText()); + } + + @Override + public void changedUpdate(DocumentEvent e) { + onSearch.accept(getText()); + } + }); + } +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java index 89db233..7cbd44a 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java @@ -7,6 +7,7 @@ import com.adamk33n3r.runelite.watchdog.ui.AlertListItem; import com.adamk33n3r.runelite.watchdog.ui.AlertListItemNew; import com.adamk33n3r.runelite.watchdog.ui.HorizontalRuleBorder; +import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout; import com.adamk33n3r.runelite.watchdog.ui.panels.AlertListPanel; import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; @@ -39,10 +40,11 @@ protected void build() { }); buttonPanel.add(alertDropDownButton, BorderLayout.EAST); - JPanel subGroupPanel = new JPanel(new DynamicGridLayout(0, 1, 3, 3)); + JPanel subGroupPanel = new JPanel(new StretchedStackedLayout(3, 3)); subGroupPanel.setBorder(new HorizontalRuleBorder(10)); subGroupPanel.add(buttonPanel); this.addSubPanel(subGroupPanel); + DragAndDropReorderPane dragAndDropReorderPane = new DragAndDropReorderPane(); dragAndDropReorderPane.addDragListener((c) -> { int pos = dragAndDropReorderPane.getPosition(c); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java index 5b6d544..0fc6560 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java @@ -3,6 +3,7 @@ import com.adamk33n3r.runelite.watchdog.alerts.Alert; import com.adamk33n3r.runelite.watchdog.notifications.IMessageNotification; import com.adamk33n3r.runelite.watchdog.ui.PlaceholderTextField; +import com.adamk33n3r.runelite.watchdog.ui.SearchBar; import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout; import com.google.common.base.Splitter; @@ -15,9 +16,7 @@ import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; -import javax.swing.JButton; -import javax.swing.JPanel; -import javax.swing.JScrollPane; +import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; @@ -34,7 +33,7 @@ import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON_HOVER; @Slf4j -@Singleton +//@Singleton public class HistoryPanel extends PluginPanel { private final Provider muxer; private final ScrollablePanel historyItems; @@ -61,25 +60,7 @@ public HistoryPanel(Provider muxer) { backButton.setPreferredSize(new Dimension(22, 16)); backButton.setBorder(new EmptyBorder(0, 0, 0, 5)); topPanel.add(backButton, BorderLayout.WEST); - PlaceholderTextField filterTextField = new PlaceholderTextField(); - filterTextField.setPlaceholder("Filter"); - filterTextField.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void insertUpdate(DocumentEvent e) { - updateFilter(filterTextField.getText()); - } - - @Override - public void removeUpdate(DocumentEvent e) { - updateFilter(filterTextField.getText()); - } - - @Override - public void changedUpdate(DocumentEvent e) { - updateFilter(filterTextField.getText()); - } - }); - topPanel.add(filterTextField); + topPanel.add(new SearchBar(this::updateFilter)); this.add(topPanel, BorderLayout.NORTH); this.historyItems = new ScrollablePanel(new StretchedStackedLayout(3, 3)); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java index 213315f..7ebb551 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java @@ -252,7 +252,11 @@ public static JButton createAlertDropDownButton(Consumer onCreate) { }; JPopupMenu popupMenu = new JPopupMenu(); - popupMenu.add(new JMenuItem(TriggerType.ALERT_GROUP.getName())); + JMenuItem alertGroupMenuItem = new JMenuItem(TriggerType.ALERT_GROUP.getName()); + alertGroupMenuItem.setToolTipText(TriggerType.ALERT_GROUP.getTooltip()); + alertGroupMenuItem.putClientProperty(TriggerType.class, TriggerType.ALERT_GROUP); + alertGroupMenuItem.addActionListener(actionListener); + popupMenu.add(alertGroupMenuItem); popupMenu.addSeparator(); Arrays.stream(TriggerType.values()) .filter(tType -> tType != TriggerType.ALERT_GROUP) diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 6fb463b..01507db 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Fri Jul 14 01:21:52 EDT 2023 -VERSION_BUILD=2491 +#Sat Jul 15 02:12:25 EDT 2023 +VERSION_BUILD=2569 VERSION_PHASE=alpha VERSION_MAJOR=3 VERSION_MINOR=0 From a8c89c5707b20ff8f4dac0261067a7ddceeda2e7 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sun, 17 Sep 2023 17:55:38 -0400 Subject: [PATCH 05/27] Handle removing an alert from a group --- .../com/adamk33n3r/runelite/watchdog/AlertManager.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java index 2a61c6b..74ccfb3 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java @@ -139,7 +139,12 @@ public void addAlert(Alert alert) { } public void removeAlert(Alert alert) { - this.alerts.remove(alert); + AlertGroup parent = alert.getParent(); + if (parent != null) { + parent.getAlerts().remove(alert); + } else { + this.alerts.remove(alert); + } this.saveAlerts(); SwingUtilities.invokeLater(this.watchdogPanel::rebuild); From 86f5ca33a8604f8edafea9e9d5b4945cda796626 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Mon, 18 Sep 2023 19:48:08 -0400 Subject: [PATCH 06/27] fixed layout of alert hub list a bit --- .../runelite/watchdog/hub/AlertHubClient.java | 26 ++++++++++- .../runelite/watchdog/hub/AlertHubItem.java | 45 ++++++++++++++++--- .../runelite/watchdog/hub/AlertHubPanel.java | 12 ++--- .../watchdog/ui/AlertListItemNew.java | 20 +++++---- .../runelite/watchdog/ui/WrappingLabel.java | 15 +++++++ .../runelite/watchdog/version.properties | 4 +- 6 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/WrappingLabel.java diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java index 296ba05..c9e762f 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java @@ -47,11 +47,35 @@ public List downloadManifest() throws IOException { new URL("https://github.com/adamk33n3r/runelite-watchdog"), "[]", false + ), new AlertManifest( + "testAlert", + "284hfu43hhfiu24rf", + "Test Alert", + "This is a test alert on the hub", + "4", + "adamk33n3r", + AlertHubCategory.COMBAT, + Arrays.asList("afk", "combat"), + new URL("https://github.com/adamk33n3r/runelite-watchdog"), + "[]", + false + ), new AlertManifest( + "testAlert", + "284hfu43hhfiu24rf", + "Test Alert", + "This is a test alert on the hub", + "4", + "adamk33n3r", + AlertHubCategory.COMBAT, + Arrays.asList("afk", "combat"), + new URL("https://github.com/adamk33n3r/runelite-watchdog"), + "[]", + false ), new AlertManifest( "testAlert2", "284hfu43hhfiu24rf", "Test Alert 2", - "This is a test alert on the hub", + "This is a test alert on the hub with an extra long description to test wrapping", "4", "adamk33n3r", AlertHubCategory.SKILLING, diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java index f267863..edd177c 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java @@ -1,7 +1,11 @@ package com.adamk33n3r.runelite.watchdog.hub; +import com.adamk33n3r.runelite.watchdog.ui.WrappingLabel; +import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; import lombok.Getter; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.DynamicGridLayout; +import net.runelite.client.ui.PluginPanel; import javax.swing.*; import java.awt.*; @@ -12,11 +16,42 @@ public class AlertHubItem extends JPanel { public AlertHubItem(AlertManifest manifest) { this.manifest = manifest; + this.setBackground(ColorScheme.BRAND_ORANGE); + this.setMaximumSize(new Dimension(PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH, Short.MAX_VALUE)); - this.setLayout(new DynamicGridLayout(0, 1, 5, 5)); - this.add(new JLabel(this.manifest.toString())); - this.add(new JLabel(this.manifest.getAuthor())); - this.add(new JLabel(this.manifest.getDescription())); - this.add(new JLabel(this.manifest.getRepo().toString())); + GroupLayout layout = new GroupLayout(this); + this.setLayout(layout); + + JLabel alertName = new JLabel(this.manifest.toString()); + JLabel alertAuthor = new JLabel(this.manifest.getAuthor()); + WrappingLabel alertDescLabel = new WrappingLabel(this.manifest.getDescription()); + alertDescLabel.setForeground(Color.CYAN); + +// container.add(alertDescription); +// this.add(alertDescription); + + layout.setVerticalGroup(layout.createSequentialGroup() + .addGap(5) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING) + .addComponent(alertName) + .addComponent(alertAuthor)) + .addComponent(alertDescLabel) + ); + + layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addGroup(layout.createSequentialGroup() +// .addGap(5) + .addComponent(alertName) +// .addGap(50) + .addComponent(alertAuthor)) +// .addGap(5)) + .addComponent(alertDescLabel) + ); + +// this.setLayout(new DynamicGridLayout(0, 1, 5, 5)); +// this.add(new JLabel(this.manifest.toString())); +// this.add(new JLabel(this.manifest.getAuthor())); +// this.add(new JLabel(this.manifest.getDescription())); +// this.add(new JLabel(this.manifest.getRepo().toString())); } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java index 16c8100..5f95aa6 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java @@ -27,7 +27,6 @@ import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON_HOVER; @Slf4j -//@Singleton public class AlertHubPanel extends PluginPanel { private final Provider muxer; private final AlertHubClient alertHubClient; @@ -94,14 +93,15 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale this.container = new JPanel(new DynamicGridLayout(0, 1, 0, 5)); // this.container.setMaximumSize(new Dimension(PANEL_WIDTH, 9999)); this.container.setBackground(ColorScheme.GRAND_EXCHANGE_LIMIT); - this.container.setBorder(BorderFactory.createEmptyBorder(0, 7, 15, 7)); +// this.container.setBorder(BorderFactory.createEmptyBorder(0, 7, 15, 7)); // this.container.setAlignmentX(Component.LEFT_ALIGNMENT); -// JPanel wrapper = new JPanel(new BorderLayout()); -// wrapper.add(this.container, BorderLayout.CENTER); - JScrollPane scrollPane = new JScrollPane(this.container, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + JPanel wrapper = new JPanel(new BorderLayout()); + wrapper.add(this.container, BorderLayout.NORTH); + JScrollPane scrollPane = new JScrollPane(wrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); scrollPane.setBackground(ColorScheme.GRAND_EXCHANGE_ALCH); scrollPane.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999)); + this.container.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999)); layout.setVerticalGroup(layout.createSequentialGroup() .addGap(5) @@ -132,7 +132,7 @@ public void reloadList() { try { List alertManifests = this.alertHubClient.downloadManifest(); - System.out.println(alertManifests.stream().map(AlertManifest::toString).collect(Collectors.joining(", "))); +// System.out.println(alertManifests.stream().map(AlertManifest::toString).collect(Collectors.joining(", "))); this.reloadList(alertManifests); } catch (IOException e) { e.printStackTrace(); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java index d842143..12b8bf4 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java @@ -125,16 +125,18 @@ public void rebuild() { nameWrapper.addMouseListener(this.mouseDragEventForwarder); nameWrapper.addMouseMotionListener(this.mouseDragEventForwarder); nameLabel.addMouseListener(this.mouseDragEventForwarder); - nameLabel.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON1) { - collapsed = !collapsed; - rebuild(); - revalidate(); + if (alert instanceof AlertGroup) { + nameLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + collapsed = !collapsed; + rebuild(); + revalidate(); + } } - } - }); + }); + } nameLabel.addMouseMotionListener(this.mouseDragEventForwarder); final JPanel rightActions = new JPanel(new FlowLayout(FlowLayout.RIGHT, 3, 0)); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/WrappingLabel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/WrappingLabel.java new file mode 100644 index 0000000..397f937 --- /dev/null +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/WrappingLabel.java @@ -0,0 +1,15 @@ +package com.adamk33n3r.runelite.watchdog.ui; + +import javax.swing.*; + +public class WrappingLabel extends JTextArea { + public WrappingLabel(String text) { + super(text); + + this.setOpaque(false); + this.setEditable(false); + this.setFocusable(false); + this.setLineWrap(true); + this.setWrapStyleWord(true); + } +} diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 01507db..bb21f50 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Sat Jul 15 02:12:25 EDT 2023 -VERSION_BUILD=2569 +#Mon Sep 18 19:38:52 EDT 2023 +VERSION_BUILD=2664 VERSION_PHASE=alpha VERSION_MAJOR=3 VERSION_MINOR=0 From 5bbaa925939e92c457daf52699c48f0538dd4d14 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sat, 23 Sep 2023 16:47:58 -0400 Subject: [PATCH 07/27] Manual shadowjar so we can leave it in the build file --- build.gradle | 35 ++++++++++++++----- .../runelite/watchdog/hub/AlertManifest.java | 2 +- .../runelite/watchdog/version.properties | 4 +-- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 0837e63..ce75d5f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,7 @@ plugins { id 'java' -// id 'com.github.johnrengelman.shadow' version '6.1.0' } -//shadowJar { -// from sourceSets.test.output -// configurations = [project.configurations.testRuntimeClasspath] -// manifest { -// attributes 'Main-Class': 'com.adamk33n3r.runelite.watchdog.WatchdogPluginLauncher' -// } -//} - repositories { mavenLocal() maven { @@ -56,7 +47,33 @@ if (!phase.empty) version = version+'-'+phase version = version+'+'+build sourceCompatibility = '1.8' +targetCompatibility = '1.8' tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } + +tasks.register("shadowJar", Jar) { + dependsOn configurations.testRuntimeClasspath + manifest { + attributes 'Main-Class': 'com.adamk33n3r.runelite.watchdog.WatchdogPluginLauncher' + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from sourceSets.main.output + from sourceSets.test.output + from({ + configurations.testRuntimeClasspath.collect { + it.isDirectory() ? it : zipTree(it) + } + }) + exclude("META-INF/INDEX.LIST") + exclude("META-INF/*.SF") + exclude("META-INF/*.DSA") + exclude("META-INF/*.RSA") + exclude "**/module-info.class" + + group = BasePlugin.BUILD_GROUP + archiveClassifier = "shadow" + archiveFileName = rootProject.name + "-" + project.version + "-all.jar" +} diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java index a1caf62..9e7c7fa 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java @@ -15,7 +15,7 @@ public class AlertManifest { private final String displayName; private final String description; - private final String createdVersion; + private final String compatibleVersion; private final String author; private final AlertHubCategory category; private final List tags; diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index bb21f50..0da90d9 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Mon Sep 18 19:38:52 EDT 2023 -VERSION_BUILD=2664 +#Sat Sep 23 16:47:10 EDT 2023 +VERSION_BUILD=2711 VERSION_PHASE=alpha VERSION_MAJOR=3 VERSION_MINOR=0 From 0b66d45fad80ae33c30b699afbbb97e4e67e94eb Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sat, 23 Sep 2023 20:35:12 -0400 Subject: [PATCH 08/27] fix up panels after merging in the search bar fix cloning in alert groups --- .../runelite/watchdog/AlertManager.java | 52 +++---------- .../runelite/watchdog/WatchdogPanel.java | 77 ++----------------- .../runelite/watchdog/alerts/Alert.java | 24 +++++- .../runelite/watchdog/ui/AlertListItem.java | 1 + .../watchdog/ui/AlertListItemNew.java | 20 +++-- .../runelite/watchdog/ui/SearchBar.java | 2 +- .../watchdog/ui/alerts/AlertGroupPanel.java | 10 +-- .../watchdog/ui/panels/AlertListPanel.java | 51 ++++++++++-- .../runelite/watchdog/version.properties | 4 +- 9 files changed, 104 insertions(+), 137 deletions(-) diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java index 4c326bd..3365825 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java @@ -39,8 +39,7 @@ import javax.inject.Singleton; import javax.swing.SwingUtilities; import java.lang.reflect.Type; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Supplier; import java.util.stream.Stream; @@ -154,50 +153,21 @@ public Alert cloneAlert(Alert alert) { String json = this.gson.toJson(alert, ALERT_TYPE); Alert clonedAlert = this.gson.fromJson(json, ALERT_TYPE); clonedAlert.setName(clonedAlert.getName() + " Clone"); + if (clonedAlert instanceof AlertGroup) { + Util.setParentsOnAlerts(Collections.singletonList(clonedAlert)); + } return clonedAlert; } public void moveAlertTo(Alert alert, int pos) { - this.alerts.remove(alert); - this.alerts.add(pos, alert); - this.saveAlerts(); - } - - public void moveAlertToTop(Alert alert) { - this.alerts.remove(alert); - this.alerts.add(0, alert); - this.saveAlerts(); - } - - public void moveAlertToBottom(Alert alert) { - this.alerts.remove(alert); - this.alerts.add(alert); - this.saveAlerts(); - } - - public void moveAlertUp(Alert alert) { - int curIdx = this.alerts.indexOf(alert); - int newIdx = curIdx - 1; - - if (newIdx < 0) { - return; - } - - this.alerts.remove(alert); - this.alerts.add(newIdx, alert); - this.saveAlerts(); - } - - public void moveAlertDown(Alert alert) { - int curIdx = this.alerts.indexOf(alert); - int newIdx = curIdx + 1; - - if (newIdx >= this.alerts.size()) { - return; + AlertGroup parent = alert.getParent(); + if (parent != null) { + parent.getAlerts().remove(alert); + parent.getAlerts().add(pos, alert); + } else { + this.alerts.remove(alert); + this.alerts.add(pos, alert); } - - this.alerts.remove(alert); - this.alerts.add(newIdx, alert); this.saveAlerts(); } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java index ee24f40..8a7a2aa 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java @@ -3,28 +3,20 @@ import com.adamk33n3r.runelite.watchdog.alerts.*; import com.adamk33n3r.runelite.watchdog.ui.*; import com.adamk33n3r.runelite.watchdog.ui.alerts.*; -import com.adamk33n3r.runelite.watchdog.ui.panels.AlertListPanel; -import com.adamk33n3r.runelite.watchdog.ui.dropdownbutton.DropDownButtonFactory; -import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel; -import com.adamk33n3r.runelite.watchdog.ui.panels.HistoryPanel; -import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; +import com.adamk33n3r.runelite.watchdog.ui.panels.*; import com.adamk33n3r.runelite.watchdog.hub.AlertHubPanel; -import com.google.common.base.Splitter; -import net.runelite.api.Skill; import net.runelite.client.plugins.config.ConfigPlugin; import net.runelite.client.plugins.info.InfoPanel; import net.runelite.client.plugins.timetracking.TimeTrackingPlugin; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.MultiplexingPluginPanel; import net.runelite.client.ui.PluginPanel; -import net.runelite.client.ui.components.DragAndDropReorderPane; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.LinkBrowser; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import net.runelite.client.util.Text; import okhttp3.OkHttpClient; @@ -40,9 +32,6 @@ import java.awt.Font; import java.awt.GridLayout; import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; @Slf4j public class WatchdogPanel extends PluginPanel { @@ -83,11 +72,6 @@ public class WatchdogPanel extends PluginPanel { @Inject private AlertManager alertManager; - private String filterText = ""; - private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings(); -// private IconTextField searchBar; - DragAndDropReorderPane dragAndDropReorderPane = new DragAndDropReorderPane(); - private final List alertListItems = new ArrayList<>(); @Inject private OkHttpClient httpClient; @@ -132,11 +116,6 @@ public class WatchdogPanel extends PluginPanel { public WatchdogPanel() { super(false); - this.dragAndDropReorderPane.addDragListener((c) -> { - int pos = this.dragAndDropReorderPane.getPosition(c); - AlertListItem alertListItem = (AlertListItem) c; - alertManager.moveAlertTo(alertListItem.getAlert(), pos); - }); } public void rebuild() { @@ -186,22 +165,6 @@ public void rebuild() { JButton historyButton = PanelUtils.createActionButton(HISTORY_ICON, HISTORY_ICON_HOVER, "History", (btn, modifiers) -> { this.muxer.pushState(this.historyPanelProvider.get()); -// System.out.println(this.alertManager.getAllEnabledAlertsOfType(InventoryAlert.class).count()); -// this.alertManager.getAllEnabledAlertsOfType(InventoryAlert.class) -// .forEach(alert -> { -// List ancestors = alert.getAncestors(); -// if (ancestors != null) { -// String isEnabled = ancestors.stream().allMatch(Alert::isEnabled) ? "Enabled: " : "Disabled: "; -// System.out.print(isEnabled); -// String ancestorStr = ancestors.stream().map(ancestor -> ancestor.getName() + (ancestor.isEnabled() ? "*" : "")).collect(Collectors.joining(" -> ")); -// System.out.print(ancestorStr); -// System.out.print(" -> "); -// } else { -// System.out.print(alert.isEnabled() ? "Enabled: " : "Disabled: "); -// } -// System.out.print(alert.getName()); -// System.out.println(); -// }); }); actionButtons.add(historyButton); @@ -213,32 +176,17 @@ public void rebuild() { topPanel.add(actionButtons, BorderLayout.EAST); - SearchBar searchBar = new SearchBar(this::filter); - Arrays.stream(TriggerType.values()).map(TriggerType::getName).forEach(searchBar.getSuggestionListModel()::addElement); - JPanel searchWrapper = new JPanel(new BorderLayout(0, 6)); - searchWrapper.add(searchBar); - searchWrapper.setBorder(new EmptyBorder(0, 5, 0, 5)); - topPanel.add(searchWrapper, BorderLayout.SOUTH); - this.add(topPanel, BorderLayout.NORTH); - AlertListPanel alertPanel = new AlertListPanel(this.alertManager.getAlerts(), dragAndDropReorderPane, this::rebuild); - JPanel wrapper = new JPanel(new StretchedStackedLayout(3, 3)); - wrapper.add(alertPanel); - JScrollPane scroll = new JScrollPane(wrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + AlertListPanel alertPanel = new AlertListPanel(this.alertManager.getAlerts(), this::rebuild); + ScrollablePanel scrollablePanel = new ScrollablePanel(new StretchedStackedLayout(3, 3)); + scrollablePanel.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT); + scrollablePanel.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH); + scrollablePanel.setScrollableBlockIncrement(ScrollablePanel.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10); + scrollablePanel.add(alertPanel); + JScrollPane scroll = new JScrollPane(scrollablePanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); this.add(scroll, BorderLayout.CENTER); - this.alertListItems.clear(); - this.dragAndDropReorderPane.removeAll(); -// this.alertManager.getAlerts().stream() -// .map(alert -> new AlertListItem(this, this.alertManager, alert, this.dragAndDropReorderPane)) -// .forEach(alertListItem -> { -// this.alertListItems.add(alertListItem); -// this.dragAndDropReorderPane.add(alertListItem); -// }); - if (!this.filterText.isEmpty()) - this.filter(this.filterText); - JPanel importExportGroup = new JPanel(new GridLayout(1, 2, 5, 0)); JButton importButton = new JButton("Import", IMPORT_ICON); importButton.setHorizontalTextPosition(SwingConstants.LEFT); @@ -307,13 +255,4 @@ private PluginPanel createPluginPanel(Alert alert) { public void onActivate() { this.rebuild(); } - - private void filter(String text) { - this.filterText = text; - this.dragAndDropReorderPane.removeAll(); - this.alertListItems.stream() - .filter(alertListItem -> Text.matchesSearchTerms(SPLITTER.split(this.filterText.toUpperCase()), alertListItem.getAlert().getKeywords())) - .forEach(this.dragAndDropReorderPane::add); - this.revalidate(); - } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java index 1ca88d5..e7881fe 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java @@ -1,6 +1,7 @@ package com.adamk33n3r.runelite.watchdog.alerts; import com.adamk33n3r.runelite.watchdog.TriggerType; +import com.adamk33n3r.runelite.watchdog.notifications.MessageNotification; import com.adamk33n3r.runelite.watchdog.notifications.Notification; import lombok.AccessLevel; @@ -94,11 +95,26 @@ public List getAncestors() { } public List getKeywords() { - return Stream.concat(Stream.of( + Stream selfKeywords = Stream.of( this.getName(), this.getType().getName() - ), this.getNotifications().stream().map(notification -> notification.getType().getName())) - .map(String::toUpperCase) - .collect(Collectors.toList()); + ); + + if (this instanceof AlertGroup) { + return Stream.concat(selfKeywords, ((AlertGroup) this).getAlerts().stream().flatMap(alert -> alert.getKeywords().stream())) + .collect(Collectors.toList()); + } else { + return Stream.concat( + selfKeywords, + this.getNotifications().stream() + .flatMap(notification -> { + if (notification instanceof MessageNotification) { + return Stream.of(notification.getType().getName(), ((MessageNotification) notification).getMessage()); + } + return Stream.of(notification.getType().getName()); + })) + .map(String::toUpperCase) + .collect(Collectors.toList()); + } } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java index ff74abc..44ff067 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java @@ -24,6 +24,7 @@ import java.awt.Dimension; import java.util.List; +@Deprecated public class AlertListItem extends JPanel { public static final ImageIcon DELETE_ICON_HOVER; public static final ImageIcon CLONE_ICON = new ImageIcon(ImageUtil.loadImageResource(ConfigPlugin.class, "mdi_content-duplicate.png")); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java index 12b8bf4..ec8579e 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java @@ -60,16 +60,15 @@ public class AlertListItemNew extends JPanel { @Getter private final Alert alert; + private final Runnable onChange; - public AlertListItemNew(WatchdogPanel panel, AlertManager alertManager, Alert alert, List parentList, JComponent parent, Runnable onChange) { + public AlertListItemNew(WatchdogPanel panel, AlertManager alertManager, Alert alert, JComponent parent, Runnable onChange) { this.panel = panel; this.alert = alert; this.alertManager = alertManager; + this.onChange = onChange; this.setLayout(new BorderLayout(5, 0)); this.setBorder(new EmptyBorder(PADDING, 0, PADDING, 0)); - this.setMaximumSize(new Dimension(PluginPanel.PANEL_WIDTH, 999)); - this.setAlignmentX(JPanel.LEFT_ALIGNMENT); -// this.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, ROW_HEIGHT + PADDING * 2)); this.setBackground(ColorScheme.DARK_GRAY_COLOR); this.mouseDragEventForwarder = new MouseDragEventForwarder(parent); @@ -95,7 +94,6 @@ public void rebuild() { this.alert.setEnabled(toggleButton.isSelected()); this.alertManager.saveAlerts(); }); -// toggleButton.setOpaque(false); topWrapper.add(toggleButton, BorderLayout.WEST); final JPanel nameWrapper = new JPanel(new DynamicGridLayout(1, 0, 2, 2)); @@ -149,13 +147,23 @@ public void mouseClicked(MouseEvent e) { })); rightActions.add(PanelUtils.createActionButton(Icons.CLONE_ICON, Icons.CLONE_ICON, "Clone Alert", (btn, modifiers) -> { - this.alertManager.cloneAlert(this.alert); + Alert cloned = this.alertManager.cloneAlert(this.alert); + AlertGroup parent = this.alert.getParent(); + if (parent != null) { + cloned.setParent(parent); + parent.getAlerts().add(cloned); + } else { + this.alertManager.getAlerts().add(cloned); + } + this.alertManager.saveAlerts(); + this.onChange.run(); })); final JButton deleteButton = PanelUtils.createActionButton(Icons.DELETE_ICON, Icons.DELETE_ICON, "Delete Alert", (btn, modifiers) -> { int result = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete the " + this.alert.getName() + " alert?", "Delete?", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE); if (result == JOptionPane.YES_OPTION) { this.alertManager.removeAlert(this.alert); + this.onChange.run(); } }); rightActions.add(deleteButton); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/SearchBar.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/SearchBar.java index 1bd7c77..ce54a7a 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/SearchBar.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/SearchBar.java @@ -15,7 +15,7 @@ public class SearchBar extends IconTextField { public SearchBar(Consumer onSearch) { super(); this.setIcon(new ImageIcon(Objects.requireNonNull(IconTextField.class.getResource(Icon.SEARCH.getFile())))); - this.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - 20, 30)); + this.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - PluginPanel.SCROLLBAR_WIDTH, 30)); this.setBackground(ColorScheme.DARKER_GRAY_COLOR); this.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); this.getDocument().addDocumentListener(new DocumentListener() { diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java index 7cbd44a..101c4a2 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java @@ -45,14 +45,6 @@ protected void build() { subGroupPanel.add(buttonPanel); this.addSubPanel(subGroupPanel); - DragAndDropReorderPane dragAndDropReorderPane = new DragAndDropReorderPane(); - dragAndDropReorderPane.addDragListener((c) -> { - int pos = dragAndDropReorderPane.getPosition(c); - AlertListItemNew alertListItem = (AlertListItemNew) c; - this.alert.getAlerts().remove(alertListItem.getAlert()); - this.alert.getAlerts().add(pos, alertListItem.getAlert()); - alertManager.saveAlerts(); - }); - subGroupPanel.add(new AlertListPanel(this.alert.getAlerts(), dragAndDropReorderPane, this::rebuild)); + subGroupPanel.add(new AlertListPanel(this.alert.getAlerts(), this::rebuild)); } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java index b8e2f90..75d45a1 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java @@ -1,24 +1,65 @@ package com.adamk33n3r.runelite.watchdog.ui.panels; import com.adamk33n3r.runelite.watchdog.AlertManager; +import com.adamk33n3r.runelite.watchdog.TriggerType; import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; import com.adamk33n3r.runelite.watchdog.alerts.Alert; import com.adamk33n3r.runelite.watchdog.ui.AlertListItemNew; +import com.adamk33n3r.runelite.watchdog.ui.SearchBar; +import com.google.common.base.Splitter; +import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.components.DragAndDropReorderPane; +import net.runelite.client.util.Text; import javax.swing.*; +import javax.swing.border.EmptyBorder; import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class AlertListPanel extends JPanel { - public AlertListPanel(List alerts, DragAndDropReorderPane dragAndDropReorderPane, Runnable onChange) { - this.setLayout(new BorderLayout()); + private String filterText = ""; + private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings(); + private final List alertListItems = new ArrayList<>(); + private final DragAndDropReorderPane dragAndDropReorderPane = new DragAndDropReorderPane(); + + public AlertListPanel(List alerts, Runnable onChange) { AlertManager alertManager = WatchdogPlugin.getInstance().getAlertManager(); - alerts.forEach((subAlert) -> { - AlertListItemNew alertListItem = new AlertListItemNew(WatchdogPlugin.getInstance().getPanel(), alertManager, subAlert, alerts, dragAndDropReorderPane, onChange); - dragAndDropReorderPane.add(alertListItem); + this.dragAndDropReorderPane.setBackground(ColorScheme.GRAND_EXCHANGE_LIMIT); + this.dragAndDropReorderPane.addDragListener((c) -> { + int pos = this.dragAndDropReorderPane.getPosition(c); + AlertListItemNew alertListItem = (AlertListItemNew) c; + alertManager.moveAlertTo(alertListItem.getAlert(), pos); }); + + this.setLayout(new BorderLayout()); + + SearchBar searchBar = new SearchBar(this::filter); + Arrays.stream(TriggerType.values()).map(TriggerType::getName).forEach(searchBar.getSuggestionListModel()::addElement); + JPanel searchWrapper = new JPanel(new BorderLayout(0, 6)); + searchWrapper.add(searchBar); + searchWrapper.setBorder(new EmptyBorder(0, 5, 0, 5)); + this.add(searchBar, BorderLayout.NORTH); + // TODO: move the scroll pane in here so that the search bar doesn't scroll + this.add(dragAndDropReorderPane, BorderLayout.CENTER); + + alerts.stream() + .map(alert -> new AlertListItemNew(WatchdogPlugin.getInstance().getPanel(), alertManager, alert, dragAndDropReorderPane, onChange)) + .forEach(alertListItem -> { + this.alertListItems.add(alertListItem); + this.dragAndDropReorderPane.add(alertListItem); + }); + } + + private void filter(String text) { + this.filterText = text; + this.dragAndDropReorderPane.removeAll(); + this.alertListItems.stream() + .filter(alertListItem -> Text.matchesSearchTerms(SPLITTER.split(this.filterText.toUpperCase()), alertListItem.getAlert().getKeywords())) + .forEach(this.dragAndDropReorderPane::add); + this.revalidate(); } } diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index f80b92a..ea26243 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Sat Sep 23 16:48:22 EDT 2023 -VERSION_BUILD=2712 +#Sat Sep 23 20:30:44 EDT 2023 +VERSION_BUILD=2861 VERSION_PHASE=alpha VERSION_MAJOR=3 VERSION_MINOR=0 From 0fb79afa9a59233992adccb6ec71c4dc23f679cc Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sun, 24 Sep 2023 15:00:17 -0400 Subject: [PATCH 09/27] Alert hub panel from github Updated runtimetypeadapter --- .../runelite/watchdog/AlertManager.java | 1 + .../watchdog/RuntimeTypeAdapterFactory.java | 99 +++++++-- .../runelite/watchdog/hub/AlertHubClient.java | 206 ++++++++++++------ .../runelite/watchdog/hub/AlertHubItem.java | 30 ++- .../runelite/watchdog/hub/AlertHubPanel.java | 24 +- .../runelite/watchdog/hub/AlertManifest.java | 11 +- .../runelite/watchdog/version.properties | 4 +- 7 files changed, 267 insertions(+), 108 deletions(-) diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java index 3365825..db8de06 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java @@ -81,6 +81,7 @@ private void init() { .ignoreSubtype("ResourceAlert") .ignoreSubtype("SoundFiredAlert") .ignoreSubtype("AlertGroup") + .recognizeSubtypes() .registerSubtype(ChatAlert.class) .registerSubtype(NotificationFiredAlert.class) .registerSubtype(StatDrainAlert.class) diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/RuntimeTypeAdapterFactory.java b/src/main/java/com/adamk33n3r/runelite/watchdog/RuntimeTypeAdapterFactory.java index 8903f12..64ab7a4 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/RuntimeTypeAdapterFactory.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/RuntimeTypeAdapterFactory.java @@ -16,6 +16,7 @@ package com.adamk33n3r.runelite.watchdog; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -23,11 +24,9 @@ import com.google.gson.JsonPrimitive; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; -import com.google.gson.internal.Streams; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; - import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -95,7 +94,7 @@ * Both the type field name ({@code "type"}) and the type labels ({@code * "Rectangle"}) are configurable. * - *

Registering Types

+ *

Registering Types

* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field * name to the {@link #of} factory method. If you don't supply an explicit type * field name, {@code "type"} will be used.
   {@code
@@ -106,9 +105,9 @@
  * registered. This protects your application from injection attacks. If you
  * don't supply an explicit type label, the type's simple name will be used.
  * 
   {@code
- *   shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
- *   shapeAdapter.registerSubtype(Circle.class, "Circle");
- *   shapeAdapter.registerSubtype(Diamond.class, "Diamond");
+ *   shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
+ *   shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
+ *   shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
  * }
* Finally, register the type adapter factory in your application's GSON builder: *
   {@code
@@ -122,20 +121,46 @@
  *       .registerSubtype(Circle.class)
  *       .registerSubtype(Diamond.class);
  * }
+ * + *

Serialization and deserialization

+ * In order to serialize and deserialize a polymorphic object, + * you must specify the base type explicitly. + *
   {@code
+ *   Diamond diamond = new Diamond();
+ *   String json = gson.toJson(diamond, Shape.class);
+ * }
+ * And then: + *
   {@code
+ *   Shape shape = gson.fromJson(json, Shape.class);
+ * }
*/ public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { private final Class baseType; private final String typeFieldName; - private final Map> labelToSubtype = new LinkedHashMap>(); - private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); + private final Map> labelToSubtype = new LinkedHashMap<>(); + private final Map, String> subtypeToLabel = new LinkedHashMap<>(); private final List labelsToIgnore = new ArrayList<>(); + private final boolean maintainType; + private boolean recognizeSubtypes; - private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName) { + private RuntimeTypeAdapterFactory( + Class baseType, String typeFieldName, boolean maintainType) { if (typeFieldName == null || baseType == null) { throw new NullPointerException(); } this.baseType = baseType; this.typeFieldName = typeFieldName; + this.maintainType = maintainType; + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + * + * @param maintainType true if the type field should be included in deserialized objects + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName, boolean maintainType) { + return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); } /** @@ -143,7 +168,7 @@ private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName) { * typeFieldName} as the type field name. Type field names are case sensitive. */ public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { - return new RuntimeTypeAdapterFactory(baseType, typeFieldName); + return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false); } /** @@ -151,7 +176,17 @@ public static RuntimeTypeAdapterFactory of(Class baseType, String type * the type field name. */ public static RuntimeTypeAdapterFactory of(Class baseType) { - return new RuntimeTypeAdapterFactory(baseType, "type"); + return new RuntimeTypeAdapterFactory<>(baseType, "type", false); + } + + /** + * Ensures that this factory will handle not just the given {@code baseType}, but any subtype + * of that type. + */ + @CanIgnoreReturnValue + public RuntimeTypeAdapterFactory recognizeSubtypes() { + this.recognizeSubtypes = true; + return this; } /** @@ -161,6 +196,7 @@ public static RuntimeTypeAdapterFactory of(Class baseType) { * @throws IllegalArgumentException if either {@code type} or {@code label} * have already been registered on this type adapter. */ + @CanIgnoreReturnValue public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { if (type == null || label == null) { throw new NullPointerException(); @@ -180,6 +216,7 @@ public RuntimeTypeAdapterFactory registerSubtype(Class type, Str * @throws IllegalArgumentException if either {@code type} or its simple name * have already been registered on this type adapter. */ + @CanIgnoreReturnValue public RuntimeTypeAdapterFactory registerSubtype(Class type) { return registerSubtype(type, type.getSimpleName()); } @@ -189,15 +226,21 @@ public RuntimeTypeAdapterFactory ignoreSubtype(String label) { return this; } + @Override public TypeAdapter create(Gson gson, TypeToken type) { - if (type.getRawType() != baseType) { + if (type == null) { + return null; + } + Class rawType = type.getRawType(); + boolean handle = + recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType); + if (!handle) { return null; } - final Map> labelToDelegate - = new LinkedHashMap>(); - final Map, TypeAdapter> subtypeToDelegate - = new LinkedHashMap, TypeAdapter>(); + final TypeAdapter jsonElementAdapter = gson.getAdapter(JsonElement.class); + final Map> labelToDelegate = new LinkedHashMap<>(); + final Map, TypeAdapter> subtypeToDelegate = new LinkedHashMap<>(); for (Map.Entry> entry : labelToSubtype.entrySet()) { TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); labelToDelegate.put(entry.getKey(), delegate); @@ -206,8 +249,14 @@ public TypeAdapter create(Gson gson, TypeToken type) { return new TypeAdapter() { @Override public R read(JsonReader in) throws IOException { - JsonElement jsonElement = Streams.parse(in); - JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); + JsonElement jsonElement = jsonElementAdapter.read(in); + JsonElement labelJsonElement; + if (maintainType) { + labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); + } else { + labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); + } + if (labelJsonElement == null) { throw new JsonParseException("cannot deserialize " + baseType + " because it does not define a field named " + typeFieldName); @@ -236,17 +285,25 @@ public TypeAdapter create(Gson gson, TypeToken type) { + "; did you forget to register a subtype?"); } JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); + + if (maintainType) { + jsonElementAdapter.write(out, jsonObject); + return; + } + + JsonObject clone = new JsonObject(); + if (jsonObject.has(typeFieldName)) { throw new JsonParseException("cannot serialize " + srcType.getName() + " because it already defines a field named " + typeFieldName); } - JsonObject clone = new JsonObject(); clone.add(typeFieldName, new JsonPrimitive(label)); + for (Map.Entry e : jsonObject.entrySet()) { clone.add(e.getKey(), e.getValue()); } - Streams.write(clone, out); + jsonElementAdapter.write(out, clone); } }.nullSafe(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java index c9e762f..1ad6d35 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java @@ -1,98 +1,142 @@ package com.adamk33n3r.runelite.watchdog.hub; -import com.google.gson.reflect.TypeToken; +import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; +import com.google.common.base.Charsets; +import com.google.common.io.CharStreams; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.util.ImageUtil; import net.runelite.http.api.RuneLiteAPI; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; +import okhttp3.*; import javax.imageio.ImageIO; import javax.inject.Inject; +import javax.swing.*; import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.net.URI; +import java.io.InputStreamReader; import java.net.URL; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; @Slf4j public class AlertHubClient { private final OkHttpClient cachingClient; + private static final HttpUrl GITHUB = Objects.requireNonNull(HttpUrl.parse("https://github.com/adamk33n3r/runelite-watchdog")); @Inject public AlertHubClient(OkHttpClient cachingClient) { - this.cachingClient = cachingClient; + this.cachingClient = cachingClient.newBuilder() + .addInterceptor(new CacheInterceptor(15)) + .build(); } - public List downloadManifest() throws IOException { + public List downloadManifest() throws IOException { HttpUrl manifest = Objects.requireNonNull(HttpUrl.parse("https://raw.githubusercontent.com/melkypie/resource-packs")) .newBuilder() .addPathSegment("github-actions") .addPathSegment("manifest.js") .build(); + HttpUrl allAlerts = GITHUB.newBuilder() + .addPathSegment("archive") + .addPathSegment("alert-hub.zip") + .build(); + + HashMap alerts = new HashMap<>(); + try (Response res = this.cachingClient.newCall(new Request.Builder().url(allAlerts).build()).execute()) { + if (res.code() != 200) { + throw new IOException("Non-OK response code: " + res.code()); + } - return Arrays.asList(new AlertManifest( - "testAlert", - "284hfu43hhfiu24rf", - "Test Alert", - "This is a test alert on the hub", - "4", - "adamk33n3r", - AlertHubCategory.COMBAT, - Arrays.asList("afk", "combat"), - new URL("https://github.com/adamk33n3r/runelite-watchdog"), - "[]", - false - ), new AlertManifest( - "testAlert", - "284hfu43hhfiu24rf", - "Test Alert", - "This is a test alert on the hub", - "4", - "adamk33n3r", - AlertHubCategory.COMBAT, - Arrays.asList("afk", "combat"), - new URL("https://github.com/adamk33n3r/runelite-watchdog"), - "[]", - false - ), new AlertManifest( - "testAlert", - "284hfu43hhfiu24rf", - "Test Alert", - "This is a test alert on the hub", - "4", - "adamk33n3r", - AlertHubCategory.COMBAT, - Arrays.asList("afk", "combat"), - new URL("https://github.com/adamk33n3r/runelite-watchdog"), - "[]", - false - ), new AlertManifest( - "testAlert2", - "284hfu43hhfiu24rf", - "Test Alert 2", - "This is a test alert on the hub with an extra long description to test wrapping", - "4", - "adamk33n3r", - AlertHubCategory.SKILLING, - Arrays.asList("mining", "tts"), - new URL("https://github.com/adamk33n3r/runelite-watchdog"), - "[]", - false - )); -// try (Response res = cachingClient.newCall(new Request.Builder().url(manifest).build()).execute()) { -// if (res.code() != 200) { -// throw new IOException("Non-OK response code: " + res.code()); -// } -// + BufferedInputStream is = new BufferedInputStream(res.body().byteStream()); + ZipInputStream zipInputStream = new ZipInputStream(is); + ZipEntry entry; + while ((entry = zipInputStream.getNextEntry()) != null) { + String filePath = entry.getName().replaceAll("runelite-watchdog-alert-hub/", ""); + String[] splitPath = filePath.split("/", 2); + if (splitPath.length == 2) { + String alertName = splitPath[0]; + if (!alerts.containsKey(alertName)) { + alerts.put(alertName, new AlertDisplayInfo()); + } + AlertDisplayInfo alertDisplayInfo = alerts.get(alertName); + String alertFile = splitPath[1]; + if (alertFile.equals("alert.json")) { + String json = CharStreams.toString(new InputStreamReader(zipInputStream, Charsets.UTF_8)); + AlertManifest alertManifest = WatchdogPlugin.getInstance().getAlertManager().getGson().fromJson(json, AlertManifest.class); + alertManifest.setInternalName(alertName); + alertDisplayInfo.manifest = alertManifest; + } else if (alertFile.equals("icon.png")) { + BufferedImage icon = ImageIO.read(zipInputStream); + alertDisplayInfo.icon = ImageUtil.resizeImage(icon, PluginPanel.PANEL_WIDTH, 147, true); + } + } + System.out.println(filePath); +// if (!entry.isDirectory()) { + } // String data = Objects.requireNonNull(res.body()).string(); -// + // return RuneLiteAPI.GSON.fromJson(data, new TypeToken>() {}.getType()); -// } + return alerts.values().stream().sorted(Comparator.comparing(alert -> alert.manifest.getDisplayName())) + .collect(Collectors.toList()); + } + + +// return Arrays.asList(new AlertManifest( +// "testAlert", +// "284hfu43hhfiu24rf", +// "Test Alert", +// "This is a test alert on the hub", +// "4", +// "adamk33n3r", +// AlertHubCategory.COMBAT, +// Arrays.asList("afk", "combat"), +// new URL("https://github.com/adamk33n3r/runelite-watchdog"), +// null, +// false +// ), new AlertManifest( +// "testAlert", +// "284hfu43hhfiu24rf", +// "Test Alert", +// "This is a test alert on the hub", +// "4", +// "adamk33n3r", +// AlertHubCategory.COMBAT, +// Arrays.asList("afk", "combat"), +// new URL("https://github.com/adamk33n3r/runelite-watchdog"), +// null, +// false +// ), new AlertManifest( +// "testAlert", +// "284hfu43hhfiu24rf", +// "Test Alert", +// "This is a test alert on the hub", +// "4", +// "adamk33n3r", +// AlertHubCategory.COMBAT, +// Arrays.asList("afk", "combat"), +// new URL("https://github.com/adamk33n3r/runelite-watchdog"), +// null, +// false +// ), new AlertManifest( +// "testAlert2", +// "284hfu43hhfiu24rf", +// "Test Alert 2", +// "This is a test alert on the hub with an extra long description to test wrapping", +// "4", +// "adamk33n3r", +// AlertHubCategory.SKILLING, +// Arrays.asList("mining", "tts"), +// new URL("https://github.com/adamk33n3r/runelite-watchdog"), +// null, +// false +// )); } public BufferedImage downloadIcon(AlertManifest alertManifest) throws IOException { @@ -118,4 +162,32 @@ public BufferedImage downloadIcon(AlertManifest alertManifest) throws IOExceptio } } } + + class CacheInterceptor implements Interceptor { + private int minutes; + public CacheInterceptor(int minutes) { + this.minutes = minutes; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + CacheControl cacheControl = new CacheControl.Builder() + .maxAge(this.minutes, TimeUnit.MINUTES) + .build(); + + return response.newBuilder() + .removeHeader("Pragma") + .removeHeader("Cache-Control") + .header("Cache-Control", cacheControl.toString()) + .build(); + } + } + + @Getter + class AlertDisplayInfo { + private AlertManifest manifest; + private BufferedImage icon; + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java index edd177c..8b97c65 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java @@ -1,10 +1,12 @@ package com.adamk33n3r.runelite.watchdog.hub; +import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; +import com.adamk33n3r.runelite.watchdog.alerts.Alert; +import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; import com.adamk33n3r.runelite.watchdog.ui.WrappingLabel; -import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; +import com.google.gson.Gson; import lombok.Getter; import net.runelite.client.ui.ColorScheme; -import net.runelite.client.ui.DynamicGridLayout; import net.runelite.client.ui.PluginPanel; import javax.swing.*; @@ -12,21 +14,31 @@ @Getter public class AlertHubItem extends JPanel { - private final AlertManifest manifest; + private final AlertHubClient.AlertDisplayInfo alertDisplayInfo; - public AlertHubItem(AlertManifest manifest) { - this.manifest = manifest; + public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) { + this.alertDisplayInfo = alertDisplayInfo; this.setBackground(ColorScheme.BRAND_ORANGE); this.setMaximumSize(new Dimension(PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH, Short.MAX_VALUE)); GroupLayout layout = new GroupLayout(this); this.setLayout(layout); - JLabel alertName = new JLabel(this.manifest.toString()); - JLabel alertAuthor = new JLabel(this.manifest.getAuthor()); - WrappingLabel alertDescLabel = new WrappingLabel(this.manifest.getDescription()); + JLabel alertName = new JLabel(this.alertDisplayInfo.getManifest().toString()); + JLabel alertAuthor = new JLabel(this.alertDisplayInfo.getManifest().getAuthor()); + WrappingLabel alertDescLabel = new WrappingLabel(this.alertDisplayInfo.getManifest().getDescription()); alertDescLabel.setForeground(Color.CYAN); + JLabel icon = new JLabel(new ImageIcon(this.alertDisplayInfo.getIcon())); + icon.setHorizontalAlignment(JLabel.CENTER); + + Gson gson = WatchdogPlugin.getInstance().getAlertManager().getGson().newBuilder().setPrettyPrinting().create(); + System.out.println(this.alertDisplayInfo.getManifest().getAlert().getClass().getCanonicalName()); + String s = gson.toJson(this.alertDisplayInfo.getManifest().getAlert()); + System.out.println(s); + + icon.setToolTipText("
"+s);
+
 //        container.add(alertDescription);
 //        this.add(alertDescription);
 
@@ -36,6 +48,7 @@ public AlertHubItem(AlertManifest manifest) {
                 .addComponent(alertName)
                 .addComponent(alertAuthor))
             .addComponent(alertDescLabel)
+            .addComponent(icon, 147, GroupLayout.DEFAULT_SIZE, 147)
         );
 
         layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER)
@@ -46,6 +59,7 @@ public AlertHubItem(AlertManifest manifest) {
                 .addComponent(alertAuthor))
 //                .addGap(5))
             .addComponent(alertDescLabel)
+            .addComponent(icon, PluginPanel.PANEL_WIDTH, PluginPanel.PANEL_WIDTH, PluginPanel.PANEL_WIDTH)
         );
 
 //        this.setLayout(new DynamicGridLayout(0, 1, 5, 5));
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
index 5f95aa6..edf8ed1 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
@@ -23,6 +23,8 @@
 import java.util.List;
 import java.util.stream.Collectors;
 
+import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.HISTORY_ICON;
+import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.HISTORY_ICON_HOVER;
 import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON;
 import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON_HOVER;
 
@@ -35,6 +37,7 @@ public class AlertHubPanel extends PluginPanel {
     private final IconTextField searchBar;
     private final JPanel container;
     private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings();
+    private final JButton refresh;
 
     @Inject
     public AlertHubPanel(Provider muxer, AlertHubClient alertHubClient) {
@@ -98,16 +101,21 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
 
         JPanel wrapper = new JPanel(new BorderLayout());
         wrapper.add(this.container, BorderLayout.NORTH);
-        JScrollPane scrollPane = new JScrollPane(wrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+        JScrollPane scrollPane = new JScrollPane(wrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
         scrollPane.setBackground(ColorScheme.GRAND_EXCHANGE_ALCH);
         scrollPane.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999));
         this.container.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999));
 
+        this.refresh = PanelUtils.createActionButton(HISTORY_ICON, HISTORY_ICON_HOVER, "Refresh", (btn, mod) -> {
+            this.reloadList();
+        });
+
         layout.setVerticalGroup(layout.createSequentialGroup()
             .addGap(5)
             .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                 .addComponent(backButton, 24, 24, 24)
-                .addComponent(this.searchBar, 24, 24, 24))
+                .addComponent(this.searchBar, 24, 24, 24)
+                .addComponent(this.refresh, 24, 24, 24))
             .addGap(10)
             .addComponent(scrollPane)
         );
@@ -118,6 +126,8 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
                 .addComponent(backButton)
                 .addGap(3)
                 .addComponent(this.searchBar)
+                .addGap(3)
+                .addComponent(this.refresh)
                 .addGap(7))
             .addComponent(scrollPane)
         );
@@ -131,16 +141,16 @@ public void reloadList() {
         this.container.removeAll();
 
         try {
-            List alertManifests = this.alertHubClient.downloadManifest();
+            List alerts = this.alertHubClient.downloadManifest();
 //            System.out.println(alertManifests.stream().map(AlertManifest::toString).collect(Collectors.joining(", ")));
-            this.reloadList(alertManifests);
+            this.reloadList(alerts);
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 
-    private void reloadList(List alertManifests) {
-        this.alertHubItems = alertManifests.stream().map(AlertHubItem::new).collect(Collectors.toList());
+    private void reloadList(List alerts) {
+        this.alertHubItems = alerts.stream().map(AlertHubItem::new).collect(Collectors.toList());
         this.updateFilter(this.searchBar.getText());
     }
 
@@ -149,7 +159,7 @@ private void updateFilter(String search) {
         String upperSearch = search.toUpperCase();
         this.alertHubItems.stream().filter(alertHubItem -> {
 //            Alert alert = WatchdogPlugin.getInstance().getAlertManager().getGson().fromJson(alertHubItem.getManifest().getJson(), ALERT_LIST_TYPE);
-            AlertManifest manifest = alertHubItem.getManifest();
+            AlertManifest manifest = alertHubItem.getAlertDisplayInfo().getManifest();
             return Text.matchesSearchTerms(SPLITTER.split(upperSearch), manifest.getKeywords());
         }).forEach(this.container::add);
         this.revalidate();
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
index 9e7c7fa..8632918 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
@@ -1,16 +1,20 @@
 package com.adamk33n3r.runelite.watchdog.hub;
 
+import com.adamk33n3r.runelite.watchdog.alerts.Alert;
 import lombok.Data;
+import lombok.Setter;
 
 import java.net.URL;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 @Data
 public class AlertManifest {
-    private final String internalName;
+    @Setter
+    private String internalName;
     private final String commit;
 
     private final String displayName;
@@ -20,7 +24,7 @@ public class AlertManifest {
     private final AlertHubCategory category;
     private final List tags;
     private final URL repo;
-    private final String json;
+    private final Alert alert;
     private final boolean hasIcon;
 
     @Override
@@ -35,8 +39,9 @@ public List getKeywords() {
             this.getInternalName(),
             this.getAuthor()
 //            this.getCategory().getName()
-        );
+        ).filter(Objects::nonNull);
         if (this.getTags() != null) {
+            System.out.println(this.getTags());
             return Stream.concat(keywords, this.getTags().stream()).map(String::toUpperCase).collect(Collectors.toList());
         }
         return keywords.map(String::toUpperCase).collect(Collectors.toList());
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index ea26243..5f71c85 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,5 +1,5 @@
-#Sat Sep 23 20:30:44 EDT 2023
-VERSION_BUILD=2861
+#Sun Sep 24 00:45:31 EDT 2023
+VERSION_BUILD=2901
 VERSION_PHASE=alpha
 VERSION_MAJOR=3
 VERSION_MINOR=0

From abf564404e9cb7c550146a37b3afca8dea0f5c13 Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Sun, 24 Sep 2023 15:16:05 -0400
Subject: [PATCH 10/27] Fix group alert collapse button alignment

---
 .../com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java | 4 ++--
 .../com/adamk33n3r/runelite/watchdog/version.properties       | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java
index ec8579e..e4e1a0c 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java
@@ -96,7 +96,7 @@ public void rebuild() {
         });
         topWrapper.add(toggleButton, BorderLayout.WEST);
 
-        final JPanel nameWrapper = new JPanel(new DynamicGridLayout(1, 0, 2, 2));
+        final JPanel nameWrapper = new JPanel(new BorderLayout());
         nameWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR);
         topWrapper.add(nameWrapper, BorderLayout.CENTER);
 
@@ -111,7 +111,7 @@ public void rebuild() {
                     this.revalidate();
                 }
             );
-            nameWrapper.add(collapseButton);
+            nameWrapper.add(collapseButton, BorderLayout.WEST);
         }
 
         final JLabel nameLabel = new JLabel(this.alert.getName());
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index 5f71c85..69aded7 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,5 +1,5 @@
-#Sun Sep 24 00:45:31 EDT 2023
-VERSION_BUILD=2901
+#Sun Sep 24 15:15:26 EDT 2023
+VERSION_BUILD=2914
 VERSION_PHASE=alpha
 VERSION_MAJOR=3
 VERSION_MINOR=0

From 81a478e427162156c7264e8b65a660546929aa53 Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Sun, 24 Sep 2023 18:59:58 -0400
Subject: [PATCH 11/27] Added add alert hub alert button

That's a mouthful
---
 .../runelite/watchdog/hub/AlertHubClient.java |  6 ++
 .../runelite/watchdog/hub/AlertHubItem.java   | 99 +++++++++++++------
 .../runelite/watchdog/hub/AlertHubPanel.java  | 47 +++++----
 .../runelite/watchdog/hub/AlertManifest.java  |  4 +-
 .../runelite/watchdog/ui/WrappingLabel.java   |  7 +-
 .../watchdog/ui/panels/HistoryPanel.java      |  2 +-
 .../runelite/watchdog/version.properties      |  4 +-
 7 files changed, 116 insertions(+), 53 deletions(-)

diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
index 1ad6d35..773620d 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
@@ -71,6 +71,12 @@ public List downloadManifest() throws IOException {
                         String json = CharStreams.toString(new InputStreamReader(zipInputStream, Charsets.UTF_8));
                         AlertManifest alertManifest = WatchdogPlugin.getInstance().getAlertManager().getGson().fromJson(json, AlertManifest.class);
                         alertManifest.setInternalName(alertName);
+                        HttpUrl repoPage = GITHUB.newBuilder()
+                            .addPathSegment("tree")
+                            .addPathSegment("alert-hub")
+                            .addPathSegment(alertName)
+                            .build();
+                        alertManifest.setRepo(repoPage.url());
                         alertDisplayInfo.manifest = alertManifest;
                     } else if (alertFile.equals("icon.png")) {
                         BufferedImage icon = ImageIO.read(zipInputStream);
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
index 8b97c65..498f444 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
@@ -1,71 +1,114 @@
 package com.adamk33n3r.runelite.watchdog.hub;
 
+import com.adamk33n3r.runelite.watchdog.WatchdogPanel;
 import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
 import com.adamk33n3r.runelite.watchdog.alerts.Alert;
 import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup;
 import com.adamk33n3r.runelite.watchdog.ui.WrappingLabel;
+import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
 import com.google.gson.Gson;
 import lombok.Getter;
 import net.runelite.client.ui.ColorScheme;
+import net.runelite.client.ui.FontManager;
 import net.runelite.client.ui.PluginPanel;
+import net.runelite.client.util.ImageUtil;
+import net.runelite.client.util.LinkBrowser;
 
 import javax.swing.*;
+import javax.swing.border.LineBorder;
 import java.awt.*;
+import java.awt.image.BufferedImage;
 
 @Getter
 public class AlertHubItem extends JPanel {
+    private static final int LINE_HEIGHT = 16;
+
     private final AlertHubClient.AlertDisplayInfo alertDisplayInfo;
 
     public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) {
         this.alertDisplayInfo = alertDisplayInfo;
-        this.setBackground(ColorScheme.BRAND_ORANGE);
-        this.setMaximumSize(new Dimension(PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH, Short.MAX_VALUE));
+        this.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+//        this.setMaximumSize(new Dimension(PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH, Short.MAX_VALUE));
 
         GroupLayout layout = new GroupLayout(this);
         this.setLayout(layout);
 
-        JLabel alertName = new JLabel(this.alertDisplayInfo.getManifest().toString());
+        JLabel alertName = new JLabel(this.alertDisplayInfo.getManifest().getDisplayName());
+        alertName.setFont(FontManager.getRunescapeBoldFont());
+        alertName.setToolTipText(this.alertDisplayInfo.getManifest().getDisplayName());
         JLabel alertAuthor = new JLabel(this.alertDisplayInfo.getManifest().getAuthor());
+        alertAuthor.setFont(FontManager.getRunescapeSmallFont());
+        alertAuthor.setToolTipText(this.alertDisplayInfo.getManifest().getAuthor());
+//        alertAuthor.setText("te9");
+
+        JLabel compatVersion = new JLabel(this.alertDisplayInfo.getManifest().getCompatibleVersion());
+        compatVersion.setFont(FontManager.getRunescapeSmallFont());
+        compatVersion.setToolTipText("Compatible with Watchdog v" + this.alertDisplayInfo.getManifest().getCompatibleVersion());
+
+
+//        JPanel titlePanel = new JPanel(new BorderLayout());
+//        titlePanel.add(alertName, BorderLayout.WEST);
+//        titlePanel.add(alertAuthor, BorderLayout.EAST);
+//        titlePanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+
         WrappingLabel alertDescLabel = new WrappingLabel(this.alertDisplayInfo.getManifest().getDescription());
         alertDescLabel.setForeground(Color.CYAN);
 
         JLabel icon = new JLabel(new ImageIcon(this.alertDisplayInfo.getIcon()));
         icon.setHorizontalAlignment(JLabel.CENTER);
 
-        Gson gson = WatchdogPlugin.getInstance().getAlertManager().getGson().newBuilder().setPrettyPrinting().create();
-        System.out.println(this.alertDisplayInfo.getManifest().getAlert().getClass().getCanonicalName());
-        String s = gson.toJson(this.alertDisplayInfo.getManifest().getAlert());
-        System.out.println(s);
+        JButton moreInfoButton = PanelUtils.createActionButton(WatchdogPanel.HELP_ICON, WatchdogPanel.HELP_ICON_HOVER, "More info", (btn, mod) -> {
+            LinkBrowser.browse(this.alertDisplayInfo.getManifest().getRepo().toString());
+        });
+        if (this.alertDisplayInfo.getManifest().getRepo() == null) {
+            moreInfoButton.setVisible(false);
+        }
 
-        icon.setToolTipText("
"+s);
-
-//        container.add(alertDescription);
-//        this.add(alertDescription);
+        JButton addButton = new JButton();
+        addButton.setText("Add");
+        BufferedImage addIcon = ImageUtil.recolorImage(WatchdogPanel.ADD_ICON.getImage(), Color.white);
+        addButton.setIcon(new ImageIcon(addIcon));
+        addButton.setHorizontalTextPosition(SwingConstants.LEFT);
+        addButton.setBackground(new Color(0x28BE28));
+        addButton.setBorder(new LineBorder(addButton.getBackground().darker()));
+        addButton.setFocusPainted(false);
+        addButton.addActionListener((ev) -> {
+            WatchdogPlugin.getInstance().getAlertManager().addAlert(this.alertDisplayInfo.getManifest().getAlert());
+            JOptionPane.showMessageDialog(this, "Added " + this.alertDisplayInfo.getManifest().getDisplayName() + "to your alerts", "Successfully Added", JOptionPane.INFORMATION_MESSAGE);
+        });
 
+        layout.setAutoCreateContainerGaps(true);
         layout.setVerticalGroup(layout.createSequentialGroup()
             .addGap(5)
-            .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING)
+            .addGroup(layout.createParallelGroup()
                 .addComponent(alertName)
-                .addComponent(alertAuthor))
+                .addComponent(moreInfoButton, LINE_HEIGHT, LINE_HEIGHT, LINE_HEIGHT)
+                .addComponent(addButton, LINE_HEIGHT, LINE_HEIGHT, LINE_HEIGHT))
+            .addGap(5)
+            .addGroup(layout.createParallelGroup()
+                .addComponent(alertAuthor)
+                .addComponent(compatVersion)
+            )
             .addComponent(alertDescLabel)
             .addComponent(icon, 147, GroupLayout.DEFAULT_SIZE, 147)
         );
 
-        layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER)
-            .addGroup(layout.createSequentialGroup()
-//                .addGap(5)
-                .addComponent(alertName)
-//                .addGap(50)
-                .addComponent(alertAuthor))
-//                .addGap(5))
-            .addComponent(alertDescLabel)
-            .addComponent(icon, PluginPanel.PANEL_WIDTH, PluginPanel.PANEL_WIDTH, PluginPanel.PANEL_WIDTH)
+        layout.setHorizontalGroup(layout.createSequentialGroup()
+            .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER)
+                .addGroup(layout.createSequentialGroup()
+                    .addComponent(alertName, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
+                    .addComponent(moreInfoButton, 0, 24, 24)
+                    .addComponent(addButton, 0, 50, GroupLayout.PREFERRED_SIZE)
+                )
+                .addGroup(layout.createSequentialGroup()
+                    .addComponent(alertAuthor, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
+                    .addComponent(compatVersion, 0, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
+                )
+                .addComponent(alertDescLabel)
+                .addComponent(icon, PluginPanel.PANEL_WIDTH, PluginPanel.PANEL_WIDTH, PluginPanel.PANEL_WIDTH)
+            )
         );
-
-//        this.setLayout(new DynamicGridLayout(0, 1, 5, 5));
-//        this.add(new JLabel(this.manifest.toString()));
-//        this.add(new JLabel(this.manifest.getAuthor()));
-//        this.add(new JLabel(this.manifest.getDescription()));
-//        this.add(new JLabel(this.manifest.getRepo().toString()));
     }
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
index edf8ed1..72c54cf 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
@@ -3,6 +3,7 @@
 import com.adamk33n3r.runelite.watchdog.ui.SearchBar;
 import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
 
+import com.adamk33n3r.runelite.watchdog.ui.panels.ScrollablePanel;
 import com.google.common.base.Splitter;
 import net.runelite.client.ui.ColorScheme;
 import net.runelite.client.ui.DynamicGridLayout;
@@ -22,6 +23,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.HISTORY_ICON;
 import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.HISTORY_ICON_HOVER;
@@ -38,6 +40,7 @@ public class AlertHubPanel extends PluginPanel {
     private final JPanel container;
     private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings();
     private final JButton refresh;
+    private final JScrollPane scrollPane;
 
     @Inject
     public AlertHubPanel(Provider muxer, AlertHubClient alertHubClient) {
@@ -90,21 +93,23 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
 
         GroupLayout layout = new GroupLayout(this);
         this.setLayout(layout);
-        this.setBackground(ColorScheme.PROGRESS_ERROR_COLOR);
+//        this.setBackground(ColorScheme.PROGRESS_ERROR_COLOR);
         this.searchBar = new SearchBar(this::updateFilter);
 
         this.container = new JPanel(new DynamicGridLayout(0, 1, 0, 5));
 //        this.container.setMaximumSize(new Dimension(PANEL_WIDTH, 9999));
-        this.container.setBackground(ColorScheme.GRAND_EXCHANGE_LIMIT);
+//        this.container.setBackground(ColorScheme.GRAND_EXCHANGE_LIMIT);
 //        this.container.setBorder(BorderFactory.createEmptyBorder(0, 7, 15, 7));
 //        this.container.setAlignmentX(Component.LEFT_ALIGNMENT);
 
-        JPanel wrapper = new JPanel(new BorderLayout());
+        ScrollablePanel wrapper = new ScrollablePanel(new BorderLayout());
         wrapper.add(this.container, BorderLayout.NORTH);
-        JScrollPane scrollPane = new JScrollPane(wrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
-        scrollPane.setBackground(ColorScheme.GRAND_EXCHANGE_ALCH);
-        scrollPane.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999));
-        this.container.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999));
+        wrapper.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
+        wrapper.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
+        this.scrollPane = new JScrollPane(wrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+//        scrollPane.setBackground(ColorScheme.GRAND_EXCHANGE_ALCH);
+//        scrollPane.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999));
+//        this.container.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999));
 
         this.refresh = PanelUtils.createActionButton(HISTORY_ICON, HISTORY_ICON_HOVER, "Refresh", (btn, mod) -> {
             this.reloadList();
@@ -138,19 +143,24 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
     }
 
     public void reloadList() {
-        this.container.removeAll();
-
-        try {
-            List alerts = this.alertHubClient.downloadManifest();
-//            System.out.println(alertManifests.stream().map(AlertManifest::toString).collect(Collectors.joining(", ")));
-            this.reloadList(alerts);
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
+//        this.container.removeAll();
+
+        SwingUtilities.invokeLater(() -> {
+            try {
+                List alerts = this.alertHubClient.downloadManifest();
+    //            System.out.println(alertManifests.stream().map(AlertManifest::toString).collect(Collectors.joining(", ")));
+                this.reloadList(alerts);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        });
     }
 
     private void reloadList(List alerts) {
-        this.alertHubItems = alerts.stream().map(AlertHubItem::new).collect(Collectors.toList());
+        this.alertHubItems = alerts.stream()
+//            .flatMap(e -> Stream.of(e, e, e, e))
+            .map(AlertHubItem::new)
+            .collect(Collectors.toList());
         this.updateFilter(this.searchBar.getText());
     }
 
@@ -163,7 +173,6 @@ private void updateFilter(String search) {
             return Text.matchesSearchTerms(SPLITTER.split(upperSearch), manifest.getKeywords());
         }).forEach(this.container::add);
         this.revalidate();
-        // Idk why I need to repaint sometimes and the PluginListPanel doesn't
-        this.repaint();
+        this.scrollPane.getVerticalScrollBar().setValue(0);
     }
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
index 8632918..5086a92 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
@@ -15,7 +15,8 @@
 public class AlertManifest {
     @Setter
     private String internalName;
-    private final String commit;
+    private URL repo;
+    private String commit;
 
     private final String displayName;
     private final String description;
@@ -23,7 +24,6 @@ public class AlertManifest {
     private final String author;
     private final AlertHubCategory category;
     private final List tags;
-    private final URL repo;
     private final Alert alert;
     private final boolean hasIcon;
 
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/WrappingLabel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/WrappingLabel.java
index 397f937..a065ae7 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/WrappingLabel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/WrappingLabel.java
@@ -1,15 +1,20 @@
 package com.adamk33n3r.runelite.watchdog.ui;
 
 import javax.swing.*;
+import javax.swing.text.DefaultCaret;
 
 public class WrappingLabel extends JTextArea {
     public WrappingLabel(String text) {
-        super(text);
+        super();
 
         this.setOpaque(false);
         this.setEditable(false);
         this.setFocusable(false);
         this.setLineWrap(true);
         this.setWrapStyleWord(true);
+
+        // Need to update this before setting the text, or it will cause the scroll pane to scroll to this component
+        ((DefaultCaret)this.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
+        this.setText(text);
     }
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
index 0fc6560..35cda93 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
@@ -33,7 +33,7 @@
 import static com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel.BACK_ICON_HOVER;
 
 @Slf4j
-//@Singleton
+@Singleton
 public class HistoryPanel extends PluginPanel {
     private final Provider muxer;
     private final ScrollablePanel historyItems;
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index 1ad9157..8e67034 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,5 +1,5 @@
-#Sun Sep 24 15:26:07 EDT 2023
-VERSION_BUILD=2916
+#Sun Sep 24 18:58:18 EDT 2023
+VERSION_BUILD=3040
 VERSION_PHASE=alpha
 VERSION_MAJOR=3
 VERSION_MINOR=0

From fadbaf37620ec9d161f4b12c549c11b4e64fdf69 Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Thu, 28 Sep 2023 00:17:16 -0400
Subject: [PATCH 12/27] Fix panel sizing, added category icons

---
 .../runelite/watchdog/AlertManager.java       |  7 +-
 .../watchdog/MixedCaseEnumAdapter.java        | 23 +++++
 .../runelite/watchdog/WatchdogPanel.java      | 12 ++-
 .../watchdog/hub/AlertHubCategory.java        | 11 ++-
 .../runelite/watchdog/hub/AlertHubClient.java |  2 +-
 .../runelite/watchdog/hub/AlertHubItem.java   | 99 ++++++++++---------
 .../runelite/watchdog/hub/AlertHubPanel.java  | 44 +--------
 .../runelite/watchdog/hub/AlertManifest.java  |  5 +-
 .../runelite/watchdog/version.properties      |  4 +-
 9 files changed, 107 insertions(+), 100 deletions(-)
 create mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/MixedCaseEnumAdapter.java

diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
index 4af63d2..8629d4a 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
@@ -12,6 +12,7 @@
 import com.adamk33n3r.runelite.watchdog.alerts.StatChangedAlert;
 import com.adamk33n3r.runelite.watchdog.alerts.StatDrainAlert;
 import com.adamk33n3r.runelite.watchdog.alerts.XPDropAlert;
+import com.adamk33n3r.runelite.watchdog.hub.AlertHubCategory;
 import com.adamk33n3r.runelite.watchdog.notifications.GameMessage;
 import com.adamk33n3r.runelite.watchdog.notifications.IAudioNotification;
 import com.adamk33n3r.runelite.watchdog.notifications.INotification;
@@ -107,6 +108,7 @@ private void init() {
 //            .serializeNulls()
             .registerTypeAdapterFactory(alertTypeFactory)
             .registerTypeAdapterFactory(notificationTypeFactory)
+            .registerTypeAdapter(AlertHubCategory.class, new MixedCaseEnumAdapter())
             .create();
     }
 
@@ -222,7 +224,10 @@ public boolean importAlerts(String json, List alerts, boolean append, boo
                 }
             });
 
-        SwingUtilities.invokeLater(this.watchdogPanel::rebuild);
+        SwingUtilities.invokeLater(() -> {
+            this.watchdogPanel.rebuild();
+            SwingUtilities.invokeLater(this.watchdogPanel::scrollToBottom);
+        });
         return true;
     }
 
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/MixedCaseEnumAdapter.java b/src/main/java/com/adamk33n3r/runelite/watchdog/MixedCaseEnumAdapter.java
new file mode 100644
index 0000000..18d5e77
--- /dev/null
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/MixedCaseEnumAdapter.java
@@ -0,0 +1,23 @@
+package com.adamk33n3r.runelite.watchdog;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+import java.lang.reflect.Type;
+
+public class MixedCaseEnumAdapter implements JsonDeserializer {
+    @Override
+    public Enum deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
+        try {
+            if (type instanceof Class && ((Class) type).isEnum()) {
+                return Enum.valueOf((Class) type, jsonElement.getAsString().toUpperCase());
+            }
+            return null;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
index 8a7a2aa..5afd343 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
@@ -75,6 +75,8 @@ public class WatchdogPanel extends PluginPanel {
     @Inject
     private OkHttpClient httpClient;
 
+    private JScrollPane scroll;
+
     public static final ImageIcon ADD_ICON;
     public static final ImageIcon HELP_ICON;
     public static final ImageIcon HELP_ICON_HOVER;
@@ -184,8 +186,8 @@ public void rebuild() {
         scrollablePanel.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
         scrollablePanel.setScrollableBlockIncrement(ScrollablePanel.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
         scrollablePanel.add(alertPanel);
-        JScrollPane scroll = new JScrollPane(scrollablePanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
-        this.add(scroll, BorderLayout.CENTER);
+        this.scroll = new JScrollPane(scrollablePanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        this.add(this.scroll, BorderLayout.CENTER);
 
         JPanel importExportGroup = new JPanel(new GridLayout(1, 2, 5, 0));
         JButton importButton = new JButton("Import", IMPORT_ICON);
@@ -218,7 +220,6 @@ public void rebuild() {
         bottomPanel.add(hubButton);
         this.add(bottomPanel, BorderLayout.SOUTH);
 
-        // Need this for rebuild for some reason
         this.revalidate();
     }
 
@@ -255,4 +256,9 @@ private PluginPanel createPluginPanel(Alert alert) {
     public void onActivate() {
         this.rebuild();
     }
+
+    public void scrollToBottom() {
+        JScrollBar scrollBar = this.scroll.getVerticalScrollBar();
+        scrollBar.setValue(scrollBar.getMaximum());
+    }
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java
index d2b7ab4..485606c 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java
@@ -6,13 +6,14 @@
 @Getter
 @AllArgsConstructor
 public enum AlertHubCategory {
-    COMBAT("Combat", "Combat"),
-    SKILLING("Skilling", "Skilling"),
-    BOSSES("Bosses", "Bosses"),
-    DROPS("Drops", "Drops"),
-    AFK("AFK", "AFK"),
+    COMBAT("Combat", "Combat", "/skill_icons_small/attack.png"),
+    SKILLING("Skilling", "Skilling", "/skill_icons_small/mining.png"),
+    BOSSES("Bosses", "Bosses", "/skill_icons_small/slayer.png"),
+    DROPS("Drops", "Drops", "/skill_icons_small/clue_scroll_all.png"),
+    AFK("AFK", "AFK", "/skill_icons_small/cooking.png"),
     ;
 
     private final String name;
     private final String tooltip;
+    private final String icon;
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
index 773620d..0a42df3 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
@@ -80,7 +80,7 @@ public List downloadManifest() throws IOException {
                         alertDisplayInfo.manifest = alertManifest;
                     } else if (alertFile.equals("icon.png")) {
                         BufferedImage icon = ImageIO.read(zipInputStream);
-                        alertDisplayInfo.icon = ImageUtil.resizeImage(icon, PluginPanel.PANEL_WIDTH, 147, true);
+                        alertDisplayInfo.icon = ImageUtil.resizeImage(icon, 242, 182, true);
                     }
                 }
                 System.out.println(filePath);
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
index 498f444..f46944f 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
@@ -8,6 +8,7 @@
 import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
 import com.google.gson.Gson;
 import lombok.Getter;
+import net.runelite.client.plugins.config.ConfigPlugin;
 import net.runelite.client.ui.ColorScheme;
 import net.runelite.client.ui.FontManager;
 import net.runelite.client.ui.PluginPanel;
@@ -28,39 +29,35 @@ public class AlertHubItem extends JPanel {
     public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) {
         this.alertDisplayInfo = alertDisplayInfo;
         this.setBackground(ColorScheme.DARKER_GRAY_COLOR);
-//        this.setMaximumSize(new Dimension(PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH, Short.MAX_VALUE));
 
         GroupLayout layout = new GroupLayout(this);
         this.setLayout(layout);
 
-        JLabel alertName = new JLabel(this.alertDisplayInfo.getManifest().getDisplayName());
+        AlertManifest manifest = this.alertDisplayInfo.getManifest();
+        JLabel alertName = new JLabel(manifest.getDisplayName());
         alertName.setFont(FontManager.getRunescapeBoldFont());
-        alertName.setToolTipText(this.alertDisplayInfo.getManifest().getDisplayName());
-        JLabel alertAuthor = new JLabel(this.alertDisplayInfo.getManifest().getAuthor());
+        alertName.setToolTipText(manifest.getDisplayName());
+        JLabel alertAuthor = new JLabel(manifest.getAuthor());
         alertAuthor.setFont(FontManager.getRunescapeSmallFont());
-        alertAuthor.setToolTipText(this.alertDisplayInfo.getManifest().getAuthor());
-//        alertAuthor.setText("te9");
-
-        JLabel compatVersion = new JLabel(this.alertDisplayInfo.getManifest().getCompatibleVersion());
-        compatVersion.setFont(FontManager.getRunescapeSmallFont());
-        compatVersion.setToolTipText("Compatible with Watchdog v" + this.alertDisplayInfo.getManifest().getCompatibleVersion());
+        alertAuthor.setToolTipText(manifest.getAuthor());
 
+        alertName.setIcon(new ImageIcon(ImageUtil.loadImageResource(WatchdogPlugin.class, manifest.getCategory().getIcon())));
 
-//        JPanel titlePanel = new JPanel(new BorderLayout());
-//        titlePanel.add(alertName, BorderLayout.WEST);
-//        titlePanel.add(alertAuthor, BorderLayout.EAST);
-//        titlePanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+        JLabel compatVersion = new JLabel(manifest.getCompatibleVersion());
+        compatVersion.setHorizontalAlignment(JLabel.RIGHT);
+        compatVersion.setFont(FontManager.getRunescapeSmallFont());
+        compatVersion.setToolTipText("Compatible with Watchdog v" + manifest.getCompatibleVersion());
 
-        WrappingLabel alertDescLabel = new WrappingLabel(this.alertDisplayInfo.getManifest().getDescription());
-        alertDescLabel.setForeground(Color.CYAN);
+        WrappingLabel alertDescLabel = new WrappingLabel(manifest.getDescription());
 
         JLabel icon = new JLabel(new ImageIcon(this.alertDisplayInfo.getIcon()));
+//        JLabel icon = new JLabel(new ImageIcon(ImageUtil.loadImageResource(WatchdogPlugin.class, "detail-test.png")));
         icon.setHorizontalAlignment(JLabel.CENTER);
 
         JButton moreInfoButton = PanelUtils.createActionButton(WatchdogPanel.HELP_ICON, WatchdogPanel.HELP_ICON_HOVER, "More info", (btn, mod) -> {
-            LinkBrowser.browse(this.alertDisplayInfo.getManifest().getRepo().toString());
+            LinkBrowser.browse(manifest.getRepo().toString());
         });
-        if (this.alertDisplayInfo.getManifest().getRepo() == null) {
+        if (manifest.getRepo() == null) {
             moreInfoButton.setVisible(false);
         }
 
@@ -73,42 +70,54 @@ public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) {
         addButton.setBorder(new LineBorder(addButton.getBackground().darker()));
         addButton.setFocusPainted(false);
         addButton.addActionListener((ev) -> {
-            WatchdogPlugin.getInstance().getAlertManager().addAlert(this.alertDisplayInfo.getManifest().getAlert());
-            JOptionPane.showMessageDialog(this, "Added " + this.alertDisplayInfo.getManifest().getDisplayName() + "to your alerts", "Successfully Added", JOptionPane.INFORMATION_MESSAGE);
+            WatchdogPlugin.getInstance().getAlertManager().addAlert(manifest.getAlert());
+            JOptionPane.showMessageDialog(this, "Added " + manifest.getDisplayName() + " to your alerts", "Successfully Added", JOptionPane.INFORMATION_MESSAGE);
         });
 
-        layout.setAutoCreateContainerGaps(true);
         layout.setVerticalGroup(layout.createSequentialGroup()
             .addGap(5)
-            .addGroup(layout.createParallelGroup()
-                .addComponent(alertName)
-                .addComponent(moreInfoButton, LINE_HEIGHT, LINE_HEIGHT, LINE_HEIGHT)
-                .addComponent(addButton, LINE_HEIGHT, LINE_HEIGHT, LINE_HEIGHT))
-            .addGap(5)
-            .addGroup(layout.createParallelGroup()
-                .addComponent(alertAuthor)
-                .addComponent(compatVersion)
+            .addGroup(layout.createSequentialGroup()
+                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
+                    .addComponent(alertName, LINE_HEIGHT, LINE_HEIGHT, LINE_HEIGHT)
+                    .addComponent(moreInfoButton, LINE_HEIGHT, LINE_HEIGHT, LINE_HEIGHT)
+                    .addComponent(addButton, LINE_HEIGHT, LINE_HEIGHT, LINE_HEIGHT)
+                )
+                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
+                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
+                    .addComponent(alertAuthor, LINE_HEIGHT, LINE_HEIGHT, LINE_HEIGHT)
+                    .addComponent(compatVersion, LINE_HEIGHT, LINE_HEIGHT, LINE_HEIGHT)
+                )
+                .addComponent(alertDescLabel, 0, GroupLayout.DEFAULT_SIZE, 96)
+                .addGap(5)
             )
-            .addComponent(alertDescLabel)
-            .addComponent(icon, 147, GroupLayout.DEFAULT_SIZE, 147)
+//            .addComponent(icon, 0, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE)
         );
 
-        layout.setHorizontalGroup(layout.createSequentialGroup()
-            .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER)
-                .addGroup(layout.createSequentialGroup()
-                    .addComponent(alertName, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
-                    .addComponent(moreInfoButton, 0, 24, 24)
-                    .addComponent(addButton, 0, 50, GroupLayout.PREFERRED_SIZE)
-                )
-                .addGroup(layout.createSequentialGroup()
-                    .addComponent(alertAuthor, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
-                    .addComponent(compatVersion, 0, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
+        layout.setHorizontalGroup(layout.createParallelGroup()
+            // Info group
+            .addGroup(layout.createSequentialGroup()
+                .addGap(5)
+                .addGroup(layout.createParallelGroup()
+                    .addGroup(layout.createSequentialGroup()
+                        .addComponent(alertName, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
+                        .addComponent(moreInfoButton, 24, 24, 24)
+                        .addComponent(addButton, 50, 50, GroupLayout.PREFERRED_SIZE)
+                    )
+                    .addGroup(layout.createSequentialGroup()
+                        .addComponent(alertAuthor, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, 100)
+                        .addComponent(compatVersion, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                    )
+                    .addGroup(layout.createSequentialGroup()
+                        .addComponent(alertDescLabel, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                    )
                 )
-                .addComponent(alertDescLabel)
-                .addComponent(icon, PluginPanel.PANEL_WIDTH, PluginPanel.PANEL_WIDTH, PluginPanel.PANEL_WIDTH)
+                .addGap(5)
             )
+//            .addGroup(layout.createSequentialGroup()
+//                .addComponent(icon, 0, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
+//            )
         );
     }
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
index 72c54cf..ed8862a 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
@@ -60,56 +60,21 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
         );
         backButton.setPreferredSize(new Dimension(22, 16));
         backButton.setBorder(new EmptyBorder(0, 0, 0, 5));
-//        topPanel.add(backButton, BorderLayout.WEST);
-//        PlaceholderTextField filterTextField = new PlaceholderTextField();
-//        filterTextField.setPlaceholder("Filter");
-//        filterTextField.getDocument().addDocumentListener(new DocumentListener() {
-//            @Override
-//            public void insertUpdate(DocumentEvent e) {
-//                updateFilter(filterTextField.getText());
-//            }
-//
-//            @Override
-//            public void removeUpdate(DocumentEvent e) {
-//                updateFilter(filterTextField.getText());
-//            }
-//
-//            @Override
-//            public void changedUpdate(DocumentEvent e) {
-//                updateFilter(filterTextField.getText());
-//            }
-//        });
-//        topPanel.add(filterTextField);
-//        this.add(topPanel, BorderLayout.NORTH);
-//
-//        this.filteredAlerts = new ScrollablePanel(new StretchedStackedLayout(3, 3));
-//        this.filteredAlerts.setBorder(new EmptyBorder(0, 10, 0, 10));
-//        this.filteredAlerts.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
-//        this.filteredAlerts.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
-//        this.filteredAlerts.setScrollableBlockIncrement(ScrollablePanel.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
-//        JScrollPane scroll = new JScrollPane(this.filteredAlerts, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
-//
-//        this.add(scroll, BorderLayout.CENTER);
 
         GroupLayout layout = new GroupLayout(this);
         this.setLayout(layout);
-//        this.setBackground(ColorScheme.PROGRESS_ERROR_COLOR);
         this.searchBar = new SearchBar(this::updateFilter);
 
         this.container = new JPanel(new DynamicGridLayout(0, 1, 0, 5));
-//        this.container.setMaximumSize(new Dimension(PANEL_WIDTH, 9999));
-//        this.container.setBackground(ColorScheme.GRAND_EXCHANGE_LIMIT);
-//        this.container.setBorder(BorderFactory.createEmptyBorder(0, 7, 15, 7));
-//        this.container.setAlignmentX(Component.LEFT_ALIGNMENT);
+//        this.container.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
 
         ScrollablePanel wrapper = new ScrollablePanel(new BorderLayout());
         wrapper.add(this.container, BorderLayout.NORTH);
         wrapper.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
         wrapper.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
+        wrapper.setScrollableBlockIncrement(SwingConstants.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
+        wrapper.setScrollableUnitIncrement(SwingConstants.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
         this.scrollPane = new JScrollPane(wrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
-//        scrollPane.setBackground(ColorScheme.GRAND_EXCHANGE_ALCH);
-//        scrollPane.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999));
-//        this.container.setMaximumSize(new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, 9999));
 
         this.refresh = PanelUtils.createActionButton(HISTORY_ICON, HISTORY_ICON_HOVER, "Refresh", (btn, mod) -> {
             this.reloadList();
@@ -168,11 +133,10 @@ private void updateFilter(String search) {
         this.container.removeAll();
         String upperSearch = search.toUpperCase();
         this.alertHubItems.stream().filter(alertHubItem -> {
-//            Alert alert = WatchdogPlugin.getInstance().getAlertManager().getGson().fromJson(alertHubItem.getManifest().getJson(), ALERT_LIST_TYPE);
             AlertManifest manifest = alertHubItem.getAlertDisplayInfo().getManifest();
             return Text.matchesSearchTerms(SPLITTER.split(upperSearch), manifest.getKeywords());
         }).forEach(this.container::add);
-        this.revalidate();
+        this.container.revalidate();
         this.scrollPane.getVerticalScrollBar().setValue(0);
     }
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
index 5086a92..8885460 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
@@ -5,7 +5,6 @@
 import lombok.Setter;
 
 import java.net.URL;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
@@ -37,8 +36,8 @@ public List getKeywords() {
         Stream keywords = Stream.of(
             this.getDisplayName(),
             this.getInternalName(),
-            this.getAuthor()
-//            this.getCategory().getName()
+            this.getAuthor(),
+            this.getCategory().getName()
         ).filter(Objects::nonNull);
         if (this.getTags() != null) {
             System.out.println(this.getTags());
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index 8e67034..1112b58 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,5 +1,5 @@
-#Sun Sep 24 18:58:18 EDT 2023
-VERSION_BUILD=3040
+#Wed Sep 27 22:41:47 EDT 2023
+VERSION_BUILD=3223
 VERSION_PHASE=alpha
 VERSION_MAJOR=3
 VERSION_MINOR=0

From aad3c49038ac7a61bea23bd52eee0aed5112c0a2 Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Thu, 28 Sep 2023 01:12:10 -0400
Subject: [PATCH 13/27] Made alert download async

---
 .../runelite/watchdog/hub/AlertHubClient.java | 15 +++--
 .../runelite/watchdog/hub/AlertHubItem.java   |  7 ++-
 .../runelite/watchdog/hub/AlertHubPanel.java  | 57 ++++++++++---------
 .../runelite/watchdog/version.properties      |  4 +-
 4 files changed, 45 insertions(+), 38 deletions(-)

diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
index 0a42df3..61dbb5a 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
@@ -37,24 +37,23 @@ public AlertHubClient(OkHttpClient cachingClient) {
             .build();
     }
 
-    public List downloadManifest() throws IOException {
-        HttpUrl manifest = Objects.requireNonNull(HttpUrl.parse("https://raw.githubusercontent.com/melkypie/resource-packs"))
-            .newBuilder()
-            .addPathSegment("github-actions")
-            .addPathSegment("manifest.js")
-            .build();
+    public List downloadManifest(boolean forceDownload) throws IOException {
         HttpUrl allAlerts = GITHUB.newBuilder()
             .addPathSegment("archive")
             .addPathSegment("alert-hub.zip")
             .build();
 
         HashMap alerts = new HashMap<>();
-        try (Response res  = this.cachingClient.newCall(new Request.Builder().url(allAlerts).build()).execute()) {
+        Request.Builder reqBuilder = new Request.Builder().url(allAlerts);
+        if (forceDownload) {
+            reqBuilder.cacheControl(CacheControl.FORCE_NETWORK);
+        }
+        try (Response res  = this.cachingClient.newCall(reqBuilder.build()).execute()) {
             if (res.code() != 200) {
                 throw new IOException("Non-OK response code: " + res.code());
             }
 
-            BufferedInputStream is = new BufferedInputStream(res.body().byteStream());
+            BufferedInputStream is = new BufferedInputStream(Objects.requireNonNull(res.body()).byteStream());
             ZipInputStream zipInputStream = new ZipInputStream(is);
             ZipEntry entry;
             while ((entry = zipInputStream.getNextEntry()) != null) {
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
index f46944f..0d0776c 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
@@ -50,9 +50,12 @@ public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) {
 
         WrappingLabel alertDescLabel = new WrappingLabel(manifest.getDescription());
 
-        JLabel icon = new JLabel(new ImageIcon(this.alertDisplayInfo.getIcon()));
+//        JLabel icon = new JLabel();
 //        JLabel icon = new JLabel(new ImageIcon(ImageUtil.loadImageResource(WatchdogPlugin.class, "detail-test.png")));
-        icon.setHorizontalAlignment(JLabel.CENTER);
+//        icon.setHorizontalAlignment(JLabel.CENTER);
+//        if (this.alertDisplayInfo.getIcon() != null) {
+//            icon.setIcon(new ImageIcon(this.alertDisplayInfo.getIcon()));
+//        }
 
         JButton moreInfoButton = PanelUtils.createActionButton(WatchdogPanel.HELP_ICON, WatchdogPanel.HELP_ICON_HOVER, "More info", (btn, mod) -> {
             LinkBrowser.browse(manifest.getRepo().toString());
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
index ed8862a..1adb6e2 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
@@ -5,7 +5,6 @@
 
 import com.adamk33n3r.runelite.watchdog.ui.panels.ScrollablePanel;
 import com.google.common.base.Splitter;
-import net.runelite.client.ui.ColorScheme;
 import net.runelite.client.ui.DynamicGridLayout;
 import net.runelite.client.ui.MultiplexingPluginPanel;
 import net.runelite.client.ui.PluginPanel;
@@ -22,8 +21,8 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.HISTORY_ICON;
 import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.HISTORY_ICON_HOVER;
@@ -34,24 +33,22 @@
 public class AlertHubPanel extends PluginPanel {
     private final Provider muxer;
     private final AlertHubClient alertHubClient;
-//    private final ScrollablePanel filteredAlerts;
+    private final ScheduledExecutorService executor;
+
     private List alertHubItems = new ArrayList<>();
     private final IconTextField searchBar;
     private final JPanel container;
+    private final JLabel loading;
     private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings();
-    private final JButton refresh;
     private final JScrollPane scrollPane;
 
     @Inject
-    public AlertHubPanel(Provider muxer, AlertHubClient alertHubClient) {
+    public AlertHubPanel(Provider muxer, AlertHubClient alertHubClient, ScheduledExecutorService executor) {
         super(false);
         this.muxer = muxer;
         this.alertHubClient = alertHubClient;
+        this.executor = executor;
 
-//        this.setLayout(new BorderLayout());
-//
-//        JPanel topPanel = new JPanel(new BorderLayout());
-//        topPanel.setBorder(new EmptyBorder(0, 0, 5, 0));
         JButton backButton = PanelUtils.createActionButton(
             BACK_ICON,
             BACK_ICON_HOVER,
@@ -70,14 +67,18 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
 
         ScrollablePanel wrapper = new ScrollablePanel(new BorderLayout());
         wrapper.add(this.container, BorderLayout.NORTH);
+        this.loading = new JLabel("Loading...");
+        this.loading.setVisible(false);
+        this.loading.setHorizontalAlignment(JLabel.CENTER);
+        wrapper.add(this.loading, BorderLayout.CENTER);
         wrapper.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
         wrapper.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
         wrapper.setScrollableBlockIncrement(SwingConstants.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
         wrapper.setScrollableUnitIncrement(SwingConstants.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
         this.scrollPane = new JScrollPane(wrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
 
-        this.refresh = PanelUtils.createActionButton(HISTORY_ICON, HISTORY_ICON_HOVER, "Refresh", (btn, mod) -> {
-            this.reloadList();
+        JButton refresh = PanelUtils.createActionButton(HISTORY_ICON, HISTORY_ICON_HOVER, "Refresh", (btn, mod) -> {
+            this.reloadList(true);
         });
 
         layout.setVerticalGroup(layout.createSequentialGroup()
@@ -85,7 +86,7 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
             .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                 .addComponent(backButton, 24, 24, 24)
                 .addComponent(this.searchBar, 24, 24, 24)
-                .addComponent(this.refresh, 24, 24, 24))
+                .addComponent(refresh, 24, 24, 24))
             .addGap(10)
             .addComponent(scrollPane)
         );
@@ -97,23 +98,24 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
                 .addGap(3)
                 .addComponent(this.searchBar)
                 .addGap(3)
-                .addComponent(this.refresh)
+                .addComponent(refresh)
                 .addGap(7))
             .addComponent(scrollPane)
         );
 
-//        this.revalidate();
-
-        reloadList();
+        this.reloadList(false);
     }
 
-    public void reloadList() {
-//        this.container.removeAll();
+    public void reloadList(boolean forceDownload) {
+        if (this.loading.isVisible()) {
+            return;
+        }
 
-        SwingUtilities.invokeLater(() -> {
+        this.container.removeAll();
+        this.loading.setVisible(true);
+        this.executor.submit(() -> {
             try {
-                List alerts = this.alertHubClient.downloadManifest();
-    //            System.out.println(alertManifests.stream().map(AlertManifest::toString).collect(Collectors.joining(", ")));
+                List alerts = this.alertHubClient.downloadManifest(forceDownload);
                 this.reloadList(alerts);
             } catch (IOException e) {
                 e.printStackTrace();
@@ -122,11 +124,14 @@ public void reloadList() {
     }
 
     private void reloadList(List alerts) {
-        this.alertHubItems = alerts.stream()
-//            .flatMap(e -> Stream.of(e, e, e, e))
-            .map(AlertHubItem::new)
-            .collect(Collectors.toList());
-        this.updateFilter(this.searchBar.getText());
+        SwingUtilities.invokeLater(() -> {
+            this.loading.setVisible(false);
+            this.alertHubItems = alerts.stream()
+//                .flatMap(e -> Stream.of(e, e, e, e))
+                .map(AlertHubItem::new)
+                .collect(Collectors.toList());
+            this.updateFilter(this.searchBar.getText());
+        });
     }
 
     private void updateFilter(String search) {
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index 1112b58..bb97636 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,5 +1,5 @@
-#Wed Sep 27 22:41:47 EDT 2023
-VERSION_BUILD=3223
+#Thu Sep 28 01:07:14 EDT 2023
+VERSION_BUILD=3247
 VERSION_PHASE=alpha
 VERSION_MAJOR=3
 VERSION_MINOR=0

From dce0b7f7665f6b7964405f19e856b732cc74a29f Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Fri, 29 Sep 2023 03:43:17 -0400
Subject: [PATCH 14/27] Fix skill icon img loading when not in shadow jar

---
 .../adamk33n3r/runelite/watchdog/hub/AlertHubItem.java    | 8 ++------
 .../com/adamk33n3r/runelite/watchdog/version.properties   | 4 ++--
 2 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
index 0d0776c..68fddef 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
@@ -2,16 +2,12 @@
 
 import com.adamk33n3r.runelite.watchdog.WatchdogPanel;
 import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
-import com.adamk33n3r.runelite.watchdog.alerts.Alert;
-import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup;
 import com.adamk33n3r.runelite.watchdog.ui.WrappingLabel;
 import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
-import com.google.gson.Gson;
 import lombok.Getter;
-import net.runelite.client.plugins.config.ConfigPlugin;
+import net.runelite.client.RuneLite;
 import net.runelite.client.ui.ColorScheme;
 import net.runelite.client.ui.FontManager;
-import net.runelite.client.ui.PluginPanel;
 import net.runelite.client.util.ImageUtil;
 import net.runelite.client.util.LinkBrowser;
 
@@ -41,7 +37,7 @@ public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) {
         alertAuthor.setFont(FontManager.getRunescapeSmallFont());
         alertAuthor.setToolTipText(manifest.getAuthor());
 
-        alertName.setIcon(new ImageIcon(ImageUtil.loadImageResource(WatchdogPlugin.class, manifest.getCategory().getIcon())));
+        alertName.setIcon(new ImageIcon(ImageUtil.loadImageResource(RuneLite.class, manifest.getCategory().getIcon())));
 
         JLabel compatVersion = new JLabel(manifest.getCompatibleVersion());
         compatVersion.setHorizontalAlignment(JLabel.RIGHT);
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index bb97636..48e1edc 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,5 +1,5 @@
-#Thu Sep 28 01:07:14 EDT 2023
-VERSION_BUILD=3247
+#Fri Sep 29 03:24:19 EDT 2023
+VERSION_BUILD=3272
 VERSION_PHASE=alpha
 VERSION_MAJOR=3
 VERSION_MINOR=0

From 5a00c546b723454d64c6573c0e8f6ac60a74ed5c Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Sat, 7 Oct 2023 23:22:45 -0400
Subject: [PATCH 15/27] Release candidate for alert hub

---
 .../runelite/watchdog/WatchdogPanel.java      | 21 +++-----
 .../runelite/watchdog/hub/AlertHubClient.java | 14 +++--
 .../runelite/watchdog/hub/AlertHubItem.java   |  8 +++
 .../runelite/watchdog/hub/AlertHubPanel.java  | 18 +++----
 .../runelite/watchdog/hub/AlertManifest.java  |  9 ++--
 .../watchdog/ui/HorizontalRuleBorder.java     |  2 +-
 .../watchdog/ui/alerts/AlertGroupPanel.java   | 17 +++---
 .../watchdog/ui/panels/AlertListPanel.java    | 25 ++++++---
 .../watchdog/ui/panels/AlertPanel.java        | 52 +++++++++----------
 .../watchdog/ui/panels/HistoryPanel.java      |  7 ++-
 .../ui/panels/NotificationsPanel.java         | 23 ++++----
 .../watchdog/ui/panels/PanelUtils.java        |  1 +
 .../runelite/watchdog/version.properties      |  6 +--
 13 files changed, 118 insertions(+), 85 deletions(-)

diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
index 5afd343..45943bf 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
@@ -75,7 +75,7 @@ public class WatchdogPanel extends PluginPanel {
     @Inject
     private OkHttpClient httpClient;
 
-    private JScrollPane scroll;
+    private AlertListPanel alertListPanel;
 
     public static final ImageIcon ADD_ICON;
     public static final ImageIcon HELP_ICON;
@@ -123,11 +123,12 @@ public WatchdogPanel() {
     public void rebuild() {
         this.removeAll();
         this.setLayout(new BorderLayout(0, 3));
+        this.setBorder(new EmptyBorder(0, 5, 0, 5));
         this.setBackground(ColorScheme.DARK_GRAY_COLOR);
 
         JPanel topPanel = new JPanel(new BorderLayout());
-        JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-        titlePanel.setBorder(new EmptyBorder(5, 0, 0, 0));
+        topPanel.setBorder(new EmptyBorder(5, 0, 0, 0));
+        JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 3));
         JLabel title = new JLabel(WatchdogPlugin.getInstance().getName());
         title.setFont(title.getFont().deriveFont(Font.BOLD));
         title.setHorizontalAlignment(JLabel.LEFT);
@@ -142,7 +143,7 @@ public void rebuild() {
         titlePanel.add(version);
         topPanel.add(titlePanel);
 
-        JPanel actionButtons = new JPanel();
+        JPanel actionButtons = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
 
         JButton discordButton = PanelUtils.createActionButton(DISCORD_ICON, DISCORD_ICON_HOVER, "Discord", (btn, modifiers) -> {
             LinkBrowser.browse(DISCORD_URL);
@@ -180,14 +181,8 @@ public void rebuild() {
 
         this.add(topPanel, BorderLayout.NORTH);
 
-        AlertListPanel alertPanel = new AlertListPanel(this.alertManager.getAlerts(), this::rebuild);
-        ScrollablePanel scrollablePanel = new ScrollablePanel(new StretchedStackedLayout(3, 3));
-        scrollablePanel.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
-        scrollablePanel.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
-        scrollablePanel.setScrollableBlockIncrement(ScrollablePanel.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
-        scrollablePanel.add(alertPanel);
-        this.scroll = new JScrollPane(scrollablePanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
-        this.add(this.scroll, BorderLayout.CENTER);
+        this.alertListPanel = new AlertListPanel(this.alertManager.getAlerts(), this::rebuild);
+        this.add(this.alertListPanel, BorderLayout.CENTER);
 
         JPanel importExportGroup = new JPanel(new GridLayout(1, 2, 5, 0));
         JButton importButton = new JButton("Import", IMPORT_ICON);
@@ -258,7 +253,7 @@ public void onActivate() {
     }
 
     public void scrollToBottom() {
-        JScrollBar scrollBar = this.scroll.getVerticalScrollBar();
+        JScrollBar scrollBar = this.alertListPanel.getScrollPane().getVerticalScrollBar();
         scrollBar.setValue(scrollBar.getMaximum());
     }
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
index 61dbb5a..d0b77df 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
@@ -5,20 +5,17 @@
 import com.google.common.io.CharStreams;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-import net.runelite.client.ui.PluginPanel;
 import net.runelite.client.util.ImageUtil;
-import net.runelite.http.api.RuneLiteAPI;
 import okhttp3.*;
 
+import javax.annotation.Nonnull;
 import javax.imageio.ImageIO;
 import javax.inject.Inject;
-import javax.swing.*;
 import java.awt.image.BufferedImage;
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.net.URL;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -156,7 +153,7 @@ public BufferedImage downloadIcon(AlertManifest alertManifest) throws IOExceptio
             .addPathSegment("icon.png")
             .build();
 
-        try (Response res  = cachingClient.newCall(new Request.Builder().url(url).build()).execute()) {
+        try (Response res  = this.cachingClient.newCall(new Request.Builder().url(url).build()).execute()) {
             if (res.code() != 200) {
                 throw new IOException("Non-OK response code: " + res.code());
             }
@@ -168,13 +165,14 @@ public BufferedImage downloadIcon(AlertManifest alertManifest) throws IOExceptio
         }
     }
 
-    class CacheInterceptor implements Interceptor {
-        private int minutes;
+    static class CacheInterceptor implements Interceptor {
+        private final int minutes;
         public CacheInterceptor(int minutes) {
             this.minutes = minutes;
         }
 
         @Override
+        @Nonnull
         public Response intercept(Chain chain) throws IOException {
             Response response = chain.proceed(chain.request());
 
@@ -191,7 +189,7 @@ public Response intercept(Chain chain) throws IOException {
     }
 
     @Getter
-    class AlertDisplayInfo {
+    static class AlertDisplayInfo {
         private AlertManifest manifest;
         private BufferedImage icon;
     }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
index 68fddef..5a9f381 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
@@ -73,6 +73,12 @@ public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) {
             JOptionPane.showMessageDialog(this, "Added " + manifest.getDisplayName() + " to your alerts", "Successfully Added", JOptionPane.INFORMATION_MESSAGE);
         });
 
+        JLabel dependsOn = new JLabel();
+        dependsOn.setFont(FontManager.getRunescapeSmallFont());
+        if (manifest.getDependsOn() != null) {
+            dependsOn.setText("Depends On: " + String.join(", ", manifest.getDependsOn()));
+        }
+
         layout.setVerticalGroup(layout.createSequentialGroup()
             .addGap(5)
             .addGroup(layout.createSequentialGroup()
@@ -87,6 +93,7 @@ public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) {
                     .addComponent(compatVersion, LINE_HEIGHT, LINE_HEIGHT, LINE_HEIGHT)
                 )
                 .addComponent(alertDescLabel, 0, GroupLayout.DEFAULT_SIZE, 96)
+                .addComponent(dependsOn, 0, GroupLayout.DEFAULT_SIZE, LINE_HEIGHT)
                 .addGap(5)
             )
 //            .addComponent(icon, 0, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE)
@@ -111,6 +118,7 @@ public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) {
                     .addGroup(layout.createSequentialGroup()
                         .addComponent(alertDescLabel, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                     )
+                    .addComponent(dependsOn)
                 )
                 .addGap(5)
             )
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
index 1adb6e2..9fe50b6 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
@@ -23,6 +23,7 @@
 import java.util.List;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.HISTORY_ICON;
 import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.HISTORY_ICON_HOVER;
@@ -87,20 +88,20 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
                 .addComponent(backButton, 24, 24, 24)
                 .addComponent(this.searchBar, 24, 24, 24)
                 .addComponent(refresh, 24, 24, 24))
-            .addGap(10)
-            .addComponent(scrollPane)
+            .addGap(5)
+            .addComponent(this.scrollPane)
         );
 
         layout.setHorizontalGroup(layout.createParallelGroup()
             .addGroup(layout.createSequentialGroup()
-                .addGap(7)
                 .addComponent(backButton)
-                .addGap(3)
                 .addComponent(this.searchBar)
-                .addGap(3)
-                .addComponent(refresh)
-                .addGap(7))
-            .addComponent(scrollPane)
+                .addComponent(refresh))
+            .addGroup(layout.createSequentialGroup()
+                .addGap(5)
+                .addComponent(this.scrollPane)
+                .addGap(5)
+            )
         );
 
         this.reloadList(false);
@@ -127,7 +128,6 @@ private void reloadList(List alerts) {
         SwingUtilities.invokeLater(() -> {
             this.loading.setVisible(false);
             this.alertHubItems = alerts.stream()
-//                .flatMap(e -> Stream.of(e, e, e, e))
                 .map(AlertHubItem::new)
                 .collect(Collectors.toList());
             this.updateFilter(this.searchBar.getText());
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
index 8885460..bf9ae63 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
@@ -23,13 +23,14 @@ public class AlertManifest {
     private final String author;
     private final AlertHubCategory category;
     private final List tags;
+    private final List dependsOn;
     private final Alert alert;
     private final boolean hasIcon;
 
     @Override
     public String toString()
     {
-        return displayName;
+        return this.displayName;
     }
 
     public List getKeywords() {
@@ -40,8 +41,10 @@ public List getKeywords() {
             this.getCategory().getName()
         ).filter(Objects::nonNull);
         if (this.getTags() != null) {
-            System.out.println(this.getTags());
-            return Stream.concat(keywords, this.getTags().stream()).map(String::toUpperCase).collect(Collectors.toList());
+            keywords = Stream.concat(keywords, this.getTags().stream());
+        }
+        if (this.getDependsOn() != null) {
+            keywords = Stream.concat(keywords, this.getDependsOn().stream());
         }
         return keywords.map(String::toUpperCase).collect(Collectors.toList());
     }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/HorizontalRuleBorder.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/HorizontalRuleBorder.java
index f2c4b74..b3ea411 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/HorizontalRuleBorder.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/HorizontalRuleBorder.java
@@ -24,7 +24,7 @@ public HorizontalRuleBorder(int size) {
      */
     public Insets getBorderInsets(Component c, Insets insets) {
         Insets outerInsets = this.outsideBorder.getBorderInsets(c);
-        insets.set(this.size + outerInsets.top, 2, this.size + outerInsets.bottom, 2);
+        insets.set(this.size + outerInsets.top, 0, 0, 0);
         return insets;
     }
 
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java
index 101c4a2..dce9946 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java
@@ -18,7 +18,9 @@
 import javax.swing.JButton;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
-import java.awt.BorderLayout;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
 
 public class AlertGroupPanel extends AlertPanel {
     public AlertGroupPanel(WatchdogPanel watchdogPanel, AlertGroup alert) {
@@ -40,11 +42,14 @@ protected void build() {
         });
 
         buttonPanel.add(alertDropDownButton, BorderLayout.EAST);
-        JPanel subGroupPanel = new JPanel(new StretchedStackedLayout(3, 3));
-        subGroupPanel.setBorder(new HorizontalRuleBorder(10));
-        subGroupPanel.add(buttonPanel);
-        this.addSubPanel(subGroupPanel);
+        buttonPanel.setBorder(new EmptyBorder(0, 0, 8, 0));
+        JPanel subGroupPanel = new JPanel(new BorderLayout());
+        subGroupPanel.setBorder(new CompoundBorder(new EmptyBorder(0, 5, 0, 5), new HorizontalRuleBorder(10)));
+        subGroupPanel.add(buttonPanel, BorderLayout.NORTH);
+
+        AlertListPanel alertListPanel = new AlertListPanel(this.alert.getAlerts(), this::rebuild);
 
-        subGroupPanel.add(new AlertListPanel(this.alert.getAlerts(), this::rebuild));
+        subGroupPanel.add(alertListPanel);
+        this.addSubPanel(subGroupPanel);
     }
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java
index 75d45a1..7e84ee8 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java
@@ -7,7 +7,11 @@
 import com.adamk33n3r.runelite.watchdog.ui.AlertListItemNew;
 
 import com.adamk33n3r.runelite.watchdog.ui.SearchBar;
+import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout;
+
 import com.google.common.base.Splitter;
+import lombok.Getter;
+
 import net.runelite.client.ui.ColorScheme;
 import net.runelite.client.ui.components.DragAndDropReorderPane;
 import net.runelite.client.util.Text;
@@ -18,12 +22,15 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Stream;
 
 public class AlertListPanel extends JPanel {
     private String filterText = "";
     private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings();
     private final List alertListItems = new ArrayList<>();
     private final DragAndDropReorderPane dragAndDropReorderPane = new DragAndDropReorderPane();
+    @Getter
+    private final JScrollPane scrollPane;
 
     public AlertListPanel(List alerts, Runnable onChange) {
         AlertManager alertManager = WatchdogPlugin.getInstance().getAlertManager();
@@ -38,16 +45,22 @@ public AlertListPanel(List alerts, Runnable onChange) {
 
         SearchBar searchBar = new SearchBar(this::filter);
         Arrays.stream(TriggerType.values()).map(TriggerType::getName).forEach(searchBar.getSuggestionListModel()::addElement);
-        JPanel searchWrapper = new JPanel(new BorderLayout(0, 6));
+        JPanel searchWrapper = new JPanel(new BorderLayout());
         searchWrapper.add(searchBar);
-        searchWrapper.setBorder(new EmptyBorder(0, 5, 0, 5));
-        this.add(searchBar, BorderLayout.NORTH);
-        // TODO: move the scroll pane in here so that the search bar doesn't scroll
+        searchWrapper.setBorder(new EmptyBorder(0, 0, 2, 0));
+        this.add(searchWrapper, BorderLayout.NORTH);
+
+        ScrollablePanel scrollablePanel = new ScrollablePanel(new StretchedStackedLayout(3, 3));
+        scrollablePanel.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
+        scrollablePanel.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
+        scrollablePanel.setScrollableBlockIncrement(ScrollablePanel.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
+        scrollablePanel.add(this.dragAndDropReorderPane);
+        this.scrollPane = new JScrollPane(scrollablePanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
 
-        this.add(dragAndDropReorderPane, BorderLayout.CENTER);
+        this.add(this.scrollPane, BorderLayout.CENTER);
 
         alerts.stream()
-            .map(alert -> new AlertListItemNew(WatchdogPlugin.getInstance().getPanel(), alertManager, alert, dragAndDropReorderPane, onChange))
+            .map(alert -> new AlertListItemNew(WatchdogPlugin.getInstance().getPanel(), alertManager, alert, this.dragAndDropReorderPane, onChange))
             .forEach(alertListItem -> {
                 this.alertListItems.add(alertListItem);
                 this.dragAndDropReorderPane.add(alertListItem);
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java
index b4a86fa..b44ebdc 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java
@@ -24,6 +24,7 @@
 import org.apache.commons.text.WordUtils;
 
 import javax.swing.*;
+import javax.swing.border.CompoundBorder;
 import javax.swing.border.EmptyBorder;
 import java.awt.*;
 import java.awt.event.FocusEvent;
@@ -41,12 +42,11 @@
 
 @Slf4j
 public abstract class AlertPanel extends PluginPanel {
-    private final ScrollablePanel container;
+    private final JPanel controlContainer;
+    private final JPanel centerContainer;
     protected final WatchdogPanel watchdogPanel;
     protected final MultiplexingPluginPanel muxer;
     protected final T alert;
-    private final JPanel wrapper;
-    private final JScrollPane scroll;
 
     private final AlertManager alertManager;
 
@@ -85,17 +85,11 @@ public AlertPanel(WatchdogPanel watchdogPanel, T alert) {
 
         this.setLayout(new BorderLayout());
 
-        this.wrapper = new JPanel(new BorderLayout());
-        this.container = new ScrollablePanel(new StretchedStackedLayout(3, 3));
-        this.container.setBorder(new EmptyBorder(0, 10, 0, 10));
-        this.container.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
-        this.container.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
-        this.container.setScrollableBlockIncrement(ScrollablePanel.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
-        this.scroll = new JScrollPane(this.container, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
-        this.wrapper.add(scroll, BorderLayout.CENTER);
+        JPanel northPanel = new JPanel(new StretchedStackedLayout(3, 3));
+        this.add(northPanel, BorderLayout.NORTH);
 
         JPanel nameGroup = new JPanel(new BorderLayout());
-        nameGroup.setBorder(new EmptyBorder(10, 10, 10, 10));
+        nameGroup.setBorder(new EmptyBorder(10, 5, 10, 5));
 
         TriggerType triggerType = this.alert.getType();
         JLabel nameLabel = new JLabel(triggerType.getName());
@@ -174,14 +168,19 @@ public AlertPanel(WatchdogPanel watchdogPanel, T alert) {
         backButton.setBorder(new EmptyBorder(0, 0, 0, 5));
         nameGroup.add(backButton, BorderLayout.WEST);
 
-        this.wrapper.add(nameGroup, BorderLayout.NORTH);
+        northPanel.add(nameGroup, BorderLayout.NORTH);
 
-        this.add(wrapper, BorderLayout.CENTER);
+        this.controlContainer = new JPanel(new StretchedStackedLayout(3, 3));
+        this.controlContainer.setBorder(new EmptyBorder(0, 5, 0, 5));
+        northPanel.add(this.controlContainer, BorderLayout.NORTH);
+
+        this.centerContainer = new JPanel(new BorderLayout());
+        this.add(this.centerContainer, BorderLayout.CENTER);
     }
 
     public AlertPanel addLabel(String label) {
         JLabel labelComp = new JLabel(label);
-        this.container.add(labelComp);
+        this.controlContainer.add(labelComp);
         return this;
     }
 
@@ -190,7 +189,7 @@ public AlertPanel addRichTextPane(String text) {
         richTextPane.setContentType("text/html");
         richTextPane.setText(text);
         richTextPane.setForeground(Color.WHITE);
-        this.container.add(richTextPane);
+        this.controlContainer.add(richTextPane);
         return this;
     }
 
@@ -210,7 +209,7 @@ public void focusLost(FocusEvent e) {
                 alertManager.saveAlerts();
             }
         });
-        this.container.add(textField);
+        this.controlContainer.add(textField);
         return this;
     }
 
@@ -219,7 +218,7 @@ public AlertPanel addTextArea(String placeholder, String tooltip, String init
             saveAction.accept(val);
             this.alertManager.saveAlerts();
         });
-        this.container.add(textArea);
+        this.controlContainer.add(textArea);
         return this;
     }
 
@@ -232,7 +231,7 @@ public AlertPanel addSpinner(String name, String tooltip, int initialValue, C
             saveAction.accept(val);
             this.alertManager.saveAlerts();
         });
-        this.container.add(PanelUtils.createLabeledComponent(name, tooltip, spinner));
+        this.controlContainer.add(PanelUtils.createLabeledComponent(name, tooltip, spinner));
         return this;
     }
 
@@ -252,7 +251,7 @@ public > AlertPanel addSelect(String name, String tooltip,
             saveAction.accept(select.getItemAt(select.getSelectedIndex()));
             this.alertManager.saveAlerts();
         });
-        this.container.add(PanelUtils.createLabeledComponent(name, tooltip, select));
+        this.controlContainer.add(PanelUtils.createLabeledComponent(name, tooltip, select));
         return this;
     }
 
@@ -261,7 +260,7 @@ public AlertPanel addCheckbox(String name, String tooltip, boolean initialVal
             saveAction.accept(val);
             this.alertManager.saveAlerts();
         });
-        this.container.add(checkbox);
+        this.controlContainer.add(checkbox);
         return this;
     }
 
@@ -273,7 +272,7 @@ public AlertPanel addInputGroup(JComponent mainComponent, List pr
         InputGroup textFieldGroup = new InputGroup(mainComponent)
             .addPrefixes(prefixes)
             .addSuffixes(suffixes);
-        this.container.add(textFieldGroup);
+        this.controlContainer.add(textFieldGroup);
         return this;
     }
 
@@ -324,21 +323,22 @@ public AlertPanel addRegexMatcher(RegexMatcher regexMatcher, String placehold
     public AlertPanel addNotifications() {
         NotificationsPanel notificationPanel = new NotificationsPanel(this.alert);
         WatchdogPlugin.getInstance().getInjector().injectMembers(notificationPanel);
-        notificationPanel.setBorder(new HorizontalRuleBorder(10));
-        this.container.add(notificationPanel);
+        notificationPanel.setBorder(new CompoundBorder(new EmptyBorder(0, 5, 0, 5), new HorizontalRuleBorder(10)));
+        this.centerContainer.add(notificationPanel);
 
         return this;
     }
 
     public AlertPanel addSubPanel(JPanel sub) {
-        this.container.add(sub);
+        this.centerContainer.add(sub);
 
         return this;
     }
 
     protected abstract void build();
     protected void rebuild() {
-        this.container.removeAll();
+        this.controlContainer.removeAll();
+        this.centerContainer.removeAll();
         this.build();
         this.revalidate();
         this.repaint();
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
index 35cda93..b2a58dd 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
@@ -38,6 +38,7 @@ public class HistoryPanel extends PluginPanel {
     private final Provider muxer;
     private final ScrollablePanel historyItems;
     private final List previousAlerts = new ArrayList<>();
+    private final JLabel noHistory;
 
     private static final int MAX_HISTORY_ITEMS = 100;
     private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings();
@@ -49,7 +50,7 @@ public HistoryPanel(Provider muxer) {
 
         this.setLayout(new BorderLayout());
 
-        JPanel topPanel = new JPanel(new BorderLayout());
+        JPanel topPanel = new JPanel(new BorderLayout(0, 5));
         topPanel.setBorder(new EmptyBorder(0, 0, 5, 0));
         JButton backButton = PanelUtils.createActionButton(
             BACK_ICON,
@@ -61,6 +62,9 @@ public HistoryPanel(Provider muxer) {
         backButton.setBorder(new EmptyBorder(0, 0, 0, 5));
         topPanel.add(backButton, BorderLayout.WEST);
         topPanel.add(new SearchBar(this::updateFilter));
+        this.noHistory = new JLabel("No history items");
+        this.noHistory.setHorizontalAlignment(SwingConstants.CENTER);
+        topPanel.add(this.noHistory, BorderLayout.SOUTH);
         this.add(topPanel, BorderLayout.NORTH);
 
         this.historyItems = new ScrollablePanel(new StretchedStackedLayout(3, 3));
@@ -74,6 +78,7 @@ public HistoryPanel(Provider muxer) {
     }
 
     public void addEntry(Alert alert, String[] triggerValues) {
+        this.noHistory.setVisible(false);
         HistoryEntryPanel historyEntryPanel = new HistoryEntryPanel(alert, triggerValues);
         this.previousAlerts.add(0, historyEntryPanel);
         this.historyItems.add(historyEntryPanel, 0);
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java
index 67f684c..1f88917 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java
@@ -14,6 +14,7 @@
 import com.adamk33n3r.runelite.watchdog.notifications.SoundEffect;
 import com.adamk33n3r.runelite.watchdog.notifications.TextToSpeech;
 import com.adamk33n3r.runelite.watchdog.notifications.TrayNotification;
+import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout;
 import com.adamk33n3r.runelite.watchdog.ui.dropdownbutton.DropDownButtonFactory;
 import com.adamk33n3r.runelite.watchdog.ui.notifications.panels.MessageNotificationPanel;
 import com.adamk33n3r.runelite.watchdog.ui.notifications.panels.NotificationPanel;
@@ -33,12 +34,8 @@
 import lombok.extern.slf4j.Slf4j;
 
 import javax.inject.Inject;
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JMenuItem;
-import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
-import java.awt.BorderLayout;
+import javax.swing.*;
+import java.awt.*;
 import java.awt.event.ActionListener;
 import java.util.Arrays;
 
@@ -59,7 +56,7 @@ public class NotificationsPanel extends JPanel {
 
     public NotificationsPanel(Alert alert) {
         this.alert = alert;
-        this.setLayout(new DynamicGridLayout(0, 1, 3, 3));
+        this.setLayout(new BorderLayout(0, 5));
         this.notificationContainer = new DragAndDropReorderPane();
         this.notificationContainer.addDragListener((c) -> {
             int pos = this.notificationContainer.getPosition(c);
@@ -86,13 +83,21 @@ public NotificationsPanel(Alert alert) {
             popupMenu.add(c);
         });
         JButton addDropDownButton = DropDownButtonFactory.createDropDownButton(ADD_ICON, popupMenu);
+        addDropDownButton.setPreferredSize(new Dimension(40, addDropDownButton.getPreferredSize().height));
         addDropDownButton.setToolTipText("Create New Notification");
         JPanel buttonPanel = new JPanel(new BorderLayout());
         buttonPanel.add(new JLabel("Notifications"), BorderLayout.WEST);
         buttonPanel.add(addDropDownButton, BorderLayout.EAST);
 
-        this.add(buttonPanel);
-        this.add(this.notificationContainer);
+        this.add(buttonPanel, BorderLayout.NORTH);
+
+        ScrollablePanel scrollablePanel = new ScrollablePanel(new StretchedStackedLayout(3, 3));
+        scrollablePanel.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
+        scrollablePanel.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
+        scrollablePanel.setScrollableBlockIncrement(ScrollablePanel.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
+        scrollablePanel.add(this.notificationContainer);
+        JScrollPane scrollPane = new JScrollPane(scrollablePanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        this.add(scrollPane, BorderLayout.CENTER);
     }
 
     // After inject, build
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java
index 72c9728..82bd2a6 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java
@@ -316,6 +316,7 @@ public static JButton createAlertDropDownButton(Consumer onCreate) {
                 popupMenu.add(c);
             });
         JButton addDropDownButton = DropDownButtonFactory.createDropDownButton(ADD_ICON, popupMenu);
+        addDropDownButton.setPreferredSize(new Dimension(40, addDropDownButton.getPreferredSize().height));
         addDropDownButton.setToolTipText("Create New Alert");
         return addDropDownButton;
     }
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index 48e1edc..cbe6554 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,6 +1,6 @@
-#Fri Sep 29 03:24:19 EDT 2023
-VERSION_BUILD=3272
-VERSION_PHASE=alpha
+#Sat Oct 07 23:20:07 EDT 2023
+VERSION_BUILD=3822
+VERSION_PHASE=rc1
 VERSION_MAJOR=3
 VERSION_MINOR=0
 VERSION_PATCH=0

From f07115604252f1faa7c4dd4712660761ef95414e Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Tue, 10 Oct 2023 02:44:48 -0400
Subject: [PATCH 16/27] Organized imports

---
 .../runelite/watchdog/AlertManager.java       |  4 +++-
 .../watchdog/CenteredImageComponent.java      |  3 ++-
 .../runelite/watchdog/FlashOverlay.java       |  4 +---
 .../watchdog/NotificationOverlay.java         |  6 +-----
 .../watchdog/RuntimeTypeAdapterFactory.java   |  1 +
 .../runelite/watchdog/WatchdogPanel.java      | 16 ++++++---------
 .../runelite/watchdog/WatchdogProperties.java |  1 -
 .../watchdog/WrappedTitleComponent.java       |  8 +-------
 .../watchdog/elevenlabs/ElevenLabs.java       |  1 -
 .../runelite/watchdog/hub/AlertHubClient.java | 20 ++++++++++++-------
 .../runelite/watchdog/hub/AlertHubItem.java   |  4 +++-
 .../runelite/watchdog/hub/AlertHubPanel.java  |  9 ++++-----
 .../runelite/watchdog/hub/AlertManifest.java  |  1 +
 .../watchdog/notifications/Sound.java         | 13 +-----------
 .../watchdog/notifications/tts/TTSSource.java |  1 +
 .../runelite/watchdog/ui/AlertListItem.java   |  9 ++-------
 .../watchdog/ui/AlertListItemNew.java         |  2 --
 .../runelite/watchdog/ui/FlatTextArea.java    | 12 ++---------
 .../watchdog/ui/HorizontalRuleBorder.java     |  4 +---
 .../watchdog/ui/ImportExportDialog.java       | 17 +++-------------
 .../watchdog/ui/PlaceholderTextArea.java      |  7 ++-----
 .../watchdog/ui/PlaceholderTextField.java     |  7 ++-----
 .../watchdog/ui/StretchedStackedLayout.java   |  6 +-----
 .../runelite/watchdog/ui/ToggleButton.java    |  5 ++---
 .../watchdog/ui/alerts/AlertGroupPanel.java   | 10 +---------
 .../ui/dropdownbutton/DropDownButton.java     |  8 ++------
 .../dropdownbutton/DropDownButtonFactory.java |  4 +---
 .../ui/dropdownbutton/IconWithArrow.java      |  8 ++------
 .../ui/dropdownbutton/VectorIcon.java         |  8 ++------
 .../ui/notifications/VoiceChooser.java        |  3 +--
 .../panels/NotificationPanel.java             |  9 ++-------
 .../panels/ScreenFlashNotificationPanel.java  |  4 +---
 .../panels/SoundNotificationPanel.java        |  3 +--
 .../panels/TextToSpeechNotificationPanel.java |  1 -
 .../watchdog/ui/panels/AlertListPanel.java    |  8 +++-----
 .../watchdog/ui/panels/AlertPanel.java        |  2 +-
 .../watchdog/ui/panels/HistoryEntryPanel.java |  4 +---
 .../watchdog/ui/panels/HistoryPanel.java      | 12 +++--------
 .../watchdog/ui/panels/InputGroup.java        |  6 ++----
 .../ui/panels/NotificationsPanel.java         |  1 -
 .../watchdog/ui/panels/PanelUtils.java        |  2 +-
 .../watchdog/ui/panels/ScrollablePanel.java   | 10 ++--------
 .../runelite/watchdog/version.properties      |  4 ++--
 43 files changed, 81 insertions(+), 187 deletions(-)

diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
index 8629d4a..261640b 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
@@ -41,7 +41,9 @@
 import javax.inject.Singleton;
 import javax.swing.SwingUtilities;
 import java.lang.reflect.Type;
-import java.util.*;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/CenteredImageComponent.java b/src/main/java/com/adamk33n3r/runelite/watchdog/CenteredImageComponent.java
index 45fd7c5..7553eff 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/CenteredImageComponent.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/CenteredImageComponent.java
@@ -1,9 +1,10 @@
 package com.adamk33n3r.runelite.watchdog;
 
+import net.runelite.client.ui.overlay.components.LayoutableRenderableEntity;
+
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import lombok.Setter;
-import net.runelite.client.ui.overlay.components.LayoutableRenderableEntity;
 
 import java.awt.*;
 import java.awt.image.BufferedImage;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/FlashOverlay.java b/src/main/java/com/adamk33n3r/runelite/watchdog/FlashOverlay.java
index a57077c..331ba12 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/FlashOverlay.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/FlashOverlay.java
@@ -13,9 +13,7 @@
 import lombok.extern.slf4j.Slf4j;
 
 import javax.inject.Inject;
-import java.awt.Dimension;
-import java.awt.Graphics2D;
-import java.awt.Rectangle;
+import java.awt.*;
 import java.time.Instant;
 
 @Slf4j
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/NotificationOverlay.java b/src/main/java/com/adamk33n3r/runelite/watchdog/NotificationOverlay.java
index c0bb62c..c7c55e9 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/NotificationOverlay.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/NotificationOverlay.java
@@ -14,11 +14,7 @@
 
 import javax.imageio.ImageIO;
 import javax.inject.Inject;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Graphics2D;
-import java.awt.Point;
-import java.awt.Rectangle;
+import java.awt.*;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/RuntimeTypeAdapterFactory.java b/src/main/java/com/adamk33n3r/runelite/watchdog/RuntimeTypeAdapterFactory.java
index 64ab7a4..69cdcbc 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/RuntimeTypeAdapterFactory.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/RuntimeTypeAdapterFactory.java
@@ -27,6 +27,7 @@
 import com.google.gson.reflect.TypeToken;
 import com.google.gson.stream.JsonReader;
 import com.google.gson.stream.JsonWriter;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
index 45943bf..eea49a2 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
@@ -1,10 +1,12 @@
 package com.adamk33n3r.runelite.watchdog;
 
 import com.adamk33n3r.runelite.watchdog.alerts.*;
-import com.adamk33n3r.runelite.watchdog.ui.*;
-import com.adamk33n3r.runelite.watchdog.ui.alerts.*;
-import com.adamk33n3r.runelite.watchdog.ui.panels.*;
 import com.adamk33n3r.runelite.watchdog.hub.AlertHubPanel;
+import com.adamk33n3r.runelite.watchdog.ui.ImportExportDialog;
+import com.adamk33n3r.runelite.watchdog.ui.alerts.*;
+import com.adamk33n3r.runelite.watchdog.ui.panels.AlertListPanel;
+import com.adamk33n3r.runelite.watchdog.ui.panels.HistoryPanel;
+import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
 
 import net.runelite.client.plugins.config.ConfigPlugin;
 import net.runelite.client.plugins.info.InfoPanel;
@@ -17,7 +19,6 @@
 
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-
 import okhttp3.OkHttpClient;
 
 import javax.inject.Inject;
@@ -25,12 +26,7 @@
 import javax.inject.Provider;
 import javax.swing.*;
 import javax.swing.border.EmptyBorder;
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.Font;
-import java.awt.GridLayout;
+import java.awt.*;
 import java.awt.image.BufferedImage;
 
 @Slf4j
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogProperties.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogProperties.java
index d3fb8ed..6a9faed 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogProperties.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogProperties.java
@@ -4,7 +4,6 @@
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.Objects;
 import java.util.Properties;
 
 public class WatchdogProperties {
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WrappedTitleComponent.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WrappedTitleComponent.java
index 4589915..f920837 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/WrappedTitleComponent.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WrappedTitleComponent.java
@@ -10,13 +10,7 @@
 import lombok.Getter;
 import lombok.Setter;
 
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Graphics2D;
-import java.awt.Point;
-import java.awt.Rectangle;
+import java.awt.*;
 
 /**
  * Adapted from LineComponent and TitleComponent
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/elevenlabs/ElevenLabs.java b/src/main/java/com/adamk33n3r/runelite/watchdog/elevenlabs/ElevenLabs.java
index f66ea44..f30b4a0 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/elevenlabs/ElevenLabs.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/elevenlabs/ElevenLabs.java
@@ -12,7 +12,6 @@
 import java.io.InputStream;
 import java.util.Objects;
 import java.util.function.Consumer;
-import java.util.stream.Collectors;
 
 import static net.runelite.http.api.RuneLiteAPI.JSON;
 
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
index d0b77df..6dd0dfd 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java
@@ -1,12 +1,19 @@
 package com.adamk33n3r.runelite.watchdog.hub;
 
 import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
+
+import net.runelite.client.util.ImageUtil;
+
 import com.google.common.base.Charsets;
 import com.google.common.io.CharStreams;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-import net.runelite.client.util.ImageUtil;
-import okhttp3.*;
+import okhttp3.CacheControl;
+import okhttp3.HttpUrl;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
 
 import javax.annotation.Nonnull;
 import javax.imageio.ImageIO;
@@ -16,7 +23,10 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.util.*;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
@@ -79,12 +89,8 @@ public List downloadManifest(boolean forceDownload) throws IOE
                         alertDisplayInfo.icon = ImageUtil.resizeImage(icon, 242, 182, true);
                     }
                 }
-                System.out.println(filePath);
-//                if (!entry.isDirectory()) {
             }
-//            String data = Objects.requireNonNull(res.body()).string();
 
-//            return RuneLiteAPI.GSON.fromJson(data, new TypeToken>() {}.getType());
             return alerts.values().stream().sorted(Comparator.comparing(alert -> alert.manifest.getDisplayName()))
                 .collect(Collectors.toList());
         }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
index 5a9f381..257e95e 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java
@@ -4,13 +4,15 @@
 import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
 import com.adamk33n3r.runelite.watchdog.ui.WrappingLabel;
 import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
-import lombok.Getter;
+
 import net.runelite.client.RuneLite;
 import net.runelite.client.ui.ColorScheme;
 import net.runelite.client.ui.FontManager;
 import net.runelite.client.util.ImageUtil;
 import net.runelite.client.util.LinkBrowser;
 
+import lombok.Getter;
+
 import javax.swing.*;
 import javax.swing.border.LineBorder;
 import java.awt.*;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
index 9fe50b6..4c805c7 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
@@ -2,17 +2,17 @@
 
 import com.adamk33n3r.runelite.watchdog.ui.SearchBar;
 import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
-
 import com.adamk33n3r.runelite.watchdog.ui.panels.ScrollablePanel;
-import com.google.common.base.Splitter;
+
 import net.runelite.client.ui.DynamicGridLayout;
 import net.runelite.client.ui.MultiplexingPluginPanel;
 import net.runelite.client.ui.PluginPanel;
-
-import lombok.extern.slf4j.Slf4j;
 import net.runelite.client.ui.components.IconTextField;
 import net.runelite.client.util.Text;
 
+import com.google.common.base.Splitter;
+import lombok.extern.slf4j.Slf4j;
+
 import javax.inject.Inject;
 import javax.inject.Provider;
 import javax.swing.*;
@@ -23,7 +23,6 @@
 import java.util.List;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.HISTORY_ICON;
 import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.HISTORY_ICON_HOVER;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
index bf9ae63..aeef691 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
@@ -1,6 +1,7 @@
 package com.adamk33n3r.runelite.watchdog.hub;
 
 import com.adamk33n3r.runelite.watchdog.alerts.Alert;
+
 import lombok.Data;
 import lombok.Setter;
 
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java
index 0c741de..097874a 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java
@@ -2,25 +2,14 @@
 
 import com.adamk33n3r.runelite.watchdog.Util;
 import com.adamk33n3r.runelite.watchdog.WatchdogConfig;
-
 import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
+
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 
 import javax.inject.Inject;
-import javax.sound.sampled.AudioInputStream;
-import javax.sound.sampled.AudioSystem;
-import javax.sound.sampled.Clip;
-import javax.sound.sampled.FloatControl;
-import javax.sound.sampled.LineEvent;
-import java.awt.Toolkit;
-import java.io.BufferedInputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.util.concurrent.CompletableFuture;
-import jaco.mp3.player.MP3Player;
 
 @Slf4j
 @Getter
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/tts/TTSSource.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/tts/TTSSource.java
index bde98a6..76d8970 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/tts/TTSSource.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/tts/TTSSource.java
@@ -1,6 +1,7 @@
 package com.adamk33n3r.runelite.watchdog.notifications.tts;
 
 import com.adamk33n3r.runelite.watchdog.Displayable;
+
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java
index 44ff067..823f86e 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java
@@ -14,14 +14,9 @@
 
 import lombok.Getter;
 
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
+import javax.swing.*;
 import javax.swing.border.EmptyBorder;
-import java.awt.BorderLayout;
-import java.awt.Dimension;
+import java.awt.*;
 import java.util.List;
 
 @Deprecated
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java
index e4e1a0c..9b4bc76 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItemNew.java
@@ -9,8 +9,6 @@
 
 import net.runelite.client.plugins.config.ConfigPlugin;
 import net.runelite.client.ui.ColorScheme;
-import net.runelite.client.ui.DynamicGridLayout;
-import net.runelite.client.ui.PluginPanel;
 import net.runelite.client.ui.components.MouseDragEventForwarder;
 import net.runelite.client.util.ImageUtil;
 
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/FlatTextArea.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/FlatTextArea.java
index edc9718..a2f5220 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/FlatTextArea.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/FlatTextArea.java
@@ -4,17 +4,9 @@
 
 import lombok.Getter;
 
-import javax.swing.AbstractAction;
-import javax.swing.ActionMap;
-import javax.swing.InputMap;
-import javax.swing.JComponent;
-import javax.swing.JPanel;
-import javax.swing.JTextArea;
-import javax.swing.KeyStroke;
+import javax.swing.*;
 import javax.swing.text.Document;
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Insets;
+import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
 import java.awt.event.KeyListener;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/HorizontalRuleBorder.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/HorizontalRuleBorder.java
index b3ea411..9fc04b4 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/HorizontalRuleBorder.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/HorizontalRuleBorder.java
@@ -3,9 +3,7 @@
 import javax.swing.border.Border;
 import javax.swing.border.EmptyBorder;
 import javax.swing.border.EtchedBorder;
-import java.awt.Component;
-import java.awt.Graphics;
-import java.awt.Insets;
+import java.awt.*;
 
 public class HorizontalRuleBorder extends EtchedBorder {
     private final int size;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java
index fab8465..3bef8b5 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java
@@ -1,26 +1,15 @@
 package com.adamk33n3r.runelite.watchdog.ui;
 
-import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
-
 import lombok.extern.slf4j.Slf4j;
 
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
+import javax.swing.*;
 import javax.swing.border.EmptyBorder;
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.GridLayout;
-import java.awt.Toolkit;
+import java.awt.*;
 import java.awt.datatransfer.Clipboard;
 import java.awt.datatransfer.StringSelection;
 import java.awt.event.ActionListener;
-import java.util.function.Function;
 import java.util.function.BiFunction;
+import java.util.function.Function;
 
 @Slf4j
 public class ImportExportDialog extends JDialog {
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/PlaceholderTextArea.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/PlaceholderTextArea.java
index b02c016..9ede431 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/PlaceholderTextArea.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/PlaceholderTextArea.java
@@ -3,12 +3,9 @@
 import lombok.Getter;
 import lombok.Setter;
 
-import javax.swing.JTextArea;
+import javax.swing.*;
 import javax.swing.text.Document;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.RenderingHints;
-import java.awt.Toolkit;
+import java.awt.*;
 import java.util.Map;
 
 public class PlaceholderTextArea extends JTextArea {
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/PlaceholderTextField.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/PlaceholderTextField.java
index f232b0e..7de6c2a 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/PlaceholderTextField.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/PlaceholderTextField.java
@@ -3,12 +3,9 @@
 import lombok.Getter;
 import lombok.Setter;
 
-import javax.swing.JTextField;
+import javax.swing.*;
 import javax.swing.text.Document;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.RenderingHints;
-import java.awt.Toolkit;
+import java.awt.*;
 import java.util.Map;
 
 public class PlaceholderTextField extends JTextField {
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/StretchedStackedLayout.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/StretchedStackedLayout.java
index a82072d..7cf478b 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/StretchedStackedLayout.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/StretchedStackedLayout.java
@@ -1,10 +1,6 @@
 package com.adamk33n3r.runelite.watchdog.ui;
 
-import java.awt.Component;
-import java.awt.Container;
-import java.awt.Dimension;
-import java.awt.GridLayout;
-import java.awt.Insets;
+import java.awt.*;
 import java.util.function.Function;
 
 /**
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ToggleButton.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ToggleButton.java
index ee74f39..0ef68af 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ToggleButton.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ToggleButton.java
@@ -8,9 +8,8 @@
 import net.runelite.client.util.ImageUtil;
 import net.runelite.client.util.SwingUtil;
 
-import javax.swing.ImageIcon;
-import javax.swing.JToggleButton;
-import java.awt.Dimension;
+import javax.swing.*;
+import java.awt.*;
 import java.awt.image.BufferedImage;
 
 public class ToggleButton extends JToggleButton {
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java
index dce9946..ee6ab7e 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java
@@ -4,20 +4,12 @@
 import com.adamk33n3r.runelite.watchdog.WatchdogPanel;
 import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
 import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup;
-import com.adamk33n3r.runelite.watchdog.ui.AlertListItem;
-import com.adamk33n3r.runelite.watchdog.ui.AlertListItemNew;
 import com.adamk33n3r.runelite.watchdog.ui.HorizontalRuleBorder;
-import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout;
 import com.adamk33n3r.runelite.watchdog.ui.panels.AlertListPanel;
 import com.adamk33n3r.runelite.watchdog.ui.panels.AlertPanel;
 import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
 
-import net.runelite.client.ui.DynamicGridLayout;
-import net.runelite.client.ui.components.DragAndDropReorderPane;
-
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
+import javax.swing.*;
 import javax.swing.border.CompoundBorder;
 import javax.swing.border.EmptyBorder;
 import java.awt.*;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/DropDownButton.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/DropDownButton.java
index 46b8598..77ad3d5 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/DropDownButton.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/DropDownButton.java
@@ -19,14 +19,10 @@
 
 package com.adamk33n3r.runelite.watchdog.ui.dropdownbutton;
 
-import javax.swing.DefaultButtonModel;
-import javax.swing.Icon;
-import javax.swing.JButton;
-import javax.swing.JPopupMenu;
-import javax.swing.SwingUtilities;
+import javax.swing.*;
 import javax.swing.event.PopupMenuEvent;
 import javax.swing.event.PopupMenuListener;
-import java.awt.Point;
+import java.awt.*;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseMotionAdapter;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/DropDownButtonFactory.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/DropDownButtonFactory.java
index ef07bc0..160a065 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/DropDownButtonFactory.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/DropDownButtonFactory.java
@@ -19,9 +19,7 @@
 
 package com.adamk33n3r.runelite.watchdog.ui.dropdownbutton;
 
-import javax.swing.Icon;
-import javax.swing.JButton;
-import javax.swing.JPopupMenu;
+import javax.swing.*;
 
 /**
  * Factory creating buttons with a small arrow icon that shows a popup menu when clicked.
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/IconWithArrow.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/IconWithArrow.java
index 624827c..a66acb7 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/IconWithArrow.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/IconWithArrow.java
@@ -19,12 +19,8 @@
 
 package com.adamk33n3r.runelite.watchdog.ui.dropdownbutton;
 
-import javax.swing.Icon;
-import javax.swing.UIManager;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
+import javax.swing.*;
+import java.awt.*;
 import java.awt.geom.Path2D;
 
 /**
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/VectorIcon.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/VectorIcon.java
index bb73148..74bacca 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/VectorIcon.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/dropdownbutton/VectorIcon.java
@@ -18,12 +18,8 @@
  */
 package com.adamk33n3r.runelite.watchdog.ui.dropdownbutton;
 
-import javax.swing.Icon;
-import java.awt.Component;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.RenderingHints;
-import java.awt.Toolkit;
+import javax.swing.*;
+import java.awt.*;
 import java.awt.geom.AffineTransform;
 import java.io.Serializable;
 import java.util.LinkedHashMap;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/VoiceChooser.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/VoiceChooser.java
index 1f98ac4..ce75f8c 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/VoiceChooser.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/VoiceChooser.java
@@ -3,8 +3,7 @@
 import com.adamk33n3r.runelite.watchdog.notifications.TextToSpeech;
 import com.adamk33n3r.runelite.watchdog.notifications.tts.Voice;
 
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.JComboBox;
+import javax.swing.*;
 
 public class VoiceChooser extends JComboBox {
     public VoiceChooser(TextToSpeech notification) {
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java
index 49b004a..64c1315 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java
@@ -13,16 +13,11 @@
 
 import lombok.Getter;
 
-import javax.swing.BorderFactory;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
+import javax.swing.*;
 import javax.swing.border.Border;
 import javax.swing.border.CompoundBorder;
 import javax.swing.border.EmptyBorder;
-import java.awt.BorderLayout;
-import java.awt.FlowLayout;
+import java.awt.*;
 import java.awt.image.BufferedImage;
 
 public abstract class NotificationPanel extends JPanel {
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java
index 0835c2d..a79ac24 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java
@@ -8,9 +8,7 @@
 import net.runelite.client.ui.components.ColorJButton;
 import net.runelite.client.ui.components.colorpicker.ColorPickerManager;
 
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.JComboBox;
-import javax.swing.JSpinner;
+import javax.swing.*;
 
 public class ScreenFlashNotificationPanel extends NotificationPanel {
     public ScreenFlashNotificationPanel(ScreenFlash screenFlash, NotificationsPanel parentPanel, ColorPickerManager colorPickerManager, Runnable onChangeListener, PanelUtils.OnRemove onRemove) {
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/SoundNotificationPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/SoundNotificationPanel.java
index 0b529bc..fb93993 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/SoundNotificationPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/SoundNotificationPanel.java
@@ -9,8 +9,7 @@
 
 import javax.sound.sampled.AudioFileFormat;
 import javax.sound.sampled.AudioSystem;
-import javax.swing.JFileChooser;
-import javax.swing.JLabel;
+import javax.swing.*;
 import java.util.Arrays;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java
index 89d4445..c8e8095 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java
@@ -2,7 +2,6 @@
 
 import com.adamk33n3r.runelite.watchdog.LengthLimitFilter;
 import com.adamk33n3r.runelite.watchdog.SimpleDocumentListener;
-import com.adamk33n3r.runelite.watchdog.WatchdogPanel;
 import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
 import com.adamk33n3r.runelite.watchdog.elevenlabs.ElevenLabs;
 import com.adamk33n3r.runelite.watchdog.elevenlabs.Voice;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java
index 7e84ee8..99a411f 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java
@@ -5,24 +5,22 @@
 import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
 import com.adamk33n3r.runelite.watchdog.alerts.Alert;
 import com.adamk33n3r.runelite.watchdog.ui.AlertListItemNew;
-
 import com.adamk33n3r.runelite.watchdog.ui.SearchBar;
 import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout;
 
-import com.google.common.base.Splitter;
-import lombok.Getter;
-
 import net.runelite.client.ui.ColorScheme;
 import net.runelite.client.ui.components.DragAndDropReorderPane;
 import net.runelite.client.util.Text;
 
+import com.google.common.base.Splitter;
+import lombok.Getter;
+
 import javax.swing.*;
 import javax.swing.border.EmptyBorder;
 import java.awt.*;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.stream.Stream;
 
 public class AlertListPanel extends JPanel {
     private String filterText = "";
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java
index b44ebdc..9539735 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java
@@ -35,8 +35,8 @@
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
-import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.IMPORT_ICON;
 import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.EXPORT_ICON;
+import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.IMPORT_ICON;
 import static com.adamk33n3r.runelite.watchdog.ui.notifications.panels.NotificationPanel.TEST_ICON;
 import static com.adamk33n3r.runelite.watchdog.ui.notifications.panels.NotificationPanel.TEST_ICON_HOVER;
 
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryEntryPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryEntryPanel.java
index 0a19198..6f25a44 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryEntryPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryEntryPanel.java
@@ -8,9 +8,7 @@
 
 import lombok.Getter;
 
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JTextArea;
+import javax.swing.*;
 import javax.swing.border.EtchedBorder;
 import java.time.Instant;
 import java.time.ZoneId;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
index b2a58dd..80654a6 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
@@ -2,30 +2,24 @@
 
 import com.adamk33n3r.runelite.watchdog.alerts.Alert;
 import com.adamk33n3r.runelite.watchdog.notifications.IMessageNotification;
-import com.adamk33n3r.runelite.watchdog.ui.PlaceholderTextField;
 import com.adamk33n3r.runelite.watchdog.ui.SearchBar;
 import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout;
 
-import com.google.common.base.Splitter;
 import net.runelite.client.ui.MultiplexingPluginPanel;
 import net.runelite.client.ui.PluginPanel;
+import net.runelite.client.util.Text;
 
+import com.google.common.base.Splitter;
 import lombok.extern.slf4j.Slf4j;
-import net.runelite.client.util.Text;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
 import javax.inject.Singleton;
 import javax.swing.*;
 import javax.swing.border.EmptyBorder;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import java.awt.BorderLayout;
-import java.awt.Dimension;
+import java.awt.*;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
-import java.util.Locale;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/InputGroup.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/InputGroup.java
index b5c1a07..a613380 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/InputGroup.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/InputGroup.java
@@ -1,9 +1,7 @@
 package com.adamk33n3r.runelite.watchdog.ui.panels;
 
-import javax.swing.JComponent;
-import javax.swing.JPanel;
-import java.awt.BorderLayout;
-import java.awt.GridLayout;
+import javax.swing.*;
+import java.awt.*;
 import java.util.List;
 
 class InputGroup extends JPanel {
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java
index 1f88917..bd8c697 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java
@@ -25,7 +25,6 @@
 import com.adamk33n3r.runelite.watchdog.ui.notifications.panels.SoundNotificationPanel;
 import com.adamk33n3r.runelite.watchdog.ui.notifications.panels.TextToSpeechNotificationPanel;
 
-import net.runelite.client.ui.DynamicGridLayout;
 import net.runelite.client.ui.components.DragAndDropReorderPane;
 import net.runelite.client.ui.components.colorpicker.ColorPickerManager;
 
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java
index 82bd2a6..bf2891a 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java
@@ -1,7 +1,7 @@
 package com.adamk33n3r.runelite.watchdog.ui.panels;
 
-import com.adamk33n3r.runelite.watchdog.TriggerType;
 import com.adamk33n3r.runelite.watchdog.Displayable;
+import com.adamk33n3r.runelite.watchdog.TriggerType;
 import com.adamk33n3r.runelite.watchdog.Util;
 import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
 import com.adamk33n3r.runelite.watchdog.alerts.Alert;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/ScrollablePanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/ScrollablePanel.java
index 9e3e74d..7cca730 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/ScrollablePanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/ScrollablePanel.java
@@ -18,14 +18,8 @@
  */
 package com.adamk33n3r.runelite.watchdog.ui.panels;
 
-import javax.swing.JPanel;
-import javax.swing.JViewport;
-import javax.swing.Scrollable;
-import javax.swing.SwingConstants;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.LayoutManager;
-import java.awt.Rectangle;
+import javax.swing.*;
+import java.awt.*;
 
 public class ScrollablePanel extends JPanel
     implements Scrollable, SwingConstants
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index cbe6554..420ac7a 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,5 +1,5 @@
-#Sat Oct 07 23:20:07 EDT 2023
-VERSION_BUILD=3822
+#Tue Oct 10 02:44:17 EDT 2023
+VERSION_BUILD=3830
 VERSION_PHASE=rc1
 VERSION_MAJOR=3
 VERSION_MINOR=0

From bdb5c2035351c639d513d8896e8f9df1e380ef36 Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Thu, 12 Oct 2023 19:56:56 -0400
Subject: [PATCH 17/27] Update search

---
 .../adamk33n3r/runelite/watchdog/Util.java    |  14 +++++++++++
 .../runelite/watchdog/alerts/Alert.java       |   1 -
 .../runelite/watchdog/hub/AlertHubPanel.java  |  11 ++++-----
 .../runelite/watchdog/hub/AlertManifest.java  |   2 +-
 .../runelite/watchdog/ui/Icons.java           |   3 +++
 .../watchdog/ui/panels/AlertListPanel.java    |   3 ++-
 .../watchdog/ui/panels/HistoryPanel.java      |   6 ++---
 .../runelite/watchdog/ui/mdi_refresh.png      | Bin 0 -> 368 bytes
 .../runelite/watchdog/version.properties      |   4 +--
 .../runelite/watchdog/SearchTextTest.java     |  23 ++++++++++++++++++
 10 files changed, 53 insertions(+), 14 deletions(-)
 create mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/ui/mdi_refresh.png
 create mode 100644 src/test/java/com/adamk33n3r/runelite/watchdog/SearchTextTest.java

diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java b/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java
index e6f633b..1c466e8 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java
@@ -3,7 +3,13 @@
 import com.adamk33n3r.runelite.watchdog.alerts.Alert;
 import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup;
 
+import net.runelite.client.util.Text;
+
+import com.google.common.base.Splitter;
+
+import java.text.Normalizer;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class Util {
     public static void setParentsOnAlerts(List alerts) {
@@ -161,4 +167,12 @@ public static String processTriggerValues(String string, String[] triggerValues)
     public static int scale(int val, float srcMin, float srcMax, float destMin, float destMax) {
         return Math.round(((val - srcMin) / (srcMax - srcMin)) * (destMax - destMin) + destMin);
     }
+
+    private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings();
+    public static boolean searchText(String search, List keywords) {
+        String normalizedSearch = Normalizer.normalize(search, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "").toLowerCase();
+        return Text.matchesSearchTerms(
+            SPLITTER.split(normalizedSearch),
+            keywords.stream().map(term -> Normalizer.normalize(term, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "").toLowerCase()).collect(Collectors.toList()));
+    }
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java
index e7881fe..c604775 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java
@@ -113,7 +113,6 @@ public List getKeywords() {
                         }
                         return Stream.of(notification.getType().getName());
                     }))
-                .map(String::toUpperCase)
                 .collect(Collectors.toList());
         }
     }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
index ed7ded0..1c43975 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java
@@ -1,5 +1,6 @@
 package com.adamk33n3r.runelite.watchdog.hub;
 
+import com.adamk33n3r.runelite.watchdog.Util;
 import com.adamk33n3r.runelite.watchdog.ui.Icons;
 import com.adamk33n3r.runelite.watchdog.ui.SearchBar;
 import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
@@ -9,9 +10,7 @@
 import net.runelite.client.ui.MultiplexingPluginPanel;
 import net.runelite.client.ui.PluginPanel;
 import net.runelite.client.ui.components.IconTextField;
-import net.runelite.client.util.Text;
 
-import com.google.common.base.Splitter;
 import lombok.extern.slf4j.Slf4j;
 
 import javax.inject.Inject;
@@ -22,6 +21,7 @@
 import java.awt.Dimension;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.stream.Collectors;
@@ -36,7 +36,6 @@ public class AlertHubPanel extends PluginPanel {
     private final IconTextField searchBar;
     private final JPanel container;
     private final JLabel loading;
-    private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings();
     private final JScrollPane scrollPane;
 
     @Inject
@@ -58,6 +57,7 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
         GroupLayout layout = new GroupLayout(this);
         this.setLayout(layout);
         this.searchBar = new SearchBar(this::updateFilter);
+        Arrays.stream(AlertHubCategory.values()).map(AlertHubCategory::getName).forEach(this.searchBar.getSuggestionListModel()::addElement);
 
         this.container = new JPanel(new DynamicGridLayout(0, 1, 0, 5));
 //        this.container.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
@@ -74,7 +74,7 @@ public AlertHubPanel(Provider muxer, AlertHubClient ale
         wrapper.setScrollableUnitIncrement(SwingConstants.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
         this.scrollPane = new JScrollPane(wrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
 
-        JButton refresh = PanelUtils.createActionButton(Icons.HISTORY, Icons.HISTORY_HOVER, "Refresh", (btn, mod) -> {
+        JButton refresh = PanelUtils.createActionButton(Icons.REFRESH, Icons.REFRESH_HOVER, "Refresh", (btn, mod) -> {
             this.reloadList(true);
         });
 
@@ -132,10 +132,9 @@ private void reloadList(List alerts) {
 
     private void updateFilter(String search) {
         this.container.removeAll();
-        String upperSearch = search.toUpperCase();
         this.alertHubItems.stream().filter(alertHubItem -> {
             AlertManifest manifest = alertHubItem.getAlertDisplayInfo().getManifest();
-            return Text.matchesSearchTerms(SPLITTER.split(upperSearch), manifest.getKeywords());
+            return Util.searchText(search, manifest.getKeywords());
         }).forEach(this.container::add);
         this.container.revalidate();
         this.scrollPane.getVerticalScrollBar().setValue(0);
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
index aeef691..6e0b505 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertManifest.java
@@ -47,6 +47,6 @@ public List getKeywords() {
         if (this.getDependsOn() != null) {
             keywords = Stream.concat(keywords, this.getDependsOn().stream());
         }
-        return keywords.map(String::toUpperCase).collect(Collectors.toList());
+        return keywords.collect(Collectors.toList());
     }
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/Icons.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/Icons.java
index 3b5756c..e781d36 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/Icons.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/Icons.java
@@ -37,6 +37,9 @@ public abstract class Icons {
     public static final ImageIcon EDIT = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_pencil.png"));
     public static final ImageIcon DRAG_VERT = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_drag-vertical.png"));
 
+    public static final ImageIcon REFRESH = new ImageIcon(ImageUtil.loadImageResource(Icons.class, "mdi_refresh.png"));
+    public static final ImageIcon REFRESH_HOVER = new ImageIcon(ImageUtil.luminanceOffset(REFRESH.getImage(), -80));
+
     /*
      * Alerts
      */
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java
index 3305a50..a7f02b2 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java
@@ -2,6 +2,7 @@
 
 import com.adamk33n3r.runelite.watchdog.AlertManager;
 import com.adamk33n3r.runelite.watchdog.TriggerType;
+import com.adamk33n3r.runelite.watchdog.Util;
 import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
 import com.adamk33n3r.runelite.watchdog.alerts.Alert;
 import com.adamk33n3r.runelite.watchdog.ui.AlertListItemNew;
@@ -70,7 +71,7 @@ private void filter(String text) {
         this.filterText = text;
         this.dragAndDropReorderPane.removeAll();
         this.alertListItems.stream()
-            .filter(alertListItem -> Text.matchesSearchTerms(SPLITTER.split(this.filterText.toUpperCase()), alertListItem.getAlert().getKeywords()))
+            .filter(alertListItem -> Util.searchText(this.filterText, alertListItem.getAlert().getKeywords()))
             .forEach(this.dragAndDropReorderPane::add);
         this.revalidate();
     }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
index 7c95488..f3d14a3 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java
@@ -1,5 +1,6 @@
 package com.adamk33n3r.runelite.watchdog.ui.panels;
 
+import com.adamk33n3r.runelite.watchdog.Util;
 import com.adamk33n3r.runelite.watchdog.alerts.Alert;
 import com.adamk33n3r.runelite.watchdog.notifications.IMessageNotification;
 import com.adamk33n3r.runelite.watchdog.ui.Icons;
@@ -86,7 +87,6 @@ public void addEntry(Alert alert, String[] triggerValues) {
     // TODO: Abstract this out into a filterpanel type thing
     private void updateFilter(String search) {
         this.historyItems.removeAll();
-        String upperSearch = search.toUpperCase();
         this.previousAlerts.stream().filter(historyEntryPanel -> {
             Alert alert = historyEntryPanel.getAlert();
             Stream keywords = Stream.concat(Stream.of(
@@ -98,8 +98,8 @@ private void updateFilter(String search) {
                     return Stream.concat(notificationType, Stream.of(((IMessageNotification) notification).getMessage()));
                 }
                 return notificationType;
-            })).map(String::toUpperCase);
-            return Text.matchesSearchTerms(SPLITTER.split(upperSearch), keywords.collect(Collectors.toList()));
+            }));
+            return Util.searchText(search, keywords.collect(Collectors.toList()));
         }).forEach(this.historyItems::add);
         this.revalidate();
         // Idk why I need to repaint sometimes and the PluginListPanel doesn't
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/ui/mdi_refresh.png b/src/main/resources/com/adamk33n3r/runelite/watchdog/ui/mdi_refresh.png
new file mode 100644
index 0000000000000000000000000000000000000000..5a38c96757767e3e956dd3544a2b92827216298d
GIT binary patch
literal 368
zcmV-$0gwKPP)Px$DoI2^R5(wy(?3hrVHC#k&()MzBnn9o)X+O{OB@|qtR>e*7uTq*sgmA8A@2Gr
zBuHFzPzpEaA}|L9l_S6SJr9r1pAe)o-G9ILIoEaWxMAdm_x%%4RZs8`dr5wE9;>R`
zNlu&j0HE2#8!X{Ld+7w9u!3bACRrZEr2RMAvS#`++E4QHN(ogphfPc+
z`F&k`yDn7K9sI#;l5fKRRkes$c%I~Zn229+e}ssSv5nOv9Wk%=L0j)GzT-n{;~1Xd
zT|4i-KB|%KwVuD|DdVgKzE9GOyD_qrt7;ODaDjtP>-KB&e?QZ8th@x;ab*!!HoQ{+
O0000 keywords = Arrays.asList("me", "you", "adamk33n3r");
+        Assert.assertTrue(Util.searchText("k33n3r", keywords));
+        Assert.assertTrue(Util.searchText("Adam", keywords));
+        Assert.assertFalse(Util.searchText("frank", keywords));
+    }
+
+    @Test
+    public void test_accented_characters() {
+        List keywords = Arrays.asList("adamk33n3r", "Ðomé");
+        Assert.assertTrue(Util.searchText("Ðome", keywords));
+    }
+}

From f6047ed153a7db7cfeee1d9f38dbc7919ecc3d2c Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Wed, 18 Oct 2023 02:33:41 -0400
Subject: [PATCH 18/27] Update import to handle single alert imports

---
 build.gradle                                  |   1 +
 .../runelite/watchdog/AlertManager.java       |  19 +-
 .../runelite/watchdog/EventHandler.java.orig  | 367 ------------------
 .../runelite/watchdog/WatchdogPanel.java      |   3 +-
 .../watchdog/hub/AlertHubCategory.java        |   6 +-
 .../watchdog/ui/AlertListItem.java.orig       |  97 -----
 .../watchdog/ui/ImportExportDialog.java       |  42 +-
 .../watchdog/ui/panels/AlertPanel.java        |   2 +-
 .../runelite/watchdog/version.properties      |   4 +-
 9 files changed, 60 insertions(+), 481 deletions(-)
 delete mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/EventHandler.java.orig
 delete mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java.orig

diff --git a/build.gradle b/build.gradle
index 8754a65..336eb62 100644
--- a/build.gradle
+++ b/build.gradle
@@ -82,4 +82,5 @@ tasks.register("shadowJar", Jar) {
 	group = BasePlugin.BUILD_GROUP
 	archiveClassifier = "shadow"
 	archiveFileName = rootProject.name + "-" + project.version + "-all.jar"
+	destinationDirectory = file('jars')
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
index d21ec60..7b82116 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
@@ -9,6 +9,7 @@
 import net.runelite.client.config.FlashNotification;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
 import com.google.gson.reflect.TypeToken;
 import joptsimple.internal.Strings;
 import lombok.Getter;
@@ -162,7 +163,7 @@ public void loadAlerts() {
         this.handleUpgrades();
     }
 
-    public boolean importAlerts(String json, List alerts, boolean append, boolean checkRegex) {
+    public boolean importAlerts(String json, List alerts, boolean append, boolean checkRegex) throws JsonSyntaxException {
         if (Strings.isNullOrEmpty(json)) {
             return false;
         }
@@ -171,8 +172,7 @@ public boolean importAlerts(String json, List alerts, boolean append, boo
             alerts.clear();
         }
 
-        List importedAlerts = this.gson.fromJson(json, ALERT_LIST_TYPE);
-        Supplier> alertStream = () -> importedAlerts.stream().filter(Objects::nonNull);
+        Supplier> alertStream = this.tryImport(json);
 
         // Validate regex properties
         if (checkRegex && !alertStream.get().allMatch(alert -> {
@@ -220,6 +220,19 @@ public String toJSON() {
         return this.gson.toJson(this.alerts, ALERT_LIST_TYPE);
     }
 
+    private Supplier> tryImport(String json) throws JsonSyntaxException {
+        // Single
+        try {
+            Alert importedAlert = this.gson.fromJson(json, ALERT_TYPE);
+            return () -> Stream.of(importedAlert).filter(Objects::nonNull);
+        } catch (JsonSyntaxException ignored) {
+        }
+
+        // Multiple
+        List importedAlerts = this.gson.fromJson(json, ALERT_LIST_TYPE);
+        return () -> importedAlerts.stream().filter(Objects::nonNull);
+    }
+
     private void handleUpgrades() {
         Version currentVersion = new Version(this.pluginVersion);
         Version configVersion = new Version(this.configManager.getConfiguration(WatchdogConfig.CONFIG_GROUP_NAME, WatchdogConfig.PLUGIN_VERSION));
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/EventHandler.java.orig b/src/main/java/com/adamk33n3r/runelite/watchdog/EventHandler.java.orig
deleted file mode 100644
index fca8100..0000000
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/EventHandler.java.orig
+++ /dev/null
@@ -1,367 +0,0 @@
-package com.adamk33n3r.runelite.watchdog;
-
-import com.adamk33n3r.runelite.watchdog.alerts.Alert;
-import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup;
-import com.adamk33n3r.runelite.watchdog.alerts.ChatAlert;
-import com.adamk33n3r.runelite.watchdog.alerts.InventoryAlert;
-import com.adamk33n3r.runelite.watchdog.alerts.NotificationFiredAlert;
-import com.adamk33n3r.runelite.watchdog.alerts.PlayerChatAlert;
-import com.adamk33n3r.runelite.watchdog.alerts.RegexMatcher;
-import com.adamk33n3r.runelite.watchdog.alerts.SpawnedAlert;
-import com.adamk33n3r.runelite.watchdog.alerts.StatChangedAlert;
-import com.adamk33n3r.runelite.watchdog.alerts.XPDropAlert;
-import com.adamk33n3r.runelite.watchdog.ui.panels.HistoryPanel;
-
-import net.runelite.api.*;
-import net.runelite.api.events.*;
-import net.runelite.client.eventbus.EventBus;
-import net.runelite.client.eventbus.Subscribe;
-import net.runelite.client.events.NotificationFired;
-import net.runelite.client.game.ItemManager;
-import net.runelite.client.util.Text;
-
-import lombok.extern.slf4j.Slf4j;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-import javax.inject.Singleton;
-import javax.swing.SwingUtilities;
-import java.awt.TrayIcon;
-import java.time.Instant;
-<<<<<<< HEAD
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-=======
-import java.util.*;
->>>>>>> origin/master
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Stream;
-
-import static com.adamk33n3r.runelite.watchdog.alerts.SpawnedAlert.SpawnedDespawned.DESPAWNED;
-import static com.adamk33n3r.runelite.watchdog.alerts.SpawnedAlert.SpawnedDespawned.SPAWNED;
-import static com.adamk33n3r.runelite.watchdog.alerts.SpawnedAlert.SpawnedType.*;
-
-@Slf4j
-@Singleton
-public class EventHandler {
-    @Inject
-    private Client client;
-
-    @Inject
-    private ItemManager itemManager;
-
-    @Inject
-    private AlertManager alertManager;
-
-    @Inject
-    private EventBus eventBus;
-
-    @Inject
-    private Provider historyPanelProvider;
-
-    private final Map lastTriggered = new HashMap<>();
-
-    private final Map previousSkillLevelTable = new EnumMap<>(Skill.class);
-    private final Map previousSkillXPTable = new EnumMap<>(Skill.class);
-
-    private boolean ignoreNotificationFired = false;
-
-    public synchronized void notify(String message) {
-        this.ignoreNotificationFired = true;
-        // The event bus is synchronous
-        this.eventBus.post(new NotificationFired(message, TrayIcon.MessageType.NONE));
-        this.ignoreNotificationFired = false;
-    }
-
-    //region Chat Message
-    @Subscribe
-    public void onChatMessage(ChatMessage chatMessage) {
-        // Don't process messages sent by this plugin
-        if (chatMessage.getName().equals(WatchdogPlugin.getInstance().getName())) {
-            return;
-        }
-
-//        log.debug(chatMessage.getType().name() + ": " + chatMessage.getMessage());
-        String unformattedMessage = Text.removeFormattingTags(chatMessage.getMessage());
-
-        // Send player messages to a different handler
-        if (
-            chatMessage.getType() == ChatMessageType.PUBLICCHAT
-                || chatMessage.getType() == ChatMessageType.AUTOTYPER
-                || chatMessage.getType() == ChatMessageType.PRIVATECHAT
-                || chatMessage.getType() == ChatMessageType.PRIVATECHATOUT
-                || chatMessage.getType() == ChatMessageType.MODCHAT
-                || chatMessage.getType() == ChatMessageType.MODPRIVATECHAT
-                || chatMessage.getType() == ChatMessageType.MODAUTOTYPER
-                || chatMessage.getType() == ChatMessageType.FRIENDSCHAT
-                || chatMessage.getType() == ChatMessageType.CLAN_CHAT
-                || chatMessage.getType() == ChatMessageType.CLAN_GUEST_CHAT
-                || chatMessage.getType() == ChatMessageType.CLAN_GIM_CHAT
-        ) {
-            this.alertManager.getAllEnabledAlertsOfType(PlayerChatAlert.class)
-                .forEach(chatAlert -> {
-                    String[] groups = this.matchPattern(chatAlert, unformattedMessage);
-                    if (groups == null) return;
-
-                    this.fireAlert(chatAlert, groups);
-                });
-            return;
-        }
-
-        this.alertManager.getAllEnabledAlertsOfType(ChatAlert.class)
-            .forEach(gameAlert -> {
-                String[] groups = this.matchPattern(gameAlert, unformattedMessage);
-                if (groups == null) return;
-
-                this.fireAlert(gameAlert, groups);
-            });
-    }
-    //endregion
-
-    //region Notification
-    @Subscribe
-    public void onNotificationFired(NotificationFired notificationFired) {
-        // This flag is set when we are firing our own events, so we don't cause an infinite loop/stack overflow
-        if (this.ignoreNotificationFired) {
-            return;
-        }
-
-        this.alertManager.getAllEnabledAlertsOfType(NotificationFiredAlert.class)
-            .forEach(notificationFiredAlert -> {
-                String[] groups = this.matchPattern(notificationFiredAlert, notificationFired.getMessage());
-                if (groups == null) return;
-
-                this.fireAlert(notificationFiredAlert, groups);
-            });
-    }
-    //endregion
-
-    //region Stat Changed
-    @Subscribe
-    private void onGameStateChanged(GameStateChanged gameStateChanged) {
-        if (gameStateChanged.getGameState() == GameState.LOGGED_IN) {
-            for (Skill skill : Skill.values()) {
-                this.previousSkillLevelTable.put(skill, this.client.getBoostedSkillLevel(skill));
-                this.previousSkillXPTable.put(skill, this.client.getSkillExperience(skill));
-            }
-        }
-    }
-
-    @Subscribe
-    public void onStatChanged(StatChanged statChanged) {
-//        log.debug(String.format("%s: %s/%s", statChanged.getSkill().getName(), statChanged.getBoostedLevel(), statChanged.getLevel()));
-        this.handleStatChanged(statChanged);
-        this.handleXPDrop(statChanged);
-    }
-
-    private void handleStatChanged(StatChanged statChanged) {
-        Integer previousLevel = this.previousSkillLevelTable.put(statChanged.getSkill(), statChanged.getBoostedLevel());
-        if (previousLevel == null) {
-            return;
-        }
-
-        this.alertManager.getAllEnabledAlertsOfType(StatChangedAlert.class)
-            .filter(alert -> {
-                boolean isSkill = alert.getSkill() == statChanged.getSkill();
-                if (!isSkill) {
-                    return false;
-                }
-
-                int targetLevel = statChanged.getLevel() + alert.getChangedAmount();
-                boolean isNegative = alert.getChangedAmount() < 0;
-                boolean isLower = statChanged.getBoostedLevel() <= targetLevel;
-                boolean wasHigher = previousLevel > targetLevel;
-                boolean isHigher = statChanged.getBoostedLevel() >= targetLevel;
-                boolean wasLower = previousLevel < targetLevel;
-//                log.debug("targetLevel: " + targetLevel);
-//                log.debug("{}, {}, {}", isSkill, isLower, wasHigher);
-                return (isNegative && isLower && wasHigher) || (!isNegative && isHigher && wasLower);
-            })
-            .forEach(alert -> this.fireAlert(alert, statChanged.getSkill().getName()));
-    }
-
-    private void handleXPDrop(StatChanged statChanged) {
-        Integer previousXP = this.previousSkillXPTable.put(statChanged.getSkill(), statChanged.getXp());
-        if (previousXP == null) {
-            return;
-        }
-
-        this.alertManager.getAllEnabledAlertsOfType(XPDropAlert.class)
-            .filter(alert -> {
-                boolean isSkill = alert.getSkill() == statChanged.getSkill();
-                int gainedXP = statChanged.getXp() - previousXP;
-                return isSkill && gainedXP >= alert.getGainedAmount();
-            })
-            .forEach(alert -> this.fireAlert(alert, statChanged.getSkill().getName()));
-    }
-    //endregion
-
-    //region Inventory
-    @Subscribe
-    private void onItemContainerChanged(ItemContainerChanged itemContainerChanged) {
-        // Ignore everything but inventory
-        if (itemContainerChanged.getItemContainer().getId() != InventoryID.INVENTORY.getId())
-            return;
-        this.alertManager.getAllEnabledAlertsOfType(InventoryAlert.class)
-            .forEach(inventoryAlert -> {
-                Item[] items = itemContainerChanged.getItemContainer().getItems();
-                long itemCount = Arrays.stream(items).filter(item -> item.getId() > -1).count();
-                if (inventoryAlert.getInventoryAlertType() == InventoryAlert.InventoryAlertType.FULL && itemCount == 28) {
-                    this.fireAlert(inventoryAlert, inventoryAlert.getInventoryAlertType().getName());
-                } else if (inventoryAlert.getInventoryAlertType() == InventoryAlert.InventoryAlertType.EMPTY && itemCount == 0) {
-                    this.fireAlert(inventoryAlert, inventoryAlert.getInventoryAlertType().getName());
-                } else if (inventoryAlert.getInventoryAlertType() == InventoryAlert.InventoryAlertType.ITEM) {
-                    Map allItems = new HashMap<>();
-                    Arrays.stream(items)
-                        .forEach(item -> allItems.merge(item.getId(), item.getQuantity(), Integer::sum));
-                    allItems.entrySet().stream()
-                        .filter(itemWithCount -> inventoryAlert.getItemQuantity() == 0 || itemWithCount.getValue() == inventoryAlert.getItemQuantity())
-                        .map(itemWithCount -> this.matchPattern(inventoryAlert, this.itemManager.getItemComposition(itemWithCount.getKey()).getName()))
-                        .filter(Objects::nonNull)
-                        .findFirst()
-                        .ifPresent(groups -> this.fireAlert(inventoryAlert, groups));
-                }
-            });
-    }
-    //endregion
-
-    //region Spawned
-    @Subscribe
-    private void onItemSpawned(ItemSpawned itemSpawned) {
-        ItemComposition comp = this.itemManager.getItemComposition(itemSpawned.getItem().getId());
-        this.onSpawned(comp.getName(), SPAWNED, ITEM);
-    }
-    @Subscribe
-    private void onItemDespawned(ItemDespawned itemDespawned) {
-        ItemComposition comp = this.itemManager.getItemComposition(itemDespawned.getItem().getId());
-        this.onSpawned(comp.getName(), DESPAWNED, ITEM);
-    }
-    @Subscribe
-    private void onNpcSpawned(NpcSpawned npcSpawned) {
-        this.onActorSpawned(npcSpawned.getNpc(), NPC);
-    }
-    @Subscribe
-    private void onNpcDespawned(NpcDespawned npcDespawned) {
-        this.onActorDespawned(npcDespawned.getNpc(), NPC);
-    }
-    @Subscribe
-    private void onPlayerSpawned(PlayerSpawned playerSpawned) {
-        this.onActorSpawned(playerSpawned.getPlayer(), PLAYER);
-    }
-    @Subscribe
-    private void onPlayerDespawned(PlayerDespawned playerDespawned) {
-        this.onActorDespawned(playerDespawned.getPlayer(), PLAYER);
-    }
-    private void onActorSpawned(Actor actor, SpawnedAlert.SpawnedType type) {
-        this.onSpawned(actor.getName(), SPAWNED, type);
-    }
-    private void onActorDespawned(Actor actor, SpawnedAlert.SpawnedType type) {
-        this.onSpawned(actor.getName(), DESPAWNED, type);
-    }
-
-    @Subscribe
-    private void onGroundObjectSpawned(GroundObjectSpawned groundObjectSpawned) {
-        this.onTileObjectSpawned(groundObjectSpawned.getGroundObject(), SPAWNED, GROUND_OBJECT);
-    }
-    @Subscribe
-    private void onGroundObjectDespawned(GroundObjectDespawned groundObjectDespawned) {
-        this.onTileObjectSpawned(groundObjectDespawned.getGroundObject(), DESPAWNED, GROUND_OBJECT);
-    }
-
-    @Subscribe
-    private void onDecorativeObjectSpawned(DecorativeObjectSpawned decorativeObjectSpawned) {
-        this.onTileObjectSpawned(decorativeObjectSpawned.getDecorativeObject(), SPAWNED, DECORATIVE_OBJECT);
-    }
-    @Subscribe
-    private void onDecorativeObjectDespawned(DecorativeObjectDespawned decorativeObjectDespawned) {
-        this.onTileObjectSpawned(decorativeObjectDespawned.getDecorativeObject(), DESPAWNED, DECORATIVE_OBJECT);
-    }
-
-    @Subscribe
-    private void onGameObjectSpawned(GameObjectSpawned gameObjectSpawned) {
-        this.onTileObjectSpawned(gameObjectSpawned.getGameObject(), SPAWNED, GAME_OBJECT);
-    }
-    @Subscribe
-    private void onGameObjectDespawned(GameObjectDespawned gameObjectDespawned) {
-        this.onTileObjectSpawned(gameObjectDespawned.getGameObject(), DESPAWNED, GAME_OBJECT);
-    }
-
-    @Subscribe
-    private void onWallObjectSpawned(WallObjectSpawned wallObjectSpawned) {
-        this.onTileObjectSpawned(wallObjectSpawned.getWallObject(), SPAWNED, WALL_OBJECT);
-    }
-    @Subscribe
-    private void onWallObjectDespawned(WallObjectDespawned wallObjectDespawned) {
-        this.onTileObjectSpawned(wallObjectDespawned.getWallObject(), DESPAWNED, WALL_OBJECT);
-    }
-
-    private void onTileObjectSpawned(TileObject tileObject, SpawnedAlert.SpawnedDespawned mode, SpawnedAlert.SpawnedType type) {
-        final ObjectComposition comp = this.client.getObjectDefinition(tileObject.getId());
-        final ObjectComposition impostor = comp.getImpostorIds() != null ? comp.getImpostor() : comp;
-        if (impostor == null) {
-            return;
-        }
-        this.onSpawned(impostor.getName(), mode, type);
-    }
-
-    private void onSpawned(String name, SpawnedAlert.SpawnedDespawned mode, SpawnedAlert.SpawnedType type) {
-        String unformattedName = Text.removeFormattingTags(name);
-        this.alertManager.getAllEnabledAlertsOfType(SpawnedAlert.class)
-            .filter(spawnedAlert -> spawnedAlert.getSpawnedDespawned() == mode)
-            .filter(spawnedAlert -> spawnedAlert.getSpawnedType() == type)
-            .forEach(spawnedAlert -> {
-                String[] groups = this.matchPattern(spawnedAlert, unformattedName);
-                if (groups == null) return;
-
-                this.fireAlert(spawnedAlert, groups);
-            });
-    }
-    //endregion
-
-    private String[] matchPattern(RegexMatcher regexMatcher, String input) {
-        String regex = regexMatcher.isRegexEnabled() ? regexMatcher.getPattern() : Util.createRegexFromGlob(regexMatcher.getPattern());
-        Matcher matcher = Pattern.compile(regex, regexMatcher.isRegexEnabled() ? 0 : Pattern.CASE_INSENSITIVE).matcher(input);
-        if (!matcher.matches()) return null;
-
-        String[] groups = new String[matcher.groupCount()];
-        for (int i = 0; i < matcher.groupCount(); i++) {
-            groups[i] = matcher.group(i+1);
-        }
-        return groups;
-    }
-
-    private void fireAlert(Alert alert, String triggerValue) {
-        this.fireAlert(alert, new String[] { triggerValue });
-    }
-
-    private void fireAlert(Alert alert, String[] triggerValues) {
-        // Don't fire if it is disabled
-        if (!alert.isEnabled()) return;
-
-        List ancestors = alert.getAncestors();
-        // Don't fire if any of the ancestors are disabled
-        if (ancestors != null && !ancestors.stream().allMatch(Alert::isEnabled)) {
-            return;
-        }
-
-        Alert alertToDebounceWith = ancestors == null ? alert : Stream.concat(ancestors.stream(), Stream.of(alert))
-            .filter(ancestor -> ancestor.getDebounceTime() > 0)
-            .max(Comparator.comparingInt(Alert::getDebounceTime))
-            .orElse(alert);
-
-        // If the alert hasn't been fired yet, or has been enough time, set the last trigger time to now and fire.
-        if (!this.lastTriggered.containsKey(alertToDebounceWith) || Instant.now().compareTo(this.lastTriggered.get(alertToDebounceWith).plusMillis(alertToDebounceWith.getDebounceTime())) >= 0) {
-            SwingUtilities.invokeLater(() -> {
-                this.historyPanelProvider.get().addEntry(alert, triggerValues);
-            });
-            this.lastTriggered.put(alertToDebounceWith, Instant.now());
-            alert.getNotifications().forEach(notification -> notification.fire(triggerValues));
-        }
-    }
-}
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
index ca3c709..6f67152 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java
@@ -151,7 +151,7 @@ public void rebuild() {
         JButton exportButton = new JButton("Export", Icons.EXPORT);
         exportButton.setHorizontalTextPosition(SwingConstants.LEFT);
         exportButton.addActionListener(ev -> {
-            ImportExportDialog importExportDialog = new ImportExportDialog(SwingUtilities.getWindowAncestor(this), WatchdogPlugin.getInstance().getConfig().alerts());
+            ImportExportDialog importExportDialog = new ImportExportDialog(SwingUtilities.getWindowAncestor(this), this.alertManager.getAlerts());
             importExportDialog.setVisible(true);
         });
         importExportGroup.add(exportButton);
@@ -162,7 +162,6 @@ public void rebuild() {
         hubButton.setHorizontalTextPosition(SwingConstants.LEFT);
         hubButton.addActionListener(ev -> {
             AlertHubPanel alertHubPanel = this.alertHubPanelProvider.get();
-//            alertHubPanel.reloadList();
             this.muxer.pushState(alertHubPanel);
         });
         bottomPanel.add(hubButton);
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java
index 485606c..8089576 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubCategory.java
@@ -6,11 +6,11 @@
 @Getter
 @AllArgsConstructor
 public enum AlertHubCategory {
-    COMBAT("Combat", "Combat", "/skill_icons_small/attack.png"),
+    COMBAT("Combat", "Combat", "/skill_icons_small/combat.png"),
     SKILLING("Skilling", "Skilling", "/skill_icons_small/mining.png"),
     BOSSES("Bosses", "Bosses", "/skill_icons_small/slayer.png"),
-    DROPS("Drops", "Drops", "/skill_icons_small/clue_scroll_all.png"),
-    AFK("AFK", "AFK", "/skill_icons_small/cooking.png"),
+    AFK("AFK", "AFK", "/skill_icons_small/fishing.png"),
+    MISC("Misc", "Misc", "/skill_icons_small/clue_scroll_all.png")
     ;
 
     private final String name;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java.orig b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java.orig
deleted file mode 100644
index f720ec1..0000000
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/AlertListItem.java.orig
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.adamk33n3r.runelite.watchdog.ui;
-
-import com.adamk33n3r.runelite.watchdog.AlertManager;
-import com.adamk33n3r.runelite.watchdog.WatchdogPanel;
-import com.adamk33n3r.runelite.watchdog.alerts.Alert;
-import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
-
-import net.runelite.client.ui.DynamicGridLayout;
-import net.runelite.client.ui.PluginPanel;
-import net.runelite.client.ui.components.MouseDragEventForwarder;
-import net.runelite.client.util.SwingUtil;
-
-import lombok.Getter;
-
-<<<<<<< HEAD
-import javax.swing.*;
-=======
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
->>>>>>> origin/master
-import javax.swing.border.EmptyBorder;
-import java.awt.*;
-import java.util.List;
-
-@Deprecated
-public class AlertListItem extends JPanel {
-
-    private static final int ROW_HEIGHT = 30;
-    private static final int PADDING = 2;
-
-    @Getter
-    private final Alert alert;
-
-    public AlertListItem(WatchdogPanel panel, AlertManager alertManager, Alert alert, List parentList, JComponent parent, Runnable onChange) {
-        this.alert = alert;
-        this.setLayout(new BorderLayout(5, 0));
-
-        MouseDragEventForwarder mouseDragEventForwarder = new MouseDragEventForwarder(parent);
-
-        this.setBorder(new EmptyBorder(PADDING, 0, PADDING, 0));
-        this.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, ROW_HEIGHT + PADDING * 2));
-
-        JPanel frontGroup = new JPanel(new DynamicGridLayout(1, 0, 3, 0));
-
-        JButton dragHandle = new JButton(Icons.DRAG_VERT);
-        SwingUtil.removeButtonDecorations(dragHandle);
-        dragHandle.setPreferredSize(new Dimension(8, 16));
-        dragHandle.addMouseListener(mouseDragEventForwarder);
-        dragHandle.addMouseMotionListener(mouseDragEventForwarder);
-        frontGroup.add(dragHandle);
-
-        ToggleButton toggleButton = new ToggleButton();
-        toggleButton.setSelected(alert.isEnabled());
-        toggleButton.addItemListener(i -> {
-            alert.setEnabled(toggleButton.isSelected());
-            alertManager.saveAlerts();
-        });
-        toggleButton.setOpaque(false);
-        frontGroup.add(toggleButton);
-
-        this.add(frontGroup, BorderLayout.LINE_START);
-
-        final JButton alertButton = new JButton(alert.getName());
-        alertButton.setToolTipText(alert.getName());
-        alertButton.addActionListener(ev -> {
-            panel.openAlert(alert);
-        });
-        this.add(alertButton, BorderLayout.CENTER);
-
-        final JPanel actionButtons = new JPanel(new DynamicGridLayout(1, 0, 0, 0));
-        this.add(actionButtons, BorderLayout.LINE_END);
-
-<<<<<<< HEAD
-        actionButtons.add(PanelUtils.createActionButton(CLONE_ICON, CLONE_ICON, "Clone Alert", (btn, modifiers) -> {
-            Alert cloned = alertManager.cloneAlert(alert);
-            parentList.add(cloned);
-            alertManager.saveAlerts();
-            onChange.run();
-=======
-        actionButtons.add(PanelUtils.createActionButton(Icons.CLONE, Icons.CLONE, "Clone Alert", (btn, modifiers) -> {
-            alertManager.cloneAlert(alert);
->>>>>>> origin/master
-        }));
-
-        final JButton deleteButton = PanelUtils.createActionButton(Icons.DELETE, Icons.DELETE, "Delete Alert", (btn, modifiers) -> {
-            int result = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete the " + alert.getName() + " alert?", "Delete?", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE);
-            if (result == JOptionPane.YES_OPTION) {
-                parentList.remove(alert);
-                alertManager.saveAlerts();
-                onChange.run();
-            }
-        });
-        actionButtons.add(deleteButton);
-    }
-}
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java
index 90e82de..5aa776e 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/ImportExportDialog.java
@@ -1,5 +1,10 @@
 package com.adamk33n3r.runelite.watchdog.ui;
 
+import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
+import com.adamk33n3r.runelite.watchdog.alerts.Alert;
+import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
+
+import com.google.gson.Gson;
 import lombok.extern.slf4j.Slf4j;
 
 import javax.swing.*;
@@ -11,6 +16,7 @@
 import java.awt.datatransfer.Clipboard;
 import java.awt.datatransfer.StringSelection;
 import java.awt.event.ActionListener;
+import java.util.List;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
@@ -64,21 +70,48 @@ public ImportExportDialog(Component parent, BiFunction
     }
 
     // Export
-    public ImportExportDialog(Component parent, String exportString) {
+    public ImportExportDialog(Component parent, Alert alert) {
+        Gson gson = WatchdogPlugin.getInstance().getAlertManager().getGson();
+        String json = gson.toJson(alert);
+        String pretty = gson.newBuilder().setPrettyPrinting().create().toJson(alert);
+        this.show(parent, json, pretty);
+    }
+
+    public ImportExportDialog(Component parent, List alerts) {
+        Gson gson = WatchdogPlugin.getInstance().getAlertManager().getGson();
+        String json = gson.toJson(alerts);
+        String pretty = gson.newBuilder().setPrettyPrinting().create().toJson(alerts);
+        this.show(parent, json, pretty);
+    }
+
+    public void show(Component parent, String exportString, String prettyExportString) {
         this.setTitle("Export");
         this.setSize(500, 250);
         this.setLocationRelativeTo(parent);
         this.setModal(true);
         this.setUndecorated(true);
 
+        JTextArea textArea = new JTextArea(exportString);
+        textArea.setLineWrap(true);
+        textArea.setEditable(false);
+        SwingUtilities.invokeLater(textArea::requestFocusInWindow);
+
         JPanel wrapper = this.createWrapper();
         this.add(wrapper);
-        wrapper.add(new JLabel("Exported Alert JSON"), BorderLayout.NORTH);
+        JPanel top = new JPanel(new BorderLayout());
+        top.add(new JLabel("Exported Alert JSON"), BorderLayout.WEST);
+        JCheckBox prettyPrint = PanelUtils.createCheckbox("Pretty Print", "Pretty Print", false, (selected) -> {
+            textArea.setText(selected ? prettyExportString : exportString);
+            textArea.setCaretPosition(0);
+            textArea.requestFocusInWindow();
+        });
+        top.add(prettyPrint, BorderLayout.EAST);
+        wrapper.add(top, BorderLayout.NORTH);
         JPanel btnGroup = new JPanel(new GridLayout(1, 2, 25, 0));
         JButton copyBtn = new JButton("Copy to Clipboard");
         copyBtn.addActionListener(ev -> {
             Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
-            clipboard.setContents(new StringSelection(exportString), null);
+            clipboard.setContents(new StringSelection(prettyPrint.isSelected() ? prettyExportString : exportString), null);
             this.setVisible(false);
         });
         btnGroup.add(copyBtn);
@@ -89,9 +122,6 @@ public ImportExportDialog(Component parent, String exportString) {
         });
         wrapper.add(btnGroup, BorderLayout.SOUTH);
 
-        JTextArea textArea = new JTextArea(exportString);
-        textArea.setLineWrap(true);
-        textArea.setEditable(false);
         JScrollPane scrollPane = new JScrollPane(textArea);
         wrapper.add(scrollPane, BorderLayout.CENTER);
     }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java
index c8200e1..16067dc 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java
@@ -100,7 +100,7 @@ public AlertPanel(WatchdogPanel watchdogPanel, T alert) {
             (btn, modifiers) -> {
                 ImportExportDialog importExportDialog = new ImportExportDialog(
                     SwingUtilities.getWindowAncestor(this),
-                    this.alertManager.getGson().toJson(new Alert[] { alert })
+                    alert
                 );
                 importExportDialog.setVisible(true);
             }
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index a21a330..a6f7c7f 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,5 +1,5 @@
-#Thu Oct 12 19:36:36 EDT 2023
-VERSION_BUILD=3860
+#Wed Oct 18 01:38:25 EDT 2023
+VERSION_BUILD=3892
 VERSION_PHASE=rc1
 VERSION_MAJOR=3
 VERSION_MINOR=0

From 220e07e08594f8d9081ecd9f53443f03ebaec6ac Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Wed, 18 Oct 2023 02:48:28 -0400
Subject: [PATCH 19/27] Fix set parent

---
 .../java/com/adamk33n3r/runelite/watchdog/AlertManager.java   | 2 +-
 .../com/adamk33n3r/runelite/watchdog/version.properties       | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
index 7b82116..75a425d 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
@@ -191,7 +191,7 @@ public boolean importAlerts(String json, List alerts, boolean append, boo
         // Save immediately to save new properties
         this.saveAlerts();
 
-        Util.setParentsOnAlerts(alerts);
+        Util.setParentsOnAlerts(this.getAlerts());
 
         // Inject dependencies
         this.getAllAlertsFrom(alertStream.get())
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index a6f7c7f..927996d 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,5 +1,5 @@
-#Wed Oct 18 01:38:25 EDT 2023
-VERSION_BUILD=3892
+#Wed Oct 18 02:47:45 EDT 2023
+VERSION_BUILD=3894
 VERSION_PHASE=rc1
 VERSION_MAJOR=3
 VERSION_MINOR=0

From 612312d7b63a72a07aa6003251f6aa3f7a3a6756 Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Wed, 18 Oct 2023 03:45:09 -0400
Subject: [PATCH 20/27] Change parent logic to lazy

---
 .../runelite/watchdog/AlertManager.java       | 29 +++++++++++++------
 .../adamk33n3r/runelite/watchdog/Util.java    | 21 --------------
 .../runelite/watchdog/alerts/Alert.java       | 14 +++++++++
 .../runelite/watchdog/version.properties      |  4 +--
 4 files changed, 36 insertions(+), 32 deletions(-)

diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
index 75a425d..4e550ee 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
@@ -104,13 +104,29 @@ public  Stream getAllEnabledAlertsOfType(Class type) {
     }
 
     public Stream getAllAlerts() {
-        return this.getAllAlertsFrom(this.alerts.stream());
+        return this.getAllAlertsFrom(this.alerts.stream(), false);
     }
 
-    public Stream getAllAlertsFrom(Stream alerts) {
+    public  Stream getAllAlertsOfType(Class type) {
+        return this.getAllAlerts()
+            .filter(type::isInstance)
+            .map(type::cast);
+    }
+
+    public Stream getAllAlertGroups() {
+        return this.getAllAlertsFrom(this.alerts.stream(), true)
+            .filter(AlertGroup.class::isInstance)
+            .map(AlertGroup.class::cast);
+    }
+
+    public Stream getAllAlertsFrom(Stream alerts, boolean includeGroups) {
         return alerts.flatMap(alert -> {
             if (alert instanceof AlertGroup) {
-                return this.getAllAlertsFrom(((AlertGroup) alert).getAlerts().stream());
+                Stream children = this.getAllAlertsFrom(((AlertGroup) alert).getAlerts().stream(), includeGroups);
+                if (includeGroups) {
+                    return Stream.concat(Stream.of(alert), children);
+                }
+                return children;
             }
             return Stream.of(alert);
         });
@@ -139,9 +155,6 @@ public Alert cloneAlert(Alert alert) {
         String json = this.gson.toJson(alert, ALERT_TYPE);
         Alert clonedAlert = this.gson.fromJson(json, ALERT_TYPE);
         clonedAlert.setName(clonedAlert.getName() + " Clone");
-        if (clonedAlert instanceof AlertGroup) {
-            Util.setParentsOnAlerts(Collections.singletonList(clonedAlert));
-        }
         return clonedAlert;
     }
 
@@ -191,10 +204,8 @@ public boolean importAlerts(String json, List alerts, boolean append, boo
         // Save immediately to save new properties
         this.saveAlerts();
 
-        Util.setParentsOnAlerts(this.getAlerts());
-
         // Inject dependencies
-        this.getAllAlertsFrom(alertStream.get())
+        this.getAllAlertsFrom(alertStream.get(), false)
             .filter(alert -> !(alert instanceof AlertGroup))
             .forEach(alert -> {
                 WatchdogPlugin.getInstance().getInjector().injectMembers(alert);
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java b/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java
index 1c466e8..450adea 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/Util.java
@@ -1,8 +1,5 @@
 package com.adamk33n3r.runelite.watchdog;
 
-import com.adamk33n3r.runelite.watchdog.alerts.Alert;
-import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup;
-
 import net.runelite.client.util.Text;
 
 import com.google.common.base.Splitter;
@@ -12,24 +9,6 @@
 import java.util.stream.Collectors;
 
 public class Util {
-    public static void setParentsOnAlerts(List alerts) {
-        for (Alert alert : alerts) {
-            if (alert instanceof AlertGroup) {
-                AlertGroup group = (AlertGroup) alert;
-                setParentsOnAlertsHelper(group);
-            }
-        }
-    }
-
-    private static void setParentsOnAlertsHelper(AlertGroup parent) {
-        for (Alert childAlert : parent.getAlerts()) {
-            childAlert.setParent(parent);
-            if (childAlert instanceof AlertGroup) {
-                setParentsOnAlertsHelper((AlertGroup) childAlert);
-            }
-        }
-    }
-
     public static  T defaultArg(T thing, T defaultValue) {
         if (thing instanceof String) {
             String string = (String) thing;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java
index c604775..b75d196 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java
@@ -1,6 +1,7 @@
 package com.adamk33n3r.runelite.watchdog.alerts;
 
 import com.adamk33n3r.runelite.watchdog.TriggerType;
+import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
 import com.adamk33n3r.runelite.watchdog.notifications.MessageNotification;
 import com.adamk33n3r.runelite.watchdog.notifications.Notification;
 
@@ -12,6 +13,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -24,6 +26,18 @@ public abstract class Alert {
 
     @Nullable
     private transient AlertGroup parent;
+    public AlertGroup getParent() {
+        if (this.parent == null) {
+            this.parent = WatchdogPlugin.getInstance()
+                .getAlertManager()
+                .getAllAlertGroups()
+                .filter(alertGroup -> alertGroup.getAlerts().contains(this))
+                .findFirst()
+                .orElse(null);
+        }
+
+        return this.parent;
+    }
 
     @Setter(AccessLevel.PROTECTED)
     private List notifications = new ArrayList<>();
diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
index 927996d..7cf9cd6 100644
--- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
+++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties
@@ -1,5 +1,5 @@
-#Wed Oct 18 02:47:45 EDT 2023
-VERSION_BUILD=3894
+#Wed Oct 18 03:38:22 EDT 2023
+VERSION_BUILD=3918
 VERSION_PHASE=rc1
 VERSION_MAJOR=3
 VERSION_MINOR=0

From ca4b7e4d5b332152576b28a5ccd4540387c5e90b Mon Sep 17 00:00:00 2001
From: Adam Keenan 
Date: Sat, 21 Oct 2023 02:39:59 -0400
Subject: [PATCH 21/27] Add alert manager test

---
 build.gradle                                  |  30 +-
 .../runelite/watchdog/AlertManager.java       |   4 +-
 .../adamk33n3r/runelite/watchdog/Version.java |   8 +-
 .../runelite/watchdog/WatchdogPanel.java.orig | 308 ---------------
 .../panels/NotificationPanel.java.orig        | 123 ------
 .../ScreenFlashNotificationPanel.java.orig    |  64 ---
 .../watchdog/ui/panels/AlertPanel.java.orig   | 369 ------------------
 .../watchdog/ui/panels/HistoryPanel.java.orig | 115 ------
 .../ui/panels/NotificationsPanel.java.orig    | 158 --------
 .../watchdog/ui/panels/PanelUtils.java.orig   | 331 ----------------
 .../runelite/watchdog/version.properties      |   6 +-
 .../runelite/watchdog/version.properties.orig |  14 -
 .../runelite/watchdog/AlertManagerTest.java   | 134 +++++++
 13 files changed, 162 insertions(+), 1502 deletions(-)
 delete mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java.orig
 delete mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java.orig
 delete mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java.orig
 delete mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java.orig
 delete mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java.orig
 delete mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java.orig
 delete mode 100644 src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java.orig
 delete mode 100644 src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties.orig
 create mode 100644 src/test/java/com/adamk33n3r/runelite/watchdog/AlertManagerTest.java

diff --git a/build.gradle b/build.gradle
index 336eb62..399e3ce 100644
--- a/build.gradle
+++ b/build.gradle
@@ -19,7 +19,9 @@ dependencies {
 	compileOnly 'org.projectlombok:lombok:1.18.20'
 	annotationProcessor 'org.projectlombok:lombok:1.18.20'
 
-	testImplementation 'junit:junit:4.12'
+	testImplementation 'junit:junit:4.13.2'
+	testImplementation 'org.mockito:mockito-core:3.4.0'
+	testImplementation 'com.google.inject.extensions:guice-testlib:4.1.0'
 	testImplementation group: 'net.runelite', name:'client', version: runeLiteVersion
 	testImplementation group: 'net.runelite', name:'jshell', version: runeLiteVersion
 }
@@ -31,30 +33,32 @@ def versionPropsFile = file("src/main/resources/${group.replace('.', '/')}/versi
 if (versionPropsFile.exists())
 	versionProps.load(versionPropsFile.newReader())
 
-def updateBuildNumber = 1
-if (tasks.findByName('shadowJar')) {
-	updateBuildNumber = 0
-}
-
 def major = (versionProps['VERSION_MAJOR'] as String ?: '0').toInteger()
 def minor = (versionProps['VERSION_MINOR'] as String ?: '0').toInteger()
 def patch = (versionProps['VERSION_PATCH'] as String ?: '0').toInteger()
-def build = (versionProps['VERSION_BUILD'] as String ?: '0').toInteger() + updateBuildNumber
+def build = (versionProps['VERSION_BUILD'] as String ?: '0').toInteger()
 def phase = (versionProps['VERSION_PHASE'] as String ?: '')
-versionProps['VERSION_MAJOR'] = major.toString()
-versionProps['VERSION_MINOR'] = minor.toString()
-versionProps['VERSION_PATCH'] = patch.toString()
-versionProps['VERSION_BUILD'] = build.toString()
-versionProps['VERSION_PHASE'] = phase
-versionProps.store(versionPropsFile.newWriter(), null)
 
 version = major+'.'+minor+'.'+patch
 if (!phase.empty)
 	version = version+'-'+phase
 version = version+'+'+build
+
 sourceCompatibility = '1.8'
 targetCompatibility = '1.8'
 
+compileJava.doFirst {
+	def runTasks = gradle.startParameter.taskNames
+	if ("shadowJar" in runTasks) return
+	build += 1
+	versionProps['VERSION_BUILD'] = build.toString()
+	versionProps.store(versionPropsFile.newWriter(), null)
+	version = major+'.'+minor+'.'+patch
+	if (!phase.empty)
+		version = version+'-'+phase
+	version = version+'+'+build
+}
+
 tasks.withType(JavaCompile) {
 	options.encoding = 'UTF-8'
 }
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
index 4e550ee..3ee3381 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java
@@ -20,7 +20,6 @@
 import javax.inject.Singleton;
 import javax.swing.SwingUtilities;
 import java.lang.reflect.Type;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -40,6 +39,7 @@ public class AlertManager {
     @Getter
     private final List alerts = new CopyOnWriteArrayList<>();
 
+    // TODO: Kinda weird this is in here...
     @Getter
     @Inject
     private WatchdogPanel watchdogPanel;
@@ -280,6 +280,7 @@ private void handleUpgrades() {
             }
 
             if (configVersion.compareTo(new Version("2.8.0")) < 0) {
+                log.debug("Need to convert flash notifications to new properties");
                 this.alerts.stream()
                     .flatMap(alert -> alert.getNotifications().stream())
                     .filter(notification -> notification instanceof ScreenFlash)
@@ -293,6 +294,7 @@ private void handleUpgrades() {
             }
 
             if (configVersion.compareTo(new Version("2.13.0")) < 0) {
+                log.debug("Need to set default overlay notification text color");
                 this.alerts.stream()
                     .flatMap(alert -> alert.getNotifications().stream())
                     .filter(notification -> notification instanceof Overlay)
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/Version.java b/src/main/java/com/adamk33n3r/runelite/watchdog/Version.java
index 9a60574..69d3c97 100644
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/Version.java
+++ b/src/main/java/com/adamk33n3r/runelite/watchdog/Version.java
@@ -7,7 +7,7 @@ public class Version implements Comparable {
     private final String version;
 
     public Version(String version) {
-        if (version != null && !version.matches("[0-9]+(\\.[0-9]+)*")) {
+        if (version != null && !version.matches("[0-9]+(\\.[0-9]+)*(-\\w+)?")) {
             throw new IllegalArgumentException("Invalid version format");
         }
         this.version = version;
@@ -23,8 +23,10 @@ public int compareTo(Version o) {
             return -1;
         }
 
-        String[] thisParts = this.version.split("\\.");
-        String[] thatParts = o.version.split("\\.");
+        String thisVer = this.version.lastIndexOf('-') < 0 ? this.version : this.version.substring(0, this.version.lastIndexOf("-"));
+        String thatVer = o.version.lastIndexOf('-') < 0 ? o.version : o.version.substring(0, o.version.lastIndexOf("-"));
+        String[] thisParts = thisVer.split("\\.");
+        String[] thatParts = thatVer.split("\\.");
         int length = Math.max(thisParts.length, thatParts.length);
         for (int i = 0; i < length; i++) {
             int thisPart = i < thisParts.length ? Integer.parseInt(thisParts[i]) : 0;
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java.orig b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java.orig
deleted file mode 100644
index 0f81f97..0000000
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java.orig
+++ /dev/null
@@ -1,308 +0,0 @@
-package com.adamk33n3r.runelite.watchdog;
-
-import com.adamk33n3r.runelite.watchdog.alerts.*;
-<<<<<<< HEAD
-import com.adamk33n3r.runelite.watchdog.hub.AlertHubPanel;
-=======
-import com.adamk33n3r.runelite.watchdog.ui.AlertListItem;
-import com.adamk33n3r.runelite.watchdog.ui.Icons;
->>>>>>> origin/master
-import com.adamk33n3r.runelite.watchdog.ui.ImportExportDialog;
-import com.adamk33n3r.runelite.watchdog.ui.alerts.*;
-import com.adamk33n3r.runelite.watchdog.ui.panels.AlertListPanel;
-import com.adamk33n3r.runelite.watchdog.ui.panels.HistoryPanel;
-import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
-
-<<<<<<< HEAD
-import net.runelite.client.plugins.config.ConfigPlugin;
-import net.runelite.client.plugins.info.InfoPanel;
-import net.runelite.client.plugins.timetracking.TimeTrackingPlugin;
-import net.runelite.client.ui.ColorScheme;
-import net.runelite.client.ui.MultiplexingPluginPanel;
-import net.runelite.client.ui.PluginPanel;
-import net.runelite.client.util.ImageUtil;
-=======
-import net.runelite.api.Skill;
-import net.runelite.client.ui.ColorScheme;
-import net.runelite.client.ui.MultiplexingPluginPanel;
-import net.runelite.client.ui.PluginPanel;
-import net.runelite.client.ui.components.DragAndDropReorderPane;
->>>>>>> origin/master
-import net.runelite.client.util.LinkBrowser;
-import net.runelite.client.util.Text;
-
-import com.google.common.base.Splitter;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-import okhttp3.OkHttpClient;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-import javax.swing.*;
-import javax.swing.border.EmptyBorder;
-import java.awt.*;
-<<<<<<< HEAD
-import java.awt.image.BufferedImage;
-=======
-import java.awt.event.ActionListener;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
->>>>>>> origin/master
-
-@Slf4j
-public class WatchdogPanel extends PluginPanel {
-    @Inject
-    @Named("watchdog.helpURL")
-    private String HELP_URL;
-
-    @Inject
-    @Named("watchdog.discordURL")
-    private String DISCORD_URL;
-
-    @Inject
-    @Named("watchdog.kofiURL")
-    private String KOFI_URL;
-
-    @Inject
-    @Named("watchdog.pluginVersion")
-    private String PLUGIN_VERSION;
-
-    @Inject
-    @Named("watchdog.pluginVersionFull")
-    private String PLUGIN_VERSION_FULL;
-
-    @Inject
-    @Named("VERSION_PHASE")
-    private String PLUGIN_VERSION_PHASE;
-
-    @Getter
-    private final MultiplexingPluginPanel muxer = new MultiplexingPluginPanel(this);
-
-    @Getter
-    @Inject
-    private Provider historyPanelProvider;
-
-    @Inject
-    private Provider alertHubPanelProvider;
-
-    @Inject
-    private AlertManager alertManager;
-
-<<<<<<< HEAD
-    @Inject
-    private OkHttpClient httpClient;
-
-    private AlertListPanel alertListPanel;
-
-    public static final ImageIcon ADD_ICON;
-    public static final ImageIcon HELP_ICON;
-    public static final ImageIcon HELP_ICON_HOVER;
-    public static final ImageIcon HISTORY_ICON;
-    public static final ImageIcon HISTORY_ICON_HOVER;
-    public static final ImageIcon DISCORD_ICON;
-    public static final ImageIcon DISCORD_ICON_HOVER;
-    public static final ImageIcon KOFI_ICON;
-    public static final ImageIcon KOFI_ICON_HOVER;
-    public static final ImageIcon CONFIG_ICON;
-    public static final ImageIcon CONFIG_ICON_HOVER;
-    public static final ImageIcon EXPORT_ICON = new ImageIcon(ImageUtil.loadImageResource(ConfigPlugin.class, "mdi_export.png"));
-    public static final ImageIcon IMPORT_ICON = new ImageIcon(ImageUtil.loadImageResource(WatchdogPanel.class, "mdi_import.png"));
-
-    static {
-        final BufferedImage addIcon = ImageUtil.loadImageResource(TimeTrackingPlugin.class, "add_icon.png");
-        ADD_ICON = new ImageIcon(addIcon);
-
-        final BufferedImage helpIcon = ImageUtil.loadImageResource(WatchdogPanel.class, "help_icon.png");
-        HELP_ICON = new ImageIcon(helpIcon);
-        HELP_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(helpIcon, -100));
-
-        final BufferedImage historyIcon = ImageUtil.loadImageResource(WatchdogPanel.class, "history_icon.png");
-        HISTORY_ICON = new ImageIcon(ImageUtil.luminanceOffset(historyIcon, -40));
-        HISTORY_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(historyIcon, -160));
-
-        final BufferedImage discordIcon = ImageUtil.loadImageResource(InfoPanel.class, "discord_icon.png");
-        DISCORD_ICON = new ImageIcon(discordIcon);
-        DISCORD_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(discordIcon, -100));
-
-        final BufferedImage kofiIcon = ImageUtil.loadImageResource(WatchdogPanel.class, "kofi_icon.png");
-        KOFI_ICON = new ImageIcon(kofiIcon);
-        KOFI_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(kofiIcon, -100));
-
-        final BufferedImage configIcon = ImageUtil.loadImageResource(ConfigPlugin.class, "config_edit_icon.png");
-        CONFIG_ICON = new ImageIcon(configIcon);
-        CONFIG_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(configIcon, -100));
-    }
-
-=======
-    private String filterText = "";
-    private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings();
-    DragAndDropReorderPane dragAndDropReorderPane = new DragAndDropReorderPane();
-    private final List alertListItems = new ArrayList<>();
-    @Inject
-    private OkHttpClient httpClient;
-
->>>>>>> origin/master
-    public WatchdogPanel() {
-        super(false);
-    }
-
-    public void rebuild() {
-        this.removeAll();
-        this.setLayout(new BorderLayout(0, 3));
-        this.setBorder(new EmptyBorder(0, 5, 0, 5));
-        this.setBackground(ColorScheme.DARK_GRAY_COLOR);
-
-        JPanel topPanel = new JPanel(new BorderLayout());
-        topPanel.setBorder(new EmptyBorder(5, 0, 0, 0));
-        JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 3));
-        JLabel title = new JLabel(WatchdogPlugin.getInstance().getName());
-        title.setFont(title.getFont().deriveFont(Font.BOLD));
-        title.setHorizontalAlignment(JLabel.LEFT);
-        title.setForeground(Color.WHITE);
-        boolean isPreRelease = !PLUGIN_VERSION_PHASE.equals("release") && !PLUGIN_VERSION_PHASE.isEmpty();
-        title.setToolTipText("Watchdog v" + (isPreRelease ? PLUGIN_VERSION_FULL : PLUGIN_VERSION));
-        titlePanel.add(title);
-        JLabel version = new JLabel("v"+PLUGIN_VERSION);
-        title.setToolTipText(version.getText());
-        version.setFont(version.getFont().deriveFont(10f));
-        version.setBorder(new EmptyBorder(5, 0, 0, 0));
-        titlePanel.add(version);
-        topPanel.add(titlePanel);
-
-        JPanel actionButtons = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
-
-        JButton discordButton = PanelUtils.createActionButton(Icons.DISCORD, Icons.DISCORD_HOVER, "Discord", (btn, modifiers) -> {
-            LinkBrowser.browse(DISCORD_URL);
-        });
-        actionButtons.add(discordButton);
-
-        JButton kofiButton = PanelUtils.createActionButton(Icons.KOFI, Icons.KOFI_HOVER, "Buy me a coffee :)", (btn, modifiers) -> {
-            LinkBrowser.browse(KOFI_URL);
-        });
-        kofiButton.setPreferredSize(new Dimension(17, 17));
-        actionButtons.add(kofiButton);
-
-        JButton helpButton = PanelUtils.createActionButton(Icons.HELP, Icons.HELP_HOVER, "Wiki", (btn, modifiers) -> {
-            LinkBrowser.browse(HELP_URL);
-        });
-        actionButtons.add(helpButton);
-
-        JButton configButton = PanelUtils.createActionButton(Icons.CONFIG, Icons.CONFIG_HOVER, "Config", (btn, modifiers) -> {
-            WatchdogPlugin.getInstance().openConfiguration();
-        });
-        actionButtons.add(configButton);
-
-        JButton historyButton = PanelUtils.createActionButton(Icons.HISTORY, Icons.HISTORY_HOVER, "History", (btn, modifiers) -> {
-            this.muxer.pushState(this.historyPanelProvider.get());
-        });
-        actionButtons.add(historyButton);
-
-<<<<<<< HEAD
-        JButton alertDropDownButton = PanelUtils.createAlertDropDownButton(createdAlert -> {
-            this.alertManager.addAlert(createdAlert);
-            this.openAlert(createdAlert);
-        });
-        actionButtons.add(alertDropDownButton);
-=======
-        JPopupMenu popupMenu = new JPopupMenu();
-        ActionListener actionListener = e -> {
-            JMenuItem menuItem = (JMenuItem) e.getSource();
-            TriggerType tType = (TriggerType) menuItem.getClientProperty(TriggerType.class);
-            this.createAlert(tType);
-        };
-
-        Arrays.stream(TriggerType.values())
-            .forEach(tType -> {
-                JMenuItem c = new JMenuItem(WordUtils.capitalizeFully(tType.name().replaceAll("_", " ")));
-                c.setToolTipText(tType.getTooltip());
-                c.putClientProperty(TriggerType.class, tType);
-                c.addActionListener(actionListener);
-                popupMenu.add(c);
-            });
-        JButton addDropDownButton = DropDownButtonFactory.createDropDownButton(Icons.ADD, popupMenu);
-        addDropDownButton.setToolTipText("Create New Alert");
-        actionButtons.add(addDropDownButton);
->>>>>>> origin/master
-
-        topPanel.add(actionButtons, BorderLayout.EAST);
-
-        this.add(topPanel, BorderLayout.NORTH);
-
-        this.alertListPanel = new AlertListPanel(this.alertManager.getAlerts(), this::rebuild);
-        this.add(this.alertListPanel, BorderLayout.CENTER);
-
-        JPanel importExportGroup = new JPanel(new GridLayout(1, 2, 5, 0));
-        JButton importButton = new JButton("Import", Icons.IMPORT);
-        importButton.setHorizontalTextPosition(SwingConstants.LEFT);
-        importButton.addActionListener(ev -> {
-            ImportExportDialog importExportDialog = new ImportExportDialog(
-                SwingUtilities.getWindowAncestor(this),
-                (json, append) -> WatchdogPlugin.getInstance().getAlertManager().importAlerts(json, this.alertManager.getAlerts(), append, true)
-            );
-            importExportDialog.setVisible(true);
-        });
-        importExportGroup.add(importButton);
-        JButton exportButton = new JButton("Export", Icons.EXPORT);
-        exportButton.setHorizontalTextPosition(SwingConstants.LEFT);
-        exportButton.addActionListener(ev -> {
-            ImportExportDialog importExportDialog = new ImportExportDialog(SwingUtilities.getWindowAncestor(this), WatchdogPlugin.getInstance().getConfig().alerts());
-            importExportDialog.setVisible(true);
-        });
-        importExportGroup.add(exportButton);
-
-        JPanel bottomPanel = new JPanel(new GridLayout(0, 1, 3, 3));
-        bottomPanel.add(importExportGroup);
-        JButton hubButton = new JButton("Alert Hub", Icons.DOWNLOAD_ICON);
-        hubButton.setHorizontalTextPosition(SwingConstants.LEFT);
-        hubButton.addActionListener(ev -> {
-            AlertHubPanel alertHubPanel = this.alertHubPanelProvider.get();
-//            alertHubPanel.reloadList();
-            this.muxer.pushState(alertHubPanel);
-        });
-        bottomPanel.add(hubButton);
-        this.add(bottomPanel, BorderLayout.SOUTH);
-
-        this.revalidate();
-    }
-
-    public void openAlert(Alert alert) {
-        PluginPanel panel = this.createPluginPanel(alert);
-        if (panel != null) {
-            this.muxer.pushState(panel);
-        } else {
-            log.error(String.format("Tried to open an alert of type %s that doesn't have a panel.", alert.getClass()));
-        }
-    }
-
-    private PluginPanel createPluginPanel(Alert alert) {
-        if (alert instanceof ChatAlert) {
-            return new GameMessageAlertPanel(this, (ChatAlert) alert);
-        } else if (alert instanceof NotificationFiredAlert) {
-            return new NotificationFiredAlertPanel(this, (NotificationFiredAlert) alert);
-        } else if (alert instanceof StatChangedAlert) {
-            return new StatChangedAlertPanel(this, (StatChangedAlert) alert);
-        } else if (alert instanceof XPDropAlert) {
-            return new XPDropAlertPanel(this, (XPDropAlert) alert);
-        } else if (alert instanceof SpawnedAlert) {
-            return new SpawnedAlertPanel(this, (SpawnedAlert) alert);
-        } else if (alert instanceof InventoryAlert) {
-            return new InventoryAlertPanel(this, (InventoryAlert) alert);
-        } else if (alert instanceof AlertGroup) {
-            return new AlertGroupPanel(this, (AlertGroup) alert);
-        }
-
-        return null;
-    }
-
-    @Override
-    public void onActivate() {
-        this.rebuild();
-    }
-
-    public void scrollToBottom() {
-        JScrollBar scrollBar = this.alertListPanel.getScrollPane().getVerticalScrollBar();
-        scrollBar.setValue(scrollBar.getMaximum());
-    }
-}
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java.orig b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java.orig
deleted file mode 100644
index 0c3f5a8..0000000
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java.orig
+++ /dev/null
@@ -1,123 +0,0 @@
-package com.adamk33n3r.runelite.watchdog.ui.notifications.panels;
-
-import com.adamk33n3r.runelite.watchdog.NotificationType;
-import com.adamk33n3r.runelite.watchdog.notifications.Notification;
-import com.adamk33n3r.runelite.watchdog.ui.Icons;
-import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout;
-import com.adamk33n3r.runelite.watchdog.ui.panels.NotificationsPanel;
-import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
-
-import net.runelite.client.ui.ColorScheme;
-import net.runelite.client.ui.components.MouseDragEventForwarder;
-
-import lombok.Getter;
-
-<<<<<<< HEAD
-import javax.swing.*;
-import javax.swing.border.Border;
-import javax.swing.border.CompoundBorder;
-import javax.swing.border.EmptyBorder;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-=======
-import javax.swing.BorderFactory;
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.border.Border;
-import javax.swing.border.CompoundBorder;
-import javax.swing.border.EmptyBorder;
-import java.awt.BorderLayout;
-import java.awt.FlowLayout;
->>>>>>> origin/master
-
-public abstract class NotificationPanel extends JPanel {
-    // worldhopper - arrow down
-    // screenmarker - border color icon - pencil
-    // screenmarker/timetracking - delete icon - X
-    // timetracking - notify - bell
-    // timetracking - reset - circle arrow - used for in-focus?
-    // timetracking - start - right chevron
-    // loottracker - back arrow
-    // loottracker - collapsed/expanded
-    // info - import cloud
-    // info - github
-    // config - edit/back
-
-    @Getter
-    protected Notification notification;
-    protected Runnable onChangeListener;
-    protected PanelUtils.OnRemove onRemove;
-    protected JPanel settings = new JPanel(new StretchedStackedLayout(3, 3));
-
-    private static final Border NAME_BOTTOM_BORDER = new CompoundBorder(
-        BorderFactory.createMatteBorder(0, 0, 1, 0, ColorScheme.DARK_GRAY_COLOR),
-        BorderFactory.createMatteBorder(5, 10, 5, 0, ColorScheme.DARKER_GRAY_COLOR));
-
-    public NotificationPanel(Notification notification, NotificationsPanel parentPanel, Runnable onChangeListener, PanelUtils.OnRemove onRemove) {
-        this.notification = notification;
-        this.onChangeListener = onChangeListener;
-        this.onRemove = onRemove;
-
-        this.setLayout(new BorderLayout());
-        this.setBorder(new EmptyBorder(3, 0, 0, 0));
-        JPanel container = new JPanel(new StretchedStackedLayout(3, 3));
-        container.setBackground(ColorScheme.DARKER_GRAY_COLOR);
-
-        JPanel nameWrapper = new JPanel(new BorderLayout(3, 3));
-        container.add(nameWrapper);
-
-        nameWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR);
-        nameWrapper.setBorder(NAME_BOTTOM_BORDER);
-        NotificationType notificationType = notification.getType();
-        JLabel nameLabel = new JLabel(notificationType.getName());
-        nameLabel.setToolTipText(notificationType.getTooltip());
-        nameWrapper.add(nameLabel, BorderLayout.WEST);
-
-        MouseDragEventForwarder mouseDragEventForwarder = new MouseDragEventForwarder(parentPanel.getNotificationContainer());
-        nameWrapper.addMouseListener(mouseDragEventForwarder);
-        nameWrapper.addMouseMotionListener(mouseDragEventForwarder);
-        nameLabel.addMouseListener(mouseDragEventForwarder);
-        nameLabel.addMouseMotionListener(mouseDragEventForwarder);
-
-        // Right buttons
-        JPanel rightActions = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
-        rightActions.setBorder(new EmptyBorder(4, 0, 0, 0));
-        rightActions.setBackground(ColorScheme.DARKER_GRAY_COLOR);
-        nameWrapper.add(rightActions, BorderLayout.EAST);
-
-        JButton focusBtn = PanelUtils.createToggleActionButton(
-            Icons.FOREGROUND,
-            Icons.FOREGROUND_HOVER,
-            Icons.BACKGROUND,
-            Icons.BACKGROUND_HOVER,
-            "Switch to only fire notification while the game is in the background",
-            "Enable notification while the game is in the foreground",
-            notification.isFireWhenFocused(),
-            (btn, modifiers) -> {
-                notification.setFireWhenFocused(btn.isSelected());
-                onChangeListener.run();
-            });
-        rightActions.add(focusBtn, BorderLayout.EAST);
-
-        JButton testBtn = PanelUtils.createActionButton(
-            Icons.TEST,
-            Icons.TEST_HOVER,
-            "Test the notification",
-            (btn, modifiers) -> notification.fireForced(new String[]{ "1", "2", "3", "4", "5" }));
-        rightActions.add(testBtn);
-
-        JButton deleteBtn = PanelUtils.createActionButton(
-            Icons.DELETE,
-            Icons.DELETE_HOVER,
-            "Remove this notification",
-            (btn, modifiers) -> onRemove.elementRemoved(this));
-        rightActions.add(deleteBtn);
-
-        this.settings.setBorder(new EmptyBorder(5, 10, 5, 10));
-        this.settings.setBackground(ColorScheme.DARKER_GRAY_COLOR);
-        container.add(this.settings);
-
-        this.add(container, BorderLayout.CENTER);
-    }
-}
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java.orig b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java.orig
deleted file mode 100644
index a4df4d0..0000000
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/ScreenFlashNotificationPanel.java.orig
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.adamk33n3r.runelite.watchdog.ui.notifications.panels;
-
-import com.adamk33n3r.runelite.watchdog.alerts.FlashMode;
-import com.adamk33n3r.runelite.watchdog.notifications.ScreenFlash;
-import com.adamk33n3r.runelite.watchdog.ui.Icons;
-import com.adamk33n3r.runelite.watchdog.ui.panels.NotificationsPanel;
-import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils;
-
-import net.runelite.client.ui.components.ColorJButton;
-import net.runelite.client.ui.components.colorpicker.ColorPickerManager;
-
-<<<<<<< HEAD
-import javax.swing.*;
-=======
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.JComboBox;
-import javax.swing.JSpinner;
->>>>>>> origin/master
-
-public class ScreenFlashNotificationPanel extends NotificationPanel {
-    public ScreenFlashNotificationPanel(ScreenFlash screenFlash, NotificationsPanel parentPanel, ColorPickerManager colorPickerManager, Runnable onChangeListener, PanelUtils.OnRemove onRemove) {
-        super(screenFlash, parentPanel, onChangeListener, onRemove);
-
-        ColorJButton colorPickerBtn = PanelUtils.createColorPicker(
-            "Pick a color",
-            "The color to flash the screen",
-            "Flash Color",
-            this,
-            screenFlash.getColor(),
-            colorPickerManager,
-            true,
-            val -> {
-                screenFlash.setColor(val);
-                onChangeListener.run();
-            });
-        this.settings.add(colorPickerBtn);
-
-        JComboBox flashModeSelect = new JComboBox<>(FlashMode.values());
-        flashModeSelect.setToolTipText("The screen flash mode");
-        // TODO: Would be nice to move this somewhere else on import or something
-        if (screenFlash.getFlashMode() == null) {
-            screenFlash.setFlashMode(FlashMode.FLASH);
-            if (screenFlash.getFlashDuration() == 0) {
-                screenFlash.setFlashDuration(2);
-            }
-        }
-        flashModeSelect.setSelectedItem(screenFlash.getFlashMode());
-        flashModeSelect.setRenderer((list, value, index, isSelected, cellHasFocus) -> {
-            list.setToolTipText(value.getTooltip());
-            return new DefaultListCellRenderer().getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
-        });
-        flashModeSelect.addActionListener(e -> {
-            screenFlash.setFlashMode(flashModeSelect.getItemAt(flashModeSelect.getSelectedIndex()));
-            onChangeListener.run();
-        });
-        this.settings.add(flashModeSelect);
-
-        JSpinner flashDuration = PanelUtils.createSpinner(screenFlash.getFlashDuration(), 0, 10, 1, val -> {
-            screenFlash.setFlashDuration(val);
-            onChangeListener.run();
-        });
-        this.settings.add(PanelUtils.createIconComponent(Icons.CLOCK, "Duration of flash, use 0 to flash until cancelled", flashDuration));
-    }
-}
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java.orig b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java.orig
deleted file mode 100644
index 65cc84d..0000000
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java.orig
+++ /dev/null
@@ -1,369 +0,0 @@
-package com.adamk33n3r.runelite.watchdog.ui.panels;
-
-import com.adamk33n3r.runelite.watchdog.AlertManager;
-import com.adamk33n3r.runelite.watchdog.Displayable;
-import com.adamk33n3r.runelite.watchdog.TriggerType;
-import com.adamk33n3r.runelite.watchdog.WatchdogPanel;
-import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
-import com.adamk33n3r.runelite.watchdog.alerts.Alert;
-import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup;
-import com.adamk33n3r.runelite.watchdog.alerts.RegexMatcher;
-import com.adamk33n3r.runelite.watchdog.ui.*;
-
-import net.runelite.client.plugins.info.JRichTextPane;
-import net.runelite.client.ui.MultiplexingPluginPanel;
-import net.runelite.client.ui.PluginPanel;
-
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.text.WordUtils;
-
-import javax.swing.*;
-import javax.swing.border.CompoundBorder;
-import javax.swing.border.EmptyBorder;
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.GridLayout;
-import java.awt.event.FocusEvent;
-import java.awt.event.FocusListener;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-<<<<<<< HEAD
-import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.EXPORT_ICON;
-import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.IMPORT_ICON;
-import static com.adamk33n3r.runelite.watchdog.ui.notifications.panels.NotificationPanel.TEST_ICON;
-import static com.adamk33n3r.runelite.watchdog.ui.notifications.panels.NotificationPanel.TEST_ICON_HOVER;
-
-=======
->>>>>>> origin/master
-@Slf4j
-public abstract class AlertPanel extends PluginPanel {
-    private final JPanel controlContainer;
-    private final JPanel centerContainer;
-    protected final WatchdogPanel watchdogPanel;
-    protected final MultiplexingPluginPanel muxer;
-    protected final T alert;
-
-    private final AlertManager alertManager;
-
-<<<<<<< HEAD
-    public static final ImageIcon BACK_ICON;
-    public static final ImageIcon BACK_ICON_HOVER;
-    public static final ImageIcon IMPORT_ICON_HOVER;
-    public static final ImageIcon EXPORT_ICON_HOVER;
-    public static final ImageIcon REGEX_ICON;
-    public static final ImageIcon REGEX_ICON_HOVER;
-    public static final ImageIcon REGEX_SELECTED_ICON;
-    public static final ImageIcon REGEX_SELECTED_ICON_HOVER;
-
-    static {
-        final BufferedImage backIcon = ImageUtil.loadImageResource(ConfigPlugin.class, "config_back_icon.png");
-        BACK_ICON = new ImageIcon(backIcon);
-        BACK_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(backIcon, -120));
-
-        IMPORT_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(IMPORT_ICON.getImage(), -120));
-        EXPORT_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(EXPORT_ICON.getImage(), -120));
-
-        final BufferedImage regexIcon = ImageUtil.loadImageResource(AlertPanel.class, "regex_icon.png");
-        final BufferedImage regexIconSelected = ImageUtil.loadImageResource(AlertPanel.class, "regex_icon_selected.png");
-        REGEX_ICON = new ImageIcon(ImageUtil.luminanceOffset(regexIcon, -80));
-        REGEX_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(regexIcon, -120));
-        REGEX_SELECTED_ICON = new ImageIcon(regexIconSelected);
-        REGEX_SELECTED_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(regexIconSelected, -80));
-    }
-
-    public AlertPanel(WatchdogPanel watchdogPanel, T alert) {
-=======
-    private AlertPanel(MultiplexingPluginPanel muxer, Alert alert) {
->>>>>>> origin/master
-        super(false);
-
-        this.watchdogPanel = watchdogPanel;
-        this.muxer = watchdogPanel.getMuxer();
-        this.alert = alert;
-        this.alertManager = WatchdogPlugin.getInstance().getAlertManager();
-
-        this.setLayout(new BorderLayout());
-
-        JPanel northPanel = new JPanel(new StretchedStackedLayout(3, 3));
-        this.add(northPanel, BorderLayout.NORTH);
-
-        JPanel nameGroup = new JPanel(new BorderLayout());
-        nameGroup.setBorder(new EmptyBorder(10, 5, 10, 5));
-
-        TriggerType triggerType = this.alert.getType();
-        JLabel nameLabel = new JLabel(triggerType.getName());
-        nameLabel.setToolTipText(triggerType.getTooltip());
-        nameLabel.setForeground(Color.WHITE);
-        nameGroup.add(nameLabel, BorderLayout.CENTER);
-
-        JPanel rightButtons = new JPanel(new GridLayout(1, 0));
-
-        if (alert instanceof AlertGroup) {
-            JButton importAlertBtn = PanelUtils.createActionButton(
-                IMPORT_ICON,
-                IMPORT_ICON_HOVER,
-                "Import alert into this group",
-                (btn, modifiers) -> {
-                    ImportExportDialog importExportDialog = new ImportExportDialog(
-                        SwingUtilities.getWindowAncestor(this),
-                        (json, append) -> {
-                            boolean result = WatchdogPlugin.getInstance().getAlertManager().importAlerts(json, ((AlertGroup) alert).getAlerts(), append, true);
-                            this.rebuild();
-                            return result;
-                        }
-                    );
-                    importExportDialog.setVisible(true);
-                }
-            );
-            rightButtons.add(importAlertBtn);
-        } else {
-            JButton testAlert = PanelUtils.createActionButton(
-                TEST_ICON,
-                TEST_ICON_HOVER,
-                "Test the whole alert",
-                (btn, modifiers) -> {
-                    String[] triggerValues = {"1", "2", "3", "4", "5"};
-                    WatchdogPlugin.getInstance().getPanel().getHistoryPanelProvider().get().addEntry(alert, triggerValues);
-                    alert.getNotifications().forEach(notification -> notification.fireForced(triggerValues));
-                }
-            );
-            rightButtons.add(testAlert);
-        }
-
-        JButton exportAlertBtn = PanelUtils.createActionButton(
-            Icons.EXPORT,
-            Icons.EXPORT_HOVER,
-            "Export this alert",
-            (btn, modifiers) -> {
-                ImportExportDialog importExportDialog = new ImportExportDialog(
-                    SwingUtilities.getWindowAncestor(this),
-                    this.alertManager.getGson().toJson(new Alert[] { alert })
-                );
-                importExportDialog.setVisible(true);
-            }
-        );
-        rightButtons.add(exportAlertBtn);
-
-<<<<<<< HEAD
-=======
-        JButton testAlert = PanelUtils.createActionButton(
-            Icons.TEST,
-            Icons.TEST_HOVER,
-            "Test the whole alert",
-            (btn, modifiers) -> {
-                String[] triggerValues = {"1", "2", "3", "4", "5"};
-                WatchdogPlugin.getInstance().getPanel().getHistoryPanelProvider().get().addEntry(alert, triggerValues);
-                alert.getNotifications().forEach(notification -> notification.fireForced(triggerValues));
-            }
-        );
-        rightButtons.add(testAlert);
-
->>>>>>> origin/master
-        ToggleButton toggleButton = new ToggleButton();
-        toggleButton.setSelected(alert.isEnabled());
-        toggleButton.addItemListener(i -> {
-            alert.setEnabled(toggleButton.isSelected());
-            this.alertManager.saveAlerts();
-        });
-        rightButtons.add(toggleButton);
-
-        nameGroup.add(rightButtons, BorderLayout.EAST);
-
-        JButton backButton = PanelUtils.createActionButton(
-            Icons.BACK,
-            Icons.BACK_HOVER,
-            "Back",
-            (btn, modifiers) -> {
-                this.alertManager.saveAlerts();
-                this.muxer.popState();
-            }
-        );
-        backButton.setPreferredSize(new Dimension(22, 16));
-        backButton.setBorder(new EmptyBorder(0, 0, 0, 5));
-        nameGroup.add(backButton, BorderLayout.WEST);
-
-        northPanel.add(nameGroup, BorderLayout.NORTH);
-
-        this.controlContainer = new JPanel(new StretchedStackedLayout(3, 3));
-        this.controlContainer.setBorder(new EmptyBorder(0, 5, 0, 5));
-        northPanel.add(this.controlContainer, BorderLayout.NORTH);
-
-        this.centerContainer = new JPanel(new BorderLayout());
-        this.add(this.centerContainer, BorderLayout.CENTER);
-    }
-
-    public AlertPanel addLabel(String label) {
-        JLabel labelComp = new JLabel(label);
-        this.controlContainer.add(labelComp);
-        return this;
-    }
-
-    public AlertPanel addRichTextPane(String text) {
-        JRichTextPane richTextPane = new JRichTextPane();
-        richTextPane.setContentType("text/html");
-        richTextPane.setText(text);
-        richTextPane.setForeground(Color.WHITE);
-        this.controlContainer.add(richTextPane);
-        return this;
-    }
-
-    public AlertPanel addTextField(String placeholder, String tooltip, String initialValue, Consumer saveAction) {
-        PlaceholderTextField textField = new PlaceholderTextField(initialValue);
-        textField.setPlaceholder(placeholder);
-        textField.setToolTipText(tooltip);
-        textField.addFocusListener(new FocusListener() {
-            @Override
-            public void focusGained(FocusEvent e) {
-                textField.selectAll();
-            }
-
-            @Override
-            public void focusLost(FocusEvent e) {
-                saveAction.accept(textField.getText());
-                alertManager.saveAlerts();
-            }
-        });
-        this.controlContainer.add(textField);
-        return this;
-    }
-
-    public AlertPanel addTextArea(String placeholder, String tooltip, String initialValue, Consumer saveAction) {
-        JTextArea textArea = PanelUtils.createTextArea(placeholder, tooltip, initialValue, val -> {
-            saveAction.accept(val);
-            this.alertManager.saveAlerts();
-        });
-        this.controlContainer.add(textArea);
-        return this;
-    }
-
-    public AlertPanel addSpinner(String name, String tooltip, int initialValue, Consumer saveAction) {
-        return this.addSpinner(name, tooltip, initialValue, saveAction, -99, 99, 1);
-    }
-
-    public AlertPanel addSpinner(String name, String tooltip, int initialValue, Consumer saveAction, int min, int max, int step) {
-        JSpinner spinner = PanelUtils.createSpinner(initialValue, min, max, step, val -> {
-            saveAction.accept(val);
-            this.alertManager.saveAlerts();
-        });
-        this.controlContainer.add(PanelUtils.createLabeledComponent(name, tooltip, spinner));
-        return this;
-    }
-
-    public > AlertPanel addSelect(String name, String tooltip, Class enumType, E initialValue, Consumer saveAction) {
-        JComboBox select = new JComboBox<>(enumType.getEnumConstants());
-        select.setSelectedItem(initialValue);
-        select.setRenderer((list, value, index, isSelected, cellHasFocus) -> {
-            if (value instanceof Displayable) {
-                list.setToolTipText(((Displayable) value).getTooltip());
-                return new DefaultListCellRenderer().getListCellRendererComponent(list, ((Displayable) value).getName(), index, isSelected, cellHasFocus);
-            }
-            String titleized = WordUtils.capitalizeFully(value.name());
-            list.setToolTipText(titleized);
-            return new DefaultListCellRenderer().getListCellRendererComponent(list, titleized, index, isSelected, cellHasFocus);
-        });
-        select.addActionListener(e -> {
-            saveAction.accept(select.getItemAt(select.getSelectedIndex()));
-            this.alertManager.saveAlerts();
-        });
-        this.controlContainer.add(PanelUtils.createLabeledComponent(name, tooltip, select));
-        return this;
-    }
-
-    public AlertPanel addCheckbox(String name, String tooltip, boolean initialValue, Consumer saveAction) {
-        JCheckBox checkbox = PanelUtils.createCheckbox(name, tooltip, initialValue, val -> {
-            saveAction.accept(val);
-            this.alertManager.saveAlerts();
-        });
-        this.controlContainer.add(checkbox);
-        return this;
-    }
-
-    public AlertPanel addInputGroupWithSuffix(JComponent mainComponent, JComponent suffix) {
-        return this.addInputGroup(mainComponent, null, Collections.singletonList(suffix));
-    }
-
-    public AlertPanel addInputGroup(JComponent mainComponent, List prefixes, List suffixes) {
-        InputGroup textFieldGroup = new InputGroup(mainComponent)
-            .addPrefixes(prefixes)
-            .addSuffixes(suffixes);
-        this.controlContainer.add(textFieldGroup);
-        return this;
-    }
-
-    public AlertPanel addAlertDefaults() {
-        return this.addTextField("Enter the alert name...", "Name of Alert", this.alert.getName(), this.alert::setName)
-            .addSpinner(
-                "Debounce Time (ms)",
-                "How long to wait before allowing this alert to trigger again in milliseconds",
-                this.alert.getDebounceTime(),
-                this.alert::setDebounceTime,
-                0,
-                8640000, // 6 hours - max time a player can be logged in
-                100
-            );
-    }
-
-    public AlertPanel addIf(Consumer> panel, Supplier ifFunc) {
-        if (ifFunc.get()) {
-            panel.accept(this);
-        }
-        return this;
-    }
-
-    public AlertPanel addRegexMatcher(RegexMatcher regexMatcher, String placeholder, String tooltip) {
-        return this.addInputGroupWithSuffix(
-            PanelUtils.createTextArea(placeholder, tooltip, regexMatcher.getPattern(), msg -> {
-                if (!PanelUtils.isPatternValid(this, msg, regexMatcher.isRegexEnabled()))
-                    return;
-                regexMatcher.setPattern(msg);
-                this.alertManager.saveAlerts();
-            }),
-            PanelUtils.createToggleActionButton(
-                Icons.REGEX_SELECTED,
-                Icons.REGEX_SELECTED_HOVER,
-                Icons.REGEX,
-                Icons.REGEX_HOVER,
-                "Disable regex",
-                "Enable regex",
-                regexMatcher.isRegexEnabled(),
-                (btn, modifiers) -> {
-                    regexMatcher.setRegexEnabled(btn.isSelected());
-                    this.alertManager.saveAlerts();
-                }
-            )
-        );
-    }
-
-    public AlertPanel addNotifications() {
-        NotificationsPanel notificationPanel = new NotificationsPanel(this.alert);
-        WatchdogPlugin.getInstance().getInjector().injectMembers(notificationPanel);
-        notificationPanel.setBorder(new CompoundBorder(new EmptyBorder(0, 5, 0, 5), new HorizontalRuleBorder(10)));
-        this.centerContainer.add(notificationPanel);
-
-        return this;
-    }
-
-    public AlertPanel addSubPanel(JPanel sub) {
-        this.centerContainer.add(sub);
-
-        return this;
-    }
-
-    protected abstract void build();
-    protected void rebuild() {
-        this.controlContainer.removeAll();
-        this.centerContainer.removeAll();
-        this.build();
-        this.revalidate();
-        this.repaint();
-    }
-
-    @Override
-    public void onActivate() {
-        this.rebuild();
-    }
-}
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java.orig b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java.orig
deleted file mode 100644
index 56e1fb4..0000000
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java.orig
+++ /dev/null
@@ -1,115 +0,0 @@
-package com.adamk33n3r.runelite.watchdog.ui.panels;
-
-import com.adamk33n3r.runelite.watchdog.alerts.Alert;
-import com.adamk33n3r.runelite.watchdog.notifications.IMessageNotification;
-<<<<<<< HEAD
-=======
-import com.adamk33n3r.runelite.watchdog.ui.Icons;
->>>>>>> origin/master
-import com.adamk33n3r.runelite.watchdog.ui.SearchBar;
-import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout;
-
-import net.runelite.client.ui.MultiplexingPluginPanel;
-import net.runelite.client.ui.PluginPanel;
-import net.runelite.client.util.Text;
-
-import com.google.common.base.Splitter;
-import lombok.extern.slf4j.Slf4j;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-import javax.inject.Singleton;
-import javax.swing.*;
-import javax.swing.border.EmptyBorder;
-<<<<<<< HEAD
-import java.awt.*;
-=======
-import java.awt.BorderLayout;
-import java.awt.Dimension;
->>>>>>> origin/master
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-@Slf4j
-@Singleton
-public class HistoryPanel extends PluginPanel {
-    private final Provider muxer;
-    private final ScrollablePanel historyItems;
-    private final List previousAlerts = new ArrayList<>();
-    private final JLabel noHistory;
-
-    private static final int MAX_HISTORY_ITEMS = 100;
-    private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings();
-
-    @Inject
-    public HistoryPanel(Provider muxer) {
-        super(false);
-        this.muxer = muxer;
-
-        this.setLayout(new BorderLayout());
-
-        JPanel topPanel = new JPanel(new BorderLayout(0, 5));
-        topPanel.setBorder(new EmptyBorder(0, 0, 5, 0));
-        JButton backButton = PanelUtils.createActionButton(
-            Icons.BACK,
-            Icons.BACK_HOVER,
-            "Back",
-            (btn, modifiers) -> this.muxer.get().popState()
-        );
-        backButton.setPreferredSize(new Dimension(22, 16));
-        backButton.setBorder(new EmptyBorder(0, 0, 0, 5));
-        topPanel.add(backButton, BorderLayout.WEST);
-        topPanel.add(new SearchBar(this::updateFilter));
-        this.noHistory = new JLabel("No history items");
-        this.noHistory.setHorizontalAlignment(SwingConstants.CENTER);
-        topPanel.add(this.noHistory, BorderLayout.SOUTH);
-        this.add(topPanel, BorderLayout.NORTH);
-
-        this.historyItems = new ScrollablePanel(new StretchedStackedLayout(3, 3));
-        this.historyItems.setBorder(new EmptyBorder(0, 10, 0, 10));
-        this.historyItems.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
-        this.historyItems.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
-        this.historyItems.setScrollableBlockIncrement(ScrollablePanel.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
-        JScrollPane scroll = new JScrollPane(this.historyItems, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
-
-        this.add(scroll, BorderLayout.CENTER);
-    }
-
-    public void addEntry(Alert alert, String[] triggerValues) {
-        this.noHistory.setVisible(false);
-        HistoryEntryPanel historyEntryPanel = new HistoryEntryPanel(alert, triggerValues);
-        this.previousAlerts.add(0, historyEntryPanel);
-        this.historyItems.add(historyEntryPanel, 0);
-        if (this.historyItems.getComponents().length > MAX_HISTORY_ITEMS) {
-            this.previousAlerts.remove(this.previousAlerts.size() - 1);
-            this.historyItems.remove(this.historyItems.getComponents().length - 1);
-        }
-        this.revalidate();
-        this.repaint();
-    }
-
-    // TODO: Abstract this out into a filterpanel type thing
-    private void updateFilter(String search) {
-        this.historyItems.removeAll();
-        String upperSearch = search.toUpperCase();
-        this.previousAlerts.stream().filter(historyEntryPanel -> {
-            Alert alert = historyEntryPanel.getAlert();
-            Stream keywords = Stream.concat(Stream.of(
-                alert.getName(),
-                alert.getType().getName()
-            ), alert.getNotifications().stream().flatMap(notification -> {
-                Stream notificationType = Stream.of(notification.getType().getName());
-                if (notification instanceof IMessageNotification) {
-                    return Stream.concat(notificationType, Stream.of(((IMessageNotification) notification).getMessage()));
-                }
-                return notificationType;
-            })).map(String::toUpperCase);
-            return Text.matchesSearchTerms(SPLITTER.split(upperSearch), keywords.collect(Collectors.toList()));
-        }).forEach(this.historyItems::add);
-        this.revalidate();
-        // Idk why I need to repaint sometimes and the PluginListPanel doesn't
-        this.repaint();
-    }
-}
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java.orig b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java.orig
deleted file mode 100644
index 845de77..0000000
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java.orig
+++ /dev/null
@@ -1,158 +0,0 @@
-package com.adamk33n3r.runelite.watchdog.ui.panels;
-
-import com.adamk33n3r.runelite.watchdog.AlertManager;
-import com.adamk33n3r.runelite.watchdog.NotificationType;
-import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
-import com.adamk33n3r.runelite.watchdog.alerts.Alert;
-<<<<<<< HEAD
-import com.adamk33n3r.runelite.watchdog.notifications.GameMessage;
-import com.adamk33n3r.runelite.watchdog.notifications.Notification;
-import com.adamk33n3r.runelite.watchdog.notifications.NotificationEvent;
-import com.adamk33n3r.runelite.watchdog.notifications.Overhead;
-import com.adamk33n3r.runelite.watchdog.notifications.Overlay;
-import com.adamk33n3r.runelite.watchdog.notifications.ScreenFlash;
-import com.adamk33n3r.runelite.watchdog.notifications.Sound;
-import com.adamk33n3r.runelite.watchdog.notifications.SoundEffect;
-import com.adamk33n3r.runelite.watchdog.notifications.TextToSpeech;
-import com.adamk33n3r.runelite.watchdog.notifications.TrayNotification;
-import com.adamk33n3r.runelite.watchdog.ui.StretchedStackedLayout;
-=======
-import com.adamk33n3r.runelite.watchdog.notifications.*;
-import com.adamk33n3r.runelite.watchdog.ui.Icons;
->>>>>>> origin/master
-import com.adamk33n3r.runelite.watchdog.ui.dropdownbutton.DropDownButtonFactory;
-import com.adamk33n3r.runelite.watchdog.ui.notifications.panels.*;
-
-import net.runelite.client.ui.components.DragAndDropReorderPane;
-import net.runelite.client.ui.components.colorpicker.ColorPickerManager;
-
-import com.google.inject.Injector;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-
-import javax.inject.Inject;
-import javax.swing.*;
-<<<<<<< HEAD
-import java.awt.*;
-=======
-import java.awt.BorderLayout;
->>>>>>> origin/master
-import java.awt.event.ActionListener;
-import java.util.Arrays;
-
-@Slf4j
-public class NotificationsPanel extends JPanel {
-    private final Alert alert;
-
-    @Inject
-    private ColorPickerManager colorPickerManager;
-
-    @Inject
-    private AlertManager alertManager;
-
-    @Getter
-    private final DragAndDropReorderPane notificationContainer;
-
-    public NotificationsPanel(Alert alert) {
-        this.alert = alert;
-        this.setLayout(new BorderLayout(0, 5));
-        this.notificationContainer = new DragAndDropReorderPane();
-        this.notificationContainer.addDragListener((c) -> {
-            int pos = this.notificationContainer.getPosition(c);
-            NotificationPanel notificationPanel = (NotificationPanel) c;
-            Notification notification = notificationPanel.getNotification();
-//            log.debug("drag listener: " + notification.getType().getName() + " to " + pos);
-            notification.getAlert().moveNotificationTo(notification, pos);
-            this.alertManager.saveAlerts();
-        });
-
-        JPopupMenu popupMenu = new JPopupMenu();
-        ActionListener actionListener = e -> {
-            JMenuItem menuItem = (JMenuItem) e.getSource();
-            NotificationType nType = (NotificationType) menuItem.getClientProperty(NotificationType.class);
-            this.addPanel(this.createNotification(nType));
-            this.notificationContainer.revalidate();
-            this.alertManager.saveAlerts();
-        };
-        Arrays.stream(NotificationType.values()).sorted().forEach(nType -> {
-            JMenuItem c = new JMenuItem(nType.getName());
-            c.setToolTipText(nType.getTooltip());
-            c.putClientProperty(NotificationType.class, nType);
-            c.addActionListener(actionListener);
-            popupMenu.add(c);
-        });
-<<<<<<< HEAD
-        JButton addDropDownButton = DropDownButtonFactory.createDropDownButton(ADD_ICON, popupMenu);
-        addDropDownButton.setPreferredSize(new Dimension(40, addDropDownButton.getPreferredSize().height));
-=======
-        JButton addDropDownButton = DropDownButtonFactory.createDropDownButton(Icons.ADD, popupMenu);
->>>>>>> origin/master
-        addDropDownButton.setToolTipText("Create New Notification");
-        JPanel buttonPanel = new JPanel(new BorderLayout());
-        buttonPanel.add(new JLabel("Notifications"), BorderLayout.WEST);
-        buttonPanel.add(addDropDownButton, BorderLayout.EAST);
-
-        this.add(buttonPanel, BorderLayout.NORTH);
-
-        ScrollablePanel scrollablePanel = new ScrollablePanel(new StretchedStackedLayout(3, 3));
-        scrollablePanel.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
-        scrollablePanel.setScrollableHeight(ScrollablePanel.ScrollableSizeHint.STRETCH);
-        scrollablePanel.setScrollableBlockIncrement(ScrollablePanel.VERTICAL, ScrollablePanel.IncrementType.PERCENT, 10);
-        scrollablePanel.add(this.notificationContainer);
-        JScrollPane scrollPane = new JScrollPane(scrollablePanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
-        this.add(scrollPane, BorderLayout.CENTER);
-    }
-
-    // After inject, build
-    @Inject
-    public void rebuild() {
-        this.notificationContainer.removeAll();
-
-        for (Notification notification : this.alert.getNotifications()) {
-            this.addPanel(notification);
-        }
-
-        this.notificationContainer.revalidate();
-        this.notificationContainer.repaint();
-    }
-
-    private void addPanel(Notification notification) {
-        PanelUtils.OnRemove removeNotification = (removedPanel) -> {
-            this.alert.getNotifications().remove(notification);
-            this.notificationContainer.remove(removedPanel);
-            this.notificationContainer.revalidate();
-            this.alertManager.saveAlerts();
-        };
-
-        NotificationPanel notificationPanel = null;
-        if (notification instanceof GameMessage) {
-            notificationPanel = new MessageNotificationPanel((GameMessage) notification, this, this.alertManager::saveAlerts, removeNotification);
-        } else if (notification instanceof TextToSpeech)
-            notificationPanel = new TextToSpeechNotificationPanel((TextToSpeech) notification, this, this.alertManager::saveAlerts, removeNotification);
-        else if (notification instanceof Sound)
-            notificationPanel = new SoundNotificationPanel((Sound)notification, this, this.alertManager::saveAlerts, removeNotification);
-        else if (notification instanceof SoundEffect)
-            notificationPanel = new SoundEffectNotificationPanel((SoundEffect)notification, this, this.alertManager::saveAlerts, removeNotification);
-        else if (notification instanceof TrayNotification)
-            notificationPanel = new MessageNotificationPanel((TrayNotification)notification, this, this.alertManager::saveAlerts, removeNotification);
-        else if (notification instanceof ScreenFlash)
-            notificationPanel = new ScreenFlashNotificationPanel((ScreenFlash) notification, this, this.colorPickerManager, this.alertManager::saveAlerts, removeNotification);
-        else if (notification instanceof Overhead)
-            notificationPanel = new OverheadNotificationPanel((Overhead) notification, this, this.alertManager::saveAlerts, removeNotification);
-        else if (notification instanceof Overlay)
-            notificationPanel = new OverlayNotificationPanel((Overlay) notification, this, this.colorPickerManager, this.alertManager::saveAlerts, removeNotification);
-        else if (notification instanceof NotificationEvent)
-            notificationPanel = new MessageNotificationPanel((NotificationEvent) notification, this, this.alertManager::saveAlerts, removeNotification);
-
-        if (notificationPanel != null)
-            this.notificationContainer.add(notificationPanel);
-    }
-
-    private Notification createNotification(NotificationType notificationType) {
-        Injector injector = WatchdogPlugin.getInstance().getInjector();
-        Notification notification = injector.getInstance(notificationType.getImplClass());
-        notification.setAlert(this.alert);
-        this.alert.getNotifications().add(notification);
-        return notification;
-    }
-}
diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java.orig b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java.orig
deleted file mode 100644
index 63c4f12..0000000
--- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java.orig
+++ /dev/null
@@ -1,331 +0,0 @@
-package com.adamk33n3r.runelite.watchdog.ui.panels;
-
-import com.adamk33n3r.runelite.watchdog.Displayable;
-import com.adamk33n3r.runelite.watchdog.TriggerType;
-import com.adamk33n3r.runelite.watchdog.Util;
-<<<<<<< HEAD
-import com.adamk33n3r.runelite.watchdog.WatchdogPlugin;
-import com.adamk33n3r.runelite.watchdog.alerts.Alert;
-=======
-import com.adamk33n3r.runelite.watchdog.ui.Icons;
->>>>>>> origin/master
-import com.adamk33n3r.runelite.watchdog.ui.PlaceholderTextArea;
-import com.adamk33n3r.runelite.watchdog.ui.dropdownbutton.DropDownButtonFactory;
-
-import net.runelite.client.ui.DynamicGridLayout;
-import net.runelite.client.ui.components.ColorJButton;
-import net.runelite.client.ui.components.colorpicker.ColorPickerManager;
-import net.runelite.client.ui.components.colorpicker.RuneliteColorPicker;
-import net.runelite.client.util.ColorUtil;
-import net.runelite.client.util.ImageUtil;
-import net.runelite.client.util.SwingUtil;
-
-import org.apache.commons.text.WordUtils;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import javax.swing.filechooser.FileFilter;
-import java.awt.*;
-<<<<<<< HEAD
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.FocusEvent;
-import java.awt.event.FocusListener;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-=======
-import java.awt.event.*;
->>>>>>> origin/master
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.util.Arrays;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-import java.util.stream.Collectors;
-
-import static com.adamk33n3r.runelite.watchdog.WatchdogPanel.ADD_ICON;
-
-public class PanelUtils {
-    private static final ImageIcon FOLDER;
-    static {
-        final BufferedImage folderImg = ImageUtil.loadImageResource(Icons.class, "mdi_folder-open.png");
-
-        FOLDER = new ImageIcon(folderImg);
-    }
-
-    private PanelUtils () {}
-
-    public static JPanel createLabeledComponent(String label, String tooltip, Component component) {
-        return createLabeledComponent(label, tooltip, component, false);
-    }
-
-    public static JPanel createLabeledComponent(String label, String tooltip, Component component, boolean twoLines) {
-        JPanel panel = new JPanel();
-        if (twoLines) {
-            panel.setLayout(new DynamicGridLayout(2, 0, 5, 5));
-        } else {
-            panel.setLayout(new BorderLayout(5, 0));
-        }
-        JLabel jLabel = new JLabel(label);
-        jLabel.setToolTipText(tooltip);
-        panel.add(jLabel, BorderLayout.WEST);
-        panel.add(component);
-        return panel;
-    }
-
-    public static JPanel createIconComponent(ImageIcon icon, String tooltip, Component component) {
-        JPanel panel = new JPanel(new BorderLayout(5, 0));
-        panel.setBackground(null);
-        JLabel jLabel = new JLabel(icon);
-        jLabel.setToolTipText(tooltip);
-        panel.add(jLabel, BorderLayout.WEST);
-        panel.add(component);
-        return panel;
-    }
-
-    public static JPanel createFileChooser(String label, String tooltip, ActionListener actionListener, String path, String filterLabel, String... filters) {
-        JPanel panel = new JPanel(new GridLayout(1, 1));
-        panel.setBackground(null);
-        if (label != null) {
-            panel.setLayout(new GridLayout(2, 1));
-            JLabel jLabel = new JLabel(label);
-            jLabel.setToolTipText(tooltip);
-            panel.add(jLabel);
-        }
-        JPanel chooserPanel = new JPanel(new BorderLayout(5, 0));
-        chooserPanel.setBackground(null);
-        panel.add(chooserPanel);
-        JTextField pathField = new JTextField(path);
-        pathField.setToolTipText(path);
-        pathField.setEditable(false);
-        chooserPanel.add(pathField);
-        JFileChooser fileChooser = new JFileChooser(path);
-        fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
-        fileChooser.setAcceptAllFileFilterUsed(false);
-        fileChooser.setFileFilter(new FileFilter() {
-            @Override
-            public boolean accept(File file) {
-                if (file.isDirectory()) {
-                    return true;
-                }
-                String fileName = file.getName();
-                int i = fileName.lastIndexOf('.');
-                String extension = null;
-                if (i > 0 &&  i < fileName.length() - 1) {
-                    extension = fileName.substring(i+1).toLowerCase();
-                }
-                if (extension != null) {
-                    return Arrays.asList(filters).contains(extension);
-                }
-                return false;
-            }
-
-            @Override
-            public String getDescription() {
-                return filterLabel + Arrays.stream(filters).map(ft -> "*." + ft).collect(Collectors.joining(", ", " (", ")"));
-            }
-        });
-        JButton fileChooserButton = new JButton(null, FOLDER);
-        fileChooserButton.setToolTipText(tooltip);
-        fileChooserButton.addActionListener(e -> {
-            int result = fileChooser.showOpenDialog(panel);
-            if (result == JFileChooser.APPROVE_OPTION) {
-                String absPath = fileChooser.getSelectedFile().getAbsolutePath();
-                pathField.setText(absPath);
-                pathField.setToolTipText(absPath);
-                actionListener.actionPerformed(new ActionEvent(fileChooser, result, "selected"));
-            }
-        });
-        chooserPanel.add(fileChooserButton, BorderLayout.EAST);
-        return panel;
-    }
-
-    public static JButton createActionButton(ImageIcon icon, ImageIcon rolloverIcon, String tooltip, ButtonClickListener listener) {
-        JButton actionButton = new JButton();
-        SwingUtil.removeButtonDecorations(actionButton);
-        actionButton.setPreferredSize(new Dimension(16, 16));
-        actionButton.setIcon(icon);
-        actionButton.setRolloverIcon(rolloverIcon);
-        actionButton.setToolTipText(tooltip);
-        actionButton.addActionListener(ev -> listener.clickPerformed(actionButton, ev.getModifiers()));
-        return actionButton;
-    }
-
-    public interface ButtonClickListener {
-        void clickPerformed(JButton button, int modifiers);
-    }
-
-    public interface OnRemove {
-        void elementRemoved(JComponent removed);
-    }
-
-    public static JButton createToggleActionButton(ImageIcon onIcon, ImageIcon onRolloverIcon, ImageIcon offIcon, ImageIcon offRolloverIcon, String onTooltip, String offTooltip, boolean initialValue, ButtonClickListener listener) {
-        JButton actionButton = createActionButton(offIcon, offRolloverIcon, offTooltip, (btn, modifiers) -> {
-            btn.setSelected(!btn.isSelected());
-            listener.clickPerformed(btn, modifiers);
-        });
-        SwingUtil.addModalTooltip(actionButton, onTooltip, offTooltip);
-        actionButton.setSelectedIcon(onIcon);
-        actionButton.setRolloverSelectedIcon(onRolloverIcon);
-        actionButton.setSelected(initialValue);
-        return actionButton;
-    }
-
-    public static JCheckBox createCheckbox(String name, String tooltip, boolean initialValue, Consumer onChange) {
-        JCheckBox checkbox = new JCheckBox(name, initialValue);
-        checkbox.setToolTipText(tooltip);
-        checkbox.addItemListener(ev -> {
-            onChange.accept(checkbox.isSelected());
-        });
-        return checkbox;
-    }
-
-
-    public static JTextArea createTextArea(String placeholder, String tooltip, String initialValue, Consumer onChange) {
-        PlaceholderTextArea textArea = new PlaceholderTextArea(initialValue);
-        textArea.setPlaceholder(placeholder);
-        textArea.setToolTipText(tooltip);
-        textArea.setLineWrap(true);
-        textArea.setWrapStyleWord(true);
-        textArea.setMargin(new Insets(4, 6, 5, 6));
-        textArea.addFocusListener(new FocusListener() {
-            @Override
-            public void focusGained(FocusEvent e) {
-                textArea.selectAll();
-            }
-
-            @Override
-            public void focusLost(FocusEvent e) {
-                onChange.accept(textArea.getText());
-            }
-        });
-
-        return textArea;
-    }
-
-    public static JSpinner createSpinner(int initialValue, int min, int max, int step, Consumer onChange) {
-        JSpinner spinner = new JSpinner(new SpinnerNumberModel(initialValue, min, max, step));
-        spinner.addChangeListener(e -> {
-            onChange.accept((Integer) spinner.getValue());
-        });
-
-        return spinner;
-    }
-
-    public static ColorJButton createColorPicker(String placeholder, String tooltip, String windowTitle, Component parentComponent, Color initialValue, ColorPickerManager colorPickerManager, boolean showAlpha, Consumer onChange) {
-        ColorJButton colorPickerBtn = new ColorJButton(placeholder, Color.BLACK);
-        if (initialValue != null) {
-            String colorHex = "#" + ColorUtil.colorToAlphaHexCode(initialValue).toUpperCase();
-            colorPickerBtn.setText(colorHex);
-            colorPickerBtn.setColor(initialValue);
-        }
-        colorPickerBtn.setToolTipText(tooltip);
-        colorPickerBtn.setFocusable(false);
-        colorPickerBtn.addMouseListener(new MouseAdapter() {
-            @Override
-            public void mouseClicked(MouseEvent e) {
-                RuneliteColorPicker colorPicker = colorPickerManager.create(
-                    SwingUtilities.getWindowAncestor(colorPickerBtn),
-                    colorPickerBtn.getColor(),
-                    windowTitle,
-                    !showAlpha);
-                colorPicker.setLocation(parentComponent.getLocationOnScreen());
-                colorPicker.setOnColorChange(c -> {
-                    colorPickerBtn.setColor(c);
-                    colorPickerBtn.setText("#" + ColorUtil.colorToAlphaHexCode(c).toUpperCase());
-                });
-                colorPicker.setOnClose(onChange);
-                colorPicker.setVisible(true);
-            }
-        });
-
-        return colorPickerBtn;
-    }
-
-    public static > JComboBox createSelect(T[] items, T initialValue, Consumer onChange) {
-        JComboBox select = new JComboBox<>(items);
-        select.setSelectedItem(initialValue);
-        select.setRenderer((list, value, index, isSelected, cellHasFocus) -> {
-            if (value instanceof Displayable) {
-                list.setToolTipText(((Displayable) value).getTooltip());
-                return new DefaultListCellRenderer().getListCellRendererComponent(list, ((Displayable) value).getName(), index, isSelected, cellHasFocus);
-            }
-            String titleized = value == null ? "null" : WordUtils.capitalizeFully(value.name());
-            list.setToolTipText(titleized);
-            return new DefaultListCellRenderer().getListCellRendererComponent(list, titleized, index, isSelected, cellHasFocus);
-        });
-        select.addActionListener(e -> {
-            onChange.accept(select.getItemAt(select.getSelectedIndex()));
-        });
-
-        return select;
-    }
-
-    public static  JComboBox createSelect(T[] items, T initialValue, @Nullable Function onRender, Consumer onChange) {
-        JComboBox select = new JComboBox<>(items);
-        select.setSelectedItem(initialValue);
-        select.setRenderer((list, value, index, isSelected, cellHasFocus) -> {
-            if (onRender != null) {
-                String title = value == null ? "Loading..." : onRender.apply(value);
-                return new DefaultListCellRenderer().getListCellRendererComponent(list, title, index, isSelected, cellHasFocus);
-            }
-
-            if (value instanceof Displayable) {
-                list.setToolTipText(((Displayable) value).getTooltip());
-                return new DefaultListCellRenderer().getListCellRendererComponent(list, ((Displayable) value).getName(), index, isSelected, cellHasFocus);
-            }
-            String titleized = value == null ? "null" : WordUtils.capitalizeFully(value.toString());
-            list.setToolTipText(titleized);
-            return new DefaultListCellRenderer().getListCellRendererComponent(list, titleized, index, isSelected, cellHasFocus);
-        });
-        select.addActionListener(e -> {
-            onChange.accept(select.getItemAt(select.getSelectedIndex()));
-        });
-
-        return select;
-    }
-
-    public static boolean isPatternValid(Component parent, String pattern, boolean isRegex) {
-        try {
-            Pattern.compile(isRegex ? pattern : Util.createRegexFromGlob(pattern));
-            return true;
-        } catch (PatternSyntaxException ex) {
-            JLabel errorLabel = new JLabel("" + ex.getMessage().replaceAll("\n", "
").replaceAll(" ", " ") + ""); - errorLabel.setFont(new Font("Monospaced", Font.PLAIN, 12)); - JOptionPane.showMessageDialog(parent, errorLabel, "Error in regex/pattern", JOptionPane.ERROR_MESSAGE); - return false; - } - } - - public static JButton createAlertDropDownButton(Consumer onCreate) { - ActionListener actionListener = e -> { - JMenuItem menuItem = (JMenuItem) e.getSource(); - TriggerType tType = (TriggerType) menuItem.getClientProperty(TriggerType.class); - Alert createdAlert = WatchdogPlugin.getInstance().getInjector().getInstance(tType.getImplClass()); - onCreate.accept(createdAlert); - }; - - JPopupMenu popupMenu = new JPopupMenu(); - JMenuItem alertGroupMenuItem = new JMenuItem(TriggerType.ALERT_GROUP.getName()); - alertGroupMenuItem.setToolTipText(TriggerType.ALERT_GROUP.getTooltip()); - alertGroupMenuItem.putClientProperty(TriggerType.class, TriggerType.ALERT_GROUP); - alertGroupMenuItem.addActionListener(actionListener); - popupMenu.add(alertGroupMenuItem); - popupMenu.addSeparator(); - Arrays.stream(TriggerType.values()) - .filter(tType -> tType != TriggerType.ALERT_GROUP) - .forEach(tType -> { - JMenuItem c = new JMenuItem(tType.getName()); - c.setToolTipText(tType.getTooltip()); - c.putClientProperty(TriggerType.class, tType); - c.addActionListener(actionListener); - popupMenu.add(c); - }); - JButton addDropDownButton = DropDownButtonFactory.createDropDownButton(ADD_ICON, popupMenu); - addDropDownButton.setPreferredSize(new Dimension(40, addDropDownButton.getPreferredSize().height)); - addDropDownButton.setToolTipText("Create New Alert"); - return addDropDownButton; - } -} diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 7cf9cd6..287ce8d 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,6 +1,6 @@ -#Wed Oct 18 03:38:22 EDT 2023 -VERSION_BUILD=3918 -VERSION_PHASE=rc1 +#Sat Oct 21 02:39:24 EDT 2023 +VERSION_BUILD=4073 +VERSION_PHASE=rc2 VERSION_MAJOR=3 VERSION_MINOR=0 VERSION_PATCH=0 diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties.orig b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties.orig deleted file mode 100644 index 7a54b81..0000000 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties.orig +++ /dev/null @@ -1,14 +0,0 @@ -<<<<<<< HEAD -#Tue Oct 10 02:44:17 EDT 2023 -VERSION_BUILD=3830 -VERSION_PHASE=rc1 -VERSION_MAJOR=3 -VERSION_MINOR=0 -======= -#Thu Oct 12 04:51:42 EDT 2023 -VERSION_BUILD=2513 -VERSION_PHASE=release -VERSION_MAJOR=2 -VERSION_MINOR=15 ->>>>>>> origin/master -VERSION_PATCH=0 diff --git a/src/test/java/com/adamk33n3r/runelite/watchdog/AlertManagerTest.java b/src/test/java/com/adamk33n3r/runelite/watchdog/AlertManagerTest.java new file mode 100644 index 0000000..9f9c2a0 --- /dev/null +++ b/src/test/java/com/adamk33n3r/runelite/watchdog/AlertManagerTest.java @@ -0,0 +1,134 @@ +package com.adamk33n3r.runelite.watchdog; + +import com.adamk33n3r.runelite.watchdog.alerts.Alert; +import com.adamk33n3r.runelite.watchdog.alerts.ChatAlert; +import com.adamk33n3r.runelite.watchdog.notifications.Notification; +import com.adamk33n3r.runelite.watchdog.notifications.Overlay; + +import net.runelite.api.Client; +import net.runelite.client.account.SessionManager; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.ui.ClientUI; +import net.runelite.client.ui.MultiplexingPluginPanel; +import net.runelite.http.api.RuneLiteAPI; + +import com.google.gson.Gson; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.internal.util.reflection.FieldSetter; +import org.mockito.junit.MockitoJUnitRunner; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import java.util.concurrent.ScheduledExecutorService; + +@RunWith(MockitoJUnitRunner.class) +public class AlertManagerTest { + @Mock + @Bind + EventBus eventBus; + + @Mock + @Bind + ScheduledExecutorService executor; + @Mock + @Bind + Client client; + @Mock + @Bind + ClientUI clientUI; + @Mock + @Bind + SessionManager sessionManager; + @Mock + @Bind + ConfigManager configManager; + @Bind + Gson clientGson = RuneLiteAPI.GSON; + @Bind + @Named("watchdog.pluginVersion") + private final String pluginVersion = "3.0.0-TEST"; + + @Mock + @Bind + WatchdogConfig watchdogConfig; + + @Mock + @Bind + WatchdogPanel watchdogPanel; + + @Bind + @Named("watchdog.helpURL") + private final String HELP_URL = ""; + + @Bind + @Named("watchdog.discordURL") + private final String DISCORD_URL = ""; + + @Bind + @Named("watchdog.kofiURL") + private final String KOFI_URL = ""; + + @Bind + @Named("watchdog.pluginVersionFull") + private final String PLUGIN_VERSION_FULL = ""; + + @Bind + @Named("VERSION_PHASE") + private final String PLUGIN_VERSION_PHASE = ""; + + @Inject + AlertManager alertManager; + + @Bind + Provider multiplexingPluginPanelProvider = () -> alertManager.getWatchdogPanel().getMuxer(); + + @Before + public void before() throws NoSuchFieldException { + BoundFieldModule module = BoundFieldModule.of(this); + Injector injector = Guice.createInjector(module); + injector.injectMembers(this); + + WatchdogPlugin watchdogPlugin = new WatchdogPlugin(); + FieldSetter.setField(watchdogPlugin, Plugin.class.getDeclaredField("injector"), injector); + } + + @Test + public void test_import() { + String json = "[{\"type\":\"ChatAlert\",\"message\":\"*is ready to harvest*\",\"regexEnabled\":false,\"enabled\":true,\"name\":\"Ready to Harvest\",\"debounceTime\":500,\"notifications\":[{\"type\":\"TrayNotification\",\"message\":\"Time to harvest your crops!\",\"fireWhenFocused\":true},{\"type\":\"Sound\",\"path\":\"C:\\\\Users\\\\adamg\\\\Music\\\\airplane_seatbelt.mp3\",\"gain\":10,\"fireWhenFocused\":true},{\"type\":\"TextToSpeech\",\"gain\":5,\"rate\":1,\"voice\":\"GEORGE\",\"source\":\"LEGACY\",\"message\":\"\",\"fireWhenFocused\":true},{\"type\":\"Overhead\",\"displayTime\":3,\"message\":\"\",\"fireWhenFocused\":true}]},{\"type\":\"NotificationFiredAlert\",\"message\":\"You are now out of combat!\",\"regexEnabled\":false,\"enabled\":true,\"name\":\"Out of Combat\",\"debounceTime\":0,\"notifications\":[{\"type\":\"ScreenFlash\",\"color\":\"#46FF0000\",\"flashMode\":\"FLASH\",\"flashDuration\":0,\"fireWhenFocused\":true}]},{\"type\":\"SpawnedAlert\",\"spawnedDespawned\":\"SPAWNED\",\"spawnedType\":\"ITEM\",\"spawnedName\":\"Bones\",\"regexEnabled\":false,\"enabled\":false,\"name\":\"Bones Spawned\",\"debounceTime\":0,\"notifications\":[{\"type\":\"SoundEffect\",\"soundID\":3925,\"gain\":10,\"fireWhenFocused\":true},{\"type\":\"ScreenFlash\",\"color\":\"#46FF0000\",\"flashMode\":\"FLASH\",\"flashDuration\":0,\"fireWhenFocused\":true}]},{\"type\":\"SpawnedAlert\",\"spawnedDespawned\":\"SPAWNED\",\"spawnedType\":\"NPC\",\"spawnedName\":\"Gee\",\"regexEnabled\":false,\"enabled\":false,\"name\":\"NPC Spawn\",\"debounceTime\":0,\"notifications\":[{\"type\":\"Overhead\",\"displayTime\":3,\"message\":\"The dude is here\",\"fireWhenFocused\":true},{\"type\":\"TextToSpeech\",\"gain\":10,\"rate\":1,\"voice\":\"GEORGE\",\"source\":\"LEGACY\",\"message\":\"\",\"fireWhenFocused\":true}]},{\"type\":\"SpawnedAlert\",\"spawnedDespawned\":\"SPAWNED\",\"spawnedType\":\"GAME_OBJECT\",\"spawnedName\":\"Tree\",\"regexEnabled\":false,\"enabled\":false,\"name\":\"New Spawned Alert\",\"debounceTime\":0,\"notifications\":[{\"type\":\"Overhead\",\"displayTime\":3,\"message\":\"CHOP THE TREE\",\"fireWhenFocused\":true},{\"type\":\"SoundEffect\",\"soundID\":3924,\"gain\":10,\"fireWhenFocused\":true}]},{\"type\":\"ChatAlert\",\"message\":\"\",\"regexEnabled\":false,\"enabled\":true,\"name\":\"test 11labs lag\",\"debounceTime\":0,\"notifications\":[{\"type\":\"TextToSpeech\",\"gain\":10,\"rate\":1,\"voice\":\"GEORGE\",\"source\":\"LEGACY\",\"message\":\"this is a test\",\"fireWhenFocused\":true}]}]"; + Mockito.when(this.configManager.getConfiguration(WatchdogConfig.CONFIG_GROUP_NAME, WatchdogConfig.ALERTS)) + .thenReturn(json); + Mockito.when(this.configManager.getConfiguration(WatchdogConfig.CONFIG_GROUP_NAME, WatchdogConfig.PLUGIN_VERSION)) + .thenReturn(this.pluginVersion); + Assert.assertEquals(alertManager.getAlerts().size(), 0); + alertManager.loadAlerts(); + Assert.assertEquals(alertManager.getAlerts().size(), 6); + } + + @Test + public void test_upgrade() { + String json = "[{\"type\":\"ChatAlert\",\"message\":\"\",\"regexEnabled\":false,\"enabled\":true,\"name\":\"Upgrade Test\",\"debounceTime\":0,\"notifications\":[{\"type\":\"Overlay\",\"color\":\"#46FF0000\",\"sticky\":false,\"timeToLive\":5,\"imagePath\":\"\",\"message\":\"Overlay notification\",\"fireWhenFocused\":true}]}]"; + Mockito.when(this.configManager.getConfiguration(WatchdogConfig.CONFIG_GROUP_NAME, WatchdogConfig.ALERTS)) + .thenReturn(json); + Mockito.when(this.configManager.getConfiguration(WatchdogConfig.CONFIG_GROUP_NAME, WatchdogConfig.PLUGIN_VERSION)) + .thenReturn("2.12.0"); + alertManager.loadAlerts(); + Alert alert = alertManager.getAlerts().get(0); + Assert.assertTrue(alert instanceof ChatAlert); + Notification notification = alert.getNotifications().get(0); + Assert.assertTrue(notification instanceof Overlay); + Assert.assertNotNull(((Overlay) notification).getTextColor()); + + } +} From 858fb01c8452b5d80a4fe7746dcca16e850d1709 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sat, 21 Oct 2023 03:09:44 -0400 Subject: [PATCH 22/27] Padding tweaks --- .../java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java | 5 ++++- .../runelite/watchdog/ui/alerts/AlertGroupPanel.java | 2 +- .../runelite/watchdog/ui/panels/NotificationsPanel.java | 2 ++ .../adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java | 2 ++ .../com/adamk33n3r/runelite/watchdog/version.properties | 4 ++-- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java index 6f67152..d2698ba 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java @@ -128,7 +128,10 @@ public void rebuild() { this.alertManager.addAlert(createdAlert); this.openAlert(createdAlert); }); - actionButtons.add(alertDropDownButton); + JPanel addAlertWrapper = new JPanel(new BorderLayout()); + addAlertWrapper.setBorder(new EmptyBorder(0, 5, 0, 0)); + addAlertWrapper.add(alertDropDownButton); + actionButtons.add(addAlertWrapper); topPanel.add(actionButtons, BorderLayout.EAST); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java index c5e1f83..63b077c 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/AlertGroupPanel.java @@ -27,6 +27,7 @@ protected void build() { this.addAlertDefaults(); JPanel buttonPanel = new JPanel(new BorderLayout()); buttonPanel.add(new JLabel("Alerts"), BorderLayout.WEST); + buttonPanel.setBorder(new EmptyBorder(0, 5, 8, 0)); JButton alertDropDownButton = PanelUtils.createAlertDropDownButton(createdAlert -> { this.alert.getAlerts().add(createdAlert); @@ -36,7 +37,6 @@ protected void build() { }); buttonPanel.add(alertDropDownButton, BorderLayout.EAST); - buttonPanel.setBorder(new EmptyBorder(0, 0, 8, 0)); JPanel subGroupPanel = new JPanel(new BorderLayout()); subGroupPanel.setBorder(new CompoundBorder(new EmptyBorder(0, 5, 0, 5), new HorizontalRuleBorder(10))); subGroupPanel.add(buttonPanel, BorderLayout.NORTH); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java index dc8fd2e..81b6666 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/NotificationsPanel.java @@ -19,6 +19,7 @@ import javax.inject.Inject; import javax.swing.*; +import javax.swing.border.EmptyBorder; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionListener; @@ -71,6 +72,7 @@ public NotificationsPanel(Alert alert) { JPanel buttonPanel = new JPanel(new BorderLayout()); buttonPanel.add(new JLabel("Notifications"), BorderLayout.WEST); buttonPanel.add(addDropDownButton, BorderLayout.EAST); + buttonPanel.setBorder(new EmptyBorder(0, 5, 0, 0)); this.add(buttonPanel, BorderLayout.NORTH); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java index d8321c6..74b95ef 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/PanelUtils.java @@ -21,6 +21,7 @@ import javax.annotation.Nullable; import javax.swing.*; +import javax.swing.border.EmptyBorder; import javax.swing.filechooser.FileFilter; import java.awt.*; import java.awt.event.*; @@ -52,6 +53,7 @@ public static JPanel createLabeledComponent(String label, String tooltip, Compon if (twoLines) { panel.setLayout(new DynamicGridLayout(2, 0, 5, 5)); } else { + panel.setBorder(new EmptyBorder(0, 5, 0, 0)); panel.setLayout(new BorderLayout(5, 0)); } JLabel jLabel = new JLabel(label); diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 287ce8d..646123f 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Sat Oct 21 02:39:24 EDT 2023 -VERSION_BUILD=4073 +#Sat Oct 21 03:24:55 EDT 2023 +VERSION_BUILD=4084 VERSION_PHASE=rc2 VERSION_MAJOR=3 VERSION_MINOR=0 From 1e2eaf36b788d6e7cc819359cfad2996f2a7f784 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sat, 21 Oct 2023 03:46:27 -0400 Subject: [PATCH 23/27] fix: XP drop alert spinner range Fixes #101 --- .../runelite/watchdog/ui/alerts/XPDropAlertPanel.java | 2 +- .../com/adamk33n3r/runelite/watchdog/version.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/XPDropAlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/XPDropAlertPanel.java index dd1b126..0ce0540 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/XPDropAlertPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/alerts/XPDropAlertPanel.java @@ -15,7 +15,7 @@ public XPDropAlertPanel(WatchdogPanel watchdogPanel, XPDropAlert alert) { protected void build() { this.addAlertDefaults() .addSelect("Skill", "The skill to track", Skill.class, this.alert.getSkill(), this.alert::setSkill) - .addSpinner("Gained Amount", "How much xp needed to trigger this alert", this.alert.getGainedAmount(), this.alert::setGainedAmount) + .addSpinner("Gained Amount", "How much xp needed to trigger this alert", this.alert.getGainedAmount(), this.alert::setGainedAmount, 0, Integer.MAX_VALUE, 1) .addNotifications(); } } diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 646123f..9b238d8 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Sat Oct 21 03:24:55 EDT 2023 -VERSION_BUILD=4084 +#Sat Oct 21 03:28:20 EDT 2023 +VERSION_BUILD=4085 VERSION_PHASE=rc2 VERSION_MAJOR=3 VERSION_MINOR=0 From 53f883872823cbf27e20c5edc1035ab6d710cea0 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sat, 21 Oct 2023 12:26:22 -0400 Subject: [PATCH 24/27] fix: tts voice init set --- build.gradle | 2 ++ .../adamk33n3r/runelite/watchdog/AlertManager.java | 1 - .../panels/TextToSpeechNotificationPanel.java | 12 ++++-------- .../adamk33n3r/runelite/watchdog/version.properties | 6 +++--- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 399e3ce..49260ff 100644 --- a/build.gradle +++ b/build.gradle @@ -74,6 +74,8 @@ tasks.register("shadowJar", Jar) { from sourceSets.test.output from({ configurations.testRuntimeClasspath.collect { + // Ignore test framework stuff, especially bytebuddy because it's like 4MB + if (it.path.contains("mockito") || it.path.contains("guice-testlib") || it.path.contains("junit") || it.path.contains("bytebuddy")) return null it.isDirectory() ? it : zipTree(it) } }) diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java index 3ee3381..a5745d4 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java @@ -206,7 +206,6 @@ public boolean importAlerts(String json, List alerts, boolean append, boo // Inject dependencies this.getAllAlertsFrom(alertStream.get(), false) - .filter(alert -> !(alert instanceof AlertGroup)) .forEach(alert -> { WatchdogPlugin.getInstance().getInjector().injectMembers(alert); for (INotification notification : alert.getNotifications()) { diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java index 12a7479..7862008 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java @@ -89,26 +89,22 @@ public void focusLost(FocusEvent e) { notification.setElevenLabsVoice(voice); }); - // Kinda hacky, but it'd also be hacky to modify the createSelect method so...shrug - ActionListener actionListener = voiceSelect.getActionListeners()[0]; - voiceSelect.removeActionListener(actionListener); - ElevenLabs.getVoices(WatchdogPlugin.getInstance().getHttpClient(), (voices) -> { SwingUtilities.invokeLater(() -> { + // Store the voice id prior to adding to the list because adding the first item will select it + String elevenLabsVoiceId = notification.getElevenLabsVoiceId(); voices.getVoices().forEach((voice) -> { voiceSelect.addItem(voice); - if (notification.getElevenLabsVoiceId() == null) { + if (elevenLabsVoiceId == null) { if (voice.getName().equals(WatchdogPlugin.getInstance().getConfig().defaultElevenLabsVoice())) { voiceSelect.setSelectedItem(voice); } } else { - if (voice.getVoiceId().equals(notification.getElevenLabsVoiceId())) { + if (voice.getVoiceId().equals(elevenLabsVoiceId)) { voiceSelect.setSelectedItem(voice); } } }); - - voiceSelect.addActionListener(actionListener); }); }); this.settings.add(voiceSelect); diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 9b238d8..31ea146 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,6 +1,6 @@ -#Sat Oct 21 03:28:20 EDT 2023 -VERSION_BUILD=4085 -VERSION_PHASE=rc2 +#Sat Oct 21 12:23:27 EDT 2023 +VERSION_BUILD=4092 +VERSION_PHASE=release VERSION_MAJOR=3 VERSION_MINOR=0 VERSION_PATCH=0 From 592e4bc1b365c0ccb2058336f3aff94bb6b490c5 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sat, 21 Oct 2023 12:47:10 -0400 Subject: [PATCH 25/27] fix: Inject dependencies to added alerts Was affecting alert hub alerts --- .../runelite/watchdog/AlertManager.java | 21 ++++++++++++------- .../watchdog/notifications/Overhead.java | 11 +++++++--- .../runelite/watchdog/version.properties | 4 ++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java index a5745d4..94b53c5 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java @@ -134,6 +134,7 @@ public Stream getAllAlertsFrom(Stream alerts, boolean includeGroup public void addAlert(Alert alert) { this.alerts.add(alert); + this.setUpAlert(alert); this.saveAlerts(); SwingUtilities.invokeLater(this.watchdogPanel::rebuild); @@ -206,13 +207,7 @@ public boolean importAlerts(String json, List alerts, boolean append, boo // Inject dependencies this.getAllAlertsFrom(alertStream.get(), false) - .forEach(alert -> { - WatchdogPlugin.getInstance().getInjector().injectMembers(alert); - for (INotification notification : alert.getNotifications()) { - WatchdogPlugin.getInstance().getInjector().injectMembers(notification); - notification.setAlert(alert); - } - }); + .forEach(this::setUpAlert); SwingUtilities.invokeLater(() -> { this.watchdogPanel.rebuild(); @@ -243,6 +238,18 @@ private Supplier> tryImport(String json) throws JsonSyntaxExceptio return () -> importedAlerts.stream().filter(Objects::nonNull); } + private void setUpAlert(Alert alert) { + WatchdogPlugin.getInstance().getInjector().injectMembers(alert); + if (alert instanceof AlertGroup) { + ((AlertGroup) alert).getAlerts().forEach(this::setUpAlert); + } else { + for (INotification notification : alert.getNotifications()) { + WatchdogPlugin.getInstance().getInjector().injectMembers(notification); + notification.setAlert(alert); + } + } + } + private void handleUpgrades() { Version currentVersion = new Version(this.pluginVersion); Version configVersion = new Version(this.configManager.getConfiguration(WatchdogConfig.CONFIG_GROUP_NAME, WatchdogConfig.PLUGIN_VERSION)); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java index 629be57..06bbb50 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java @@ -4,6 +4,7 @@ import com.adamk33n3r.runelite.watchdog.WatchdogConfig; import net.runelite.api.Client; +import net.runelite.api.Player; import lombok.Getter; import lombok.Setter; @@ -31,10 +32,14 @@ public Overhead(WatchdogConfig config) { @Override protected void fireImpl(String[] triggerValues) { String message = Util.processTriggerValues(this.message, triggerValues); - this.client.getLocalPlayer().setOverheadText(message); + Player localPlayer = this.client.getLocalPlayer(); + if (localPlayer == null) { + return; + } + localPlayer.setOverheadText(message); this.executor.schedule(() -> { - if (this.client.getLocalPlayer().getOverheadText().equals(message)) { - this.client.getLocalPlayer().setOverheadText(""); + if (localPlayer.getOverheadText().equals(message)) { + localPlayer.setOverheadText(""); } }, this.displayTime, TimeUnit.SECONDS); } diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 31ea146..08d04d5 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Sat Oct 21 12:23:27 EDT 2023 -VERSION_BUILD=4092 +#Sat Oct 21 12:38:20 EDT 2023 +VERSION_BUILD=4094 VERSION_PHASE=release VERSION_MAJOR=3 VERSION_MINOR=0 From 1133b491f2ab654e437883524d48d80db6ff3432 Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sun, 22 Oct 2023 00:31:19 -0400 Subject: [PATCH 26/27] feat: config to override imported alerts with defaults --- .../runelite/watchdog/AlertManager.java | 20 +++++++++----- .../runelite/watchdog/WatchdogConfig.java | 9 +++++++ .../runelite/watchdog/WatchdogPanel.java | 7 +++-- .../runelite/watchdog/WatchdogPlugin.java | 4 +-- .../runelite/watchdog/hub/AlertHubClient.java | 2 +- .../runelite/watchdog/hub/AlertHubItem.java | 19 +++++--------- .../runelite/watchdog/hub/AlertHubPanel.java | 7 +++-- .../watchdog/notifications/INotification.java | 1 + .../watchdog/notifications/Notification.java | 26 +++++++++++++++++-- .../watchdog/notifications/Overhead.java | 6 +++++ .../watchdog/notifications/Overlay.java | 10 +++++++ .../watchdog/notifications/ScreenFlash.java | 8 ++++++ .../watchdog/notifications/Sound.java | 6 +++++ .../watchdog/notifications/SoundEffect.java | 7 +++++ .../watchdog/notifications/TextToSpeech.java | 13 +++++++++- .../watchdog/ui/panels/AlertPanel.java | 2 +- .../runelite/watchdog/version.properties | 4 +-- 17 files changed, 118 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java index 94b53c5..cc01930 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/AlertManager.java @@ -36,6 +36,9 @@ public class AlertManager { @Getter private Gson gson; + @Inject + private WatchdogConfig watchdogConfig; + @Getter private final List alerts = new CopyOnWriteArrayList<>(); @@ -132,9 +135,9 @@ public Stream getAllAlertsFrom(Stream alerts, boolean includeGroup }); } - public void addAlert(Alert alert) { + public void addAlert(Alert alert, boolean overrideWithDefaults) { this.alerts.add(alert); - this.setUpAlert(alert); + this.setUpAlert(alert, overrideWithDefaults); this.saveAlerts(); SwingUtilities.invokeLater(this.watchdogPanel::rebuild); @@ -173,11 +176,11 @@ public void moveAlertTo(Alert alert, int pos) { public void loadAlerts() { final String json = this.configManager.getConfiguration(WatchdogConfig.CONFIG_GROUP_NAME, WatchdogConfig.ALERTS); - this.importAlerts(json, this.alerts, false, false); + this.importAlerts(json, this.alerts, false, false, false); this.handleUpgrades(); } - public boolean importAlerts(String json, List alerts, boolean append, boolean checkRegex) throws JsonSyntaxException { + public boolean importAlerts(String json, List alerts, boolean append, boolean checkRegex, boolean overrideWithDefaults) throws JsonSyntaxException { if (Strings.isNullOrEmpty(json)) { return false; } @@ -207,7 +210,7 @@ public boolean importAlerts(String json, List alerts, boolean append, boo // Inject dependencies this.getAllAlertsFrom(alertStream.get(), false) - .forEach(this::setUpAlert); + .forEach(alert -> this.setUpAlert(alert, overrideWithDefaults)); SwingUtilities.invokeLater(() -> { this.watchdogPanel.rebuild(); @@ -238,13 +241,16 @@ private Supplier> tryImport(String json) throws JsonSyntaxExceptio return () -> importedAlerts.stream().filter(Objects::nonNull); } - private void setUpAlert(Alert alert) { + private void setUpAlert(Alert alert, boolean overrideWithDefaults) { WatchdogPlugin.getInstance().getInjector().injectMembers(alert); if (alert instanceof AlertGroup) { - ((AlertGroup) alert).getAlerts().forEach(this::setUpAlert); + ((AlertGroup) alert).getAlerts().forEach(subAlert -> this.setUpAlert(subAlert, overrideWithDefaults)); } else { for (INotification notification : alert.getNotifications()) { WatchdogPlugin.getInstance().getInjector().injectMembers(notification); + if (overrideWithDefaults) { + notification.setDefaults(); + } notification.setAlert(alert); } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogConfig.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogConfig.java index 6a5aa35..92416c6 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogConfig.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogConfig.java @@ -23,6 +23,7 @@ public interface WatchdogConfig extends Config { // Core String ENABLE_TTS = "enableTTS"; + String OVERRIDE_IMPORTS_WITH_DEFAULTS = "overrideImportsWithDefaults"; // Overhead String DEFAULT_OVERHEAD_DISPLAY_TIME = "defaultOverheadDisplayTime"; @@ -90,6 +91,13 @@ public interface WatchdogConfig extends Config { ) default boolean ttsEnabled() { return false; } + @ConfigItem( + keyName = OVERRIDE_IMPORTS_WITH_DEFAULTS, + name = "Override Imports with Defaults", + description = "Will override imported alerts with your defaults set here" + ) + default boolean overrideImportsWithDefaults() { return false; } + @ConfigItem( keyName = PUT_SOUNDS_INTO_QUEUE, name = "Put Sounds Into Queue", @@ -269,6 +277,7 @@ public interface WatchdogConfig extends Config { section = afkNotificationSection ) @Units(Units.SECONDS) + @Range(min = 1) default int defaultAFKSeconds() { return 5; } //endregion diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java index d2698ba..6c44b5c 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPanel.java @@ -64,6 +64,9 @@ public class WatchdogPanel extends PluginPanel { @Inject private AlertManager alertManager; + @Inject + private WatchdogConfig watchdogConfig; + @Inject private OkHttpClient httpClient; @@ -125,7 +128,7 @@ public void rebuild() { actionButtons.add(historyButton); JButton alertDropDownButton = PanelUtils.createAlertDropDownButton(createdAlert -> { - this.alertManager.addAlert(createdAlert); + this.alertManager.addAlert(createdAlert, false); this.openAlert(createdAlert); }); JPanel addAlertWrapper = new JPanel(new BorderLayout()); @@ -146,7 +149,7 @@ public void rebuild() { importButton.addActionListener(ev -> { ImportExportDialog importExportDialog = new ImportExportDialog( SwingUtilities.getWindowAncestor(this), - (json, append) -> WatchdogPlugin.getInstance().getAlertManager().importAlerts(json, this.alertManager.getAlerts(), append, true) + (json, append) -> this.alertManager.importAlerts(json, this.alertManager.getAlerts(), append, true, this.watchdogConfig.overrideImportsWithDefaults()) ); importExportDialog.setVisible(true); }); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPlugin.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPlugin.java index 29a78f9..64754ac 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPlugin.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogPlugin.java @@ -137,12 +137,12 @@ protected void startUp() throws Exception { TrayNotification harvestNotification = this.injector.getInstance(TrayNotification.class); harvestNotification.setMessage("Time to harvest your crops!"); readyToHarvest.getNotifications().add(harvestNotification); - this.alertManager.addAlert(readyToHarvest); + this.alertManager.addAlert(readyToHarvest, false); NotificationFiredAlert outOfCombat = new NotificationFiredAlert("Out of Combat"); outOfCombat.setMessage("You are now out of combat!"); outOfCombat.getNotifications().add(this.injector.getInstance(ScreenFlash.class)); - this.alertManager.addAlert(outOfCombat); + this.alertManager.addAlert(outOfCombat, false); } this.panel = this.alertManager.getWatchdogPanel(); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java index f364268..1fc000e 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubClient.java @@ -190,7 +190,7 @@ public Response intercept(Chain chain) throws IOException { } @Getter - static class AlertDisplayInfo { + public static class AlertDisplayInfo { private AlertManifest manifest; private BufferedImage icon; } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java index bf18ec9..7de89f6 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java @@ -1,6 +1,9 @@ package com.adamk33n3r.runelite.watchdog.hub; +import com.adamk33n3r.runelite.watchdog.WatchdogConfig; import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; +import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; +import com.adamk33n3r.runelite.watchdog.notifications.TextToSpeech; import com.adamk33n3r.runelite.watchdog.ui.Icons; import com.adamk33n3r.runelite.watchdog.ui.WrappingLabel; import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; @@ -13,6 +16,7 @@ import lombok.Getter; +import javax.inject.Inject; import javax.swing.*; import javax.swing.border.LineBorder; import java.awt.Color; @@ -24,7 +28,7 @@ public class AlertHubItem extends JPanel { private final AlertHubClient.AlertDisplayInfo alertDisplayInfo; - public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) { + public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo, WatchdogConfig watchdogConfig) { this.alertDisplayInfo = alertDisplayInfo; this.setBackground(ColorScheme.DARKER_GRAY_COLOR); @@ -48,13 +52,6 @@ public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) { WrappingLabel alertDescLabel = new WrappingLabel(manifest.getDescription()); -// JLabel icon = new JLabel(); -// JLabel icon = new JLabel(new ImageIcon(ImageUtil.loadImageResource(WatchdogPlugin.class, "detail-test.png"))); -// icon.setHorizontalAlignment(JLabel.CENTER); -// if (this.alertDisplayInfo.getIcon() != null) { -// icon.setIcon(new ImageIcon(this.alertDisplayInfo.getIcon())); -// } - JButton moreInfoButton = PanelUtils.createActionButton(Icons.HELP, Icons.HELP_HOVER, "More info", (btn, mod) -> { LinkBrowser.browse(manifest.getRepo().toString()); }); @@ -71,7 +68,7 @@ public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) { addButton.setBorder(new LineBorder(addButton.getBackground().darker())); addButton.setFocusPainted(false); addButton.addActionListener((ev) -> { - WatchdogPlugin.getInstance().getAlertManager().addAlert(manifest.getAlert()); + WatchdogPlugin.getInstance().getAlertManager().addAlert(manifest.getAlert(), watchdogConfig.overrideImportsWithDefaults()); JOptionPane.showMessageDialog(this, "Added " + manifest.getDisplayName() + " to your alerts", "Successfully Added", JOptionPane.INFORMATION_MESSAGE); }); @@ -98,7 +95,6 @@ public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) { .addComponent(dependsOn, 0, GroupLayout.DEFAULT_SIZE, LINE_HEIGHT) .addGap(5) ) -// .addComponent(icon, 0, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE) ); layout.setHorizontalGroup(layout.createParallelGroup() @@ -124,9 +120,6 @@ public AlertHubItem(AlertHubClient.AlertDisplayInfo alertDisplayInfo) { ) .addGap(5) ) -// .addGroup(layout.createSequentialGroup() -// .addComponent(icon, 0, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) -// ) ); } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java index 1c43975..ff86c06 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubPanel.java @@ -1,6 +1,7 @@ package com.adamk33n3r.runelite.watchdog.hub; import com.adamk33n3r.runelite.watchdog.Util; +import com.adamk33n3r.runelite.watchdog.WatchdogConfig; import com.adamk33n3r.runelite.watchdog.ui.Icons; import com.adamk33n3r.runelite.watchdog.ui.SearchBar; import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; @@ -31,6 +32,7 @@ public class AlertHubPanel extends PluginPanel { private final Provider muxer; private final AlertHubClient alertHubClient; private final ScheduledExecutorService executor; + private final WatchdogConfig watchdogConfig; private List alertHubItems = new ArrayList<>(); private final IconTextField searchBar; @@ -39,11 +41,12 @@ public class AlertHubPanel extends PluginPanel { private final JScrollPane scrollPane; @Inject - public AlertHubPanel(Provider muxer, AlertHubClient alertHubClient, ScheduledExecutorService executor) { + public AlertHubPanel(Provider muxer, AlertHubClient alertHubClient, ScheduledExecutorService executor, WatchdogConfig watchdogConfig) { super(false); this.muxer = muxer; this.alertHubClient = alertHubClient; this.executor = executor; + this.watchdogConfig = watchdogConfig; JButton backButton = PanelUtils.createActionButton( Icons.BACK, @@ -124,7 +127,7 @@ private void reloadList(List alerts) { SwingUtilities.invokeLater(() -> { this.loading.setVisible(false); this.alertHubItems = alerts.stream() - .map(AlertHubItem::new) + .map(alertDisplayInfo -> new AlertHubItem(alertDisplayInfo, this.watchdogConfig)) .collect(Collectors.toList()); this.updateFilter(this.searchBar.getText()); }); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/INotification.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/INotification.java index e863255..075bd2a 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/INotification.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/INotification.java @@ -6,4 +6,5 @@ public interface INotification { Alert getAlert(); void setAlert(Alert alert); void fire(String[] triggerValues); + void setDefaults(); } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Notification.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Notification.java index 190b33a..2d66097 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Notification.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Notification.java @@ -1,6 +1,8 @@ package com.adamk33n3r.runelite.watchdog.notifications; +import com.adamk33n3r.runelite.watchdog.AlertManager; import com.adamk33n3r.runelite.watchdog.NotificationType; +import com.adamk33n3r.runelite.watchdog.WatchdogConfig; import com.adamk33n3r.runelite.watchdog.alerts.Alert; import net.runelite.api.Client; @@ -12,6 +14,7 @@ import javax.inject.Inject; import java.util.Arrays; +import java.util.Optional; public abstract class Notification implements INotification { @Inject @@ -20,14 +23,29 @@ public abstract class Notification implements INotification { @Inject protected transient Client client; + @Inject + protected transient AlertManager alertManager; + + @Inject + protected transient WatchdogConfig watchdogConfig; + @Getter @Setter private boolean fireWhenFocused = true; @Getter @Setter private int fireWhenAFKForSeconds = 0; - @Getter @Setter - protected transient Alert alert; + @Getter + @Setter + private transient Alert alert; + public Alert getAlert() { + if (this.alert == null) { + this.alert = this.alertManager.getAllAlerts() + .filter(a -> a.getNotifications().contains(this)).findFirst().orElse(null); + } + + return this.alert; + } protected boolean shouldFire() { int afkTime = (int)Math.floor(Math.min(client.getKeyboardIdleTicks(), client.getMouseIdleTicks()) * Constants.CLIENT_TICK_LENGTH / 1000f); @@ -56,4 +74,8 @@ public NotificationType getType() { .findFirst() .orElse(null); } + + public void setDefaults() { + this.setFireWhenAFKForSeconds(this.watchdogConfig.defaultAFKSeconds()); + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java index 06bbb50..1883a91 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java @@ -43,4 +43,10 @@ protected void fireImpl(String[] triggerValues) { } }, this.displayTime, TimeUnit.SECONDS); } + + @Override + public void setDefaults() { + super.setDefaults(); + this.setDisplayTime(this.watchdogConfig.defaultOverHeadDisplayTime()); + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overlay.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overlay.java index cc23947..2be29d4 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overlay.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overlay.java @@ -33,4 +33,14 @@ protected void fireImpl(String[] triggerValues) { WatchdogPlugin.getInstance().getNotificationOverlay() .add(this, Util.processTriggerValues(this.message, triggerValues)); } + + @Override + public void setDefaults() { + super.setDefaults(); + this.setColor(this.watchdogConfig.defaultOverlayColor()); + this.setTextColor(this.watchdogConfig.defaultOverlayTextColor()); + this.setSticky(this.watchdogConfig.defaultOverlaySticky()); + this.setTimeToLive(this.watchdogConfig.defaultOverlayTTL()); + this.setImagePath(this.watchdogConfig.defaultOverlayImagePath()); + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/ScreenFlash.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/ScreenFlash.java index 7914cc9..1ea4ab1 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/ScreenFlash.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/ScreenFlash.java @@ -33,4 +33,12 @@ public ScreenFlash(WatchdogConfig config) { protected void fireImpl(String[] triggerValues) { WatchdogPlugin.getInstance().getFlashOverlay().flash(this); } + + @Override + public void setDefaults() { + super.setDefaults(); + this.setFlashDuration(this.watchdogConfig.defaultScreenFlashDuration()); + this.setFlashMode(this.watchdogConfig.defaultScreenFlashMode()); + this.setColor(this.watchdogConfig.defaultScreenFlashColor()); + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java index 097874a..dc920bf 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java @@ -28,4 +28,10 @@ protected void fireImpl(String[] triggerValues) { String processedPath = Util.processTriggerValues(this.path, triggerValues); WatchdogPlugin.getInstance().getSoundPlayer().play(new File(processedPath), this.gain); } + + @Override + public void setDefaults() { + super.setDefaults(); + this.setGain(this.watchdogConfig.defaultSoundVolume()); + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/SoundEffect.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/SoundEffect.java index bdfe19e..11077db 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/SoundEffect.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/SoundEffect.java @@ -35,4 +35,11 @@ protected void fireImpl(String[] triggerValues) { this.client.playSoundEffect(this.soundID, Util.scale(this.gain, 0, 10, SoundEffectVolume.MUTED, SoundEffectVolume.HIGH)); }); } + + @Override + public void setDefaults() { + super.setDefaults(); + this.setSoundID(this.watchdogConfig.defaultSoundEffectID()); + this.setGain(this.watchdogConfig.defaultSoundEffectVolume()); + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TextToSpeech.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TextToSpeech.java index 7851cf5..f6c3441 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TextToSpeech.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TextToSpeech.java @@ -105,7 +105,18 @@ protected void fireImpl(String[] triggerValues) { } WatchdogPlugin.getInstance().getSoundPlayer().play(soundFile, this.gain); } catch (Exception ex) { - ex.printStackTrace(); + log.error("Exception occurred while playing text to speech", ex); } } + + @Override + public void setDefaults() { + super.setDefaults(); + this.setSource(this.watchdogConfig.defaultTTSSource()); + this.setLegacyVoice(this.watchdogConfig.defaultTTSVoice()); + // This will cause the tts panel to set the default + this.setElevenLabsVoiceId(null); + this.setGain(this.watchdogConfig.defaultTTSVolume()); + this.setRate(this.watchdogConfig.defaultTTSRate()); + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java index 16067dc..de513a1 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertPanel.java @@ -70,7 +70,7 @@ public AlertPanel(WatchdogPanel watchdogPanel, T alert) { ImportExportDialog importExportDialog = new ImportExportDialog( SwingUtilities.getWindowAncestor(this), (json, append) -> { - boolean result = WatchdogPlugin.getInstance().getAlertManager().importAlerts(json, ((AlertGroup) alert).getAlerts(), append, true); + boolean result = this.alertManager.importAlerts(json, ((AlertGroup) alert).getAlerts(), append, true, WatchdogPlugin.getInstance().getConfig().overrideImportsWithDefaults()); this.rebuild(); return result; } diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 08d04d5..5df3a43 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Sat Oct 21 12:38:20 EDT 2023 -VERSION_BUILD=4094 +#Sun Oct 22 00:27:25 EDT 2023 +VERSION_BUILD=4114 VERSION_PHASE=release VERSION_MAJOR=3 VERSION_MINOR=0 From 421957d27fea6e3533fed0c95b7727077634903d Mon Sep 17 00:00:00 2001 From: Adam Keenan Date: Sun, 22 Oct 2023 02:21:21 -0400 Subject: [PATCH 27/27] fix: afk defaults --- .../runelite/watchdog/WatchdogConfig.java | 71 +++++++++++-------- .../runelite/watchdog/alerts/Alert.java | 1 - .../runelite/watchdog/hub/AlertHubItem.java | 3 - .../notifications/AudioNotification.java | 9 +++ .../watchdog/notifications/GameMessage.java | 6 ++ .../notifications/MessageNotification.java | 9 +++ .../watchdog/notifications/Notification.java | 13 +++- .../notifications/NotificationEvent.java | 6 ++ .../watchdog/notifications/Overhead.java | 1 + .../watchdog/notifications/Overlay.java | 1 + .../watchdog/notifications/ScreenFlash.java | 1 + .../watchdog/notifications/Sound.java | 1 + .../watchdog/notifications/SoundEffect.java | 1 + .../watchdog/notifications/TextToSpeech.java | 10 +-- .../notifications/TrayNotification.java | 7 ++ .../panels/NotificationPanel.java | 13 ++-- .../panels/TextToSpeechNotificationPanel.java | 1 - .../watchdog/ui/panels/AlertListPanel.java | 1 - .../watchdog/ui/panels/HistoryPanel.java | 1 - .../runelite/watchdog/version.properties | 4 +- 20 files changed, 105 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogConfig.java b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogConfig.java index 92416c6..ca61584 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogConfig.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/WatchdogConfig.java @@ -25,6 +25,10 @@ public interface WatchdogConfig extends Config { String ENABLE_TTS = "enableTTS"; String OVERRIDE_IMPORTS_WITH_DEFAULTS = "overrideImportsWithDefaults"; + // AFK Notification + String DEFAULT_AFK_MODE = "defaultAFKMode"; + String DEFAULT_AFK_SECONDS = "defaultAFKSeconds"; + // Overhead String DEFAULT_OVERHEAD_DISPLAY_TIME = "defaultOverheadDisplayTime"; @@ -45,9 +49,6 @@ public interface WatchdogConfig extends Config { String DEFAULT_SCREEN_FLASH_MODE = "defaultScreenFlashMode"; String DEFAULT_SCREEN_FLASH_DURATION = "defaultScreenFlashDuration"; - // AFK Notification - String DEFAULT_AFK_SECONDS = "defaultAFKSeconds"; - // Sound String PUT_SOUNDS_INTO_QUEUE = "putSoundsIntoQueue"; String DEFAULT_SOUND_VOLUME = "defaultSoundVolume"; @@ -105,11 +106,39 @@ public interface WatchdogConfig extends Config { ) default boolean putSoundsIntoQueue() { return true; } + //region AFK Notification + @ConfigSection( + name = "AFK Notification", + description = "The options that control the afk notification settings", + position = 0 + ) + String afkNotificationSection = "afkNotificationSection"; + + @ConfigItem( + keyName = DEFAULT_AFK_MODE, + name = "Default AFK Mode", + description = "The default AFK mode on/off", + section = afkNotificationSection + ) + default boolean defaultAFKMode() { return false; } + + @ConfigItem( + keyName = DEFAULT_AFK_SECONDS, + name = "Default AFK Seconds", + description = "The default AFK seconds value", + section = afkNotificationSection + ) + @Units(Units.SECONDS) + @Range(min = 1) + default int defaultAFKSeconds() { return 5; } + //endregion + //region Overhead @ConfigSection( name = "Overhead", description = "The options that control the overhead notifications", - position = 0 + position = 1, + closedByDefault = true ) String overheadSection = "overheadSection"; @@ -127,7 +156,8 @@ public interface WatchdogConfig extends Config { @ConfigSection( name = "Overlay", description = "The options that control the overlay notifications", - position = 1 + position = 2, + closedByDefault = true ) String overlaySection = "overlaySection"; @@ -210,7 +240,8 @@ public interface WatchdogConfig extends Config { @ConfigSection( name = "Screen Flash", description = "The options that control the screen flash notifications", - position = 2 + position = 3, + closedByDefault = true ) String screenFlashSection = "screenFlashSection"; @@ -262,30 +293,12 @@ public interface WatchdogConfig extends Config { default int defaultScreenFlashDuration() { return 2; } //endregion - //region AFK Notification - @ConfigSection( - name = "AFK Notification", - description = "The options that control the afk alert filter settings", - position = 3 - ) - String afkNotificationSection = "afkNotificationSection"; - - @ConfigItem( - keyName = DEFAULT_AFK_SECONDS, - name = "Default AFK Seconds", - description = "The default AFK seconds value", - section = afkNotificationSection - ) - @Units(Units.SECONDS) - @Range(min = 1) - default int defaultAFKSeconds() { return 5; } - //endregion - //region Sound @ConfigSection( name = "Custom Sound", description = "The options that control the custom sound notifications", - position = 4 + position = 4, + closedByDefault = true ) String soundSection = "soundSection"; @@ -311,7 +324,8 @@ public interface WatchdogConfig extends Config { @ConfigSection( name = "Sound Effect", description = "The options that control the custom sound notifications", - position = 5 + position = 5, + closedByDefault = true ) String soundEffectSection = "soundEffectSection"; @@ -337,7 +351,8 @@ public interface WatchdogConfig extends Config { @ConfigSection( name = "Text to Speech", description = "The options that control the text to speech notifications", - position = 6 + position = 6, + closedByDefault = true ) String ttsSection = "ttsSection"; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java index b75d196..6b66452 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/alerts/Alert.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java index 7de89f6..638cdec 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/hub/AlertHubItem.java @@ -2,8 +2,6 @@ import com.adamk33n3r.runelite.watchdog.WatchdogConfig; import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; -import com.adamk33n3r.runelite.watchdog.alerts.AlertGroup; -import com.adamk33n3r.runelite.watchdog.notifications.TextToSpeech; import com.adamk33n3r.runelite.watchdog.ui.Icons; import com.adamk33n3r.runelite.watchdog.ui.WrappingLabel; import com.adamk33n3r.runelite.watchdog.ui.panels.PanelUtils; @@ -16,7 +14,6 @@ import lombok.Getter; -import javax.inject.Inject; import javax.swing.*; import javax.swing.border.LineBorder; import java.awt.Color; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/AudioNotification.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/AudioNotification.java index 4d3137d..1893a4e 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/AudioNotification.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/AudioNotification.java @@ -1,10 +1,19 @@ package com.adamk33n3r.runelite.watchdog.notifications; +import com.adamk33n3r.runelite.watchdog.WatchdogConfig; + import lombok.Getter; import lombok.Setter; +import javax.inject.Inject; + public abstract class AudioNotification extends Notification implements IAudioNotification { @Getter @Setter protected int gain = 8; + + @Inject + public AudioNotification(WatchdogConfig config) { + super(config); + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/GameMessage.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/GameMessage.java index 5723b2f..dcb56ff 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/GameMessage.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/GameMessage.java @@ -1,6 +1,7 @@ package com.adamk33n3r.runelite.watchdog.notifications; import com.adamk33n3r.runelite.watchdog.Util; +import com.adamk33n3r.runelite.watchdog.WatchdogConfig; import com.adamk33n3r.runelite.watchdog.WatchdogPlugin; import net.runelite.api.ChatMessageType; @@ -18,6 +19,11 @@ public class GameMessage extends MessageNotification { @Inject private transient ChatMessageManager chatMessageManager; + @Inject + public GameMessage(WatchdogConfig config) { + super(config); + } + @Override protected void fireImpl(String[] triggerValues) { final String formattedMessage = new ChatMessageBuilder() diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/MessageNotification.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/MessageNotification.java index 95f2f93..5afcaf2 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/MessageNotification.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/MessageNotification.java @@ -1,10 +1,19 @@ package com.adamk33n3r.runelite.watchdog.notifications; +import com.adamk33n3r.runelite.watchdog.WatchdogConfig; + import lombok.Getter; import lombok.Setter; +import javax.inject.Inject; + public abstract class MessageNotification extends Notification implements IMessageNotification { @Getter @Setter protected String message = ""; + + @Inject + public MessageNotification(WatchdogConfig config) { + super(config); + } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Notification.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Notification.java index 2d66097..5ea1392 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Notification.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Notification.java @@ -14,7 +14,6 @@ import javax.inject.Inject; import java.util.Arrays; -import java.util.Optional; public abstract class Notification implements INotification { @Inject @@ -32,11 +31,12 @@ public abstract class Notification implements INotification { @Getter @Setter private boolean fireWhenFocused = true; + @Getter @Setter + private boolean fireWhenAFK = false; @Getter @Setter private int fireWhenAFKForSeconds = 0; - @Getter - @Setter + @Getter @Setter private transient Alert alert; public Alert getAlert() { if (this.alert == null) { @@ -47,6 +47,12 @@ public Alert getAlert() { return this.alert; } + @Inject + public Notification(WatchdogConfig config) { + this.fireWhenAFK = config.defaultAFKMode(); + this.fireWhenAFKForSeconds = config.defaultAFKSeconds(); + } + protected boolean shouldFire() { int afkTime = (int)Math.floor(Math.min(client.getKeyboardIdleTicks(), client.getMouseIdleTicks()) * Constants.CLIENT_TICK_LENGTH / 1000f); if (afkTime < this.fireWhenAFKForSeconds) { @@ -76,6 +82,7 @@ public NotificationType getType() { } public void setDefaults() { + this.setFireWhenAFK(this.watchdogConfig.defaultAFKMode()); this.setFireWhenAFKForSeconds(this.watchdogConfig.defaultAFKSeconds()); } } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/NotificationEvent.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/NotificationEvent.java index bf0ff7f..19036ea 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/NotificationEvent.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/NotificationEvent.java @@ -2,6 +2,7 @@ import com.adamk33n3r.runelite.watchdog.EventHandler; import com.adamk33n3r.runelite.watchdog.Util; +import com.adamk33n3r.runelite.watchdog.WatchdogConfig; import javax.inject.Inject; @@ -9,6 +10,11 @@ public class NotificationEvent extends MessageNotification { @Inject private transient EventHandler eventHandler; + @Inject + public NotificationEvent(WatchdogConfig config) { + super(config); + } + @Override protected void fireImpl(String[] triggerValues) { this.eventHandler.notify(Util.processTriggerValues(this.message, triggerValues)); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java index 1883a91..cc8d03b 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overhead.java @@ -26,6 +26,7 @@ public class Overhead extends MessageNotification { @Inject public Overhead(WatchdogConfig config) { + super(config); this.displayTime = config.defaultOverHeadDisplayTime(); } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overlay.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overlay.java index 2be29d4..5bdfcec 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overlay.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Overlay.java @@ -21,6 +21,7 @@ public class Overlay extends MessageNotification { @Inject public Overlay(WatchdogConfig config) { + super(config); this.color = config.defaultOverlayColor(); this.textColor = config.defaultOverlayTextColor(); this.sticky = config.defaultOverlaySticky(); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/ScreenFlash.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/ScreenFlash.java index 1ea4ab1..1deefc1 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/ScreenFlash.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/ScreenFlash.java @@ -24,6 +24,7 @@ public class ScreenFlash extends Notification { @Inject public ScreenFlash(WatchdogConfig config) { + super(config); this.color = config.defaultScreenFlashColor(); this.flashMode = config.defaultScreenFlashMode(); this.flashDuration = config.defaultScreenFlashDuration(); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java index dc920bf..fa1010d 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/Sound.java @@ -19,6 +19,7 @@ public class Sound extends AudioNotification { @Inject public Sound(WatchdogConfig config) { + super(config); this.gain = config.defaultSoundVolume(); this.path = config.defaultSoundPath(); } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/SoundEffect.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/SoundEffect.java index 11077db..c7361d2 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/SoundEffect.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/SoundEffect.java @@ -25,6 +25,7 @@ public class SoundEffect extends AudioNotification { @Inject public SoundEffect(WatchdogConfig config) { + super(config); this.gain = config.defaultSoundEffectVolume(); this.soundID = config.defaultSoundEffectID(); } diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TextToSpeech.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TextToSpeech.java index f6c3441..4365fb6 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TextToSpeech.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TextToSpeech.java @@ -9,7 +9,6 @@ import com.google.gson.annotations.SerializedName; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -26,24 +25,19 @@ import static net.runelite.client.RuneLite.CACHE_DIR; @Slf4j -@NoArgsConstructor +@Getter @Setter public class TextToSpeech extends MessageNotification implements IAudioNotification { - @Getter @Setter private int gain; - @Getter @Setter private int rate; - @Getter @Setter @SerializedName("voice") private Voice legacyVoice; - @Getter @Setter private TTSSource source = TTSSource.LEGACY; - @Getter @Setter private String elevenLabsVoiceId; - @Getter @Setter private transient com.adamk33n3r.runelite.watchdog.elevenlabs.Voice elevenLabsVoice; @Inject public TextToSpeech(WatchdogConfig config) { + super(config); this.gain = config.defaultTTSVolume(); this.rate = config.defaultTTSRate(); this.legacyVoice = config.defaultTTSVoice(); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TrayNotification.java b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TrayNotification.java index 4598688..a0b9190 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TrayNotification.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/notifications/TrayNotification.java @@ -1,13 +1,20 @@ package com.adamk33n3r.runelite.watchdog.notifications; import com.adamk33n3r.runelite.watchdog.Util; +import com.adamk33n3r.runelite.watchdog.WatchdogConfig; import lombok.extern.slf4j.Slf4j; +import javax.inject.Inject; import java.awt.TrayIcon; @Slf4j public class TrayNotification extends MessageNotification { + @Inject + public TrayNotification(WatchdogConfig config) { + super(config); + } + @Override protected void fireImpl(String[] triggerValues) { if (this.clientUI.getTrayIcon() != null) { diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java index 45cf4d9..aa3fac2 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/NotificationPanel.java @@ -13,14 +13,12 @@ import net.runelite.client.ui.components.MouseDragEventForwarder; import lombok.Getter; -import net.runelite.client.util.ImageUtil; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import java.awt.*; -import java.awt.image.BufferedImage; import java.util.concurrent.atomic.AtomicInteger; public abstract class NotificationPanel extends JPanel { @@ -85,7 +83,7 @@ public NotificationPanel(Notification notification, NotificationsPanel parentPan afkTimerConfigRow.setBackground(ColorScheme.DARKER_GRAY_COLOR); JLabel afkTimerLabel = new JLabel("AFK Seconds:"); afkTimerLabel.setToolTipText("Number of seconds for which the client doesn't get any mouse or keyboard inputs."); - AtomicInteger previousAFKSeconds = new AtomicInteger(notification.getFireWhenAFKForSeconds() > 0 ? notification.getFireWhenAFKForSeconds() : config.defaultAFKSeconds()); + AtomicInteger previousAFKSeconds = new AtomicInteger(notification.getFireWhenAFKForSeconds()); JSpinner afkTimerSpinner = PanelUtils.createSpinner(Math.max(notification.getFireWhenAFKForSeconds(), 1), 1, @@ -96,7 +94,7 @@ public NotificationPanel(Notification notification, NotificationsPanel parentPan previousAFKSeconds.set(val); onChangeListener.run(); }); - if (notification.getFireWhenAFKForSeconds() != 0) { + if (notification.isFireWhenAFK()) { afkTimerConfigRow.add(afkTimerLabel); afkTimerConfigRow.add(afkTimerSpinner); } @@ -108,10 +106,11 @@ public NotificationPanel(Notification notification, NotificationsPanel parentPan Icons.NON_AFK_HOVER, "Enable notification even when you are active", "Switch to only fire notification when you have been AFK for a certain amount of time", - notification.getFireWhenAFKForSeconds() != 0, + notification.isFireWhenAFK(), (btn, modifiers) -> { - notification.setFireWhenAFKForSeconds(btn.isSelected() ? previousAFKSeconds.get() : 0); - if (notification.getFireWhenAFKForSeconds() != 0) { + notification.setFireWhenAFK(btn.isSelected()); + notification.setFireWhenAFKForSeconds(previousAFKSeconds.get()); + if (notification.isFireWhenAFK()) { afkTimerSpinner.setValue(notification.getFireWhenAFKForSeconds()); afkTimerConfigRow.add(afkTimerLabel); afkTimerConfigRow.add(afkTimerSpinner); diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java index 7862008..3f25e3b 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/notifications/panels/TextToSpeechNotificationPanel.java @@ -19,7 +19,6 @@ import javax.swing.*; import javax.swing.text.AbstractDocument; import java.awt.Font; -import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java index a7f02b2..124cc9b 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/AlertListPanel.java @@ -11,7 +11,6 @@ import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.components.DragAndDropReorderPane; -import net.runelite.client.util.Text; import com.google.common.base.Splitter; import lombok.Getter; diff --git a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java index f3d14a3..d4cf56d 100644 --- a/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java +++ b/src/main/java/com/adamk33n3r/runelite/watchdog/ui/panels/HistoryPanel.java @@ -9,7 +9,6 @@ import net.runelite.client.ui.MultiplexingPluginPanel; import net.runelite.client.ui.PluginPanel; -import net.runelite.client.util.Text; import com.google.common.base.Splitter; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties index 5df3a43..2a0bfa7 100644 --- a/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties +++ b/src/main/resources/com/adamk33n3r/runelite/watchdog/version.properties @@ -1,5 +1,5 @@ -#Sun Oct 22 00:27:25 EDT 2023 -VERSION_BUILD=4114 +#Sun Oct 22 02:13:01 EDT 2023 +VERSION_BUILD=4129 VERSION_PHASE=release VERSION_MAJOR=3 VERSION_MINOR=0