From 37fb9a0d65b75beadd74bfd6be7bcc3c4bcaf980 Mon Sep 17 00:00:00 2001 From: Yuhui Huang Date: Tue, 3 Aug 2021 22:47:53 +0800 Subject: [PATCH] feat: (modpack download): category & sort. --- .../org/jackhuang/hmcl/ui/Controllers.java | 14 ++-- .../hmcl/ui/versions/ModDownloadListPage.java | 80 ++++++++++++++++--- .../jackhuang/hmcl/ui/versions/Versions.java | 1 - .../resources/assets/lang/I18N.properties | 7 ++ .../assets/lang/I18N_zh_CN.properties | 7 ++ .../hmcl/mod/curse/CurseModManager.java | 4 +- .../org/jackhuang/hmcl/util/StringUtils.java | 8 ++ 7 files changed, 102 insertions(+), 19 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index a855163ea..1ba814a05 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -42,7 +42,6 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.ui.construct.PromptDialogPane; import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane; import org.jackhuang.hmcl.ui.decorator.DecoratorController; -import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; import org.jackhuang.hmcl.ui.main.RootPage; import org.jackhuang.hmcl.ui.versions.GameListPage; @@ -84,12 +83,13 @@ public final class Controllers { private static AuthlibInjectorServersPage serversPage = null; private static Lazy rootPage = new Lazy<>(RootPage::new); private static DecoratorController decorator; - private static Lazy modDownloadListPage = new Lazy<>(() -> - new ModDownloadListPage(CurseModManager.SECTION_MODPACK, Versions::downloadModpackImpl) { - { - state.set(State.fromTitle(i18n("modpack.download"))); - } - }); + private static Lazy modDownloadListPage = new Lazy<>(() -> { + return new ModDownloadListPage(CurseModManager.SECTION_MODPACK, Versions::downloadModpackImpl) { + { + state.set(State.fromTitle(i18n("modpack.download"))); + } + }; + }); private Controllers() { } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java index d8b478da9..e1d02624d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java @@ -17,9 +17,7 @@ */ package org.jackhuang.hmcl.ui.versions; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXListView; -import com.jfoenix.controls.JFXTextField; +import com.jfoenix.controls.*; import javafx.beans.binding.Bindings; import javafx.beans.property.*; import javafx.collections.FXCollections; @@ -44,6 +42,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.StringUtils; import java.io.File; +import java.util.*; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -174,13 +173,37 @@ public class ModDownloadListPage extends Control implements DecoratorPage { } }); - JFXTextField categoryField = new JFXTextField(); - categoryField.setPromptText(i18n("mods.category")); - searchPane.add(categoryField, 0, 1); + StackPane categoryStackPane = new StackPane(); + JFXComboBox categoryComboBox = new JFXComboBox<>(); + categoryStackPane.getChildren().setAll(categoryComboBox); + categoryComboBox.prefWidthProperty().bind(categoryStackPane.widthProperty()); + categoryComboBox.getStyleClass().add("fit-width"); + categoryComboBox.setPromptText(i18n("mods.category")); + Task.supplyAsync(() -> CurseModManager.getCategories(getSkinnable().section)) + .thenAcceptAsync(Schedulers.javafx(), categories -> { + List result = new ArrayList<>(); + for (CurseModManager.Category category : categories) { + resolveCategory(category, 0, result); + } + categoryComboBox.getItems().setAll(result); + }).start(); + searchPane.add(categoryStackPane, 0, 1); - JFXTextField sortField = new JFXTextField(); - sortField.setPromptText(i18n("search.sort")); - searchPane.add(sortField, 1, 1); + StackPane sortStackPane = new StackPane(); + JFXComboBox sortComboBox = new JFXComboBox<>(); + sortStackPane.getChildren().setAll(sortComboBox); + sortComboBox.prefWidthProperty().bind(sortStackPane.widthProperty()); + sortComboBox.getStyleClass().add("fit-width"); + sortComboBox.setPromptText(i18n("search.sort")); + sortComboBox.getItems().setAll( + i18n("curse.sort.date_created"), + i18n("curse.sort.popularity"), + i18n("curse.sort.last_updated"), + i18n("curse.sort.name"), + i18n("curse.sort.author"), + i18n("curse.sort.total_downloads")); + sortComboBox.getSelectionModel().select(0); + searchPane.add(sortStackPane, 1, 1); VBox vbox = new VBox(); vbox.setAlignment(Pos.CENTER_RIGHT); @@ -189,7 +212,14 @@ public class ModDownloadListPage extends Control implements DecoratorPage { JFXButton searchButton = new JFXButton(); searchButton.setText(i18n("search")); searchButton.setOnAction(e -> { - getSkinnable().search(gameVersionField.getText(), 0, 0, nameField.getText(), 0); + getSkinnable().search(gameVersionField.getText(), + Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem()) + .map(CategoryIndented::getCategory) + .map(CurseModManager.Category::getId) + .orElse(0), + 0, + nameField.getText(), + sortComboBox.getSelectionModel().getSelectedIndex()); }); searchPane.add(searchButton, 0, 2); vbox.getChildren().setAll(searchButton); @@ -256,5 +286,35 @@ public class ModDownloadListPage extends Control implements DecoratorPage { getChildren().setAll(pane); } + + private static class CategoryIndented { + private final int indent; + private final CurseModManager.Category category; + + public CategoryIndented(int indent, CurseModManager.Category category) { + this.indent = indent; + this.category = category; + } + + public int getIndent() { + return indent; + } + + public CurseModManager.Category getCategory() { + return category; + } + + @Override + public String toString() { + return StringUtils.repeats(' ', indent) + i18n("curse.category." + category.getId()); + } + } + + private static void resolveCategory(CurseModManager.Category category, int indent, List result) { + result.add(new CategoryIndented(indent, category)); + for (CurseModManager.Category subcategory : category.getSubcategories()) { + resolveCategory(subcategory, indent + 1, result); + } + } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java index 1bfe52339..57d377195 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java @@ -51,7 +51,6 @@ import java.nio.file.Path; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; -import static org.jackhuang.hmcl.ui.download.LocalModpackPage.MODPACK_FILE; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class Versions { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 689504edd..dd3dc8d83 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -156,6 +156,13 @@ curse.category.4558=Redstone curse.category.4843=Automation curse.category.4906=MCreator +curse.sort.author=Author +curse.sort.date_created=Date Created +curse.sort.last_updated=Last Updated +curse.sort.name=Name +curse.sort.popularity=Popularity +curse.sort.total_downloads=Total Downloads + download=Download download.code.404=File not found on the remote server: %s download.failed=Failed to download %1$s, response code: %2$d 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 2d9f37dc7..7b5705cce 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -157,6 +157,13 @@ curse.category.4558=红石 curse.category.4843=自动化 curse.category.4906=MCreator +curse.sort.author=作者 +curse.sort.date_created=创建日期 +curse.sort.last_updated=最近更新 +curse.sort.name=名称 +curse.sort.popularity=热度 +curse.sort.total_downloads=下载量 + download=下载 download.code.404=远程服务器不包含需要下载的文件: %s download.failed=下载失败: %1$s,错误码:%2$d 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 e4490c79c..9094ed38a 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 @@ -11,7 +11,9 @@ import java.util.*; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; -public class CurseModManager { +public final class CurseModManager { + private CurseModManager() { + } public static List searchPaginated(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws IOException { String response = NetworkUtils.doGet(new URL(NetworkUtils.withQuery("https://addons-ecs.forgesvc.net/api/v2/addon/search", mapOf( diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java index 3a71fdeb2..2fde81c16 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java @@ -235,4 +235,12 @@ public final class StringUtils { } return builder.toString(); } + + public static String repeats(char ch, int repeat) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < repeat; i++) { + result.append(ch); + } + return result.toString(); + } }