feat: download mods and modpacks from CurseForge.
This commit is contained in:
@@ -28,6 +28,7 @@ import javafx.stage.StageStyle;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.download.java.JavaRepository;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -41,11 +42,15 @@ 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;
|
||||
import org.jackhuang.hmcl.ui.versions.ModDownloadListPage;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
@@ -65,11 +70,26 @@ public final class Controllers {
|
||||
|
||||
private static Scene scene;
|
||||
private static Stage stage;
|
||||
private static VersionPage versionPage = null;
|
||||
private static GameListPage gameListPage = null;
|
||||
private static Lazy<VersionPage> versionPage = new Lazy<>(VersionPage::new);
|
||||
private static Lazy<GameListPage> gameListPage = new Lazy<>(() -> {
|
||||
GameListPage gameListPage = new GameListPage();
|
||||
gameListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
||||
gameListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
||||
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||
});
|
||||
return gameListPage;
|
||||
});
|
||||
private static AuthlibInjectorServersPage serversPage = null;
|
||||
private static RootPage rootPage;
|
||||
private static Lazy<RootPage> rootPage = new Lazy<>(RootPage::new);
|
||||
private static DecoratorController decorator;
|
||||
private static Lazy<ModDownloadListPage> modDownloadListPage = new Lazy<>(() ->
|
||||
new ModDownloadListPage(CurseModManager.SECTION_MODPACK, Versions::downloadModpackImpl) {
|
||||
{
|
||||
state.set(State.fromTitle(i18n("modpack.download")));
|
||||
}
|
||||
});
|
||||
|
||||
private Controllers() {
|
||||
}
|
||||
@@ -84,30 +104,17 @@ public final class Controllers {
|
||||
|
||||
// FXThread
|
||||
public static VersionPage getVersionPage() {
|
||||
if (versionPage == null)
|
||||
versionPage = new VersionPage();
|
||||
return versionPage;
|
||||
return versionPage.get();
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static GameListPage getGameListPage() {
|
||||
if (gameListPage == null) {
|
||||
gameListPage = new GameListPage();
|
||||
gameListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
||||
gameListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
||||
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||
});
|
||||
}
|
||||
return gameListPage;
|
||||
return gameListPage.get();
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static RootPage getRootPage() {
|
||||
if (rootPage == null)
|
||||
rootPage = new RootPage();
|
||||
return rootPage;
|
||||
return rootPage.get();
|
||||
}
|
||||
|
||||
// FXThread
|
||||
@@ -117,6 +124,11 @@ public final class Controllers {
|
||||
return serversPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static ModDownloadListPage getModpackDownloadListPage() {
|
||||
return modDownloadListPage.get();
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static DecoratorController getDecorator() {
|
||||
return decorator;
|
||||
@@ -233,6 +245,8 @@ public final class Controllers {
|
||||
rootPage = null;
|
||||
versionPage = null;
|
||||
serversPage = null;
|
||||
gameListPage = null;
|
||||
modDownloadListPage = null;
|
||||
decorator = null;
|
||||
stage = null;
|
||||
scene = null;
|
||||
|
||||
@@ -24,6 +24,8 @@ import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -112,6 +114,15 @@ public final class FXUtils {
|
||||
return onWeakChange(value, consumer);
|
||||
}
|
||||
|
||||
public static WeakInvalidationListener observeWeak(Runnable runnable, Observable... observables) {
|
||||
WeakInvalidationListener listener = new WeakInvalidationListener(observable -> runnable.run());
|
||||
for (Observable observable : observables) {
|
||||
observable.addListener(listener);
|
||||
}
|
||||
runnable.run();
|
||||
return listener;
|
||||
}
|
||||
|
||||
public static void runLaterIf(BooleanSupplier condition, Runnable runnable) {
|
||||
if (condition.getAsBoolean()) Platform.runLater(() -> runLaterIf(condition, runnable));
|
||||
else runnable.run();
|
||||
|
||||
@@ -246,4 +246,16 @@ public final class SVG {
|
||||
public static Node texture(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M9.29,21H12.12L21,12.12V9.29M19,21C19.55,21 20.05,20.78 20.41,20.41C20.78,20.05 21,19.55 21,19V17L17,21M5,3A2,2 0 0,0 3,5V7L7,3M11.88,3L3,11.88V14.71L14.71,3M19.5,3.08L3.08,19.5C3.17,19.85 3.35,20.16 3.59,20.41C3.84,20.65 4.15,20.83 4.5,20.92L20.93,4.5C20.74,3.8 20.2,3.26 19.5,3.08Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node alphaCircleOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M11,7H13A2,2 0 0,1 15,9V17H13V13H11V17H9V9A2,2 0 0,1 11,7M11,9V11H13V9H11M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node betaCircleOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M15,10.5C15,11.3 14.3,12 13.5,12C14.3,12 15,12.7 15,13.5V15A2,2 0 0,1 13,17H9V7H13A2,2 0 0,1 15,9V10.5M13,15V13H11V15H13M13,11V9H11V11H13M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node releaseCircleOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z", fill, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,13 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
|
||||
root.setCenter(scrollPane);
|
||||
}
|
||||
|
||||
spinnerPane.loadingProperty().bind(skinnable.loadingProperty());
|
||||
FXUtils.onChangeAndOperate(skinnable.loadingProperty(), loading -> {
|
||||
if (loading) {
|
||||
spinnerPane.showSpinner();
|
||||
} else {
|
||||
spinnerPane.hideSpinner();
|
||||
}
|
||||
});
|
||||
spinnerPane.setContent(root);
|
||||
|
||||
getChildren().setAll(spinnerPane);
|
||||
|
||||
@@ -18,34 +18,30 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationHandler;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
|
||||
@DefaultProperty("content")
|
||||
public class SpinnerPane extends Control {
|
||||
private final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading");
|
||||
private final StringProperty failedReason = new SimpleStringProperty(this, "failedReason");
|
||||
|
||||
public void showSpinner() {
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
public void hideSpinner() {
|
||||
setFailedReason(null);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -73,6 +69,18 @@ public class SpinnerPane extends Control {
|
||||
this.loading.set(loading);
|
||||
}
|
||||
|
||||
public String getFailedReason() {
|
||||
return failedReason.get();
|
||||
}
|
||||
|
||||
public StringProperty failedReasonProperty() {
|
||||
return failedReason;
|
||||
}
|
||||
|
||||
public void setFailedReason(String failedReason) {
|
||||
this.failedReason.set(failedReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin createDefaultSkin() {
|
||||
return new Skin(this);
|
||||
@@ -82,62 +90,57 @@ public class SpinnerPane extends Control {
|
||||
private final JFXSpinner spinner = new JFXSpinner();
|
||||
private final StackPane contentPane = new StackPane();
|
||||
private final StackPane topPane = new StackPane();
|
||||
private final StackPane root = new StackPane();
|
||||
private Timeline animation;
|
||||
private final TransitionPane root = new TransitionPane();
|
||||
private final StackPane failedPane = new StackPane();
|
||||
private final Label failedReasonLabel = new Label();
|
||||
@SuppressWarnings("FieldCanBeLocal") // prevent from gc.
|
||||
private final WeakInvalidationListener observer;
|
||||
|
||||
protected Skin(SpinnerPane control) {
|
||||
super(control);
|
||||
|
||||
root.getStyleClass().add("spinner-pane");
|
||||
topPane.getChildren().setAll(spinner);
|
||||
root.getChildren().setAll(contentPane, topPane);
|
||||
FXUtils.onChangeAndOperate(getSkinnable().content, newValue -> contentPane.getChildren().setAll(newValue));
|
||||
topPane.getStyleClass().add("notice-pane");
|
||||
failedPane.getChildren().setAll(failedReasonLabel);
|
||||
|
||||
FXUtils.onChangeAndOperate(getSkinnable().content, newValue -> {
|
||||
if (newValue == null) {
|
||||
contentPane.getChildren().clear();
|
||||
} else {
|
||||
contentPane.getChildren().setAll(newValue);
|
||||
}
|
||||
});
|
||||
getChildren().setAll(root);
|
||||
|
||||
FXUtils.onChangeAndOperate(getSkinnable().loadingProperty(), newValue -> {
|
||||
Timeline prev = animation;
|
||||
if (prev != null) prev.stop();
|
||||
observer = FXUtils.observeWeak(() -> {
|
||||
if (getSkinnable().getFailedReason() != null) {
|
||||
root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
failedReasonLabel.setText(getSkinnable().getFailedReason());
|
||||
} else if (getSkinnable().isLoading()) {
|
||||
root.setContent(topPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
} else {
|
||||
root.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
}, getSkinnable().loadingProperty(), getSkinnable().failedReasonProperty());
|
||||
}
|
||||
}
|
||||
|
||||
AnimationProducer transition;
|
||||
topPane.setMouseTransparent(true);
|
||||
topPane.setVisible(true);
|
||||
topPane.getStyleClass().add("gray-background");
|
||||
if (newValue)
|
||||
transition = ContainerAnimations.FADE_IN.getAnimationProducer();
|
||||
else
|
||||
transition = ContainerAnimations.FADE_OUT.getAnimationProducer();
|
||||
public interface State {}
|
||||
|
||||
AnimationHandler handler = new AnimationHandler() {
|
||||
@Override
|
||||
public Duration getDuration() {
|
||||
return Duration.millis(160);
|
||||
}
|
||||
public static class LoadedState implements State {}
|
||||
|
||||
@Override
|
||||
public Pane getCurrentRoot() {
|
||||
return root;
|
||||
}
|
||||
public static class LoadingState implements State {}
|
||||
|
||||
@Override
|
||||
public Node getPreviousNode() {
|
||||
return null;
|
||||
}
|
||||
public static class FailedState implements State {
|
||||
private final String reason;
|
||||
|
||||
@Override
|
||||
public Node getCurrentNode() {
|
||||
return topPane;
|
||||
}
|
||||
};
|
||||
public FailedState(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
Timeline now = new Timeline();
|
||||
now.getKeyFrames().addAll(transition.animate(handler));
|
||||
now.getKeyFrames().add(new KeyFrame(handler.getDuration(), e -> {
|
||||
topPane.setMouseTransparent(!newValue);
|
||||
topPane.setVisible(newValue);
|
||||
}));
|
||||
now.play();
|
||||
animation = now;
|
||||
});
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,18 +20,22 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
public class TwoLineListItem extends VBox {
|
||||
private static final String DEFAULT_STYLE_CLASS = "two-line-list-item";
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final StringProperty tag = new SimpleStringProperty(this, "tag");
|
||||
private final ObservableList<String> tags = FXCollections.observableArrayList();
|
||||
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle");
|
||||
|
||||
private final ObservableList<Label> tagLabels;
|
||||
|
||||
public TwoLineListItem(String titleString, String subtitleString) {
|
||||
this();
|
||||
|
||||
@@ -48,15 +52,17 @@ public class TwoLineListItem extends VBox {
|
||||
lblTitle.getStyleClass().add("title");
|
||||
lblTitle.textProperty().bind(title);
|
||||
|
||||
Label lblTag = new Label();
|
||||
lblTag.getStyleClass().add("tag");
|
||||
lblTag.textProperty().bind(tag);
|
||||
HBox tagContainer = new HBox();
|
||||
|
||||
lblTag.visibleProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> StringUtils.isNotBlank(tag.getValue()),
|
||||
tag));
|
||||
tagLabels = MappedObservableList.create(tags, tag -> {
|
||||
Label tagLabel = new Label();
|
||||
tagLabel.getStyleClass().add("tag");
|
||||
tagLabel.setText(tag);
|
||||
return tagLabel;
|
||||
});
|
||||
Bindings.bindContent(tagContainer.getChildren(), tagLabels);
|
||||
|
||||
firstLine.getChildren().addAll(lblTitle, lblTag);
|
||||
firstLine.getChildren().addAll(lblTitle, tagContainer);
|
||||
|
||||
Label lblSubtitle = new Label();
|
||||
lblSubtitle.getStyleClass().add("subtitle");
|
||||
@@ -90,16 +96,8 @@ public class TwoLineListItem extends VBox {
|
||||
this.subtitle.set(subtitle);
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag.get();
|
||||
}
|
||||
|
||||
public StringProperty tagProperty() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public void setTag(String tag) {
|
||||
this.tag.set(tag);
|
||||
public ObservableList<String> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -45,7 +45,9 @@ public class GameItemSkin extends SkinBase<GameItem> {
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.titleProperty().bind(skinnable.titleProperty());
|
||||
item.tagProperty().bind(skinnable.tagProperty());
|
||||
FXUtils.onChangeAndOperate(skinnable.tagProperty(), tag -> {
|
||||
item.getTags().setAll(tag);
|
||||
});
|
||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
center.getChildren().setAll(imageView, item);
|
||||
|
||||
@@ -152,6 +152,13 @@ public class GameListPage extends ListPageBase<GameListItem> implements Decorato
|
||||
installModpackItem.setLeftGraphic(VersionPage.wrap(SVG.pack(Theme.blackFillBinding(), 24, 24)));
|
||||
installModpackItem.setOnAction(e -> Versions.importModpack());
|
||||
|
||||
AdvancedListItem downloadModpackItem = new AdvancedListItem();
|
||||
downloadModpackItem.getStyleClass().add("navigation-drawer-item");
|
||||
downloadModpackItem.setTitle(i18n("modpack.download"));
|
||||
downloadModpackItem.setActionButtonVisible(false);
|
||||
downloadModpackItem.setLeftGraphic(VersionPage.wrap(SVG.fire(Theme.blackFillBinding(), 24, 24)));
|
||||
downloadModpackItem.setOnAction(e -> Versions.downloadModpack());
|
||||
|
||||
AdvancedListItem refreshItem = new AdvancedListItem();
|
||||
refreshItem.getStyleClass().add("navigation-drawer-item");
|
||||
refreshItem.setTitle(i18n("button.refresh"));
|
||||
@@ -169,9 +176,10 @@ public class GameListPage extends ListPageBase<GameListItem> implements Decorato
|
||||
AdvancedListBox bottomLeftCornerList = new AdvancedListBox()
|
||||
.add(installNewGameItem)
|
||||
.add(installModpackItem)
|
||||
.add(downloadModpackItem)
|
||||
.add(refreshItem)
|
||||
.add(globalManageItem);
|
||||
FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 12 * 2);
|
||||
FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 5 + 12 * 2);
|
||||
left.setBottom(bottomLeftCornerList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,74 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.FloatListCell;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class ModDownloadListPage extends Control {
|
||||
public class ModDownloadListPage extends Control implements DecoratorPage {
|
||||
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
||||
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
||||
private final ListProperty<CurseAddon> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||
private final ModDownloadPage.DownloadCallback callback;
|
||||
|
||||
/**
|
||||
* @see org.jackhuang.hmcl.mod.curse.CurseModManager#SECTION_MODPACK
|
||||
* @see org.jackhuang.hmcl.mod.curse.CurseModManager#SECTION_MOD
|
||||
*/
|
||||
private final int section;
|
||||
private Profile profile;
|
||||
private String version;
|
||||
|
||||
public ModDownloadListPage(int section) {
|
||||
public ModDownloadListPage(int section, ModDownloadPage.DownloadCallback callback) {
|
||||
this.section = section;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String version) {
|
||||
this.profile = profile;
|
||||
this.version = version;
|
||||
this.version.set(new Profile.ProfileVersion(profile, version));
|
||||
|
||||
setLoading(false);
|
||||
setFailed(false);
|
||||
@@ -66,17 +98,34 @@ public class ModDownloadListPage extends Control {
|
||||
this.loading.set(loading);
|
||||
}
|
||||
|
||||
public void search(String gameVersion, int category, int pageOffset, String searchFilter, int sort) {
|
||||
public void search(String userGameVersion, int category, int pageOffset, String searchFilter, int sort) {
|
||||
setLoading(true);
|
||||
Task.supplyAsync(() -> CurseModManager.searchPaginated(gameVersion, category, section, pageOffset, searchFilter, sort))
|
||||
.whenComplete(Schedulers.javafx(), (exception, result) -> {
|
||||
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
|
||||
? version.get().getProfile().getRepository().getVersionJar(version.get().getVersion())
|
||||
: null;
|
||||
Task.supplyAsync(() -> {
|
||||
String gameVersion;
|
||||
if (StringUtils.isBlank(version.get().getVersion())) {
|
||||
gameVersion = userGameVersion;
|
||||
} else {
|
||||
gameVersion = GameVersion.minecraftVersion(versionJar).orElse("");
|
||||
}
|
||||
return gameVersion;
|
||||
}).thenApplyAsync(gameVersion -> {
|
||||
return CurseModManager.searchPaginated(gameVersion, category, section, pageOffset, searchFilter, sort);
|
||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
setLoading(false);
|
||||
if (exception == null) {
|
||||
|
||||
items.setAll(result);
|
||||
} else {
|
||||
|
||||
failed.set(true);
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,10 +139,10 @@ public class ModDownloadListPage extends Control {
|
||||
super(control);
|
||||
|
||||
VBox pane = new VBox();
|
||||
pane.getStyleClass().add("card-list");
|
||||
|
||||
GridPane searchPane = new GridPane();
|
||||
searchPane.getStyleClass().add("card");
|
||||
searchPane.getStyleClass().addAll("card");
|
||||
VBox.setMargin(searchPane, new Insets(10, 10, 0, 10));
|
||||
|
||||
ColumnConstraints column1 = new ColumnConstraints();
|
||||
column1.setPercentWidth(50);
|
||||
@@ -115,6 +164,16 @@ public class ModDownloadListPage extends Control {
|
||||
gameVersionField.setPromptText(i18n("world.game_version"));
|
||||
searchPane.add(gameVersionField, 1, 0);
|
||||
|
||||
FXUtils.onChangeAndOperate(getSkinnable().version, version -> {
|
||||
searchPane.getChildren().remove(gameVersionField);
|
||||
if (StringUtils.isNotBlank(version.getVersion())) {
|
||||
GridPane.setColumnSpan(nameField, 2);
|
||||
} else {
|
||||
searchPane.add(gameVersionField, 1, 0);
|
||||
GridPane.setColumnSpan(nameField, 1);
|
||||
}
|
||||
});
|
||||
|
||||
JFXTextField categoryField = new JFXTextField();
|
||||
categoryField.setPromptText(i18n("mods.category"));
|
||||
searchPane.add(categoryField, 0, 1);
|
||||
@@ -130,22 +189,70 @@ public class ModDownloadListPage extends Control {
|
||||
JFXButton searchButton = new JFXButton();
|
||||
searchButton.setText(i18n("search"));
|
||||
searchButton.setOnAction(e -> {
|
||||
getSkinnable().search(gameVersionField.getText(), categoryField.getText(), 0, nameField.getText(), sortField.getText())
|
||||
.whenComplete();
|
||||
getSkinnable().search(gameVersionField.getText(), 0, 0, nameField.getText(), 0);
|
||||
});
|
||||
searchPane.add(searchButton, 0, 2);
|
||||
vbox.getChildren().setAll(searchButton);
|
||||
}
|
||||
|
||||
TransitionPane transitionPane = new TransitionPane();
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
{
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
spinnerPane.loadingProperty().bind(getSkinnable().loadingProperty());
|
||||
spinnerPane.failedReasonProperty().bind(Bindings.createStringBinding(() -> {
|
||||
if (getSkinnable().isFailed()) {
|
||||
return i18n("download.failed.refresh");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, getSkinnable().failedProperty()));
|
||||
|
||||
JFXListView<CurseAddon> listView = new JFXListView<>();
|
||||
spinnerPane.setContent(listView);
|
||||
Bindings.bindContent(listView.getItems(), getSkinnable().items);
|
||||
listView.setOnMouseClicked(e -> {
|
||||
if (listView.getSelectionModel().getSelectedIndex() < 0)
|
||||
return;
|
||||
CurseAddon selectedItem = listView.getSelectionModel().getSelectedItem();
|
||||
Controllers.navigate(new ModDownloadPage(selectedItem, getSkinnable().version.get(), getSkinnable().callback));
|
||||
});
|
||||
listView.setCellFactory(x -> new FloatListCell<CurseAddon>() {
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
ImageView imageView = new ImageView();
|
||||
|
||||
{
|
||||
Region clippedContainer = (Region) listView.lookup(".clipped-container");
|
||||
setPrefWidth(0);
|
||||
HBox container = new HBox(8);
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
pane.getChildren().add(container);
|
||||
if (clippedContainer != null) {
|
||||
maxWidthProperty().bind(clippedContainer.widthProperty());
|
||||
prefWidthProperty().bind(clippedContainer.widthProperty());
|
||||
minWidthProperty().bind(clippedContainer.widthProperty());
|
||||
}
|
||||
|
||||
container.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControl(CurseAddon dataItem, boolean empty) {
|
||||
if (empty) return;
|
||||
content.setTitle(dataItem.getName());
|
||||
content.setSubtitle(dataItem.getSummary());
|
||||
content.getTags().setAll(dataItem.getCategories().stream()
|
||||
.map(category -> i18n("curse.category." + category.getCategoryId()))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
for (CurseAddon.Attachment attachment : dataItem.getAttachments()) {
|
||||
if (attachment.isDefault()) {
|
||||
imageView.setImage(new Image(attachment.getThumbnailUrl(), 40, 40, true, true, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pane.getChildren().setAll(searchPane, transitionPane);
|
||||
pane.getChildren().setAll(searchPane, spinnerPane);
|
||||
|
||||
getChildren().setAll(pane);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,127 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
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.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class ModDownloadPage extends Control implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
private final ListProperty<CurseAddon.LatestFile> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
||||
private final CurseAddon addon;
|
||||
private final Version version;
|
||||
private final Profile.ProfileVersion version;
|
||||
private final DownloadCallback callback;
|
||||
|
||||
public ModDownloadPage(CurseAddon addon, Version version) {
|
||||
public ModDownloadPage(CurseAddon addon, Profile.ProfileVersion version, DownloadCallback callback) {
|
||||
this.addon = addon;
|
||||
this.version = version;
|
||||
this.callback = callback;
|
||||
|
||||
File versionJar = StringUtils.isNotBlank(version.getVersion())
|
||||
? version.getProfile().getRepository().getVersionJar(version.getVersion())
|
||||
: null;
|
||||
|
||||
Task.runAsync(() -> {
|
||||
if (StringUtils.isNotBlank(version.getVersion())) {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
|
||||
if (gameVersion.isPresent()) {
|
||||
List<CurseAddon.LatestFile> files = CurseModManager.getFiles(addon);
|
||||
items.setAll(files.stream()
|
||||
.filter(file -> file.getGameVersion().contains(gameVersion.get()))
|
||||
.collect(Collectors.toList()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
List<CurseAddon.LatestFile> files = CurseModManager.getFiles(addon);
|
||||
items.setAll(files);
|
||||
}).start();
|
||||
|
||||
this.state.set(State.fromTitle(i18n("mods.download.title", addon.getName())));
|
||||
}
|
||||
|
||||
public CurseAddon getAddon() {
|
||||
return addon;
|
||||
}
|
||||
|
||||
public Profile.ProfileVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
return loading.get();
|
||||
}
|
||||
|
||||
public BooleanProperty loadingProperty() {
|
||||
return loading;
|
||||
}
|
||||
|
||||
public void setLoading(boolean loading) {
|
||||
this.loading.set(loading);
|
||||
}
|
||||
|
||||
public boolean isFailed() {
|
||||
return failed.get();
|
||||
}
|
||||
|
||||
public BooleanProperty failedProperty() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
public void setFailed(boolean failed) {
|
||||
this.failed.set(failed);
|
||||
}
|
||||
|
||||
public void download(CurseAddon.LatestFile file) {
|
||||
this.callback.download(version.getProfile(), version.getVersion(), file);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -33,6 +138,108 @@ public class ModDownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
protected ModDownloadPageSkin(ModDownloadPage control) {
|
||||
super(control);
|
||||
|
||||
BorderPane pane = new BorderPane();
|
||||
|
||||
HBox descriptionPane = new HBox(8);
|
||||
descriptionPane.setAlignment(Pos.CENTER);
|
||||
pane.setTop(descriptionPane);
|
||||
descriptionPane.getStyleClass().add("card");
|
||||
BorderPane.setMargin(descriptionPane, new Insets(11, 11, 0, 11));
|
||||
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
content.setTitle(getSkinnable().addon.getName());
|
||||
content.setSubtitle(getSkinnable().addon.getSummary());
|
||||
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
|
||||
.map(category -> i18n("curse.category." + category.getCategoryId()))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
for (CurseAddon.Attachment attachment : getSkinnable().addon.getAttachments()) {
|
||||
if (attachment.isDefault()) {
|
||||
imageView.setImage(new Image(attachment.getThumbnailUrl(), 40, 40, true, true, true));
|
||||
}
|
||||
}
|
||||
|
||||
JFXButton openUrlButton = new JFXButton();
|
||||
openUrlButton.getStyleClass().add("toggle-icon4");
|
||||
openUrlButton.setGraphic(SVG.launchOutline(Theme.blackFillBinding(), -1, -1));
|
||||
openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getWebsiteUrl()));
|
||||
|
||||
descriptionPane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content, openUrlButton);
|
||||
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
pane.setCenter(spinnerPane);
|
||||
{
|
||||
spinnerPane.loadingProperty().bind(getSkinnable().loadingProperty());
|
||||
spinnerPane.failedReasonProperty().bind(Bindings.createStringBinding(() -> {
|
||||
if (getSkinnable().isFailed()) {
|
||||
return i18n("download.failed.refresh");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, getSkinnable().failedProperty()));
|
||||
|
||||
JFXListView<CurseAddon.LatestFile> listView = new JFXListView<>();
|
||||
spinnerPane.setContent(listView);
|
||||
Bindings.bindContent(listView.getItems(), getSkinnable().items);
|
||||
listView.setCellFactory(x -> new FloatListCell<CurseAddon.LatestFile>() {
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
StackPane graphicPane = new StackPane();
|
||||
|
||||
{
|
||||
Region clippedContainer = (Region)listView.lookup(".clipped-container");
|
||||
setPrefWidth(0);
|
||||
HBox container = new HBox(8);
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
pane.getChildren().add(container);
|
||||
if (clippedContainer != null) {
|
||||
maxWidthProperty().bind(clippedContainer.widthProperty());
|
||||
prefWidthProperty().bind(clippedContainer.widthProperty());
|
||||
minWidthProperty().bind(clippedContainer.widthProperty());
|
||||
}
|
||||
|
||||
container.getChildren().setAll(graphicPane, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControl(CurseAddon.LatestFile dataItem, boolean empty) {
|
||||
if (empty) return;
|
||||
content.setTitle(dataItem.getDisplayName());
|
||||
content.getTags().setAll(dataItem.getGameVersion());
|
||||
|
||||
switch (dataItem.getReleaseType()) {
|
||||
case 1: // release
|
||||
graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||
content.getTags().add(i18n("version.game.release"));
|
||||
break;
|
||||
case 2: // beta
|
||||
graphicPane.getChildren().setAll(SVG.betaCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||
content.getTags().add(i18n("version.game.snapshot"));
|
||||
break;
|
||||
case 3: // 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;
|
||||
CurseAddon.LatestFile selectedItem = listView.getSelectionModel().getSelectedItem();
|
||||
getSkinnable().download(selectedItem);
|
||||
});
|
||||
}
|
||||
|
||||
getChildren().setAll(pane);
|
||||
}
|
||||
}
|
||||
|
||||
public interface DownloadCallback {
|
||||
void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,6 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
BooleanProperty booleanProperty;
|
||||
|
||||
{
|
||||
|
||||
Region clippedContainer = (Region)listView.lookup(".clipped-container");
|
||||
setPrefWidth(0);
|
||||
HBox container = new HBox(8);
|
||||
|
||||
@@ -27,8 +27,14 @@ import javafx.scene.control.Control;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
@@ -36,14 +42,17 @@ import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class VersionPage extends Control implements DecoratorPage {
|
||||
public class VersionPage extends Control implements DecoratorPage, ModDownloadPage.DownloadCallback {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty();
|
||||
private final TabHeader.Tab versionSettingsTab = new TabHeader.Tab("versionSettingsTab");
|
||||
@@ -51,7 +60,7 @@ public class VersionPage extends Control implements DecoratorPage {
|
||||
private final TabHeader.Tab modListTab = new TabHeader.Tab("modListTab");
|
||||
private final ModListPage modListPage = new ModListPage(modListTab);
|
||||
private final TabHeader.Tab curseModListTab = new TabHeader.Tab("modListTab");
|
||||
private final ModDownloadListPage curseModListPage = new ModDownloadListPage();
|
||||
private final ModDownloadListPage curseModListPage = new ModDownloadListPage(CurseModManager.SECTION_MOD, this);
|
||||
private final TabHeader.Tab installerListTab = new TabHeader.Tab("installerListTab");
|
||||
private final InstallerListPage installerListPage = new InstallerListPage();
|
||||
private final TabHeader.Tab worldListTab = new TabHeader.Tab("worldList");
|
||||
@@ -182,6 +191,28 @@ public class VersionPage extends Control implements DecoratorPage {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file) {
|
||||
if (version == null) {
|
||||
throw new InternalError();
|
||||
}
|
||||
|
||||
Path dest = profile.getRepository().getRunDirectory(version).toPath().resolve("mods").resolve(file.getFileName());
|
||||
|
||||
TaskExecutorDialogPane downloadingPane = new TaskExecutorDialogPane(it -> {
|
||||
});
|
||||
|
||||
TaskExecutor executor = Task.composeAsync(() -> {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getDownloadUrl()), dest.toFile());
|
||||
task.setName(file.getDisplayName());
|
||||
return task;
|
||||
}).executor(false);
|
||||
|
||||
downloadingPane.setExecutor(executor, true);
|
||||
Controllers.dialog(downloadingPane);
|
||||
executor.start();
|
||||
}
|
||||
|
||||
public static class Skin extends SkinBase<VersionPage> {
|
||||
|
||||
private JFXPopup listViewItemPopup;
|
||||
|
||||
@@ -22,9 +22,11 @@ import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.game.GameDirectoryType;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
@@ -43,9 +45,13 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
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 {
|
||||
@@ -66,6 +72,41 @@ public final class Versions {
|
||||
}
|
||||
}
|
||||
|
||||
public static void downloadModpack() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile.getRepository().isLoaded()) {
|
||||
Controllers.getModpackDownloadListPage().loadVersion(profile, null);
|
||||
Controllers.navigate(Controllers.getModpackDownloadListPage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void downloadModpackImpl(Profile profile, String version, CurseAddon.LatestFile file) {
|
||||
Path modpack;
|
||||
URL downloadURL;
|
||||
try {
|
||||
modpack = Files.createTempFile("modpack", ".zip");
|
||||
downloadURL = new URL(file.getDownloadUrl());
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(
|
||||
i18n("install.failed.downloading.detail", file.getDownloadUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(downloadURL, modpack.toFile())
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack.toFile()));
|
||||
} else {
|
||||
Controllers.dialog(
|
||||
i18n("install.failed.downloading.detail", file.getDownloadUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
}).executor(true),
|
||||
i18n("message.downloading")
|
||||
);
|
||||
}
|
||||
|
||||
public static void deleteVersion(Profile profile, String version) {
|
||||
boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == GameDirectoryType.VERSION_FOLDER;
|
||||
boolean isMovingToTrashSupported = FileUtils.isMovingToTrashSupported();
|
||||
|
||||
44
HMCL/src/main/java/org/jackhuang/hmcl/util/Lazy.java
Normal file
44
HMCL/src/main/java/org/jackhuang/hmcl/util/Lazy.java
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Non thread-safe lazy initialization wrapper.
|
||||
*
|
||||
* @param <T> value type
|
||||
*/
|
||||
public class Lazy<T> {
|
||||
private Supplier<T> supplier;
|
||||
private T value = null;
|
||||
|
||||
public Lazy(Supplier<T> supplier) {
|
||||
this.supplier = Objects.requireNonNull(supplier);
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (value == null) {
|
||||
value = Objects.requireNonNull(supplier.get());
|
||||
supplier = null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -773,10 +773,36 @@
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1);
|
||||
}
|
||||
|
||||
.depth-0 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0), 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.depth-1 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
|
||||
}
|
||||
|
||||
.depth-2 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 15, 0.16, 0, 4);
|
||||
}
|
||||
|
||||
.depth-3 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 20, 0.19, 0, 6);
|
||||
}
|
||||
|
||||
.depth-4 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 25, 0.25, 0, 8);
|
||||
}
|
||||
|
||||
.depth-5 {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 30, 0.3, 0, 10);
|
||||
}
|
||||
|
||||
.card {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.8);
|
||||
-fx-background-radius: 4;
|
||||
-fx-padding: 8px;
|
||||
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
|
||||
}
|
||||
|
||||
.card:selected {
|
||||
|
||||
@@ -98,6 +98,64 @@ color.custom=Custom Color
|
||||
crash.NoClassDefFound=Please verify that the "Hello Minecraft! Launcher" software is not corrupted.
|
||||
crash.user_fault=Your OS or Java environment may not be properly installed which may result in a crash, please check your Java Runtime Environment or your computer!
|
||||
|
||||
curse.category.4474=Sci-Fi
|
||||
curse.category.4481=Small / Light
|
||||
curse.category.4483=Combat
|
||||
curse.category.4477=Mini Game
|
||||
curse.category.4478=Quests
|
||||
curse.category.4484=Multiplayer
|
||||
curse.category.4476=Exploration
|
||||
curse.category.4736=Skyblock
|
||||
curse.category.4475=Adventure and RPG
|
||||
curse.category.4487=FTB
|
||||
curse.category.4480=Map Based
|
||||
curse.category.4479=Hardcore
|
||||
curse.category.4482=Extra Large
|
||||
curse.category.4472=Tech
|
||||
curse.category.4473=Magic
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.423=Map and Information
|
||||
curse.category.426=Addons
|
||||
curse.category.434=Armor, Tools, and Weapons
|
||||
curse.category.409=Structures
|
||||
curse.category.4485=Blood Magic
|
||||
curse.category.420=Storage
|
||||
curse.category.429=Industrial Craft
|
||||
curse.category.419=Magic
|
||||
curse.category.412=Technology
|
||||
curse.category.4557=Redstone
|
||||
curse.category.428=Tinker's Construct
|
||||
curse.category.414=Player Transport
|
||||
curse.category.4486=Lucky Blocks
|
||||
curse.category.432=Buildcraft
|
||||
curse.category.418=Genetics
|
||||
curse.category.4671=Twitch Integration
|
||||
curse.category.408=Ores and Resources
|
||||
curse.category.4773=CraftTweaker
|
||||
curse.category.430=Thaumcraft
|
||||
curse.category.422=Adventure and RPG
|
||||
curse.category.413=Processing
|
||||
curse.category.417=Energy
|
||||
curse.category.415=Energy, Fluid and Item Transport
|
||||
curse.category.433=Forestry
|
||||
curse.category.425=Miscellaneous
|
||||
curse.category.4545=Applied Energistics 2
|
||||
curse.category.416=Farming
|
||||
curse.category.421=API and Library
|
||||
curse.category.4780=Fabric
|
||||
curse.category.424=Cosmetic
|
||||
curse.category.406=World gen
|
||||
curse.category.435=Server Utility
|
||||
curse.category.411=Mobs
|
||||
curse.category.407=Biomes
|
||||
curse.category.427=Thermal Expansion
|
||||
curse.category.410=Dimensions
|
||||
curse.category.436=Food
|
||||
curse.category.4558=Redstone
|
||||
curse.category.4843=Automation
|
||||
curse.category.4906=MCreator
|
||||
|
||||
download=Download
|
||||
download.code.404=File not found on the remote server: %s
|
||||
download.failed=Failed to download %1$s, response code: %2$d
|
||||
@@ -242,6 +300,7 @@ modpack.choose.remote.detail=Requires a direct download link to the remote modpa
|
||||
modpack.choose.remote.tooltip=A direct download link to the remote modpack file
|
||||
modpack.desc=Describe your modpack, including precautions and changelog. Markdown and online pictures are supported.
|
||||
modpack.description=Description
|
||||
modpack.download=Modpack Downloads
|
||||
modpack.enter_name=Enter a name for this modpack.
|
||||
modpack.export=Export Modpack
|
||||
modpack.export.as=Export Modpack As...
|
||||
|
||||
@@ -98,62 +98,64 @@ color.custom=自定义颜色
|
||||
crash.NoClassDefFound=请确认 Hello Minecraft! Launcher 本体是否完整,或更新您的 Java。
|
||||
crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,请检查您的 Java 环境或您的电脑!可以尝试重新安装 Java。
|
||||
|
||||
curse.category.modpack.sci-fi=科幻
|
||||
curse.category.modpack.small-light=轻量整合包
|
||||
curse.category.modpack.combat-pvp=战斗 PVP
|
||||
curse.category.modpack.mini-game=小游戏
|
||||
curse.category.modpack.quests=任务
|
||||
curse.category.modpack.multiplayer=多人
|
||||
curse.category.modpack.exploration=探索
|
||||
curse.category.modpack.skyblock=空岛
|
||||
curse.category.modpack.adventure-and-rpg=冒险 RPG
|
||||
curse.category.modpack.ftb-official-pack=FTB 整合包
|
||||
curse.category.modpack.map-based=有特定地图
|
||||
curse.category.modpack.hardcore=高难度
|
||||
curse.category.modpack.extra-large=大型整合包
|
||||
curse.category.modpack.tech=科技
|
||||
curse.category.modpack.magic=魔法
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/4471
|
||||
curse.category.4474=科幻
|
||||
curse.category.4481=轻量整合包
|
||||
curse.category.4483=战斗 PVP
|
||||
curse.category.4477=小游戏
|
||||
curse.category.4478=任务
|
||||
curse.category.4484=多人
|
||||
curse.category.4476=探索
|
||||
curse.category.4736=空岛
|
||||
curse.category.4475=冒险 RPG
|
||||
curse.category.4487=FTB 整合包
|
||||
curse.category.4480=有特定地图
|
||||
curse.category.4479=高难度
|
||||
curse.category.4482=大型整合包
|
||||
curse.category.4472=科技
|
||||
curse.category.4473=魔法
|
||||
|
||||
curse.category.mod.map-information=信息展示
|
||||
curse.category.mod.mc-addons=模组扩展
|
||||
curse.category.mod.armor-weapons-tools=装备武器
|
||||
curse.category.mod.world-structures=自然生成
|
||||
curse.category.mod.blood-magic=血魔法
|
||||
curse.category.mod.storage=存储
|
||||
curse.category.mod.addons-industrialcraft=工业 (Industrialcraft)
|
||||
curse.category.mod.magic=魔法
|
||||
curse.category.mod.technology=科技
|
||||
curse.category.mod.[4557]redstone=红石
|
||||
curse.category.mod.addons-tinkers-construct=匠魂
|
||||
curse.category.mod.technology-player-transport=交通运输
|
||||
curse.category.mod.[4486]lucky-blocks=Lucky Blocks
|
||||
curse.category.mod.addons-buildcraft=建筑 (Buildcraft)
|
||||
curse.category.mod.technology-genetics=基因
|
||||
curse.category.mod.twitch-integration=Twitch
|
||||
curse.category.mod.world-ores-resources=矿物资源
|
||||
curse.category.mod.crafttweaker=CraftTweaker
|
||||
curse.category.mod.addons-thaumcraft=神秘 (Thaumcraft)
|
||||
curse.category.mod.adventure-rpg=冒险 RPG
|
||||
curse.category.mod.technology-processing=机器处理
|
||||
curse.category.mod.technology-energy=能源
|
||||
curse.category.mod.technology-item-fluid-energy-transport=物流运输
|
||||
curse.category.mod.addons-forestry=林业 (Forestry)
|
||||
curse.category.mod.mc-miscellaneous=其他
|
||||
curse.category.mod.applied-energistics-2=应用能源 2 (Applied Energistics 2)
|
||||
curse.category.mod.technology-farming=农业
|
||||
curse.category.mod.library-api=支持库
|
||||
curse.category.mod.fabric=Fabric
|
||||
curse.category.mod.cosmetic=装饰
|
||||
curse.category.mod.world-gen=世界生成
|
||||
curse.category.mod.server-utility=服务器
|
||||
curse.category.mod.world-mobs=生物
|
||||
curse.category.mod.world-biomes=生物群系
|
||||
curse.category.mod.addons-thermalexpansion=热力膨胀 (Thermal Expansion)
|
||||
curse.category.mod.world-dimensions=维度
|
||||
curse.category.mod.mc-food=食物
|
||||
curse.category.mod.redstone=红石
|
||||
curse.category.mod.technology-automation=自动化
|
||||
curse.category.mod.mc-creator=MCreator
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.423=信息展示
|
||||
curse.category.426=模组扩展
|
||||
curse.category.434=装备武器
|
||||
curse.category.409=自然生成
|
||||
curse.category.4485=血魔法
|
||||
curse.category.420=存储
|
||||
curse.category.429=工业 (Industrialcraft)
|
||||
curse.category.419=魔法
|
||||
curse.category.412=科技
|
||||
curse.category.4557=红石
|
||||
curse.category.428=匠魂
|
||||
curse.category.414=交通运输
|
||||
curse.category.4486=Lucky Blocks
|
||||
curse.category.432=建筑 (Buildcraft)
|
||||
curse.category.418=基因
|
||||
curse.category.4671=Twitch
|
||||
curse.category.408=矿物资源
|
||||
curse.category.4773=CraftTweaker
|
||||
curse.category.430=神秘 (Thaumcraft)
|
||||
curse.category.422=冒险 RPG
|
||||
curse.category.413=机器处理
|
||||
curse.category.417=能源
|
||||
curse.category.415=物流运输
|
||||
curse.category.433=林业 (Forestry)
|
||||
curse.category.425=其他
|
||||
curse.category.4545=应用能源 2 (Applied Energistics 2)
|
||||
curse.category.416=农业
|
||||
curse.category.421=支持库
|
||||
curse.category.4780=Fabric
|
||||
curse.category.424=装饰
|
||||
curse.category.406=世界生成
|
||||
curse.category.435=服务器
|
||||
curse.category.411=生物
|
||||
curse.category.407=生物群系
|
||||
curse.category.427=热力膨胀 (Thermal Expansion)
|
||||
curse.category.410=维度
|
||||
curse.category.436=食物
|
||||
curse.category.4558=红石
|
||||
curse.category.4843=自动化
|
||||
curse.category.4906=MCreator
|
||||
|
||||
download=下载
|
||||
download.code.404=远程服务器不包含需要下载的文件: %s
|
||||
@@ -299,6 +301,7 @@ modpack.choose.remote.detail=需要提供整合包的下载链接
|
||||
modpack.choose.remote.tooltip=要下载的整合包的链接
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 HTML(图片请用网络图)。
|
||||
modpack.description=整合包描述
|
||||
modpack.download=下载整合包
|
||||
modpack.enter_name=给游戏起个你喜欢的名字
|
||||
modpack.export=导出整合包
|
||||
modpack.export.as=请选择整合包类型 (若无法决定,请选择我的世界中文论坛整合包标准)
|
||||
@@ -365,6 +368,7 @@ mods.add.failed=添加模组 %s 失败。
|
||||
mods.add.success=成功添加模组 %s。
|
||||
mods.choose_mod=选择模组
|
||||
mods.download=模组下载
|
||||
mods.download.title=模组下载 - %1s
|
||||
mods.enable=启用
|
||||
mods.disable=禁用
|
||||
mods.name=名称
|
||||
@@ -514,7 +518,7 @@ version.empty.add=进入版本列表安装
|
||||
version.empty.launch=没有可启动的游戏,你可以点击左侧游戏栏内的设置按钮进入版本列表安装游戏
|
||||
version.forbidden_name=此版本名称不受支持,请换一个名字
|
||||
version.game.old=远古版
|
||||
version.game.release=稳定版
|
||||
version.game.release=正式版
|
||||
version.game.snapshot=测试版
|
||||
version.launch=启动游戏
|
||||
version.launch.test=测试游戏
|
||||
|
||||
@@ -9,6 +9,7 @@ public class CurseAddon {
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final List<Author> authors;
|
||||
private final List<Attachment> attachments;
|
||||
private final String websiteUrl;
|
||||
private final int gameId;
|
||||
private final String summary;
|
||||
@@ -27,10 +28,11 @@ public class CurseAddon {
|
||||
private final boolean isAvailable;
|
||||
private final boolean isExperimental;
|
||||
|
||||
public CurseAddon(int id, String name, List<Author> authors, String websiteUrl, int gameId, String summary, int defaultFileId, List<LatestFile> latestFiles, List<Category> categories, int status, int primaryCategoryId, String slug, List<GameVersionLatestFile> gameVersionLatestFiles, boolean isFeatured, double popularityScore, int gamePopularityRank, String primaryLanguage, List<String> modLoaders, boolean isAvailable, boolean isExperimental) {
|
||||
public CurseAddon(int id, String name, List<Author> authors, List<Attachment> attachments, String websiteUrl, int gameId, String summary, int defaultFileId, List<LatestFile> latestFiles, List<Category> categories, int status, int primaryCategoryId, String slug, List<GameVersionLatestFile> gameVersionLatestFiles, boolean isFeatured, double popularityScore, int gamePopularityRank, String primaryLanguage, List<String> modLoaders, boolean isAvailable, boolean isExperimental) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.authors = authors;
|
||||
this.attachments = attachments;
|
||||
this.websiteUrl = websiteUrl;
|
||||
this.gameId = gameId;
|
||||
this.summary = summary;
|
||||
@@ -62,6 +64,10 @@ public class CurseAddon {
|
||||
return authors;
|
||||
}
|
||||
|
||||
public List<Attachment> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public String getWebsiteUrl() {
|
||||
return websiteUrl;
|
||||
}
|
||||
@@ -173,6 +179,61 @@ public class CurseAddon {
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
public static class Attachment {
|
||||
private final int id;
|
||||
private final int projectId;
|
||||
private final String description;
|
||||
private final boolean isDefault;
|
||||
private final String thumbnailUrl;
|
||||
private final String title;
|
||||
private final String url;
|
||||
private final int status;
|
||||
|
||||
public Attachment(int id, int projectId, String description, boolean isDefault, String thumbnailUrl, String title, String url, int status) {
|
||||
this.id = id;
|
||||
this.projectId = projectId;
|
||||
this.description = description;
|
||||
this.isDefault = isDefault;
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
this.title = title;
|
||||
this.url = url;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return isDefault;
|
||||
}
|
||||
|
||||
public String getThumbnailUrl() {
|
||||
return thumbnailUrl;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
public static class Dependency {
|
||||
private final int id;
|
||||
@@ -212,6 +273,7 @@ public class CurseAddon {
|
||||
public static class LatestFile {
|
||||
private final int id;
|
||||
private final String displayName;
|
||||
private final String fileName;
|
||||
private final String fileDate;
|
||||
private final int fileLength;
|
||||
private final int releaseType;
|
||||
@@ -228,12 +290,13 @@ public class CurseAddon {
|
||||
private final int restrictProjectFileAccess;
|
||||
private final int projectStatus;
|
||||
private final int projectId;
|
||||
private final int isServerPack;
|
||||
private final boolean isServerPack;
|
||||
private final int serverPackFileId;
|
||||
|
||||
public LatestFile(int id, String displayName, String fileDate, int fileLength, int releaseType, int fileStatus, String downloadUrl, boolean isAlternate, int alternateFileId, List<Dependency> dependencies, boolean isAvailable, List<String> gameVersion, boolean hasInstallScript, boolean isCompatibleWIthClient, int categorySectionPackageType, int restrictProjectFileAccess, int projectStatus, int projectId, int isServerPack, int serverPackFileId) {
|
||||
public LatestFile(int id, String displayName, String fileName, String fileDate, int fileLength, int releaseType, int fileStatus, String downloadUrl, boolean isAlternate, int alternateFileId, List<Dependency> dependencies, boolean isAvailable, List<String> gameVersion, boolean hasInstallScript, boolean isCompatibleWIthClient, int categorySectionPackageType, int restrictProjectFileAccess, int projectStatus, int projectId, boolean isServerPack, int serverPackFileId) {
|
||||
this.id = id;
|
||||
this.displayName = displayName;
|
||||
this.fileName = fileName;
|
||||
this.fileDate = fileDate;
|
||||
this.fileLength = fileLength;
|
||||
this.releaseType = releaseType;
|
||||
@@ -262,6 +325,10 @@ public class CurseAddon {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getFileDate() {
|
||||
return fileDate;
|
||||
}
|
||||
@@ -326,7 +393,7 @@ public class CurseAddon {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public int getIsServerPack() {
|
||||
public boolean isServerPack() {
|
||||
return isServerPack;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,12 @@ public class CurseModManager {
|
||||
}.getType());
|
||||
}
|
||||
|
||||
public static List<CurseAddon.LatestFile> 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<List<CurseAddon.LatestFile>>() {
|
||||
}.getType());
|
||||
}
|
||||
|
||||
public static List<Category> getCategories(int section) throws IOException {
|
||||
String response = NetworkUtils.doGet(NetworkUtils.toURL("https://addons-ecs.forgesvc.net/api/v2/category/section/" + section));
|
||||
List<Category> categories = JsonUtils.fromNonNullJson(response, new TypeToken<List<Category>>() {
|
||||
|
||||
Reference in New Issue
Block a user