Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
341c60b0de |
No files matched your search
@@ -23,7 +23,9 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
|||||||
import org.jackhuang.hmcl.terracotta.provider.GeneralProvider;
|
import org.jackhuang.hmcl.terracotta.provider.GeneralProvider;
|
||||||
import org.jackhuang.hmcl.terracotta.provider.ITerracottaProvider;
|
import org.jackhuang.hmcl.terracotta.provider.ITerracottaProvider;
|
||||||
import org.jackhuang.hmcl.terracotta.provider.MacOSProvider;
|
import org.jackhuang.hmcl.terracotta.provider.MacOSProvider;
|
||||||
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
|
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
|
||||||
import org.jackhuang.hmcl.util.i18n.LocalizedText;
|
import org.jackhuang.hmcl.util.i18n.LocalizedText;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
@@ -61,11 +63,14 @@ public final class TerracottaMetadata {
|
|||||||
|
|
||||||
@SerializedName("classifiers") Map<String, String> classifiers,
|
@SerializedName("classifiers") Map<String, String> classifiers,
|
||||||
@SerializedName("downloads") List<String> downloads,
|
@SerializedName("downloads") List<String> downloads,
|
||||||
|
@SerializedName("downloads_CN") List<String> downloadsCN,
|
||||||
@SerializedName("links") List<Link> links
|
@SerializedName("links") List<Link> links
|
||||||
) {
|
) {
|
||||||
private TerracottaNative of(String classifier) {
|
private TerracottaNative of(String classifier) {
|
||||||
List<URI> links = new ArrayList<>(this.downloads.size());
|
List<URI> links = new ArrayList<>(this.downloads.size() + this.downloadsCN.size());
|
||||||
for (String download : this.downloads) {
|
for (String download : LocaleUtils.IS_CHINA_MAINLAND
|
||||||
|
? Lang.merge(this.downloadsCN, this.downloads)
|
||||||
|
: Lang.merge(this.downloads, this.downloadsCN)) {
|
||||||
links.add(URI.create(download.replace("${version}", this.latest).replace("${classifier}", classifier)));
|
links.add(URI.create(download.replace("${version}", this.latest).replace("${classifier}", classifier)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ import org.jackhuang.hmcl.util.*;
|
|||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -433,26 +434,34 @@ public final class Controllers {
|
|||||||
dialog(new MessageDialogPane.Builder(text, title, type).actionOrCancel(actionButton, cancel).build());
|
dialog(new MessageDialogPane.Builder(text, title, type).actionOrCancel(actionButton, cancel).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void confirmActionDanger(String text, String title, Runnable resolve, Runnable cancel) {
|
public static void confirmWithCountdown(String text, String title, int seconds, MessageType messageType,
|
||||||
JFXButton btnYes = new JFXButton(i18n("button.ok"));
|
@Nullable Runnable ok, @Nullable Runnable cancel) {
|
||||||
btnYes.getStyleClass().add("dialog-error");
|
if (seconds <= 0)
|
||||||
btnYes.setOnAction(e -> resolve.run());
|
throw new IllegalArgumentException("Seconds must be greater than 0");
|
||||||
btnYes.setDisable(true);
|
|
||||||
|
|
||||||
int countdown = 10;
|
JFXButton btnOk = new JFXButton(i18n("button.ok"));
|
||||||
KeyFrame[] keyFrames = new KeyFrame[countdown + 1];
|
btnOk.getStyleClass().add(messageType == MessageType.WARNING || messageType == MessageType.ERROR
|
||||||
for (int i = 0; i < countdown; i++) {
|
? "dialog-error"
|
||||||
|
: "dialog-accept");
|
||||||
|
|
||||||
|
if (ok != null)
|
||||||
|
btnOk.setOnAction(e -> ok.run());
|
||||||
|
btnOk.setDisable(true);
|
||||||
|
|
||||||
|
KeyFrame[] keyFrames = new KeyFrame[seconds + 1];
|
||||||
|
for (int i = 0; i < seconds; i++) {
|
||||||
keyFrames[i] = new KeyFrame(Duration.seconds(i),
|
keyFrames[i] = new KeyFrame(Duration.seconds(i),
|
||||||
new KeyValue(btnYes.textProperty(), i18n("button.ok.countdown", countdown - i)));
|
new KeyValue(btnOk.textProperty(), i18n("button.ok.countdown", seconds - i)));
|
||||||
}
|
}
|
||||||
keyFrames[countdown] = new KeyFrame(Duration.seconds(countdown),
|
keyFrames[seconds] = new KeyFrame(Duration.seconds(seconds),
|
||||||
new KeyValue(btnYes.textProperty(), i18n("button.ok")),
|
new KeyValue(btnOk.textProperty(), i18n("button.ok")),
|
||||||
new KeyValue(btnYes.disableProperty(), false));
|
new KeyValue(btnOk.disableProperty(), false));
|
||||||
|
|
||||||
Timeline timeline = new Timeline(keyFrames);
|
Timeline timeline = new Timeline(keyFrames);
|
||||||
confirmAction(text, title, MessageType.WARNING, btnYes, () -> {
|
confirmAction(text, title, messageType, btnOk, () -> {
|
||||||
timeline.stop();
|
timeline.stop();
|
||||||
cancel.run();
|
if (cancel != null)
|
||||||
|
cancel.run();
|
||||||
});
|
});
|
||||||
timeline.play();
|
timeline.play();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1411,12 +1411,16 @@ public final class FXUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void copyText(String text) {
|
public static void copyText(String text) {
|
||||||
|
copyText(text, i18n("message.copied"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copyText(String text, @Nullable String toastMessage) {
|
||||||
ClipboardContent content = new ClipboardContent();
|
ClipboardContent content = new ClipboardContent();
|
||||||
content.putString(text);
|
content.putString(text);
|
||||||
Clipboard.getSystemClipboard().setContent(content);
|
Clipboard.getSystemClipboard().setContent(content);
|
||||||
|
|
||||||
if (!Controllers.isStopped()) {
|
if (toastMessage != null && !Controllers.isStopped()) {
|
||||||
Controllers.showToast(i18n("message.copied"));
|
Controllers.showToast(toastMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ package org.jackhuang.hmcl.ui.account;
|
|||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ListProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleListProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
@@ -45,51 +51,23 @@ import org.jackhuang.hmcl.util.i18n.LocaleUtils;
|
|||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
|
||||||
import org.jackhuang.hmcl.util.platform.windows.Kernel32;
|
|
||||||
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
||||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||||
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
public final class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
|
public final class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||||
static final BooleanProperty RESTRICTED = new SimpleBooleanProperty(true);
|
static final BooleanProperty RESTRICTED = new SimpleBooleanProperty(true);
|
||||||
|
|
||||||
private static boolean isExemptedRegion() {
|
|
||||||
if ("Asia/Shanghai".equals(ZoneId.systemDefault().getId()))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Check if the time zone is UTC+8
|
|
||||||
if (ZonedDateTime.now().getOffset().getTotalSeconds() == Duration.ofHours(8).toSeconds()) {
|
|
||||||
if ("CN".equals(LocaleUtils.SYSTEM_DEFAULT.getCountry()))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && NativeUtils.USE_JNA) {
|
|
||||||
Kernel32 kernel32 = Kernel32.INSTANCE;
|
|
||||||
|
|
||||||
// https://learn.microsoft.com/windows/win32/intl/table-of-geographical-locations
|
|
||||||
if (kernel32 != null && kernel32.GetUserGeoID(WinConstants.GEOCLASS_NATION) == 45) // China
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
String property = System.getProperty("hmcl.offline.auth.restricted", "auto");
|
String property = System.getProperty("hmcl.offline.auth.restricted", "auto");
|
||||||
|
|
||||||
if ("false".equals(property)
|
if ("false".equals(property)
|
||||||
|| "auto".equals(property) && isExemptedRegion()
|
|| "auto".equals(property) && LocaleUtils.IS_CHINA_MAINLAND
|
||||||
|| globalConfig().isEnableOfflineAccount())
|
|| globalConfig().isEnableOfflineAccount())
|
||||||
RESTRICTED.set(false);
|
RESTRICTED.set(false);
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -264,10 +264,12 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (factory instanceof OfflineAccountFactory && username != null && (!USERNAME_CHECKER_PATTERN.matcher(username).matches() || username.length() > 16)) {
|
if (factory instanceof OfflineAccountFactory && username != null && (!USERNAME_CHECKER_PATTERN.matcher(username).matches() || username.length() > 16)) {
|
||||||
Controllers.confirmActionDanger(i18n("account.methods.offline.name.invalid"), i18n("message.warning"), doCreate, () -> {
|
Controllers.confirmWithCountdown(i18n("account.methods.offline.name.invalid"), i18n("message.warning"), 10,
|
||||||
body.setDisable(false);
|
MessageDialogPane.MessageType.WARNING,
|
||||||
spinner.hideSpinner();
|
doCreate, () -> {
|
||||||
});
|
body.setDisable(false);
|
||||||
|
spinner.hideSpinner();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
doCreate.run();
|
doCreate.run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,8 @@ import javafx.scene.Cursor;
|
|||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.ScrollPane;
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.input.Clipboard;
|
|
||||||
import javafx.scene.input.ClipboardContent;
|
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
@@ -138,13 +135,13 @@ public class TerracottaControllerPage extends StackPane {
|
|||||||
TerracottaState state = UI_STATE.get(), next;
|
TerracottaState state = UI_STATE.get(), next;
|
||||||
if (state instanceof TerracottaState.Uninitialized || state instanceof TerracottaState.Preparing preparing && preparing.hasInstallFence()) {
|
if (state instanceof TerracottaState.Uninitialized || state instanceof TerracottaState.Preparing preparing && preparing.hasInstallFence()) {
|
||||||
if (state instanceof TerracottaState.Uninitialized uninitialized && !uninitialized.hasLegacy()) {
|
if (state instanceof TerracottaState.Uninitialized uninitialized && !uninitialized.hasLegacy()) {
|
||||||
Controllers.confirmActionDanger(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), () -> {
|
Controllers.confirmWithCountdown(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), 5,
|
||||||
TerracottaState.Preparing s = TerracottaManager.install(path);
|
MessageDialogPane.MessageType.INFO, () -> {
|
||||||
if (s != null) {
|
TerracottaState.Preparing s = TerracottaManager.install(path);
|
||||||
UI_STATE.set(s);
|
if (s != null) {
|
||||||
}
|
UI_STATE.set(s);
|
||||||
}, () -> {
|
}
|
||||||
});
|
}, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +199,7 @@ public class TerracottaControllerPage extends StackPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Controllers.confirmActionDanger(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), () -> {
|
Controllers.confirmWithCountdown(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), 5, MessageDialogPane.MessageType.INFO, () -> {
|
||||||
globalConfig().setTerracottaAgreementVersion(1);
|
globalConfig().setTerracottaAgreementVersion(1);
|
||||||
TerracottaState.Preparing s = TerracottaManager.install(null);
|
TerracottaState.Preparing s = TerracottaManager.install(null);
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
@@ -342,6 +339,7 @@ public class TerracottaControllerPage extends StackPane {
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
String cs = hostOK.getCode();
|
String cs = hostOK.getCode();
|
||||||
|
copyCode(cs);
|
||||||
|
|
||||||
statusProperty.set(i18n("terracotta.status.host_ok"));
|
statusProperty.set(i18n("terracotta.status.host_ok"));
|
||||||
progressProperty.set(1);
|
progressProperty.set(1);
|
||||||
@@ -350,36 +348,24 @@ public class TerracottaControllerPage extends StackPane {
|
|||||||
code.setAlignment(Pos.CENTER);
|
code.setAlignment(Pos.CENTER);
|
||||||
{
|
{
|
||||||
Label desc = new Label(i18n("terracotta.status.host_ok.code"));
|
Label desc = new Label(i18n("terracotta.status.host_ok.code"));
|
||||||
{
|
desc.setMouseTransparent(true);
|
||||||
ClipboardContent cp = new ClipboardContent();
|
|
||||||
cp.putString(cs);
|
|
||||||
Clipboard.getSystemClipboard().setContent(cp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: The implementation to display Room Code is ambiguous. Consider using a clearer JavaFX Element in the future.
|
Label label = new Label(cs);
|
||||||
TextField label = new TextField(cs);
|
label.setMouseTransparent(true);
|
||||||
label.setEditable(false);
|
label.setStyle("-fx-font-size: 24");
|
||||||
label.setFocusTraversable(false);
|
|
||||||
label.setAlignment(Pos.CENTER);
|
label.setAlignment(Pos.CENTER);
|
||||||
label.setStyle("-fx-background-color: transparent; -fx-border-color: transparent;");
|
|
||||||
VBox.setMargin(label, new Insets(10, 0, 10, 0));
|
VBox.setMargin(label, new Insets(10, 0, 10, 0));
|
||||||
label.setScaleX(1.8);
|
|
||||||
label.setScaleY(1.8);
|
|
||||||
holder.add(FXUtils.onWeakChange(label.selectedTextProperty(), string -> {
|
|
||||||
if (string != null && !string.isEmpty() && !cs.equals(string)) {
|
|
||||||
label.selectAll();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
code.getChildren().setAll(desc, label);
|
code.getChildren().setAll(desc, label);
|
||||||
}
|
}
|
||||||
FXUtils.onClicked(code, () -> FXUtils.copyText(cs));
|
code.setCursor(Cursor.HAND);
|
||||||
|
FXUtils.onClicked(code, () -> copyCode(cs));
|
||||||
|
|
||||||
LineButton copy = LineButton.of();
|
LineButton copy = LineButton.of();
|
||||||
copy.setLeftIcon(SVG.CONTENT_COPY);
|
copy.setLeftIcon(SVG.CONTENT_COPY);
|
||||||
copy.setTitle(i18n("terracotta.status.host_ok.code.copy"));
|
copy.setTitle(i18n("terracotta.status.host_ok.code.copy"));
|
||||||
copy.setSubtitle(i18n("terracotta.status.host_ok.code.desc"));
|
copy.setSubtitle(i18n("terracotta.status.host_ok.code.desc"));
|
||||||
FXUtils.onClicked(copy, () -> FXUtils.copyText(cs));
|
FXUtils.onClicked(copy, () -> copyCode(cs));
|
||||||
|
|
||||||
LineButton back = LineButton.of();
|
LineButton back = LineButton.of();
|
||||||
back.setLeftIcon(SVG.ARROW_BACK);
|
back.setLeftIcon(SVG.ARROW_BACK);
|
||||||
@@ -601,6 +587,10 @@ public class TerracottaControllerPage extends StackPane {
|
|||||||
return locals;
|
return locals;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void copyCode(String code) {
|
||||||
|
FXUtils.copyText(code, i18n("terracotta.status.host_ok.code.copy.toast"));
|
||||||
|
}
|
||||||
|
|
||||||
private static final class LineButton extends RipplerContainer {
|
private static final class LineButton extends RipplerContainer {
|
||||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||||
|
|
||||||
|
|||||||
@@ -19,22 +19,31 @@ package org.jackhuang.hmcl.ui.terracotta;
|
|||||||
|
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
import org.jackhuang.hmcl.Metadata;
|
import org.jackhuang.hmcl.Metadata;
|
||||||
|
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||||
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.terracotta.TerracottaMetadata;
|
import org.jackhuang.hmcl.terracotta.TerracottaMetadata;
|
||||||
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.PageAware;
|
import org.jackhuang.hmcl.ui.construct.PageAware;
|
||||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
|
import org.jackhuang.hmcl.ui.main.MainPage;
|
||||||
|
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||||
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
|
public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
|
||||||
@@ -43,6 +52,9 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
|
|||||||
private final TabHeader.Tab<TerracottaControllerPage> statusPage = new TabHeader.Tab<>("statusPage");
|
private final TabHeader.Tab<TerracottaControllerPage> statusPage = new TabHeader.Tab<>("statusPage");
|
||||||
private final TransitionPane transitionPane = new TransitionPane();
|
private final TransitionPane transitionPane = new TransitionPane();
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private ChangeListener<String> instanceChangeListenerHolder;
|
||||||
|
|
||||||
public TerracottaPage() {
|
public TerracottaPage() {
|
||||||
statusPage.setNodeSupplier(TerracottaControllerPage::new);
|
statusPage.setNodeSupplier(TerracottaControllerPage::new);
|
||||||
tab = new TabHeader(statusPage);
|
tab = new TabHeader(statusPage);
|
||||||
@@ -53,32 +65,35 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
|
|||||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||||
});
|
});
|
||||||
|
|
||||||
AdvancedListItem feedback = new AdvancedListItem();
|
BorderPane left = new BorderPane();
|
||||||
feedback.setLeftGraphic(wrap(SVG.FEEDBACK));
|
FXUtils.setLimitWidth(left, 200);
|
||||||
feedback.setActionButtonVisible(false);
|
VBox.setVgrow(left, Priority.ALWAYS);
|
||||||
feedback.setTitle(i18n("terracotta.feedback.title"));
|
setLeft(left);
|
||||||
feedback.setOnAction(e -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK));
|
|
||||||
|
|
||||||
AdvancedListItem chatItem = new AdvancedListItem();
|
|
||||||
chatItem.setLeftGraphic(wrap(SVG.CHAT));
|
|
||||||
chatItem.setActionButtonVisible(false);
|
|
||||||
chatItem.setTitle(i18n("chat"));
|
|
||||||
chatItem.setOnAction(e -> FXUtils.openLink(Metadata.GROUPS_URL));
|
|
||||||
|
|
||||||
AdvancedListItem easytierItem = new AdvancedListItem();
|
|
||||||
easytierItem.setLeftGraphic(wrap(SVG.HOST));
|
|
||||||
easytierItem.setActionButtonVisible(false);
|
|
||||||
easytierItem.setTitle(i18n("terracotta.easytier"));
|
|
||||||
easytierItem.setOnAction(e -> FXUtils.openLink("https://easytier.cn/"));
|
|
||||||
|
|
||||||
AdvancedListBox sideBar = new AdvancedListBox()
|
AdvancedListBox sideBar = new AdvancedListBox()
|
||||||
.addNavigationDrawerTab(tab, statusPage, i18n("terracotta.status"), SVG.TUNE)
|
.addNavigationDrawerTab(tab, statusPage, i18n("terracotta.status"), SVG.TUNE);
|
||||||
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
|
left.setTop(sideBar);
|
||||||
.add(feedback)
|
|
||||||
.add(chatItem)
|
AdvancedListBox toolbar = new AdvancedListBox()
|
||||||
.add(easytierItem);
|
.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, () -> {
|
||||||
FXUtils.setLimitWidth(sideBar, 200);
|
Profile profile = Profiles.getSelectedProfile();
|
||||||
setLeft(sideBar);
|
Versions.launch(profile, profile.getSelectedVersion(), LauncherHelper::setKeep);
|
||||||
|
}, item -> {
|
||||||
|
instanceChangeListenerHolder = FXUtils.onWeakChangeAndOperate(Profiles.selectedVersionProperty(),
|
||||||
|
instanceName -> item.setSubtitle(StringUtils.isNotBlank(instanceName) ? instanceName : i18n("version.empty"))
|
||||||
|
);
|
||||||
|
|
||||||
|
MainPage mainPage = Controllers.getRootPage().getMainPage();
|
||||||
|
FXUtils.onScroll(item, mainPage.getVersions(), list -> {
|
||||||
|
String currentId = mainPage.getCurrentGame();
|
||||||
|
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
|
||||||
|
}, it -> mainPage.getProfile().setSelectedVersion(it.getId()));
|
||||||
|
})
|
||||||
|
.addNavigationDrawerItem(i18n("terracotta.feedback.title"), SVG.FEEDBACK, () -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK))
|
||||||
|
.addNavigationDrawerItem(i18n("terracotta.easytier"), SVG.HOST, () -> FXUtils.openLink("https://easytier.cn/"))
|
||||||
|
.addNavigationDrawerItem(i18n("chat"), SVG.CHAT, () -> FXUtils.openLink(Metadata.GROUPS_URL));
|
||||||
|
BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));
|
||||||
|
left.setBottom(toolbar);
|
||||||
|
|
||||||
setCenter(transitionPane);
|
setCenter(transitionPane);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1460,6 +1460,7 @@ terracotta.status.host_starting.back=This will stop creating the room.
|
|||||||
terracotta.status.host_ok=Room created
|
terracotta.status.host_ok=Room created
|
||||||
terracotta.status.host_ok.code=Invitation code (Copied)
|
terracotta.status.host_ok.code=Invitation code (Copied)
|
||||||
terracotta.status.host_ok.code.copy=Copy invitation code
|
terracotta.status.host_ok.code.copy=Copy invitation code
|
||||||
|
terracotta.status.host_ok.code.copy.toast=Invitation code has been copied to clipboard
|
||||||
terracotta.status.host_ok.code.desc=Please remind your friends to select Guest mode in HMCL - Multiplayer or PCL CE and enter this invitation code.
|
terracotta.status.host_ok.code.desc=Please remind your friends to select Guest mode in HMCL - Multiplayer or PCL CE and enter this invitation code.
|
||||||
terracotta.status.host_ok.back=This will also close the room, other guests will leave and cannot rejoin.
|
terracotta.status.host_ok.back=This will also close the room, other guests will leave and cannot rejoin.
|
||||||
terracotta.status.guest_starting=Joining room
|
terracotta.status.guest_starting=Joining room
|
||||||
|
|||||||
@@ -1248,6 +1248,7 @@ terracotta.status.host_starting.back=這將會取消建立房間。
|
|||||||
terracotta.status.host_ok=已建立房間
|
terracotta.status.host_ok=已建立房間
|
||||||
terracotta.status.host_ok.code=邀請碼 (已自動複製到剪貼簿)
|
terracotta.status.host_ok.code=邀請碼 (已自動複製到剪貼簿)
|
||||||
terracotta.status.host_ok.code.copy=複製邀請碼
|
terracotta.status.host_ok.code.copy=複製邀請碼
|
||||||
|
terracotta.status.host_ok.code.copy.toast=已將邀請碼複製到剪貼簿
|
||||||
terracotta.status.host_ok.code.desc=請提醒您的朋友在 HMCL 或 PCL CE 多人遊戲功能中選擇房客模式,並輸入該邀請碼。
|
terracotta.status.host_ok.code.desc=請提醒您的朋友在 HMCL 或 PCL CE 多人遊戲功能中選擇房客模式,並輸入該邀請碼。
|
||||||
terracotta.status.host_ok.back=這將同時徹底關閉房間,其他房客將退出並不再能重新加入該房間。
|
terracotta.status.host_ok.back=這將同時徹底關閉房間,其他房客將退出並不再能重新加入該房間。
|
||||||
terracotta.status.guest_starting=正在加入房間
|
terracotta.status.guest_starting=正在加入房間
|
||||||
|
|||||||
@@ -1258,6 +1258,7 @@ terracotta.status.host_starting.back=这将会取消创建房间。
|
|||||||
terracotta.status.host_ok=已启动房间
|
terracotta.status.host_ok=已启动房间
|
||||||
terracotta.status.host_ok.code=邀请码 (已自动复制到剪贴板)
|
terracotta.status.host_ok.code=邀请码 (已自动复制到剪贴板)
|
||||||
terracotta.status.host_ok.code.copy=复制邀请码
|
terracotta.status.host_ok.code.copy=复制邀请码
|
||||||
|
terracotta.status.host_ok.code.copy.toast=已将邀请码复制到剪贴板
|
||||||
terracotta.status.host_ok.code.desc=请提醒您的朋友在 HMCL 或 PCL CE 多人联机功能中选择房客模式,并输入该邀请码。
|
terracotta.status.host_ok.code.desc=请提醒您的朋友在 HMCL 或 PCL CE 多人联机功能中选择房客模式,并输入该邀请码。
|
||||||
terracotta.status.host_ok.back=这将同时彻底关闭房间,其他房客将退出并不再能重新加入该房间。
|
terracotta.status.host_ok.back=这将同时彻底关闭房间,其他房客将退出并不再能重新加入该房间。
|
||||||
terracotta.status.guest_starting=正在加入房间
|
terracotta.status.guest_starting=正在加入房间
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
{
|
{
|
||||||
"version_legacy": "0\\.3\\.[89]-rc\\.([0-9]|10)",
|
"version_legacy": "0\\.3\\.([89]-rc\\.([0-9]|10)|10)",
|
||||||
"version_recent": [
|
"version_recent": [
|
||||||
"0.3.9-rc.10"
|
"0.3.10"
|
||||||
],
|
],
|
||||||
"version_latest": "0.3.10",
|
"version_latest": "0.3.11",
|
||||||
"classifiers": {
|
"classifiers": {
|
||||||
"linux-arm64": "sha256:001b6b1e220e98dffd84198a3e16855644cad27900562c78d610b0bf50138d17",
|
"linux-arm64": "sha256:cd321f22b200b212ba21eb7582bfe0e1bcadb528d19c70307a2aea9596118d33",
|
||||||
"linux-x86_64": "sha256:b133ddb796ff52879fb0c38e68ef7ea968d3eaec77f27ea274c701888e795782",
|
"linux-x86_64": "sha256:d4b15ec7cba7c9332b84f6acbf247e779543f958f9b240e9707bd6f37973e12a",
|
||||||
"macos-arm64": "sha256:4bb2eb432ea0ee3e3dca6b1019d8864aa14bedfe20b3d804865e216e36e856df",
|
"macos-arm64": "sha256:8456ad2c016e7c5c6f5814d98203a89ec38d13cdaafe94d328e313404ea16a2f",
|
||||||
"macos-arm64.pkg": "sha256:e4bf3d2014e417565e3ab0df579f4280e0af42bc28516f640067a52dca77098f",
|
"macos-arm64.pkg": "sha256:f85f86c563d4460ba97ed048fee22fa0e1398454443576c56b0afeebb98e8691",
|
||||||
"macos-x86_64": "sha256:17a2cc46d884951a88da6f3f0725e38ffca6fdfd1da021807121c29b944bd830",
|
"macos-x86_64": "sha256:cb1f7f5d5876a9177c426bece5ad0845e1b25d3430533646b2b9c93d97d37d79",
|
||||||
"macos-x86_64.pkg": "sha256:a4f08a706605262485c1889dfae00554fba753141f6d6ada76e29bfc3e0338aa",
|
"macos-x86_64.pkg": "sha256:e54bc35be7c8c543362b1b428e7d40d2116f1a4e57ade1c09cbef6239efb67ae",
|
||||||
"windows-arm64.exe": "sha256:245e0faf2ee1cdc5e430b5c18fbab4c4e5e3f333f0de51a621ae772db3a16924",
|
"windows-arm64.exe": "sha256:40c628bafb48d51c90f47037f8c4caedfadb52421e64b70011f448d1d2f553a3",
|
||||||
"windows-x86_64.exe": "sha256:ab99ddb729860c939b2085d8852fd62007b86d97568b687cf2a7147718b64eea"
|
"windows-x86_64.exe": "sha256:de518b2b4e60336fd272092326799966efc4e72acd65576d83e61d4d242fd61d"
|
||||||
},
|
},
|
||||||
"downloads": [
|
"downloads": [
|
||||||
"https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}",
|
"https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}"
|
||||||
|
],
|
||||||
|
"downloads_CN": [
|
||||||
"https://gitee.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}",
|
"https://gitee.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}",
|
||||||
"https://alist.8mi.tech/d/mirror/HMCL-Terracotta/Auto/v${version}/terracotta-${version}-${classifier}",
|
"https://cnb.cool/HMCL-Terracotta/Terracotta/-/releases/download/v${version}/terracotta-${version}-${classifier}",
|
||||||
"https://ghfast.top/https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}",
|
"https://alist.8mi.tech/d/mirror/HMCL-Terracotta/Auto/v${version}/terracotta-${version}-${classifier}"
|
||||||
"https://cdn.crashmc.com/https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}",
|
|
||||||
"https://cp.zkitefly.eu.org/https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}"
|
|
||||||
],
|
],
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ package org.jackhuang.hmcl.util.i18n;
|
|||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||||
|
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||||
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
|
import org.jackhuang.hmcl.util.platform.windows.Kernel32;
|
||||||
|
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.annotations.Unmodifiable;
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
@@ -27,6 +31,9 @@ import org.jetbrains.annotations.Unmodifiable;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
@@ -385,6 +392,29 @@ public final class LocaleUtils {
|
|||||||
return "zh".equals(getRootLanguage(locale));
|
return "zh".equals(getRootLanguage(locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final boolean IS_CHINA_MAINLAND = isChinaMainland();
|
||||||
|
|
||||||
|
private static boolean isChinaMainland() {
|
||||||
|
if ("Asia/Shanghai".equals(ZoneId.systemDefault().getId()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Check if the time zone is UTC+8
|
||||||
|
if (ZonedDateTime.now().getOffset().getTotalSeconds() == Duration.ofHours(8).toSeconds()) {
|
||||||
|
if ("CN".equals(LocaleUtils.SYSTEM_DEFAULT.getCountry()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && NativeUtils.USE_JNA) {
|
||||||
|
Kernel32 kernel32 = Kernel32.INSTANCE;
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/windows/win32/intl/table-of-geographical-locations
|
||||||
|
if (kernel32 != null && kernel32.GetUserGeoID(WinConstants.GEOCLASS_NATION) == 45) // China
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private LocaleUtils() {
|
private LocaleUtils() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user