Add update channel selection

This commit is contained in:
huanghongxun
2018-08-10 12:53:49 +08:00
parent 8bf6a9ec9d
commit dad7ac706f
12 changed files with 225 additions and 46 deletions

View File

@@ -23,8 +23,10 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.concurrency.JFXUtilities;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.upgrade.UpdateChecker; import org.jackhuang.hmcl.upgrade.UpdateChecker;
@@ -55,14 +57,25 @@ public final class Launcher extends Application {
primaryStage.setResizable(false); primaryStage.setResizable(false);
primaryStage.setScene(Controllers.getScene()); primaryStage.setScene(Controllers.getScene());
thread(() -> { UpdateChecker.updateChannelProperty().addListener(observable -> {
try { thread(() -> {
UpdateChecker.checkUpdate(); try {
} catch (IOException e) { UpdateChecker.checkUpdate();
LOG.log(Level.WARNING, "Failed to check for update", e); } catch (IOException e) {
} LOG.log(Level.WARNING, "Failed to check for update", e);
}
});
}); });
UpdateChecker.updateChannelProperty().bind(Bindings.createStringBinding(() -> {
switch (ConfigHolder.config().getUpdateChannel()) {
case DEVELOPMENT:
return UpdateChecker.CHANNEL_DEV;
default:
return UpdateChecker.CHANNEL_STABLE;
}
}, ConfigHolder.config().updateChannelProperty()));
primaryStage.show(); primaryStage.show();
} catch (Throwable e) { } catch (Throwable e) {
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e); CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);

View File

@@ -149,6 +149,9 @@ public final class Config implements Cloneable, Observable {
@SerializedName("authlibInjectorServers") @SerializedName("authlibInjectorServers")
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList(); private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
@SerializedName("updateChannel")
private ObjectProperty<EnumUpdateChannel> updateChannel = new SimpleObjectProperty<>(EnumUpdateChannel.STABLE);
@SerializedName("_version") @SerializedName("_version")
private IntegerProperty configVersion = new SimpleIntegerProperty(0); private IntegerProperty configVersion = new SimpleIntegerProperty(0);
@@ -435,4 +438,15 @@ public final class Config implements Cloneable, Observable {
return authlibInjectorServers; return authlibInjectorServers;
} }
public EnumUpdateChannel getUpdateChannel() {
return updateChannel.get();
}
public ObjectProperty<EnumUpdateChannel> updateChannelProperty() {
return updateChannel;
}
public void setUpdateChannel(EnumUpdateChannel updateChannel) {
this.updateChannel.set(updateChannel);
}
} }

View File

@@ -0,0 +1,23 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
public enum EnumUpdateChannel {
STABLE,
DEVELOPMENT
}

View File

@@ -39,6 +39,7 @@ import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.text.Font; import javafx.scene.text.Font;
import javafx.scene.text.Text;
import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.FontComboBox; import org.jackhuang.hmcl.ui.construct.FontComboBox;
@@ -88,6 +89,12 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
@FXML @FXML
private Label lblUpdateSub; private Label lblUpdateSub;
@FXML @FXML
private Text lblUpdateNote;
@FXML
private JFXRadioButton chkUpdateStable;
@FXML
private JFXRadioButton chkUpdateDev;
@FXML
private JFXButton btnUpdate; private JFXButton btnUpdate;
@FXML @FXML
private ScrollPane scroll; private ScrollPane scroll;
@@ -212,6 +219,12 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
lblUpdate.setText(i18n("update.found")); lblUpdate.setText(i18n("update.found"));
lblUpdate.getStyleClass().setAll("update-label"); lblUpdate.getStyleClass().setAll("update-label");
} else if (UpdateChecker.isCheckingUpdate()) {
lblUpdateSub.setText(i18n("update.checking"));
lblUpdateSub.getStyleClass().setAll("subtitle-label");
lblUpdate.setText(i18n("update"));
lblUpdate.getStyleClass().setAll();
} else { } else {
lblUpdateSub.setText(i18n("update.latest")); lblUpdateSub.setText(i18n("update.latest"));
lblUpdateSub.getStyleClass().setAll("subtitle-label"); lblUpdateSub.getStyleClass().setAll("subtitle-label");
@@ -222,7 +235,32 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
}; };
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener)); UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener)); UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
updateListener.invalidated(null); updateListener.invalidated(null);
lblUpdateNote.setWrappingWidth(470);
ObjectProperty<EnumUpdateChannel> updateChannel = new SimpleObjectProperty<EnumUpdateChannel>() {
@Override
protected void invalidated() {
EnumUpdateChannel updateChannel = Objects.requireNonNull(get());
chkUpdateDev.setSelected(updateChannel == EnumUpdateChannel.DEVELOPMENT);
chkUpdateStable.setSelected(updateChannel == EnumUpdateChannel.STABLE);
}
};
ToggleGroup updateChannelGroup = new ToggleGroup();
chkUpdateDev.setToggleGroup(updateChannelGroup);
chkUpdateDev.setUserData(EnumUpdateChannel.DEVELOPMENT);
chkUpdateStable.setToggleGroup(updateChannelGroup);
chkUpdateStable.setUserData(EnumUpdateChannel.STABLE);
updateChannelGroup.getToggles().forEach(
toggle -> toggle.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
updateChannel.set((EnumUpdateChannel) toggle.getUserData());
}
}));
updateChannel.bindBidirectional(ConfigHolder.config().updateChannelProperty());
// ==== // ====
// ==== Background ==== // ==== Background ====

View File

@@ -50,10 +50,6 @@ public class ComponentList extends StackPane {
} }
public void addChildren(Node node) { public void addChildren(Node node) {
if (node instanceof ComponentList) {
node.getProperties().put("title", ((ComponentList) node).getTitle());
node.getProperties().put("subtitle", ((ComponentList) node).getSubtitle());
}
StackPane child = new StackPane(); StackPane child = new StackPane();
child.getChildren().add(new ComponentListCell(node)); child.getChildren().add(new ComponentListCell(node));
if (vbox.getChildren().isEmpty()) if (vbox.getChildren().isEmpty())

View File

@@ -27,6 +27,8 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
@@ -75,31 +77,47 @@ public class ComponentListCell extends StackPane {
content.getStyleClass().remove("options-list"); content.getStyleClass().remove("options-list");
content.getStyleClass().add("options-sublist"); content.getStyleClass().add("options-sublist");
StackPane groupNode = new StackPane(); BorderPane groupNode = new BorderPane();
groupNode.getStyleClass().add("options-list-item-header"); groupNode.getStyleClass().add("options-list-item-header");
Node expandIcon = SVG.expand(Theme.blackFillBinding(), 10, 10); Node expandIcon = SVG.expand(Theme.blackFillBinding(), 10, 10);
JFXButton expandButton = new JFXButton(); JFXButton expandButton = new JFXButton();
expandButton.setGraphic(expandIcon); expandButton.setGraphic(expandIcon);
expandButton.getStyleClass().add("options-list-item-expand-button"); expandButton.getStyleClass().add("options-list-item-expand-button");
StackPane.setAlignment(expandButton, Pos.CENTER_RIGHT);
VBox labelVBox = new VBox(); VBox labelVBox = new VBox();
Label label = new Label();
label.textProperty().bind(list.titleProperty());
label.setMouseTransparent(true);
labelVBox.getChildren().add(label);
if (list.isHasSubtitle()) { if (list instanceof ComponentSublist) {
Label subtitleLabel = new Label(); Node leftNode = ((ComponentSublist) list).getHeaderLeft();
subtitleLabel.textProperty().bind(list.subtitleProperty()); if (leftNode != null)
subtitleLabel.setMouseTransparent(true); labelVBox.getChildren().setAll(leftNode);
subtitleLabel.getStyleClass().add("subtitle-label"); } else {
labelVBox.getChildren().add(subtitleLabel); Label label = new Label();
label.textProperty().bind(list.titleProperty());
label.setMouseTransparent(true);
labelVBox.getChildren().add(label);
if (list.isHasSubtitle()) {
Label subtitleLabel = new Label();
subtitleLabel.textProperty().bind(list.subtitleProperty());
subtitleLabel.setMouseTransparent(true);
subtitleLabel.getStyleClass().add("subtitle-label");
labelVBox.getChildren().add(subtitleLabel);
}
} }
StackPane.setAlignment(labelVBox, Pos.CENTER_LEFT); groupNode.setLeft(labelVBox);
groupNode.getChildren().setAll(labelVBox, expandButton);
HBox right = new HBox();
if (list instanceof ComponentSublist) {
Node rightNode = ((ComponentSublist) list).getHeaderRight();
if (rightNode != null)
right.getChildren().add(rightNode);
}
right.getChildren().add(expandButton);
groupNode.setRight(right);
labelVBox.setAlignment(Pos.CENTER_LEFT);
right.setAlignment(Pos.CENTER_RIGHT);
VBox container = new VBox(); VBox container = new VBox();
container.setStyle("-fx-padding: 8 0 0 0;"); container.setStyle("-fx-padding: 8 0 0 0;");

View File

@@ -0,0 +1,41 @@
package org.jackhuang.hmcl.ui.construct;
import javafx.beans.DefaultProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
@DefaultProperty("content")
public final class ComponentSublist extends ComponentList {
private final ObjectProperty<Node> headerLeft = new SimpleObjectProperty<>(this, "headerLeft");
private final ObjectProperty<Node> headerRight = new SimpleObjectProperty<>(this, "headerRight");
public ComponentSublist() {
super();
}
public Node getHeaderLeft() {
return headerLeft.get();
}
public ObjectProperty<Node> headerLeftProperty() {
return headerLeft;
}
public void setHeaderLeft(Node headerLeft) {
this.headerLeft.set(headerLeft);
}
public Node getHeaderRight() {
return headerRight.get();
}
public ObjectProperty<Node> headerRightProperty() {
return headerRight;
}
public void setHeaderRight(Node headerRight) {
this.headerRight.set(headerRight);
}
}

View File

@@ -23,17 +23,15 @@ import static org.jackhuang.hmcl.util.VersionNumber.asVersion;
import java.io.IOException; import java.io.IOException;
import com.jfoenix.concurrency.JFXUtilities;
import javafx.beans.property.*;
import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.util.ImmediateStringProperty;
import org.jackhuang.hmcl.util.NetworkUtils; import org.jackhuang.hmcl.util.NetworkUtils;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableBooleanValue;
public final class UpdateChecker { public final class UpdateChecker {
@@ -42,7 +40,7 @@ public final class UpdateChecker {
public static final String CHANNEL_STABLE = "stable"; public static final String CHANNEL_STABLE = "stable";
public static final String CHANNEL_DEV = "dev"; public static final String CHANNEL_DEV = "dev";
private static StringProperty updateChannel = new SimpleStringProperty(CHANNEL_STABLE); private static StringProperty updateChannel = new ImmediateStringProperty(null, "updateChannel", CHANNEL_STABLE);
private static ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>(); private static ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
private static BooleanBinding outdated = Bindings.createBooleanBinding( private static BooleanBinding outdated = Bindings.createBooleanBinding(
@@ -55,6 +53,7 @@ public final class UpdateChecker {
} }
}, },
latestVersion); latestVersion);
private static ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);
public static String getUpdateChannel() { public static String getUpdateChannel() {
return updateChannel.get(); return updateChannel.get();
@@ -84,22 +83,39 @@ public final class UpdateChecker {
return outdated; return outdated;
} }
public static boolean isCheckingUpdate() {
return checkingUpdate.get();
}
public static ReadOnlyBooleanProperty checkingUpdateProperty() {
return checkingUpdate.getReadOnlyProperty();
}
public static void checkUpdate() throws IOException { public static void checkUpdate() throws IOException {
if (!IntegrityChecker.isSelfVerified()) { if (!IntegrityChecker.isSelfVerified()) {
return; return;
} }
String channel = getUpdateChannel(); JFXUtilities.runInFXAndWait(() -> {
String url = NetworkUtils.withQuery(Metadata.UPDATE_URL, mapOf( checkingUpdate.set(true);
pair("version", Metadata.VERSION),
pair("channel", channel)));
RemoteVersion fetched = RemoteVersion.fetch(url);
Platform.runLater(() -> {
if (channel.equals(getUpdateChannel())) {
latestVersion.set(fetched);
}
}); });
try {
String channel = getUpdateChannel();
String url = NetworkUtils.withQuery(Metadata.UPDATE_URL, mapOf(
pair("version", Metadata.VERSION),
pair("channel", channel)));
RemoteVersion fetched = RemoteVersion.fetch(url);
Platform.runLater(() -> {
if (channel.equals(getUpdateChannel())) {
latestVersion.set(fetched);
}
});
} finally {
Platform.runLater(() -> checkingUpdate.set(false));
}
} }
private static boolean isDevelopmentVersion(String version) { private static boolean isDevelopmentVersion(String version) {

View File

@@ -6,6 +6,7 @@
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import org.jackhuang.hmcl.ui.construct.*?> <?import org.jackhuang.hmcl.ui.construct.*?>
<?import org.jackhuang.hmcl.ui.FXUtils?> <?import org.jackhuang.hmcl.ui.FXUtils?>
<?import javafx.scene.text.Text?>
<fx:root xmlns="http://javafx.com/javafx" <fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
type="StackPane"> type="StackPane">
@@ -15,21 +16,28 @@
<VBox fx:id="rootPane" style="-fx-padding: 20;"> <VBox fx:id="rootPane" style="-fx-padding: 20;">
<ComponentList fx:id="settingsPane"> <ComponentList fx:id="settingsPane">
<BorderPane> <!-- Update --> <ComponentSublist fx:id="updatePane" title="%update" hasSubtitle="true">
<left> <headerLeft>
<VBox> <VBox>
<Label fx:id="lblUpdate" text="%update" BorderPane.alignment="CENTER_LEFT"/> <Label fx:id="lblUpdate" text="%update" BorderPane.alignment="CENTER_LEFT"/>
<Label fx:id="lblUpdateSub" styleClass="subtitle-label"/> <Label fx:id="lblUpdateSub" styleClass="subtitle-label"/>
</VBox> </VBox>
</left> </headerLeft>
<right> <headerRight>
<JFXButton fx:id="btnUpdate" onMouseClicked="#onUpdate" styleClass="toggle-icon4"> <JFXButton fx:id="btnUpdate" onMouseClicked="#onUpdate" styleClass="toggle-icon4">
<graphic> <graphic>
<fx:include source="/assets/svg/update.fxml" /> <fx:include source="/assets/svg/update.fxml" />
</graphic> </graphic>
</JFXButton> </JFXButton>
</right> </headerRight>
</BorderPane> <VBox spacing="8">
<JFXRadioButton fx:id="chkUpdateStable" text="%update.channel.stable" />
<JFXRadioButton fx:id="chkUpdateDev" text="%update.channel.dev" />
<VBox style="-fx-padding: 10 0 0 0;">
<Text fx:id="lblUpdateNote" text="%update.note" />
</VBox>
</VBox>
</ComponentSublist>
<MultiFileItem fx:id="fileCommonLocation" title="%launcher.common_directory" directory="true" chooserTitle="%launcher.common_directory.choose" hasSubtitle="true" customText="%settings.custom" /> <MultiFileItem fx:id="fileCommonLocation" title="%launcher.common_directory" directory="true" chooserTitle="%launcher.common_directory.choose" hasSubtitle="true" customText="%settings.custom" />

View File

@@ -311,9 +311,13 @@ settings.type=Version setting type
settings.type.special=Specialized version settings(will not affect other versions) settings.type.special=Specialized version settings(will not affect other versions)
update=Update update=Update
update.channel.dev=Update to development version
update.channel.stable=Update to stable version
update.checking=Checking for updates
update.failed=Failed to perform upgrade update.failed=Failed to perform upgrade
update.found=Update Available! update.found=Update Available!
update.newest_version=Latest version: %s update.newest_version=Latest version: %s
update.note=Development version contains more functionality and bug fixes as well as more possible bugs. And this will affect all HMCL installations in your computer.
update.latest=This is latest Version. update.latest=This is latest Version.
update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update. update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update.
update.tooltip=Update update.tooltip=Update

View File

@@ -311,9 +311,13 @@ settings.type=版本設置類型
settings.type.special=單獨版本設置(不會影響到其他版本的設定) settings.type.special=單獨版本設置(不會影響到其他版本的設定)
update=啓動器更新 update=啓動器更新
update.channel.dev=更新到開發版
update.channel.stable=更新到推薦版本
update.checking=正在檢查更新
update.failed=更新失敗 update.failed=更新失敗
update.found=發現更新 update.found=發現更新
update.newest_version=最新版本爲:%s update.newest_version=最新版本爲:%s
update.note=開發版包含更多的功能以及錯誤修復但也可能會包含其他的問題。選擇更新到開發版導致你電腦中所有的HMCL更新至開發版
update.latest=當前版本爲最新版本 update.latest=當前版本爲最新版本
update.no_browser=無法打開瀏覽器,網址已經複製到剪貼板了,您可以手動粘貼網址打開頁面 update.no_browser=無法打開瀏覽器,網址已經複製到剪貼板了,您可以手動粘貼網址打開頁面
update.tooltip=更新 update.tooltip=更新

View File

@@ -311,9 +311,13 @@ settings.type=版本设置类型
settings.type.special=单独版本设置(不会影响到其他版本的设定) settings.type.special=单独版本设置(不会影响到其他版本的设定)
update=启动器更新 update=启动器更新
update.channel.dev=更新到开发版
update.channel.stable=更新到推荐版本
update.checking=正在检查更新
update.failed=更新失败 update.failed=更新失败
update.found=发现更新 update.found=发现更新
update.newest_version=最新版本为:%s update.newest_version=最新版本为:%s
update.note=开发版包含更多的功能以及错误修复但也可能会包含其他的问题。选择更新到开发版导致你电脑中所有的HMCL更新至开发版
update.latest=当前版本为最新版本 update.latest=当前版本为最新版本
update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面 update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面
update.tooltip=更新 update.tooltip=更新