diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 78b9f02e5..e1928bed2 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -190,9 +190,9 @@ tasks.build { dependsOn(makeExecutables) } -fun parseToolOptions(options: String?): List { +fun parseToolOptions(options: String?): MutableList { if (options == null) - return listOf() + return mutableListOf() val builder = StringBuilder() val result = mutableListOf() @@ -249,6 +249,9 @@ tasks.create("run") { workingDir = rootProject.rootDir val vmOptions = parseToolOptions(System.getenv("HMCL_JAVA_OPTS")) + if (vmOptions.none { it.startsWith("-Dhmcl.offline.auth.restricted=") }) + vmOptions += "-Dhmcl.offline.auth.restricted=false" + jvmArgs(vmOptions) val hmclJavaHome = System.getenv("HMCL_JAVA_HOME") diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 009cf6b40..ca5eca1c3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -22,6 +22,7 @@ import javafx.beans.Observable; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.auth.*; @@ -50,6 +51,7 @@ import java.util.*; import static java.util.stream.Collectors.toList; import static javafx.collections.FXCollections.observableArrayList; import static org.jackhuang.hmcl.setting.ConfigHolder.config; +import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating; import static org.jackhuang.hmcl.util.Lang.immutableListOf; import static org.jackhuang.hmcl.util.Lang.mapOf; @@ -277,6 +279,30 @@ public final class Accounts { selected = accounts.get(0); } + if (!globalConfig().isEnableOfflineAccount()) + for (Account account : accounts) { + if (account instanceof MicrosoftAccount) { + globalConfig().setEnableOfflineAccount(true); + break; + } + } + + if (!globalConfig().isEnableOfflineAccount()) + accounts.addListener(new ListChangeListener() { + @Override + public void onChanged(Change change) { + while (change.next()) { + for (Account account : change.getAddedSubList()) { + if (account instanceof MicrosoftAccount) { + accounts.removeListener(this); + globalConfig().setEnableOfflineAccount(true); + return; + } + } + } + } + }); + selectedAccount.set(selected); InvalidationListener listener = o -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java index b78acc0cc..f1bfbb695 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java @@ -21,7 +21,9 @@ import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; import javafx.beans.InvalidationListener; import javafx.beans.Observable; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableSet; @@ -53,6 +55,8 @@ public final class GlobalConfig implements Observable { private final IntegerProperty logRetention = new SimpleIntegerProperty(); + private final BooleanProperty enableOfflineAccount = new SimpleBooleanProperty(false); + private final ObservableSet userJava = FXCollections.observableSet(new LinkedHashSet<>()); private final ObservableSet disabledJava = FXCollections.observableSet(new LinkedHashSet<>()); @@ -115,6 +119,18 @@ public final class GlobalConfig implements Observable { this.logRetention.set(logRetention); } + public boolean isEnableOfflineAccount() { + return enableOfflineAccount.get(); + } + + public BooleanProperty enableOfflineAccountProperty() { + return enableOfflineAccount; + } + + public void setEnableOfflineAccount(boolean value) { + enableOfflineAccount.set(value); + } + public ObservableSet getUserJava() { return userJava; } @@ -129,7 +145,8 @@ public final class GlobalConfig implements Observable { "platformPromptVersion", "logRetention", "userJava", - "disabledJava" + "disabledJava", + "enableOfflineAccount" )); @Override @@ -142,6 +159,9 @@ public final class GlobalConfig implements Observable { jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion())); jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion())); jsonObject.add("logRetention", context.serialize(src.getLogRetention())); + if (src.enableOfflineAccount.get()) + jsonObject.addProperty("enableOfflineAccount", true); + if (!src.getUserJava().isEmpty()) jsonObject.add("userJava", context.serialize(src.getUserJava())); @@ -165,6 +185,7 @@ public final class GlobalConfig implements Observable { config.setAgreementVersion(Optional.ofNullable(obj.get("agreementVersion")).map(JsonElement::getAsInt).orElse(0)); config.setPlatformPromptVersion(Optional.ofNullable(obj.get("platformPromptVersion")).map(JsonElement::getAsInt).orElse(0)); config.setLogRetention(Optional.ofNullable(obj.get("logRetention")).map(JsonElement::getAsInt).orElse(20)); + config.setEnableOfflineAccount(Optional.ofNullable(obj.get("enableOfflineAccount")).map(JsonElement::getAsBoolean).orElse(false)); JsonElement userJava = obj.get("userJava"); if (userJava != null && userJava.isJsonArray()) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java index 2cb8e950d..7ebcaef99 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.account; import com.jfoenix.controls.JFXButton; import javafx.beans.binding.Bindings; import javafx.beans.property.*; +import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -44,14 +45,37 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.MappedObservableList; import java.net.URI; +import java.time.ZoneId; 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; -public class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage { +public final class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage { + static final BooleanProperty RESTRICTED = new SimpleBooleanProperty(true); + + static { + String property = System.getProperty("hmcl.offline.auth.restricted", "auto"); + + if ("false".equals(property) + || "auto".equals(property) && "Asia/Shanghai".equals(ZoneId.systemDefault().getId()) + || globalConfig().isEnableOfflineAccount()) + RESTRICTED.set(false); + else + globalConfig().enableOfflineAccountProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue o, Boolean oldValue, Boolean newValue) { + if (newValue) { + globalConfig().enableOfflineAccountProperty().removeListener(this); + RESTRICTED.set(false); + } + } + }); + } + private final ObservableList items; private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage"))); private final ListProperty accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList()); @@ -88,6 +112,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP private static class AccountListPageSkin extends DecoratorAnimatedPageSkin { private final ObservableList authServerItems; + private ChangeListener holder; public AccountListPageSkin(AccountListPage skinnable) { super(skinnable); @@ -96,24 +121,21 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP VBox boxMethods = new VBox(); { boxMethods.getStyleClass().add("advanced-list-box-content"); - boxMethods.getChildren().add(new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT))); FXUtils.setLimitWidth(boxMethods, 200); - AdvancedListItem offlineItem = new AdvancedListItem(); - offlineItem.getStyleClass().add("navigation-drawer-item"); - offlineItem.setActionButtonVisible(false); - offlineItem.setTitle(i18n("account.methods.offline")); - offlineItem.setLeftGraphic(wrap(SVG.PERSON)); - offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE))); - boxMethods.getChildren().add(offlineItem); - AdvancedListItem microsoftItem = new AdvancedListItem(); microsoftItem.getStyleClass().add("navigation-drawer-item"); microsoftItem.setActionButtonVisible(false); microsoftItem.setTitle(i18n("account.methods.microsoft")); microsoftItem.setLeftGraphic(wrap(SVG.MICROSOFT)); microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT))); - boxMethods.getChildren().add(microsoftItem); + + AdvancedListItem offlineItem = new AdvancedListItem(); + offlineItem.getStyleClass().add("navigation-drawer-item"); + offlineItem.setActionButtonVisible(false); + offlineItem.setTitle(i18n("account.methods.offline")); + offlineItem.setLeftGraphic(wrap(SVG.PERSON)); + offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE))); VBox boxAuthServers = new VBox(); authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> { @@ -149,7 +171,29 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP return item; }); Bindings.bindContent(boxAuthServers.getChildren(), authServerItems); - boxMethods.getChildren().add(boxAuthServers); + + ClassTitle title = new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT)); + if (RESTRICTED.get()) { + VBox wrapper = new VBox(offlineItem, boxAuthServers); + wrapper.setPadding(Insets.EMPTY); + FXUtils.installFastTooltip(wrapper, i18n("account.login.restricted")); + + offlineItem.setDisable(true); + boxAuthServers.setDisable(true); + + boxMethods.getChildren().setAll(title, microsoftItem, wrapper); + + holder = FXUtils.onWeakChange(RESTRICTED, value -> { + if (!value) { + holder = null; + offlineItem.setDisable(false); + boxAuthServers.setDisable(false); + boxMethods.getChildren().setAll(title, microsoftItem, offlineItem, boxAuthServers); + } + }); + } else { + boxMethods.getChildren().setAll(title, microsoftItem, offlineItem, boxAuthServers); + } } AdvancedListItem addAuthServerItem = new AdvancedListItem(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java index f25f7de49..ae20f1ef2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java @@ -108,12 +108,17 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware { public CreateAccountPane(AccountFactory factory) { if (factory == null) { - showMethodSwitcher = true; - String preferred = config().getPreferredLoginType(); - try { - factory = Accounts.getAccountFactory(preferred); - } catch (IllegalArgumentException e) { - factory = Accounts.FACTORY_OFFLINE; + if (AccountListPage.RESTRICTED.get()) { + showMethodSwitcher = false; + factory = Accounts.FACTORY_MICROSOFT; + } else { + showMethodSwitcher = true; + String preferred = config().getPreferredLoginType(); + try { + factory = Accounts.getAccountFactory(preferred); + } catch (IllegalArgumentException e) { + factory = Accounts.FACTORY_OFFLINE; + } } } else { showMethodSwitcher = false; diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 36ebaf1bb..edecbf447 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -88,6 +88,7 @@ account.login.skip=Log in offline account.login.retry=Retry account.login.refresh=Log in again account.login.refresh.microsoft.hint=You need to log in to your Microsoft account again because the account authorization is invalid. +account.login.restricted=Sign in to your Microsoft account to enable this feature account.logout=Logout account.register=Register account.manage=Account List diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 79edaf11f..17fd72d3e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -91,6 +91,7 @@ account.login.skip=跳過重新整理帳戶 account.login.retry=再次重新整理帳戶 account.login.refresh=重新登入 account.login.refresh.microsoft.hint=由於帳戶授權失效,你需要重新加入 Microsoft 帳戶 +account.login.restricted=登入微軟帳戶以啟用此功能 account.logout=登出 account.register=註冊 account.manage=帳戶清單 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index f79466bbb..978e07dc8 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -92,6 +92,7 @@ account.login.skip=跳过账户刷新 account.login.retry=再次刷新账户 account.login.refresh=重新登录 account.login.refresh.microsoft.hint=由于账户授权失效,你需要重新添加微软账户。 +account.login.restricted=登录微软账户以启用此功能 account.logout=登出 account.register=注册 account.manage=账户列表