Compare commits

...
12 changed files with 163 additions and 127 deletions

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.ITerracottaProvider;
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.i18n.LocaleUtils;
import org.jackhuang.hmcl.util.i18n.LocalizedText;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
@@ -61,11 +63,14 @@ public final class TerracottaMetadata {
@SerializedName("classifiers") Map<String, String> classifiers,
@SerializedName("downloads") List<String> downloads,
@SerializedName("downloads_CN") List<String> downloadsCN,
@SerializedName("links") List<Link> links
) {
private TerracottaNative of(String classifier) {
List<URI> links = new ArrayList<>(this.downloads.size());
for (String download : this.downloads) {
List<URI> links = new ArrayList<>(this.downloads.size() + this.downloadsCN.size());
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)));
}
@@ -64,6 +64,7 @@ import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.List;
@@ -433,26 +434,34 @@ public final class Controllers {
dialog(new MessageDialogPane.Builder(text, title, type).actionOrCancel(actionButton, cancel).build());
}
public static void confirmActionDanger(String text, String title, Runnable resolve, Runnable cancel) {
JFXButton btnYes = new JFXButton(i18n("button.ok"));
btnYes.getStyleClass().add("dialog-error");
btnYes.setOnAction(e -> resolve.run());
btnYes.setDisable(true);
public static void confirmWithCountdown(String text, String title, int seconds, MessageType messageType,
@Nullable Runnable ok, @Nullable Runnable cancel) {
if (seconds <= 0)
throw new IllegalArgumentException("Seconds must be greater than 0");
int countdown = 10;
KeyFrame[] keyFrames = new KeyFrame[countdown + 1];
for (int i = 0; i < countdown; i++) {
JFXButton btnOk = new JFXButton(i18n("button.ok"));
btnOk.getStyleClass().add(messageType == MessageType.WARNING || messageType == MessageType.ERROR
? "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),
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),
new KeyValue(btnYes.textProperty(), i18n("button.ok")),
new KeyValue(btnYes.disableProperty(), false));
keyFrames[seconds] = new KeyFrame(Duration.seconds(seconds),
new KeyValue(btnOk.textProperty(), i18n("button.ok")),
new KeyValue(btnOk.disableProperty(), false));
Timeline timeline = new Timeline(keyFrames);
confirmAction(text, title, MessageType.WARNING, btnYes, () -> {
confirmAction(text, title, messageType, btnOk, () -> {
timeline.stop();
cancel.run();
if (cancel != null)
cancel.run();
});
timeline.play();
}
@@ -1411,12 +1411,16 @@ public final class FXUtils {
}
public static void copyText(String text) {
copyText(text, i18n("message.copied"));
}
public static void copyText(String text, @Nullable String toastMessage) {
ClipboardContent content = new ClipboardContent();
content.putString(text);
Clipboard.getSystemClipboard().setContent(content);
if (!Controllers.isStopped()) {
Controllers.showToast(i18n("message.copied"));
if (toastMessage != null && !Controllers.isStopped()) {
Controllers.showToast(toastMessage);
}
}
@@ -19,7 +19,13 @@ package org.jackhuang.hmcl.ui.account;
import com.jfoenix.controls.JFXButton;
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.ObservableValue;
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.javafx.BindingMapping;
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 static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
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.javafx.ExtendedProperties.createSelectedItemPropertyFor;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
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 {
String property = System.getProperty("hmcl.offline.auth.restricted", "auto");
if ("false".equals(property)
|| "auto".equals(property) && isExemptedRegion()
|| "auto".equals(property) && LocaleUtils.IS_CHINA_MAINLAND
|| globalConfig().isEnableOfflineAccount())
RESTRICTED.set(false);
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)) {
Controllers.confirmActionDanger(i18n("account.methods.offline.name.invalid"), i18n("message.warning"), doCreate, () -> {
body.setDisable(false);
spinner.hideSpinner();
});
Controllers.confirmWithCountdown(i18n("account.methods.offline.name.invalid"), i18n("message.warning"), 10,
MessageDialogPane.MessageType.WARNING,
doCreate, () -> {
body.setDisable(false);
spinner.hideSpinner();
});
} else {
doCreate.run();
}
@@ -34,11 +34,8 @@ import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
@@ -138,13 +135,13 @@ public class TerracottaControllerPage extends StackPane {
TerracottaState state = UI_STATE.get(), next;
if (state instanceof TerracottaState.Uninitialized || state instanceof TerracottaState.Preparing preparing && preparing.hasInstallFence()) {
if (state instanceof TerracottaState.Uninitialized uninitialized && !uninitialized.hasLegacy()) {
Controllers.confirmActionDanger(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), () -> {
TerracottaState.Preparing s = TerracottaManager.install(path);
if (s != null) {
UI_STATE.set(s);
}
}, () -> {
});
Controllers.confirmWithCountdown(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), 5,
MessageDialogPane.MessageType.INFO, () -> {
TerracottaState.Preparing s = TerracottaManager.install(path);
if (s != null) {
UI_STATE.set(s);
}
}, null);
return;
}
@@ -202,7 +199,7 @@ public class TerracottaControllerPage extends StackPane {
}
}
} 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);
TerracottaState.Preparing s = TerracottaManager.install(null);
if (s != null) {
@@ -342,6 +339,7 @@ public class TerracottaControllerPage extends StackPane {
return;
} else {
String cs = hostOK.getCode();
copyCode(cs);
statusProperty.set(i18n("terracotta.status.host_ok"));
progressProperty.set(1);
@@ -350,36 +348,24 @@ public class TerracottaControllerPage extends StackPane {
code.setAlignment(Pos.CENTER);
{
Label desc = new Label(i18n("terracotta.status.host_ok.code"));
{
ClipboardContent cp = new ClipboardContent();
cp.putString(cs);
Clipboard.getSystemClipboard().setContent(cp);
}
desc.setMouseTransparent(true);
// FIXME: The implementation to display Room Code is ambiguous. Consider using a clearer JavaFX Element in the future.
TextField label = new TextField(cs);
label.setEditable(false);
label.setFocusTraversable(false);
Label label = new Label(cs);
label.setMouseTransparent(true);
label.setStyle("-fx-font-size: 24");
label.setAlignment(Pos.CENTER);
label.setStyle("-fx-background-color: transparent; -fx-border-color: transparent;");
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);
}
FXUtils.onClicked(code, () -> FXUtils.copyText(cs));
code.setCursor(Cursor.HAND);
FXUtils.onClicked(code, () -> copyCode(cs));
LineButton copy = LineButton.of();
copy.setLeftIcon(SVG.CONTENT_COPY);
copy.setTitle(i18n("terracotta.status.host_ok.code.copy"));
copy.setSubtitle(i18n("terracotta.status.host_ok.code.desc"));
FXUtils.onClicked(copy, () -> FXUtils.copyText(cs));
FXUtils.onClicked(copy, () -> copyCode(cs));
LineButton back = LineButton.of();
back.setLeftIcon(SVG.ARROW_BACK);
@@ -601,6 +587,10 @@ public class TerracottaControllerPage extends StackPane {
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 final WeakListenerHolder holder = new WeakListenerHolder();
@@ -19,22 +19,31 @@ package org.jackhuang.hmcl.ui.terracotta;
import javafx.beans.property.ReadOnlyObjectProperty;
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.game.LauncherHelper;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.terracotta.TerracottaMetadata;
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;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
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.TabHeader;
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
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;
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 TransitionPane transitionPane = new TransitionPane();
@SuppressWarnings("unused")
private ChangeListener<String> instanceChangeListenerHolder;
public TerracottaPage() {
statusPage.setNodeSupplier(TerracottaControllerPage::new);
tab = new TabHeader(statusPage);
@@ -53,32 +65,35 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
AdvancedListItem feedback = new AdvancedListItem();
feedback.setLeftGraphic(wrap(SVG.FEEDBACK));
feedback.setActionButtonVisible(false);
feedback.setTitle(i18n("terracotta.feedback.title"));
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/"));
BorderPane left = new BorderPane();
FXUtils.setLimitWidth(left, 200);
VBox.setVgrow(left, Priority.ALWAYS);
setLeft(left);
AdvancedListBox sideBar = new AdvancedListBox()
.addNavigationDrawerTab(tab, statusPage, i18n("terracotta.status"), SVG.TUNE)
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
.add(feedback)
.add(chatItem)
.add(easytierItem);
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
.addNavigationDrawerTab(tab, statusPage, i18n("terracotta.status"), SVG.TUNE);
left.setTop(sideBar);
AdvancedListBox toolbar = new AdvancedListBox()
.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, () -> {
Profile profile = Profiles.getSelectedProfile();
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);
}
@@ -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.code=Invitation code (Copied)
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.back=This will also close the room, other guests will leave and cannot rejoin.
terracotta.status.guest_starting=Joining room
@@ -1248,6 +1248,7 @@ terracotta.status.host_starting.back=這將會取消建立房間。
terracotta.status.host_ok=已建立房間
terracotta.status.host_ok.code=邀請碼 (已自動複製到剪貼簿)
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.back=這將同時徹底關閉房間,其他房客將退出並不再能重新加入該房間。
terracotta.status.guest_starting=正在加入房間
@@ -1258,6 +1258,7 @@ terracotta.status.host_starting.back=这将会取消创建房间。
terracotta.status.host_ok=已启动房间
terracotta.status.host_ok.code=邀请码 (已自动复制到剪贴板)
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.back=这将同时彻底关闭房间,其他房客将退出并不再能重新加入该房间。
terracotta.status.guest_starting=正在加入房间
+16 -16
View File
@@ -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": [
"0.3.9-rc.10"
"0.3.10"
],
"version_latest": "0.3.10",
"version_latest": "0.3.11",
"classifiers": {
"linux-arm64": "sha256:001b6b1e220e98dffd84198a3e16855644cad27900562c78d610b0bf50138d17",
"linux-x86_64": "sha256:b133ddb796ff52879fb0c38e68ef7ea968d3eaec77f27ea274c701888e795782",
"macos-arm64": "sha256:4bb2eb432ea0ee3e3dca6b1019d8864aa14bedfe20b3d804865e216e36e856df",
"macos-arm64.pkg": "sha256:e4bf3d2014e417565e3ab0df579f4280e0af42bc28516f640067a52dca77098f",
"macos-x86_64": "sha256:17a2cc46d884951a88da6f3f0725e38ffca6fdfd1da021807121c29b944bd830",
"macos-x86_64.pkg": "sha256:a4f08a706605262485c1889dfae00554fba753141f6d6ada76e29bfc3e0338aa",
"windows-arm64.exe": "sha256:245e0faf2ee1cdc5e430b5c18fbab4c4e5e3f333f0de51a621ae772db3a16924",
"windows-x86_64.exe": "sha256:ab99ddb729860c939b2085d8852fd62007b86d97568b687cf2a7147718b64eea"
"linux-arm64": "sha256:cd321f22b200b212ba21eb7582bfe0e1bcadb528d19c70307a2aea9596118d33",
"linux-x86_64": "sha256:d4b15ec7cba7c9332b84f6acbf247e779543f958f9b240e9707bd6f37973e12a",
"macos-arm64": "sha256:8456ad2c016e7c5c6f5814d98203a89ec38d13cdaafe94d328e313404ea16a2f",
"macos-arm64.pkg": "sha256:f85f86c563d4460ba97ed048fee22fa0e1398454443576c56b0afeebb98e8691",
"macos-x86_64": "sha256:cb1f7f5d5876a9177c426bece5ad0845e1b25d3430533646b2b9c93d97d37d79",
"macos-x86_64.pkg": "sha256:e54bc35be7c8c543362b1b428e7d40d2116f1a4e57ade1c09cbef6239efb67ae",
"windows-arm64.exe": "sha256:40c628bafb48d51c90f47037f8c4caedfadb52421e64b70011f448d1d2f553a3",
"windows-x86_64.exe": "sha256:de518b2b4e60336fd272092326799966efc4e72acd65576d83e61d4d242fd61d"
},
"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://alist.8mi.tech/d/mirror/HMCL-Terracotta/Auto/v${version}/terracotta-${version}-${classifier}",
"https://ghfast.top/https://github.com/burningtnt/Terracotta/releases/download/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}"
"https://cnb.cool/HMCL-Terracotta/Terracotta/-/releases/download/v${version}/terracotta-${version}-${classifier}",
"https://alist.8mi.tech/d/mirror/HMCL-Terracotta/Auto/v${version}/terracotta-${version}-${classifier}"
],
"links": [
{
@@ -20,6 +20,10 @@ package org.jackhuang.hmcl.util.i18n;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
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.Nullable;
import org.jetbrains.annotations.Unmodifiable;
@@ -27,6 +31,9 @@ import org.jetbrains.annotations.Unmodifiable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -385,6 +392,29 @@ public final class LocaleUtils {
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() {
}
}