添加“提前预览 HMCL 版本”选项 (#4223)

This commit is contained in:
Glavo
2025-10-03 19:56:59 +08:00
committed by GitHub
parent 8731faaac7
commit 87af44328b
9 changed files with 71 additions and 22 deletions

View File

@@ -250,6 +250,21 @@ public final class Config implements Observable {
this.promptedVersion.set(promptedVersion); this.promptedVersion.set(promptedVersion);
} }
@SerializedName("acceptPreviewUpdate")
private final BooleanProperty acceptPreviewUpdate = new SimpleBooleanProperty(false);
public BooleanProperty acceptPreviewUpdateProperty() {
return acceptPreviewUpdate;
}
public boolean isAcceptPreviewUpdate() {
return acceptPreviewUpdate.get();
}
public void setAcceptPreviewUpdate(boolean acceptPreviewUpdate) {
this.acceptPreviewUpdate.set(acceptPreviewUpdate);
}
@SerializedName("shownTips") @SerializedName("shownTips")
private final ObservableMap<String, Object> shownTips = FXCollections.observableHashMap(); private final ObservableMap<String, Object> shownTips = FXCollections.observableHashMap();

View File

@@ -48,11 +48,22 @@ public final class UpgradeDialog extends JFXDialogLayout {
setBody(new ProgressIndicator()); setBody(new ProgressIndicator());
String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html"; String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html";
boolean isPreview = remoteVersion.isPreview();
Task.supplyAsync(Schedulers.io(), () -> { Task.supplyAsync(Schedulers.io(), () -> {
Document document = Jsoup.parse(new URL(url), 30 * 1000); Document document = Jsoup.parse(new URL(url), 30 * 1000);
Node node = document.selectFirst("#nowchange"); String id = null;
Node node = null;
if (isPreview) {
id = "nowpreview";
node = document.selectFirst("#" + id);
}
if (node == null) {
id = "nowchange";
node = document.selectFirst("#" + id);
}
if (node == null || !"h1".equals(node.nodeName())) if (node == null || !"h1".equals(node.nodeName()))
throw new IOException("Cannot find #nowchange in document"); throw new IOException("Cannot find current changelog in document");
HTMLRenderer renderer = new HTMLRenderer(uri -> { HTMLRenderer renderer = new HTMLRenderer(uri -> {
LOG.info("Open link: " + uri); LOG.info("Open link: " + uri);
@@ -60,7 +71,7 @@ public final class UpgradeDialog extends JFXDialogLayout {
}); });
do { do {
if ("h1".equals(node.nodeName()) && !"nowchange".equals(node.attr("id"))) { if ("h1".equals(node.nodeName()) && !id.equals(node.attr("id"))) {
break; break;
} }
renderer.appendNode(node); renderer.appendNode(node);

View File

@@ -114,9 +114,12 @@ public final class SettingsPage extends SettingsView {
chkUpdateStable.setUserData(UpdateChannel.STABLE); chkUpdateStable.setUserData(UpdateChannel.STABLE);
ObjectProperty<UpdateChannel> updateChannel = selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class); ObjectProperty<UpdateChannel> updateChannel = selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class);
updateChannel.set(UpdateChannel.getChannel()); updateChannel.set(UpdateChannel.getChannel());
updateChannel.addListener((a, b, newValue) -> {
UpdateChecker.requestCheckUpdate(newValue); InvalidationListener checkUpdateListener = e -> {
}); UpdateChecker.requestCheckUpdate(updateChannel.get(), previewPane.isSelected());
};
updateChannel.addListener(checkUpdateListener);
previewPane.selectedProperty().addListener(checkUpdateListener);
// ==== // ====
} }

View File

@@ -58,6 +58,7 @@ public abstract class SettingsView extends StackPane {
protected final JFXRadioButton chkUpdateStable; protected final JFXRadioButton chkUpdateStable;
protected final JFXRadioButton chkUpdateDev; protected final JFXRadioButton chkUpdateDev;
protected final JFXButton btnUpdate; protected final JFXButton btnUpdate;
protected final OptionToggleButton previewPane;
protected final ScrollPane scroll; protected final ScrollPane scroll;
public SettingsView() { public SettingsView() {
@@ -147,6 +148,14 @@ public abstract class SettingsView extends StackPane {
settingsPane.getContent().add(updatePane); settingsPane.getContent().add(updatePane);
} }
{
previewPane = new OptionToggleButton();
previewPane.setTitle(i18n("update.preview"));
previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());
settingsPane.getContent().add(previewPane);
}
{ {
fileCommonLocation = new MultiFileItem<>(); fileCommonLocation = new MultiFileItem<>();
fileCommonLocationSublist = new ComponentSublist(); fileCommonLocationSublist = new ComponentSublist();

View File

@@ -29,7 +29,7 @@ import java.util.Optional;
public final class RemoteVersion { public final class RemoteVersion {
public static RemoteVersion fetch(UpdateChannel channel, String url) throws IOException { public static RemoteVersion fetch(UpdateChannel channel, boolean preview, String url) throws IOException {
try { try {
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(url), JsonObject.class); JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(url), JsonObject.class);
String version = Optional.ofNullable(response.get("version")).map(JsonElement::getAsString).orElseThrow(() -> new IOException("version is missing")); String version = Optional.ofNullable(response.get("version")).map(JsonElement::getAsString).orElseThrow(() -> new IOException("version is missing"));
@@ -37,7 +37,7 @@ public final class RemoteVersion {
String jarHash = Optional.ofNullable(response.get("jarsha1")).map(JsonElement::getAsString).orElse(null); String jarHash = Optional.ofNullable(response.get("jarsha1")).map(JsonElement::getAsString).orElse(null);
boolean force = Optional.ofNullable(response.get("force")).map(JsonElement::getAsBoolean).orElse(false); boolean force = Optional.ofNullable(response.get("force")).map(JsonElement::getAsBoolean).orElse(false);
if (jarUrl != null && jarHash != null) { if (jarUrl != null && jarHash != null) {
return new RemoteVersion(channel, version, jarUrl, Type.JAR, new IntegrityCheck("SHA-1", jarHash), force); return new RemoteVersion(channel, version, jarUrl, Type.JAR, new IntegrityCheck("SHA-1", jarHash), preview, force);
} else { } else {
throw new IOException("No download url is available"); throw new IOException("No download url is available");
} }
@@ -51,14 +51,16 @@ public final class RemoteVersion {
private final String url; private final String url;
private final Type type; private final Type type;
private final IntegrityCheck integrityCheck; private final IntegrityCheck integrityCheck;
private final boolean preview;
private final boolean force; private final boolean force;
public RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck, boolean force) { public RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck, boolean preview, boolean force) {
this.channel = channel; this.channel = channel;
this.version = version; this.version = version;
this.url = url; this.url = url;
this.type = type; this.type = type;
this.integrityCheck = integrityCheck; this.integrityCheck = integrityCheck;
this.preview = preview;
this.force = force; this.force = force;
} }
@@ -82,6 +84,10 @@ public final class RemoteVersion {
return integrityCheck; return integrityCheck;
} }
public boolean isPreview() {
return preview;
}
public boolean isForce() { public boolean isForce() {
return force; return force;
} }

View File

@@ -27,14 +27,15 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedHashMap;
import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Lang.thread; import static org.jackhuang.hmcl.util.Lang.*;
import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
public final class UpdateChecker { public final class UpdateChecker {
private UpdateChecker() {} private UpdateChecker() {
}
private static final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>(); private static final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
private static final BooleanBinding outdated = Bindings.createBooleanBinding( private static final BooleanBinding outdated = Bindings.createBooleanBinding(
@@ -55,7 +56,7 @@ public final class UpdateChecker {
private static final ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false); private static final ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);
public static void init() { public static void init() {
requestCheckUpdate(UpdateChannel.getChannel()); requestCheckUpdate(UpdateChannel.getChannel(), config().isAcceptPreviewUpdate());
} }
public static RemoteVersion getLatestVersion() { public static RemoteVersion getLatestVersion() {
@@ -82,16 +83,17 @@ public final class UpdateChecker {
return checkingUpdate.getReadOnlyProperty(); return checkingUpdate.getReadOnlyProperty();
} }
private static RemoteVersion checkUpdate(UpdateChannel channel) throws IOException { private static RemoteVersion checkUpdate(UpdateChannel channel, boolean preview) throws IOException {
if (!IntegrityChecker.DISABLE_SELF_INTEGRITY_CHECK && !IntegrityChecker.isSelfVerified()) { if (!IntegrityChecker.DISABLE_SELF_INTEGRITY_CHECK && !IntegrityChecker.isSelfVerified()) {
throw new IOException("Self verification failed"); throw new IOException("Self verification failed");
} }
String url = NetworkUtils.withQuery(Metadata.HMCL_UPDATE_URL, mapOf( var query = new LinkedHashMap<String, String>();
pair("version", Metadata.VERSION), query.put("version", Metadata.VERSION);
pair("channel", channel.channelName))); query.put("channel", preview ? channel.channelName + "-preview" : channel.channelName);
return RemoteVersion.fetch(channel, url); String url = NetworkUtils.withQuery(Metadata.HMCL_UPDATE_URL, query);
return RemoteVersion.fetch(channel, preview, url);
} }
private static boolean isDevelopmentVersion(String version) { private static boolean isDevelopmentVersion(String version) {
@@ -99,7 +101,7 @@ public final class UpdateChecker {
version.contains("SNAPSHOT"); // eg. 3.5.SNAPSHOT version.contains("SNAPSHOT"); // eg. 3.5.SNAPSHOT
} }
public static void requestCheckUpdate(UpdateChannel channel) { public static void requestCheckUpdate(UpdateChannel channel, boolean preview) {
Platform.runLater(() -> { Platform.runLater(() -> {
if (isCheckingUpdate()) if (isCheckingUpdate())
return; return;
@@ -108,8 +110,8 @@ public final class UpdateChecker {
thread(() -> { thread(() -> {
RemoteVersion result = null; RemoteVersion result = null;
try { try {
result = checkUpdate(channel); result = checkUpdate(channel, preview);
LOG.info("Latest version (" + channel + ") is " + result); LOG.info("Latest version (" + channel + ", preview=" + preview + ") is " + result);
} catch (IOException e) { } catch (IOException e) {
LOG.warning("Failed to check for update", e); LOG.warning("Failed to check for update", e);
} }

View File

@@ -1443,6 +1443,7 @@ update.note=Beta and Nightly channels may have more features or fixes, but they
update.latest=This is the latest version update.latest=This is the latest version
update.no_browser=Cannot open in system browser. But we copied the link to your clipboard, and you can open it manually. update.no_browser=Cannot open in system browser. But we copied the link to your clipboard, and you can open it manually.
update.tooltip=Update update.tooltip=Update
update.preview=Try out the HMCL version
version=Games version=Games
version.name=Instance Name version.name=Instance Name

View File

@@ -1227,6 +1227,7 @@ update.note=開發版與預覽版包含更多的功能以及錯誤修復,但
update.latest=目前版本為最新版本 update.latest=目前版本為最新版本
update.no_browser=無法開啟瀏覽器。網址已經複製到剪貼簿了,你可以手動複製網址開啟頁面。 update.no_browser=無法開啟瀏覽器。網址已經複製到剪貼簿了,你可以手動複製網址開啟頁面。
update.tooltip=更新 update.tooltip=更新
update.preview=提前預覽 HMCL 版本
version=遊戲 version=遊戲
version.name=遊戲實例名稱 version.name=遊戲實例名稱

View File

@@ -1237,6 +1237,7 @@ update.note=开发版与预览版包含更多的功能以及错误修复,但
update.latest=当前版本为最新版本 update.latest=当前版本为最新版本
update.no_browser=无法打开浏览器。网址已经复制到剪贴板,你可以手动粘贴网址打开页面。 update.no_browser=无法打开浏览器。网址已经复制到剪贴板,你可以手动粘贴网址打开页面。
update.tooltip=更新 update.tooltip=更新
update.preview=提前预览 HMCL 版本
version=游戏 version=游戏
version.name=游戏实例名称 version.name=游戏实例名称