77import com .cope .meteoraddons .config .IconSizeConfig ;
88import com .cope .meteoraddons .models .AddonMetadata ;
99import com .cope .meteoraddons .models .UpdateInfo ;
10+ import com .cope .meteoraddons .models .AddonMetadata .Feature ;
1011import com .cope .meteoraddons .systems .AddonManager ;
1112import com .cope .meteoraddons .util .GitHubReleaseAPI ;
1213import com .cope .meteoraddons .util .HashUtil ;
2930import java .util .Collections ;
3031import java .util .List ;
3132import java .util .Optional ;
33+ import java .util .stream .Collectors ;
3234
3335import static meteordevelopment .meteorclient .MeteorClient .mc ;
3436import static meteordevelopment .meteorclient .utils .Utils .getWindowWidth ;
@@ -50,17 +52,18 @@ public AddonDetailScreen(GuiTheme theme, Addon addon, Screen parent) {
5052 public void initWidgets () {
5153 // Header Section (Icon + Details)
5254 WHorizontalList header = add (theme .horizontalList ()).centerX ().widget ();
53-
55+
5456 // Icon
5557 Texture iconTexture = IconCache .get (addon );
56- header .add (theme .texture (IconSizeConfig .ADDON_ICON_SIZE , IconSizeConfig .ADDON_ICON_SIZE , 0 , iconTexture )).widget ();
58+ header .add (theme .texture (IconSizeConfig .ADDON_ICON_SIZE , IconSizeConfig .ADDON_ICON_SIZE , 0 , iconTexture ))
59+ .widget ();
5760
5861 // Details (Name, Authors, Version, Verified)
5962 WVerticalList details = header .add (theme .verticalList ()).expandX ().widget ();
60-
63+
6164 WHorizontalList titleRow = details .add (theme .horizontalList ()).widget ();
6265 titleRow .add (theme .label (addon .getName (), true )); // Title
63-
66+
6467 if (addon .isInstalled ()) {
6568 Texture installedIcon = IconCache .getInstalledIndicator ();
6669 if (installedIcon != null ) {
@@ -70,8 +73,6 @@ public void initWidgets() {
7073 titleRow .add (theme .label ("(Installed)" , true ).color (theme .textSecondaryColor ())).padLeft (4 );
7174 }
7275
73-
74-
7576 List <String > authors = addon .getAuthors ();
7677 if (!authors .isEmpty ()) {
7778 details .add (theme .label ("By: " + String .join (", " , authors )).color (theme .textSecondaryColor ()));
@@ -96,27 +97,30 @@ public void initWidgets() {
9697 }
9798 }
9899 }
99-
100+
100101 // Stats (Online Addons only)
101102 if (addon instanceof OnlineAddon ) {
102- AddonMetadata metadata = ((OnlineAddon ) addon ).getMetadata ();
103- if (metadata .repo != null ) {
104- WHorizontalList stats = add (theme .horizontalList ()).centerX ().widget ();
105- stats .add (theme .label ("Stars: " + metadata .repo .stars ));
106- stats .add (theme .label ("Downloads: " + metadata .repo .downloads )).padHorizontal (10 );
107- stats .add (theme .label ("Updated: " + TimeUtil .getRelativeTime (metadata .repo .last_update )));
108- }
109-
110- // Features Section
111- if (metadata .features != null && hasAnyFeatures (metadata .features )) {
112- WSection featuresSection = add (theme .section ("Features" , true )).expandX ().widget ();
113-
114- boolean needsSeparator = false ;
115- needsSeparator = addFeatureList (featuresSection , "Modules" , metadata .features .modules , needsSeparator ) || needsSeparator ;
116- needsSeparator = addFeatureList (featuresSection , "Commands" , metadata .features .commands , needsSeparator ) || needsSeparator ;
117- needsSeparator = addFeatureList (featuresSection , "HUD" , metadata .features .hud_elements , needsSeparator ) || needsSeparator ;
118- addFeatureList (featuresSection , "Screens" , metadata .features .custom_screens , needsSeparator );
119- }
103+ AddonMetadata metadata = ((OnlineAddon ) addon ).getMetadata ();
104+ if (metadata .repo != null ) {
105+ WHorizontalList stats = add (theme .horizontalList ()).centerX ().widget ();
106+ stats .add (theme .label ("Stars: " + metadata .repo .stars ));
107+ stats .add (theme .label ("Downloads: " + metadata .repo .downloads )).padHorizontal (10 );
108+ stats .add (theme .label ("Updated: " + TimeUtil .getRelativeTime (metadata .repo .last_update )));
109+ }
110+
111+ // Features Section
112+ if (metadata .features != null && hasAnyFeatures (metadata .features )) {
113+ WSection featuresSection = add (theme .section ("Features" , true )).expandX ().widget ();
114+
115+ boolean needsSeparator = false ;
116+ needsSeparator = addFeatureList (featuresSection , "Modules" , metadata .features .modules , needsSeparator )
117+ || needsSeparator ;
118+ needsSeparator = addFeatureList (featuresSection , "Commands" , metadata .features .commands , needsSeparator )
119+ || needsSeparator ;
120+ needsSeparator = addFeatureList (featuresSection , "HUD" , metadata .features .hud_elements , needsSeparator )
121+ || needsSeparator ;
122+ addStringFeatureList (featuresSection , "Screens" , metadata .features .custom_screens , needsSeparator );
123+ }
120124 }
121125
122126 add (theme .horizontalSeparator ()).expandX ();
@@ -153,7 +157,8 @@ public void initWidgets() {
153157 if (update .isPresent ()) {
154158 checkUpdateBtn .set ("Update Found!" );
155159 // Show updates screen with this single update
156- mc .setScreen (new UpdatesAvailableScreen (GuiThemes .get (), Collections .singletonList (update .get ())));
160+ mc .setScreen (new UpdatesAvailableScreen (GuiThemes .get (),
161+ Collections .singletonList (update .get ())));
157162 } else {
158163 checkUpdateBtn .set ("Up to date" );
159164 }
@@ -164,19 +169,19 @@ public void initWidgets() {
164169
165170 // Link Buttons
166171 if (addon .getGithubUrl ().isPresent ()) {
167- WButton btn = actions .add (theme .button ("GitHub" )).widget ();
168- final String url = addon .getGithubUrl ().get ();
169- btn .action = () -> Util .getOperatingSystem ().open (url );
172+ WButton btn = actions .add (theme .button ("GitHub" )).widget ();
173+ final String url = addon .getGithubUrl ().get ();
174+ btn .action = () -> Util .getOperatingSystem ().open (url );
170175 }
171176 if (addon .getDiscordUrl ().isPresent ()) {
172- WButton btn = actions .add (theme .button ("Discord" )).widget ();
173- final String url = addon .getDiscordUrl ().get ();
174- btn .action = () -> Util .getOperatingSystem ().open (url );
177+ WButton btn = actions .add (theme .button ("Discord" )).widget ();
178+ final String url = addon .getDiscordUrl ().get ();
179+ btn .action = () -> Util .getOperatingSystem ().open (url );
175180 }
176181 if (addon .getHomepageUrl ().isPresent ()) {
177- WButton btn = actions .add (theme .button ("Homepage" )).widget ();
178- final String url = addon .getHomepageUrl ().get ();
179- btn .action = () -> Util .getOperatingSystem ().open (url );
182+ WButton btn = actions .add (theme .button ("Homepage" )).widget ();
183+ final String url = addon .getHomepageUrl ().get ();
184+ btn .action = () -> Util .getOperatingSystem ().open (url );
180185 }
181186
182187 // Back Button
@@ -189,21 +194,48 @@ public void initWidgets() {
189194 */
190195 private boolean hasAnyFeatures (AddonMetadata .Features features ) {
191196 return (features .modules != null && !features .modules .isEmpty ()) ||
192- (features .commands != null && !features .commands .isEmpty ()) ||
193- (features .hud_elements != null && !features .hud_elements .isEmpty ()) ||
194- (features .custom_screens != null && !features .custom_screens .isEmpty ());
197+ (features .commands != null && !features .commands .isEmpty ()) ||
198+ (features .hud_elements != null && !features .hud_elements .isEmpty ()) ||
199+ (features .custom_screens != null && !features .custom_screens .isEmpty ());
195200 }
196201
197202 /**
198203 * Add a feature list to the section if items are present.
199204 *
200- * @param section The section to add to
201- * @param label The feature type label (e.g., "Modules", "Commands")
202- * @param items The list of feature names
205+ * @param section The section to add to
206+ * @param label The feature type label (e.g., "Modules", "Commands")
207+ * @param items The list of feature names
208+ * @param addSeparator Whether to add a separator before this feature group
209+ * @return true if items were added, false otherwise
210+ */
211+ private boolean addFeatureList (WSection section , String label , List <Feature > items , boolean addSeparator ) {
212+ if (items == null || items .isEmpty ()) {
213+ return false ;
214+ }
215+
216+ if (addSeparator ) {
217+ section .add (theme .horizontalSeparator ()).expandX ();
218+ }
219+
220+ section .add (theme .label (label + " (" + items .size () + "):" ));
221+ String itemsStr = items .stream ()
222+ .map (item -> item .name )
223+ .collect (Collectors .joining (", " ));
224+ section .add (theme .label (itemsStr , getWindowWidth () / 2.0 ).color (theme .textSecondaryColor ()));
225+
226+ return true ;
227+ }
228+
229+ /**
230+ * Add a string feature list to the section if items are present.
231+ *
232+ * @param section The section to add to
233+ * @param label The feature type label (e.g., "Modules", "Commands")
234+ * @param items The list of feature names
203235 * @param addSeparator Whether to add a separator before this feature group
204236 * @return true if items were added, false otherwise
205237 */
206- private boolean addFeatureList (WSection section , String label , List <String > items , boolean addSeparator ) {
238+ private boolean addStringFeatureList (WSection section , String label , List <String > items , boolean addSeparator ) {
207239 if (items == null || items .isEmpty ()) {
208240 return false ;
209241 }
@@ -262,7 +294,8 @@ private Optional<UpdateInfo> checkForUpdate(InstalledAddon installed) {
262294 MeteorAddonsAddon .LOG .info ("Local SHA256: {}" , localHash );
263295
264296 // Fetch release info from GitHub
265- Optional <GitHubReleaseAPI .ReleaseInfo > releaseOpt = GitHubReleaseAPI .getLatestRelease (ownerRepo [0 ], ownerRepo [1 ]);
297+ Optional <GitHubReleaseAPI .ReleaseInfo > releaseOpt = GitHubReleaseAPI .getLatestRelease (ownerRepo [0 ],
298+ ownerRepo [1 ]);
266299 if (releaseOpt .isEmpty ()) {
267300 MeteorAddonsAddon .LOG .warn ("No release found for {}/{}" , ownerRepo [0 ], ownerRepo [1 ]);
268301 return Optional .empty ();
@@ -284,7 +317,8 @@ private Optional<UpdateInfo> checkForUpdate(InstalledAddon installed) {
284317 MeteorAddonsAddon .LOG .info ("Remote SHA256: {}" , remoteHash );
285318
286319 if (remoteHash == null || remoteHash .isEmpty ()) {
287- MeteorAddonsAddon .LOG .warn ("No SHA256 digest available for {} (GitHub may not have computed it yet)" , installed .getName ());
320+ MeteorAddonsAddon .LOG .warn ("No SHA256 digest available for {} (GitHub may not have computed it yet)" ,
321+ installed .getName ());
288322 return Optional .empty ();
289323 }
290324
@@ -293,16 +327,15 @@ private Optional<UpdateInfo> checkForUpdate(InstalledAddon installed) {
293327 MeteorAddonsAddon .LOG .info ("UPDATE AVAILABLE for {}: hashes differ" , installed .getName ());
294328
295329 return Optional .of (new UpdateInfo (
296- installed ,
297- installed .getName (),
298- installed .getVersion (),
299- release .getVersion (),
300- release .getChangelog (),
301- asset .getDownloadUrl (),
302- remoteHash ,
303- localHash ,
304- localJarPath
305- ));
330+ installed ,
331+ installed .getName (),
332+ installed .getVersion (),
333+ release .getVersion (),
334+ release .getChangelog (),
335+ asset .getDownloadUrl (),
336+ remoteHash ,
337+ localHash ,
338+ localJarPath ));
306339 } else {
307340 MeteorAddonsAddon .LOG .info ("{} is up to date (hashes match)" , installed .getName ());
308341 return Optional .empty ();
0 commit comments