From 0b2e9c3f6232fb968ba5cb74a14a09665bd2c37e Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Mon, 20 Sep 2021 17:35:49 +0800 Subject: [PATCH] feat: Mod dependencies & Mod downloads should be classified. --- .../hmcl/ui/construct/ComponentList.java | 1 + .../hmcl/ui/construct/ComponentListCell.java | 33 +- .../hmcl/ui/versions/DownloadPage.java | 332 ++++++++++++------ .../hmcl/ui/versions/GameListPage.java | 2 +- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + .../jackhuang/hmcl/mod/DownloadManager.java | 1 + .../jackhuang/hmcl/mod/curse/CurseAddon.java | 17 +- .../hmcl/mod/curse/CurseModManager.java | 5 + .../jackhuang/hmcl/mod/modrinth/Modrinth.java | 5 + .../java/org/jackhuang/hmcl/task/Task.java | 7 +- 12 files changed, 272 insertions(+), 134 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java index 9c6d04875..b387eb4d6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java @@ -109,6 +109,7 @@ public class ComponentList extends Control { public void onExpand() { if (!expanded && lazyInitializer != null) { lazyInitializer.accept(this); + setNeedsLayout(true); } expanded = true; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java index defb43aed..210877e43 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java @@ -22,6 +22,7 @@ import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; +import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.geometry.Insets; @@ -137,25 +138,31 @@ class ComponentListCell extends StackPane { setExpanded(!isExpanded()); - double newAnimatedHeight = content.prefHeight(-1) * (isExpanded() ? 1 : -1); - double newHeight = isExpanded() ? getHeight() + newAnimatedHeight : prefHeight(-1); - double contentHeight = isExpanded() ? newAnimatedHeight : 0; - if (isExpanded()) { - updateClip(newHeight); list.onExpand(); + list.layout(); } - expandAnimation = new Timeline(new KeyFrame(new Duration(320.0), - new KeyValue(container.minHeightProperty(), contentHeight, FXUtils.SINE), - new KeyValue(container.maxHeightProperty(), contentHeight, FXUtils.SINE) - )); + Platform.runLater(() -> { + double newAnimatedHeight = content.prefHeight(-1) * (isExpanded() ? 1 : -1); + double newHeight = isExpanded() ? getHeight() + newAnimatedHeight : prefHeight(-1); + double contentHeight = isExpanded() ? newAnimatedHeight : 0; - if (!isExpanded()) { - expandAnimation.setOnFinished(e2 -> updateClip(newHeight)); - } + if (isExpanded()) { + updateClip(newHeight); + } - expandAnimation.play(); + expandAnimation = new Timeline(new KeyFrame(new Duration(320.0), + new KeyValue(container.minHeightProperty(), contentHeight, FXUtils.SINE), + new KeyValue(container.maxHeightProperty(), contentHeight, FXUtils.SINE) + )); + + if (!isExpanded()) { + expandAnimation.setOnFinished(e2 -> updateClip(newHeight)); + } + + expandAnimation.play(); + }); }); expandedProperty().addListener((a, b, newValue) -> diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 11145498d..2fd58013e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -18,23 +18,24 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXListView; +import com.jfoenix.controls.JFXScrollPane; import javafx.beans.binding.Bindings; -import javafx.beans.property.*; -import javafx.collections.FXCollections; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.property.SimpleBooleanProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.Node; import javafx.scene.control.Control; +import javafx.scene.control.ScrollPane; import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.*; import javafx.stage.FileChooser; -import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.mod.DownloadManager; import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.setting.Profile; @@ -45,29 +46,27 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.FloatListCell; -import org.jackhuang.hmcl.ui.construct.JFXHyperlink; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; -import org.jackhuang.hmcl.ui.construct.TwoLineListItem; +import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jackhuang.hmcl.util.SimpleMultimap; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.NetworkUtils; +import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jetbrains.annotations.Nullable; import java.io.File; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; -import java.util.Comparator; -import java.util.Locale; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class DownloadPage extends Control implements DecoratorPage { private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); - private final ListProperty items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList()); + private final BooleanProperty loaded = new SimpleBooleanProperty(false); private final BooleanProperty loading = new SimpleBooleanProperty(false); private final BooleanProperty failed = new SimpleBooleanProperty(false); private final DownloadManager.Mod addon; @@ -76,6 +75,9 @@ public class DownloadPage extends Control implements DecoratorPage { private final DownloadCallback callback; private final DownloadListPage page; + private List dependencies; + private SimpleMultimap versions; + public DownloadPage(DownloadListPage page, DownloadManager.Mod addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) { this.page = page; this.addon = addon; @@ -89,30 +91,57 @@ public class DownloadPage extends Control implements DecoratorPage { setLoading(true); setFailed(false); - Task.supplyAsync(() -> { - if (StringUtils.isNotBlank(version.getVersion())) { - Optional gameVersion = GameVersion.minecraftVersion(versionJar); - if (gameVersion.isPresent()) { - return addon.getData().loadVersions() - .filter(file -> file.getGameVersions().contains(gameVersion.get())); - } - } - return addon.getData().loadVersions(); - }).whenComplete(Schedulers.javafx(), (result, exception) -> { - if (exception == null) { - items.setAll(result - .sorted(Comparator.comparing(DownloadManager.Version::getDatePublished).reversed()) - .collect(Collectors.toList())); - setFailed(false); - } else { - setFailed(true); - } - setLoading(false); - }).start(); + + Task.allOf( + Task.supplyAsync(() -> addon.getData().loadDependencies()), + Task.supplyAsync(() -> { + Stream versions = addon.getData().loadVersions(); +// if (StringUtils.isNotBlank(version.getVersion())) { +// Optional gameVersion = GameVersion.minecraftVersion(versionJar); +// if (gameVersion.isPresent()) { +// return sortVersions( +// .filter(file -> file.getGameVersions().contains(gameVersion.get()))); +// } +// } + return sortVersions(versions); + })) + .whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + @SuppressWarnings("unchecked") + List dependencies = (List) result.get(0); + @SuppressWarnings("unchecked") + SimpleMultimap versions = (SimpleMultimap) result.get(1); + + this.dependencies = dependencies; + this.versions = versions; + + loaded.set(true); + setFailed(false); + } else { + setFailed(true); + } + setLoading(false); + }).start(); this.state.set(State.fromTitle(addon.getTitle())); } + private SimpleMultimap sortVersions(Stream versions) { + SimpleMultimap classifiedVersions + = new SimpleMultimap(HashMap::new, ArrayList::new); + versions.forEach(version -> { + for (String gameVersion : version.getGameVersions()) { + classifiedVersions.put(gameVersion, version); + } + }); + + for (String gameVersion : classifiedVersions.keys()) { + List versionList = (List) classifiedVersions.get(gameVersion); + versionList.sort(Comparator.comparing(DownloadManager.Version::getDatePublished).reversed()); + } + return classifiedVersions; + } + public DownloadManager.Mod getAddon() { return addon; } @@ -186,49 +215,79 @@ public class DownloadPage extends Control implements DecoratorPage { protected ModDownloadPageSkin(DownloadPage control) { super(control); - BorderPane pane = new BorderPane(); + VBox pane = new VBox(8); + pane.getStyleClass().add("gray-background"); + pane.setPadding(new Insets(10)); + ScrollPane scrollPane = new ScrollPane(pane); + JFXScrollPane.smoothScrolling(scrollPane); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); HBox descriptionPane = new HBox(8); descriptionPane.setAlignment(Pos.CENTER); - pane.setTop(descriptionPane); - descriptionPane.getStyleClass().add("card"); + pane.getChildren().add(descriptionPane); + descriptionPane.getStyleClass().add("card-non-transparent"); BorderPane.setMargin(descriptionPane, new Insets(11, 11, 0, 11)); - - ImageView imageView = new ImageView(); - if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) { - imageView.setImage(new Image(getSkinnable().addon.getIconUrl(), 40, 40, true, true, true)); - } - descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40)); - - TwoLineListItem content = new TwoLineListItem(); - HBox.setHgrow(content, Priority.ALWAYS); - ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(getSkinnable().addon.getSlug()); - content.setTitle(mod != null ? mod.getDisplayName() : getSkinnable().addon.getTitle()); - content.setSubtitle(getSkinnable().addon.getDescription()); - content.getTags().setAll(getSkinnable().addon.getCategories().stream() - .map(category -> getSkinnable().page.getLocalizedCategory(category)) - .collect(Collectors.toList())); - descriptionPane.getChildren().add(content); - - if (getSkinnable().mod != null) { - JFXHyperlink openMcmodButton = new JFXHyperlink(i18n("mods.mcmod")); - openMcmodButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcmodUrl(getSkinnable().mod.getMcmod()))); - descriptionPane.getChildren().add(openMcmodButton); - - if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) { - JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs")); - openMcbbsButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs()))); - descriptionPane.getChildren().add(openMcbbsButton); + { + ImageView imageView = new ImageView(); + if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) { + imageView.setImage(new Image(getSkinnable().addon.getIconUrl(), 40, 40, true, true, true)); } + descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40)); + + TwoLineListItem content = new TwoLineListItem(); + HBox.setHgrow(content, Priority.ALWAYS); + ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(getSkinnable().addon.getSlug()); + content.setTitle(mod != null ? mod.getDisplayName() : getSkinnable().addon.getTitle()); + content.setSubtitle(getSkinnable().addon.getDescription()); + content.getTags().setAll(getSkinnable().addon.getCategories().stream() + .map(category -> getSkinnable().page.getLocalizedCategory(category)) + .collect(Collectors.toList())); + descriptionPane.getChildren().add(content); + + if (getSkinnable().mod != null) { + JFXHyperlink openMcmodButton = new JFXHyperlink(i18n("mods.mcmod")); + openMcmodButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcmodUrl(getSkinnable().mod.getMcmod()))); + descriptionPane.getChildren().add(openMcmodButton); + + if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) { + JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs")); + openMcbbsButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs()))); + descriptionPane.getChildren().add(openMcbbsButton); + } + } + + JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage()); + openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getPageUrl())); + descriptionPane.getChildren().add(openUrlButton); } - JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage()); - openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getPageUrl())); - descriptionPane.getChildren().add(openUrlButton); + { + ComponentList dependencyPane = new ComponentList(); + dependencyPane.getStyleClass().add("no-padding"); + FXUtils.onChangeAndOperate(control.loaded, loaded -> { + if (loaded) { + dependencyPane.getContent().setAll(control.dependencies.stream() + .map(dependency -> new DependencyModItem(getSkinnable().page, dependency, control.version, control.callback)) + .collect(Collectors.toList())); + } + }); + + Node title = ComponentList.createComponentListTitle(i18n("mods.dependencies")); + + BooleanBinding show = Bindings.createBooleanBinding(() -> !control.dependencies.isEmpty(), control.loaded); + title.managedProperty().bind(show); + title.visibleProperty().bind(show); + dependencyPane.managedProperty().bind(show); + dependencyPane.visibleProperty().bind(show); + + pane.getChildren().addAll(title, dependencyPane); + } SpinnerPane spinnerPane = new SpinnerPane(); - pane.setCenter(spinnerPane); + VBox.setVgrow(spinnerPane, Priority.ALWAYS); + pane.getChildren().add(spinnerPane); { spinnerPane.loadingProperty().bind(getSkinnable().loadingProperty()); spinnerPane.failedReasonProperty().bind(Bindings.createStringBinding(() -> { @@ -239,60 +298,101 @@ public class DownloadPage extends Control implements DecoratorPage { } }, getSkinnable().failedProperty())); - JFXListView listView = new JFXListView<>(); - spinnerPane.setContent(listView); - Bindings.bindContent(listView.getItems(), getSkinnable().items); - listView.setCellFactory(x -> new FloatListCell(listView) { - TwoLineListItem content = new TwoLineListItem(); - StackPane graphicPane = new StackPane(); - JFXButton saveAsButton = new JFXButton(); + ComponentList list = new ComponentList(); + StackPane.setAlignment(list, Pos.TOP_CENTER); + spinnerPane.setContent(list); - { - HBox container = new HBox(8); - container.setAlignment(Pos.CENTER_LEFT); - pane.getChildren().add(container); + FXUtils.onChangeAndOperate(control.loaded, loaded -> { + if (control.versions == null) return; - saveAsButton.getStyleClass().add("toggle-icon4"); - saveAsButton.setGraphic(SVG.contentSaveMoveOutline(Theme.blackFillBinding(), -1, -1)); + for (String gameVersion : control.versions.keys().stream() + .sorted(VersionNumber.VERSION_COMPARATOR.reversed()) + .collect(Collectors.toList())) { + ComponentList sublist = new ComponentList(); + sublist.setLazyInitializer(self -> { + self.getContent().setAll(control.versions.get(gameVersion).stream() + .map(version -> new ModItem(version, control)) + .collect(Collectors.toList())); + }); + sublist.getStyleClass().add("no-padding"); + sublist.setTitle(gameVersion); - HBox.setHgrow(content, Priority.ALWAYS); - container.getChildren().setAll(graphicPane, content, saveAsButton); + list.getContent().add(sublist); } - - @Override - protected void updateControl(DownloadManager.Version dataItem, boolean empty) { - if (empty) return; - content.setTitle(dataItem.getName()); - content.setSubtitle(FORMATTER.format(dataItem.getDatePublished())); - content.getTags().setAll(dataItem.getGameVersions()); - saveAsButton.setOnMouseClicked(e -> getSkinnable().saveAs(dataItem)); - - switch (dataItem.getVersionType()) { - case Release: - graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24)); - content.getTags().add(i18n("version.game.release")); - break; - case Beta: - graphicPane.getChildren().setAll(SVG.betaCircleOutline(Theme.blackFillBinding(), 24, 24)); - content.getTags().add(i18n("version.game.snapshot")); - break; - case Alpha: - graphicPane.getChildren().setAll(SVG.alphaCircleOutline(Theme.blackFillBinding(), 24, 24)); - content.getTags().add(i18n("version.game.snapshot")); - break; - } - } - }); - - listView.setOnMouseClicked(e -> { - if (listView.getSelectionModel().getSelectedIndex() < 0) - return; - DownloadManager.Version selectedItem = listView.getSelectionModel().getSelectedItem(); - getSkinnable().download(selectedItem); }); } - getChildren().setAll(pane); + getChildren().setAll(scrollPane); + } + } + + private static final class DependencyModItem extends StackPane { + + DependencyModItem(DownloadListPage page, DownloadManager.Mod addon, Profile.ProfileVersion version, DownloadCallback callback) { + HBox pane = new HBox(8); + pane.setPadding(new Insets(8)); + pane.setAlignment(Pos.CENTER_LEFT); + TwoLineListItem content = new TwoLineListItem(); + HBox.setHgrow(content, Priority.ALWAYS); + ImageView imageView = new ImageView(); + pane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content); + + RipplerContainer container = new RipplerContainer(pane); + container.setOnMouseClicked(e -> Controllers.navigate(new DownloadPage(page, addon, version, callback))); + getChildren().setAll(container); + + ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(addon.getSlug()); + content.setTitle(mod != null ? mod.getDisplayName() : addon.getTitle()); + content.setSubtitle(addon.getDescription()); + content.getTags().setAll(addon.getCategories().stream() + .map(page::getLocalizedCategory) + .collect(Collectors.toList())); + + if (StringUtils.isNotBlank(addon.getIconUrl())) { + imageView.setImage(new Image(addon.getIconUrl(), 40, 40, true, true, true)); + } + } + } + + private static final class ModItem extends StackPane { + ModItem(DownloadManager.Version dataItem, DownloadPage selfPage) { + HBox pane = new HBox(8); + pane.setPadding(new Insets(8)); + pane.setAlignment(Pos.CENTER_LEFT); + TwoLineListItem content = new TwoLineListItem(); + StackPane graphicPane = new StackPane(); + JFXButton saveAsButton = new JFXButton(); + + RipplerContainer container = new RipplerContainer(pane); + container.setOnMouseClicked(e -> { + selfPage.download(dataItem); + }); + getChildren().setAll(container); + + saveAsButton.getStyleClass().add("toggle-icon4"); + saveAsButton.setGraphic(SVG.contentSaveMoveOutline(Theme.blackFillBinding(), -1, -1)); + + HBox.setHgrow(content, Priority.ALWAYS); + pane.getChildren().setAll(graphicPane, content, saveAsButton); + + content.setTitle(dataItem.getName()); + content.setSubtitle(FORMATTER.format(dataItem.getDatePublished())); + saveAsButton.setOnMouseClicked(e -> selfPage.saveAs(dataItem)); + + switch (dataItem.getVersionType()) { + case Release: + graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24)); + content.getTags().add(i18n("version.game.release")); + break; + case Beta: + graphicPane.getChildren().setAll(SVG.betaCircleOutline(Theme.blackFillBinding(), 24, 24)); + content.getTags().add(i18n("version.game.snapshot")); + break; + case Alpha: + graphicPane.getChildren().setAll(SVG.alphaCircleOutline(Theme.blackFillBinding(), 24, 24)); + content.getTags().add(i18n("version.game.snapshot")); + break; + } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java index 38d76b632..fc0e234ef 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java @@ -180,7 +180,7 @@ public class GameListPage extends ListPageBase implements Decorato Profiles.registerVersionsListener(this::loadVersions); - setOnFailedAction(e -> Controllers.navigate(Controllers.getDownloadPage())); + setOnFailedAction(e -> Controllers.navigate(Controllers.getDownloadPage())); } private void loadVersions(Profile profile) { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 3bbd10de6..bff1c3516 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -533,6 +533,7 @@ mods.add.success=Successfully installed mods %s. mods.category=Category mods.choose_mod=Choose your mods mods.curseforge=CurseForge +mods.dependencies=Dependencies mods.disable=Disable mods.download=Mod Downloads mods.download.title=Mod Downloads - %1s diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 98d14d709..c78e420f5 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -533,6 +533,7 @@ mods.add.success=成功新增模組 %s。 mods.category=類別 mods.choose_mod=選擇模組 mods.curseforge=CurseForge +mods.dependencies=前置 Mod mods.disable=停用 mods.download=模組下載 mods.download.title=模組下載 - %1s diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 7b7855c4c..6093ce532 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -533,6 +533,7 @@ mods.add.success=成功添加模组 %s。 mods.category=类别 mods.choose_mod=选择模组 mods.curseforge=CurseForge +mods.dependencies=前置 Mod mods.disable=禁用 mods.download=模组下载 mods.download.title=模组下载 - %1s diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/DownloadManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/DownloadManager.java index 689d276ff..86bfba6c8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/DownloadManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/DownloadManager.java @@ -28,6 +28,7 @@ public final class DownloadManager { } public interface IMod { + List loadDependencies() throws IOException; Stream loadVersions() throws IOException; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java index 594e29e79..cc16f29ca 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java @@ -22,8 +22,10 @@ import org.jackhuang.hmcl.util.Immutable; import java.io.IOException; import java.time.Instant; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -159,6 +161,19 @@ public class CurseAddon implements DownloadManager.IMod { return isExperimental; } + @Override + public List loadDependencies() throws IOException { + Set dependencies = latestFiles.stream() + .flatMap(latestFile -> latestFile.getDependencies().stream()) + .map(Dependency::getAddonId) + .collect(Collectors.toSet()); + List mods = new ArrayList<>(); + for (int dependencyId : dependencies) { + mods.add(CurseModManager.getAddon(dependencyId).toMod()); + } + return mods; + } + @Override public Stream loadVersions() throws IOException { return CurseModManager.getFiles(this).stream() @@ -485,7 +500,7 @@ public class CurseAddon implements DownloadManager.IMod { versionType, new DownloadManager.File(Collections.emptyMap(), getDownloadUrl(), getFileName()), Collections.emptyList(), - gameVersion, + gameVersion.stream().filter(ver -> ver.startsWith("1.") || ver.contains("w")).collect(Collectors.toList()), Collections.emptyList() ); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseModManager.java index 3158a37e0..fb733bf57 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseModManager.java @@ -47,6 +47,11 @@ public final class CurseModManager { }.getType()); } + public static CurseAddon getAddon(int id) throws IOException { + String response = NetworkUtils.doGet(NetworkUtils.toURL("https://addons-ecs.forgesvc.net/api/v2/addon/" + id)); + return JsonUtils.fromNonNullJson(response, CurseAddon.class); + } + public static List getFiles(CurseAddon addon) throws IOException { String response = NetworkUtils.doGet(NetworkUtils.toURL("https://addons-ecs.forgesvc.net/api/v2/addon/" + addon.getId() + "/files")); return JsonUtils.fromNonNullJson(response, new TypeToken>() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/Modrinth.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/Modrinth.java index a445894b0..07de4339c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/Modrinth.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/Modrinth.java @@ -418,6 +418,11 @@ public final class Modrinth { return latestVersion; } + @Override + public List loadDependencies() throws IOException { + return Collections.emptyList(); + } + @Override public Stream loadVersions() throws IOException { return Modrinth.getFiles(this).stream() diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java index 1348472f2..d02bd87d1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java @@ -865,7 +865,7 @@ public abstract class Task { * @param tasks the Tasks * @return a new Task that is completed when all of the given Tasks complete */ - public static Task allOf(Task... tasks) { + public static Task> allOf(Task... tasks) { return allOf(Arrays.asList(tasks)); } @@ -880,14 +880,15 @@ public abstract class Task { * @param tasks the Tasks * @return a new Task that is completed when all of the given Tasks complete */ - public static Task allOf(Collection> tasks) { - return new Task() { + public static Task> allOf(Collection> tasks) { + return new Task>() { { setSignificance(TaskSignificance.MINOR); } @Override public void execute() { + setResult(tasks.stream().map(Task::getResult).collect(Collectors.toList())); } @Override