默认限制离线账户功能 (#3823)

This commit is contained in:
Glavo 2025-04-14 20:34:01 +08:00 committed by GitHub
parent b4945a150e
commit cc16f84992
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 123 additions and 21 deletions

View File

@ -190,9 +190,9 @@ tasks.build {
dependsOn(makeExecutables) dependsOn(makeExecutables)
} }
fun parseToolOptions(options: String?): List<String> { fun parseToolOptions(options: String?): MutableList<String> {
if (options == null) if (options == null)
return listOf() return mutableListOf()
val builder = StringBuilder() val builder = StringBuilder()
val result = mutableListOf<String>() val result = mutableListOf<String>()
@ -249,6 +249,9 @@ tasks.create<JavaExec>("run") {
workingDir = rootProject.rootDir workingDir = rootProject.rootDir
val vmOptions = parseToolOptions(System.getenv("HMCL_JAVA_OPTS")) 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) jvmArgs(vmOptions)
val hmclJavaHome = System.getenv("HMCL_JAVA_HOME") val hmclJavaHome = System.getenv("HMCL_JAVA_HOME")

View File

@ -22,6 +22,7 @@ import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.auth.*;
@ -50,6 +51,7 @@ import java.util.*;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static javafx.collections.FXCollections.observableArrayList; import static javafx.collections.FXCollections.observableArrayList;
import static org.jackhuang.hmcl.setting.ConfigHolder.config; 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.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.Lang.immutableListOf; import static org.jackhuang.hmcl.util.Lang.immutableListOf;
import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.mapOf;
@ -277,6 +279,30 @@ public final class Accounts {
selected = accounts.get(0); 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<Account>() {
@Override
public void onChanged(Change<? extends Account> change) {
while (change.next()) {
for (Account account : change.getAddedSubList()) {
if (account instanceof MicrosoftAccount) {
accounts.removeListener(this);
globalConfig().setEnableOfflineAccount(true);
return;
}
}
}
}
});
selectedAccount.set(selected); selectedAccount.set(selected);
InvalidationListener listener = o -> { InvalidationListener listener = o -> {

View File

@ -21,7 +21,9 @@ import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.JsonAdapter;
import javafx.beans.InvalidationListener; import javafx.beans.InvalidationListener;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableSet; import javafx.collections.ObservableSet;
@ -53,6 +55,8 @@ public final class GlobalConfig implements Observable {
private final IntegerProperty logRetention = new SimpleIntegerProperty(); private final IntegerProperty logRetention = new SimpleIntegerProperty();
private final BooleanProperty enableOfflineAccount = new SimpleBooleanProperty(false);
private final ObservableSet<String> userJava = FXCollections.observableSet(new LinkedHashSet<>()); private final ObservableSet<String> userJava = FXCollections.observableSet(new LinkedHashSet<>());
private final ObservableSet<String> disabledJava = FXCollections.observableSet(new LinkedHashSet<>()); private final ObservableSet<String> disabledJava = FXCollections.observableSet(new LinkedHashSet<>());
@ -115,6 +119,18 @@ public final class GlobalConfig implements Observable {
this.logRetention.set(logRetention); 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<String> getUserJava() { public ObservableSet<String> getUserJava() {
return userJava; return userJava;
} }
@ -129,7 +145,8 @@ public final class GlobalConfig implements Observable {
"platformPromptVersion", "platformPromptVersion",
"logRetention", "logRetention",
"userJava", "userJava",
"disabledJava" "disabledJava",
"enableOfflineAccount"
)); ));
@Override @Override
@ -142,6 +159,9 @@ public final class GlobalConfig implements Observable {
jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion())); jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion()));
jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion())); jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion()));
jsonObject.add("logRetention", context.serialize(src.getLogRetention())); jsonObject.add("logRetention", context.serialize(src.getLogRetention()));
if (src.enableOfflineAccount.get())
jsonObject.addProperty("enableOfflineAccount", true);
if (!src.getUserJava().isEmpty()) if (!src.getUserJava().isEmpty())
jsonObject.add("userJava", context.serialize(src.getUserJava())); 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.setAgreementVersion(Optional.ofNullable(obj.get("agreementVersion")).map(JsonElement::getAsInt).orElse(0));
config.setPlatformPromptVersion(Optional.ofNullable(obj.get("platformPromptVersion")).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.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"); JsonElement userJava = obj.get("userJava");
if (userJava != null && userJava.isJsonArray()) { if (userJava != null && userJava.isJsonArray()) {

View File

@ -20,6 +20,7 @@ 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.*;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@ -44,14 +45,37 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList; import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import java.net.URI; import java.net.URI;
import java.time.ZoneId;
import java.util.Locale; 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.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG; 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;
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<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) {
if (newValue) {
globalConfig().enableOfflineAccountProperty().removeListener(this);
RESTRICTED.set(false);
}
}
});
}
private final ObservableList<AccountListItem> items; private final ObservableList<AccountListItem> items;
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage"))); private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage")));
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList()); private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
@ -88,6 +112,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
private static class AccountListPageSkin extends DecoratorAnimatedPageSkin<AccountListPage> { private static class AccountListPageSkin extends DecoratorAnimatedPageSkin<AccountListPage> {
private final ObservableList<AdvancedListItem> authServerItems; private final ObservableList<AdvancedListItem> authServerItems;
private ChangeListener<Boolean> holder;
public AccountListPageSkin(AccountListPage skinnable) { public AccountListPageSkin(AccountListPage skinnable) {
super(skinnable); super(skinnable);
@ -96,24 +121,21 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
VBox boxMethods = new VBox(); VBox boxMethods = new VBox();
{ {
boxMethods.getStyleClass().add("advanced-list-box-content"); boxMethods.getStyleClass().add("advanced-list-box-content");
boxMethods.getChildren().add(new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT)));
FXUtils.setLimitWidth(boxMethods, 200); 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(); AdvancedListItem microsoftItem = new AdvancedListItem();
microsoftItem.getStyleClass().add("navigation-drawer-item"); microsoftItem.getStyleClass().add("navigation-drawer-item");
microsoftItem.setActionButtonVisible(false); microsoftItem.setActionButtonVisible(false);
microsoftItem.setTitle(i18n("account.methods.microsoft")); microsoftItem.setTitle(i18n("account.methods.microsoft"));
microsoftItem.setLeftGraphic(wrap(SVG.MICROSOFT)); microsoftItem.setLeftGraphic(wrap(SVG.MICROSOFT));
microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_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(); VBox boxAuthServers = new VBox();
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> { authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
@ -149,7 +171,29 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
return item; return item;
}); });
Bindings.bindContent(boxAuthServers.getChildren(), authServerItems); 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(); AdvancedListItem addAuthServerItem = new AdvancedListItem();

View File

@ -108,6 +108,10 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
public CreateAccountPane(AccountFactory<?> factory) { public CreateAccountPane(AccountFactory<?> factory) {
if (factory == null) { if (factory == null) {
if (AccountListPage.RESTRICTED.get()) {
showMethodSwitcher = false;
factory = Accounts.FACTORY_MICROSOFT;
} else {
showMethodSwitcher = true; showMethodSwitcher = true;
String preferred = config().getPreferredLoginType(); String preferred = config().getPreferredLoginType();
try { try {
@ -115,6 +119,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
factory = Accounts.FACTORY_OFFLINE; factory = Accounts.FACTORY_OFFLINE;
} }
}
} else { } else {
showMethodSwitcher = false; showMethodSwitcher = false;
} }

View File

@ -88,6 +88,7 @@ account.login.skip=Log in offline
account.login.retry=Retry account.login.retry=Retry
account.login.refresh=Log in again 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.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.logout=Logout
account.register=Register account.register=Register
account.manage=Account List account.manage=Account List

View File

@ -91,6 +91,7 @@ account.login.skip=跳過重新整理帳戶
account.login.retry=再次重新整理帳戶 account.login.retry=再次重新整理帳戶
account.login.refresh=重新登入 account.login.refresh=重新登入
account.login.refresh.microsoft.hint=由於帳戶授權失效,你需要重新加入 Microsoft 帳戶 account.login.refresh.microsoft.hint=由於帳戶授權失效,你需要重新加入 Microsoft 帳戶
account.login.restricted=登入微軟帳戶以啟用此功能
account.logout=登出 account.logout=登出
account.register=註冊 account.register=註冊
account.manage=帳戶清單 account.manage=帳戶清單

View File

@ -92,6 +92,7 @@ account.login.skip=跳过账户刷新
account.login.retry=再次刷新账户 account.login.retry=再次刷新账户
account.login.refresh=重新登录 account.login.refresh=重新登录
account.login.refresh.microsoft.hint=由于账户授权失效,你需要重新添加微软账户。 account.login.refresh.microsoft.hint=由于账户授权失效,你需要重新添加微软账户。
account.login.restricted=登录微软账户以启用此功能
account.logout=登出 account.logout=登出
account.register=注册 account.register=注册
account.manage=账户列表 account.manage=账户列表