mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-02-11 16:59:54 +08:00
commit
9752dea0e1
@ -32,7 +32,6 @@ import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
|||||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Settings;
|
|
||||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||||
import org.jackhuang.hmcl.task.*;
|
import org.jackhuang.hmcl.task.*;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
@ -50,6 +49,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
@ -443,7 +443,7 @@ public final class LauncherHelper {
|
|||||||
System.out.print(log);
|
System.out.print(log);
|
||||||
|
|
||||||
logs.add(pair(log, level));
|
logs.add(pair(log, level));
|
||||||
if (logs.size() > Settings.instance().getLogLines())
|
if (logs.size() > config().getLogLines())
|
||||||
logs.removeFirst();
|
logs.removeFirst();
|
||||||
|
|
||||||
if (setting.isShowLogs()) {
|
if (setting.isShowLogs()) {
|
||||||
|
@ -59,7 +59,7 @@ public final class Accounts {
|
|||||||
public static final OfflineAccountFactory FACTORY_OFFLINE = OfflineAccountFactory.INSTANCE;
|
public static final OfflineAccountFactory FACTORY_OFFLINE = OfflineAccountFactory.INSTANCE;
|
||||||
public static final YggdrasilAccountFactory FACTORY_YGGDRASIL = new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE);
|
public static final YggdrasilAccountFactory FACTORY_YGGDRASIL = new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE);
|
||||||
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(
|
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(
|
||||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), () -> Settings.instance().getDownloadProvider())::getArtifactInfo,
|
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), DownloadProviders::getDownloadProvider)::getArtifactInfo,
|
||||||
Accounts::getOrCreateAuthlibInjectorServer);
|
Accounts::getOrCreateAuthlibInjectorServer);
|
||||||
|
|
||||||
private static final String TYPE_OFFLINE = "offline";
|
private static final String TYPE_OFFLINE = "offline";
|
||||||
|
@ -115,8 +115,8 @@ public final class Config implements Cloneable, Observable {
|
|||||||
@SerializedName("localization")
|
@SerializedName("localization")
|
||||||
private ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
|
private ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
|
||||||
|
|
||||||
@SerializedName("downloadtype")
|
@SerializedName("downloadType")
|
||||||
private IntegerProperty downloadType = new SimpleIntegerProperty(1);
|
private StringProperty downloadType = new SimpleStringProperty("bmclapi");
|
||||||
|
|
||||||
@SerializedName("configurations")
|
@SerializedName("configurations")
|
||||||
private ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
private ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||||
@ -359,15 +359,15 @@ public final class Config implements Cloneable, Observable {
|
|||||||
return localization;
|
return localization;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDownloadType() {
|
public String getDownloadType() {
|
||||||
return downloadType.get();
|
return downloadType.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDownloadType(int downloadType) {
|
public void setDownloadType(String downloadType) {
|
||||||
this.downloadType.set(downloadType);
|
this.downloadType.set(downloadType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntegerProperty downloadTypeProperty() {
|
public StringProperty downloadTypeProperty() {
|
||||||
return downloadType;
|
return downloadType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,5 +86,17 @@ final class ConfigUpgrader {
|
|||||||
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
|
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
|
||||||
if (!rawJson.containsKey("hasProxyAuth"))
|
if (!rawJson.containsKey("hasProxyAuth"))
|
||||||
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
|
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
|
||||||
|
|
||||||
|
if (!rawJson.containsKey("downloadType")) {
|
||||||
|
tryCast(rawJson.get("downloadtype"), Number.class)
|
||||||
|
.map(Number::intValue)
|
||||||
|
.ifPresent(id -> {
|
||||||
|
if (id == 0) {
|
||||||
|
deserialized.setDownloadType("mojang");
|
||||||
|
} else if (id == 1) {
|
||||||
|
deserialized.setDownloadType("bmclapi");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,20 +17,44 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.setting;
|
package org.jackhuang.hmcl.setting;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
|
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||||
|
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.CurseCDNDownloadProvider;
|
|
||||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.MojangDownloadProvider;
|
import org.jackhuang.hmcl.download.MojangDownloadProvider;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
|
||||||
|
|
||||||
import java.util.List;
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.ObjectBinding;
|
||||||
|
import javafx.beans.value.ObservableObjectValue;
|
||||||
|
|
||||||
public final class DownloadProviders {
|
public final class DownloadProviders {
|
||||||
private DownloadProviders() {}
|
private DownloadProviders() {}
|
||||||
|
|
||||||
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(new MojangDownloadProvider(), BMCLAPIDownloadProvider.INSTANCE, CurseCDNDownloadProvider.INSTANCE);
|
public static final Map<String, DownloadProvider> providersById = mapOf(
|
||||||
|
pair("mojang", new MojangDownloadProvider()),
|
||||||
|
pair("bmclapi", new BMCLAPIDownloadProvider()));
|
||||||
|
|
||||||
public static DownloadProvider getDownloadProvider(int index) {
|
public static final String DEFAULT_PROVIDER_ID = "bmclapi";
|
||||||
return Lang.get(DOWNLOAD_PROVIDERS, index).orElse(DOWNLOAD_PROVIDERS.get(0));
|
|
||||||
|
private static ObjectBinding<DownloadProvider> downloadProviderProperty;
|
||||||
|
|
||||||
|
static void init() {
|
||||||
|
downloadProviderProperty = Bindings.createObjectBinding(
|
||||||
|
() -> Optional.ofNullable(providersById.get(config().getDownloadType()))
|
||||||
|
.orElse(providersById.get(DEFAULT_PROVIDER_ID)),
|
||||||
|
config().downloadTypeProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DownloadProvider getDownloadProvider() {
|
||||||
|
return downloadProviderProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObservableObjectValue<DownloadProvider> downloadProviderProperty() {
|
||||||
|
return downloadProviderProperty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
|||||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||||
import org.jackhuang.hmcl.game.Version;
|
import org.jackhuang.hmcl.game.Version;
|
||||||
import org.jackhuang.hmcl.mod.ModManager;
|
import org.jackhuang.hmcl.mod.ModManager;
|
||||||
import org.jackhuang.hmcl.ui.WeakListenerHelper;
|
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||||
import org.jackhuang.hmcl.util.*;
|
import org.jackhuang.hmcl.util.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -43,7 +43,7 @@ import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
|||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public final class Profile implements Observable {
|
public final class Profile implements Observable {
|
||||||
private final WeakListenerHelper helper = new WeakListenerHelper();
|
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||||
private final HMCLGameRepository repository;
|
private final HMCLGameRepository repository;
|
||||||
private final ModManager modManager;
|
private final ModManager modManager;
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ public final class Profile implements Observable {
|
|||||||
|
|
||||||
gameDir.addListener((a, b, newValue) -> repository.changeDirectory(newValue));
|
gameDir.addListener((a, b, newValue) -> repository.changeDirectory(newValue));
|
||||||
this.selectedVersion.addListener(o -> checkSelectedVersion());
|
this.selectedVersion.addListener(o -> checkSelectedVersion());
|
||||||
helper.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion()));
|
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion()));
|
||||||
|
|
||||||
addPropertyChangedListener(onInvalidating(this::invalidate));
|
addPropertyChangedListener(onInvalidating(this::invalidate));
|
||||||
}
|
}
|
||||||
@ -162,7 +162,7 @@ public final class Profile implements Observable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DefaultDependencyManager getDependency() {
|
public DefaultDependencyManager getDependency() {
|
||||||
return new DefaultDependencyManager(repository, Settings.instance().getDownloadProvider(), HMCLCacheRepository.REPOSITORY);
|
return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), HMCLCacheRepository.REPOSITORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VersionSetting getVersionSetting(String id) {
|
public VersionSetting getVersionSetting(String id) {
|
||||||
|
@ -35,20 +35,7 @@ public final class ProxyManager {
|
|||||||
private ProxyManager() {
|
private ProxyManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ObjectBinding<Proxy> proxyProperty = Bindings.createObjectBinding(
|
private static ObjectBinding<Proxy> proxyProperty;
|
||||||
() -> {
|
|
||||||
String host = config().getProxyHost();
|
|
||||||
Integer port = Lang.toIntOrNull(config().getProxyPort());
|
|
||||||
if (!config().hasProxy() || StringUtils.isBlank(host) || port == null || config().getProxyType() == Proxy.Type.DIRECT) {
|
|
||||||
return Proxy.NO_PROXY;
|
|
||||||
} else {
|
|
||||||
return new Proxy(config().getProxyType(), new InetSocketAddress(host, port));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
config().proxyTypeProperty(),
|
|
||||||
config().proxyHostProperty(),
|
|
||||||
config().proxyPortProperty(),
|
|
||||||
config().hasProxyProperty());
|
|
||||||
|
|
||||||
public static Proxy getProxy() {
|
public static Proxy getProxy() {
|
||||||
return proxyProperty.get();
|
return proxyProperty.get();
|
||||||
@ -59,12 +46,25 @@ public final class ProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void init() {
|
static void init() {
|
||||||
proxyProperty.addListener(observable -> updateSystemProxy());
|
proxyProperty = Bindings.createObjectBinding(
|
||||||
|
() -> {
|
||||||
|
String host = config().getProxyHost();
|
||||||
|
Integer port = Lang.toIntOrNull(config().getProxyPort());
|
||||||
|
if (!config().hasProxy() || StringUtils.isBlank(host) || port == null || config().getProxyType() == Proxy.Type.DIRECT) {
|
||||||
|
return Proxy.NO_PROXY;
|
||||||
|
} else {
|
||||||
|
return new Proxy(config().getProxyType(), new InetSocketAddress(host, port));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
config().proxyTypeProperty(),
|
||||||
|
config().proxyHostProperty(),
|
||||||
|
config().proxyPortProperty(),
|
||||||
|
config().hasProxyProperty());
|
||||||
|
|
||||||
|
proxyProperty.addListener(any -> updateSystemProxy());
|
||||||
updateSystemProxy();
|
updateSystemProxy();
|
||||||
|
|
||||||
Authenticator.setDefault(new Authenticator() {
|
Authenticator.setDefault(new Authenticator() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PasswordAuthentication getPasswordAuthentication() {
|
protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
if (config().hasProxyAuth()) {
|
if (config().hasProxyAuth()) {
|
||||||
|
@ -19,7 +19,6 @@ package org.jackhuang.hmcl.setting;
|
|||||||
|
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import org.jackhuang.hmcl.Launcher;
|
import org.jackhuang.hmcl.Launcher;
|
||||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
|
||||||
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||||
import org.jackhuang.hmcl.util.CacheRepository;
|
import org.jackhuang.hmcl.util.CacheRepository;
|
||||||
|
|
||||||
@ -44,6 +43,7 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Settings() {
|
private Settings() {
|
||||||
|
DownloadProviders.init();
|
||||||
ProxyManager.init();
|
ProxyManager.init();
|
||||||
Accounts.init();
|
Accounts.init();
|
||||||
Profiles.init();
|
Profiles.init();
|
||||||
@ -61,14 +61,6 @@ public class Settings {
|
|||||||
config().setFontSize(font.getSize());
|
config().setFontSize(font.getSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLogLines() {
|
|
||||||
return Math.max(config().getLogLines(), 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLogLines(int logLines) {
|
|
||||||
config().setLogLines(logLines);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getDefaultCommonDirectory() {
|
public static String getDefaultCommonDirectory() {
|
||||||
return Launcher.MINECRAFT_DIRECTORY.getAbsolutePath();
|
return Launcher.MINECRAFT_DIRECTORY.getAbsolutePath();
|
||||||
}
|
}
|
||||||
@ -83,19 +75,4 @@ public class Settings {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************************
|
|
||||||
* DOWNLOAD PROVIDERS *
|
|
||||||
****************************************/
|
|
||||||
|
|
||||||
public DownloadProvider getDownloadProvider() {
|
|
||||||
return DownloadProviders.getDownloadProvider(config().getDownloadType());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDownloadProvider(DownloadProvider downloadProvider) {
|
|
||||||
int index = DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(downloadProvider);
|
|
||||||
if (index == -1)
|
|
||||||
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
|
|
||||||
config().setDownloadType(index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -328,8 +328,10 @@ public final class FXUtils {
|
|||||||
* @param comboBox the combo box being bound with {@code property}.
|
* @param comboBox the combo box being bound with {@code property}.
|
||||||
* @param property the property being bound with {@code combo box}.
|
* @param property the property being bound with {@code combo box}.
|
||||||
* @see #unbindEnum(JFXComboBox)
|
* @see #unbindEnum(JFXComboBox)
|
||||||
|
* @deprecated Use {@link SelectionModelSelectedItemProperty#selectedItemPropertyFor(ComboBox)}
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@Deprecated
|
||||||
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum> property) {
|
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum> property) {
|
||||||
unbindEnum(comboBox);
|
unbindEnum(comboBox);
|
||||||
ChangeListener<Number> listener = (a, b, newValue) ->
|
ChangeListener<Number> listener = (a, b, newValue) ->
|
||||||
@ -346,6 +348,7 @@ public final class FXUtils {
|
|||||||
* @see #bindEnum(JFXComboBox, Property)
|
* @see #bindEnum(JFXComboBox, Property)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@Deprecated
|
||||||
public static void unbindEnum(JFXComboBox<?> comboBox) {
|
public static void unbindEnum(JFXComboBox<?> comboBox) {
|
||||||
ChangeListener listener = tryCast(comboBox.getProperties().get("FXUtils.bindEnum.listener"), ChangeListener.class).orElse(null);
|
ChangeListener listener = tryCast(comboBox.getProperties().get("FXUtils.bindEnum.listener"), ChangeListener.class).orElse(null);
|
||||||
if (listener == null) return;
|
if (listener == null) return;
|
||||||
|
@ -181,7 +181,7 @@ public final class LogWindow extends Stage {
|
|||||||
if (newValue == Worker.State.SUCCEEDED) {
|
if (newValue == Worker.State.SUCCEEDED) {
|
||||||
document = engine.getDocument();
|
document = engine.getDocument();
|
||||||
body = document.getElementsByTagName("body").item(0);
|
body = document.getElementsByTagName("body").item(0);
|
||||||
engine.executeScript("limitedLogs=" + Settings.instance().getLogLines());
|
engine.executeScript("limitedLogs=" + config().getLogLines());
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
onDone.fireEvent(new Event(LogWindow.this));
|
onDone.fireEvent(new Event(LogWindow.this));
|
||||||
}
|
}
|
||||||
@ -189,14 +189,14 @@ public final class LogWindow extends Stage {
|
|||||||
|
|
||||||
boolean flag = false;
|
boolean flag = false;
|
||||||
for (String i : cboLines.getItems())
|
for (String i : cboLines.getItems())
|
||||||
if (Integer.toString(Settings.instance().getLogLines()).equals(i)) {
|
if (Integer.toString(config().getLogLines()).equals(i)) {
|
||||||
cboLines.getSelectionModel().select(i);
|
cboLines.getSelectionModel().select(i);
|
||||||
flag = true;
|
flag = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cboLines.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
cboLines.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||||
Settings.instance().setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
|
config().setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
|
||||||
engine.executeScript("limitedLogs=" + Settings.instance().getLogLines());
|
engine.executeScript("limitedLogs=" + config().getLogLines());
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!flag)
|
if (!flag)
|
||||||
|
@ -25,9 +25,6 @@ import javafx.beans.WeakInvalidationListener;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.When;
|
import javafx.beans.binding.When;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
@ -48,6 +45,7 @@ import java.util.Objects;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
|
import static org.jackhuang.hmcl.util.SelectionModelSelectedItemProperty.selectedItemPropertyFor;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public final class SettingsPage extends SettingsView implements DecoratorPage {
|
public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||||
@ -60,10 +58,12 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
|||||||
public SettingsPage() {
|
public SettingsPage() {
|
||||||
FXUtils.smoothScrolling(scroll);
|
FXUtils.smoothScrolling(scroll);
|
||||||
|
|
||||||
chkEnableGameList.selectedProperty().bindBidirectional(config().enableMainPageGameListProperty());
|
// ==== Download sources ====
|
||||||
|
cboDownloadSource.getItems().setAll(DownloadProviders.providersById.keySet());
|
||||||
|
selectedItemPropertyFor(cboDownloadSource).bindBidirectional(config().downloadTypeProperty());
|
||||||
|
// ====
|
||||||
|
|
||||||
cboDownloadSource.getSelectionModel().select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.instance().getDownloadProvider()));
|
chkEnableGameList.selectedProperty().bindBidirectional(config().enableMainPageGameListProperty());
|
||||||
cboDownloadSource.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.instance().setDownloadProvider(DownloadProviders.getDownloadProvider(newValue.intValue())));
|
|
||||||
|
|
||||||
cboFont.initValue(Settings.instance().getFont());
|
cboFont.initValue(Settings.instance().getFont());
|
||||||
cboFont.valueProperty().addListener((a, b, newValue) -> {
|
cboFont.valueProperty().addListener((a, b, newValue) -> {
|
||||||
@ -84,13 +84,10 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
|||||||
|
|
||||||
lblDisplay.setStyle("-fx-font: " + Settings.instance().getFont().getSize() + " \"" + Settings.instance().getFont().getFamily() + "\";");
|
lblDisplay.setStyle("-fx-font: " + Settings.instance().getFont().getSize() + " \"" + Settings.instance().getFont().getFamily() + "\";");
|
||||||
|
|
||||||
ObservableList<Label> list = FXCollections.observableArrayList();
|
// ==== Languages ====
|
||||||
for (Locales.SupportedLocale locale : Locales.LOCALES)
|
cboLanguage.getItems().setAll(Locales.LOCALES);
|
||||||
list.add(new Label(locale.getName(config().getLocalization().getResourceBundle())));
|
selectedItemPropertyFor(cboLanguage).bindBidirectional(config().localizationProperty());
|
||||||
|
// ====
|
||||||
cboLanguage.setItems(list);
|
|
||||||
cboLanguage.getSelectionModel().select(Locales.LOCALES.indexOf(config().getLocalization()));
|
|
||||||
cboLanguage.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> config().setLocalization(Locales.getLocale(newValue.intValue())));
|
|
||||||
|
|
||||||
// ==== Proxy ====
|
// ==== Proxy ====
|
||||||
txtProxyHost.textProperty().bindBidirectional(config().proxyHostProperty());
|
txtProxyHost.textProperty().bindBidirectional(config().proxyHostProperty());
|
||||||
|
@ -26,11 +26,16 @@ import javafx.scene.control.ScrollPane;
|
|||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.scene.text.TextAlignment;
|
import javafx.scene.text.TextAlignment;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
|
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.ui.construct.*;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
|
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||||
|
|
||||||
public abstract class SettingsView extends StackPane {
|
public abstract class SettingsView extends StackPane {
|
||||||
protected final JFXTextField txtProxyHost;
|
protected final JFXTextField txtProxyHost;
|
||||||
@ -38,8 +43,8 @@ public abstract class SettingsView extends StackPane {
|
|||||||
protected final JFXTextField txtProxyUsername;
|
protected final JFXTextField txtProxyUsername;
|
||||||
protected final JFXPasswordField txtProxyPassword;
|
protected final JFXPasswordField txtProxyPassword;
|
||||||
protected final JFXTextField txtFontSize;
|
protected final JFXTextField txtFontSize;
|
||||||
protected final JFXComboBox<Label> cboLanguage;
|
protected final JFXComboBox<SupportedLocale> cboLanguage;
|
||||||
protected final JFXComboBox<Label> cboDownloadSource;
|
protected final JFXComboBox<String> cboDownloadSource;
|
||||||
protected final FontComboBox cboFont;
|
protected final FontComboBox cboFont;
|
||||||
protected final MultiFileItem<EnumCommonDirectory> fileCommonLocation;
|
protected final MultiFileItem<EnumCommonDirectory> fileCommonLocation;
|
||||||
protected final Label lblDisplay;
|
protected final Label lblDisplay;
|
||||||
@ -162,11 +167,7 @@ public abstract class SettingsView extends StackPane {
|
|||||||
|
|
||||||
{
|
{
|
||||||
cboDownloadSource = new JFXComboBox<>();
|
cboDownloadSource = new JFXComboBox<>();
|
||||||
FXUtils.setLimitWidth(cboDownloadSource, 400);
|
cboDownloadSource.setConverter(stringConverter(key -> I18n.i18n("download.provider." + key)));
|
||||||
cboDownloadSource.getItems().setAll(
|
|
||||||
new Label(I18n.i18n("download.mojang")),
|
|
||||||
new Label(I18n.i18n("download.BMCL"))
|
|
||||||
);
|
|
||||||
downloadSourcePane.setRight(cboDownloadSource);
|
downloadSourcePane.setRight(cboDownloadSource);
|
||||||
}
|
}
|
||||||
settingsPane.getContent().add(downloadSourcePane);
|
settingsPane.getContent().add(downloadSourcePane);
|
||||||
@ -180,6 +181,7 @@ public abstract class SettingsView extends StackPane {
|
|||||||
languagePane.setLeft(left);
|
languagePane.setLeft(left);
|
||||||
|
|
||||||
cboLanguage = new JFXComboBox<>();
|
cboLanguage = new JFXComboBox<>();
|
||||||
|
cboLanguage.setConverter(stringConverter(locale -> locale.getName(config().getLocalization().getResourceBundle())));
|
||||||
FXUtils.setLimitWidth(cboLanguage, 400);
|
FXUtils.setLimitWidth(cboLanguage, 400);
|
||||||
languagePane.setRight(cboLanguage);
|
languagePane.setRight(cboLanguage);
|
||||||
|
|
||||||
|
@ -27,10 +27,10 @@ import javafx.collections.WeakListChangeListener;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class WeakListenerHelper {
|
public class WeakListenerHolder {
|
||||||
List<Object> refs = new LinkedList<>();
|
private List<Object> refs = new LinkedList<>();
|
||||||
|
|
||||||
public WeakListenerHelper() {
|
public WeakListenerHolder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public WeakInvalidationListener weak(InvalidationListener listener) {
|
public WeakInvalidationListener weak(InvalidationListener listener) {
|
@ -24,7 +24,7 @@ import org.jackhuang.hmcl.event.EventBus;
|
|||||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.ui.WeakListenerHelper;
|
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -32,16 +32,16 @@ import java.io.File;
|
|||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public class GameAdvancedListItem extends AdvancedListItem {
|
public class GameAdvancedListItem extends AdvancedListItem {
|
||||||
private final WeakListenerHelper helper = new WeakListenerHelper();
|
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||||
|
|
||||||
private Profile profile;
|
private Profile profile;
|
||||||
private InvalidationListener listener = o -> loadVersion();
|
private InvalidationListener listener = o -> loadVersion();
|
||||||
|
|
||||||
public GameAdvancedListItem() {
|
public GameAdvancedListItem() {
|
||||||
Profiles.selectedProfileProperty().addListener(helper.weak((a, b, newValue) -> {
|
Profiles.selectedProfileProperty().addListener(listenerHolder.weak((a, b, newValue) -> {
|
||||||
JFXUtilities.runInFX(() -> loadProfile(newValue));
|
JFXUtilities.runInFX(() -> loadProfile(newValue));
|
||||||
}));
|
}));
|
||||||
helper.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
||||||
JFXUtilities.runInFX(() -> {
|
JFXUtilities.runInFX(() -> {
|
||||||
if (profile != null && profile.getRepository() == event.getSource())
|
if (profile != null && profile.getRepository() == event.getSource())
|
||||||
loadVersion();
|
loadVersion();
|
||||||
|
@ -31,7 +31,7 @@ import org.jackhuang.hmcl.game.HMCLGameRepository;
|
|||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.WeakListenerHelper;
|
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
||||||
import org.jackhuang.hmcl.util.VersionNumber;
|
import org.jackhuang.hmcl.util.VersionNumber;
|
||||||
@ -70,8 +70,8 @@ public class GameList extends Control implements DecoratorPage {
|
|||||||
|
|
||||||
private void loadVersions(HMCLGameRepository repository) {
|
private void loadVersions(HMCLGameRepository repository) {
|
||||||
toggleGroup = new ToggleGroup();
|
toggleGroup = new ToggleGroup();
|
||||||
WeakListenerHelper helper = new WeakListenerHelper();
|
WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||||
toggleGroup.getProperties().put("ReferenceHolder", helper);
|
toggleGroup.getProperties().put("ReferenceHolder", listenerHolder);
|
||||||
List<GameListItem> children = repository.getVersions().parallelStream()
|
List<GameListItem> children = repository.getVersions().parallelStream()
|
||||||
.filter(version -> !version.isHidden())
|
.filter(version -> !version.isHidden())
|
||||||
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
||||||
@ -83,7 +83,7 @@ public class GameList extends Control implements DecoratorPage {
|
|||||||
items.setAll(children);
|
items.setAll(children);
|
||||||
children.forEach(GameListItem::checkSelection);
|
children.forEach(GameListItem::checkSelection);
|
||||||
|
|
||||||
profile.selectedVersionProperty().addListener(helper.weak((a, b, newValue) -> {
|
profile.selectedVersionProperty().addListener(listenerHolder.weak((a, b, newValue) -> {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
children.forEach(it -> it.selectedProperty().set(false));
|
children.forEach(it -> it.selectedProperty().set(false));
|
||||||
children.stream()
|
children.stream()
|
||||||
|
@ -84,11 +84,11 @@ crash.NoClassDefFound=Please check "HMCL" software is complete.
|
|||||||
crash.user_fault=Your OS or Java environment may not be properly installed resulting in crashing of this software, please check your Java Environment or your computer!
|
crash.user_fault=Your OS or Java environment may not be properly installed resulting in crashing of this software, please check your Java Environment or your computer!
|
||||||
|
|
||||||
download=Download
|
download=Download
|
||||||
download.BMCL=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
|
||||||
download.failed=Failed to download
|
download.failed=Failed to download
|
||||||
download.failed.empty=No candidates. Click here to return.
|
download.failed.empty=No candidates. Click here to return.
|
||||||
download.failed.refresh=Unable to load version list. Click here to retry.
|
download.failed.refresh=Unable to load version list. Click here to retry.
|
||||||
download.mojang=Mojang
|
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||||
|
download.provider.mojang=Mojang
|
||||||
|
|
||||||
extension.bat=Windows Bat file
|
extension.bat=Windows Bat file
|
||||||
extension.mod=Mod file
|
extension.mod=Mod file
|
||||||
|
@ -84,11 +84,11 @@ crash.NoClassDefFound=請確認 HMCL 本體是否完整,或更新您的 Java
|
|||||||
crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟體崩潰,請檢查您的 Java 環境或您的電腦!可以嘗試重新安裝 Java。
|
crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟體崩潰,請檢查您的 Java 環境或您的電腦!可以嘗試重新安裝 Java。
|
||||||
|
|
||||||
download=下載
|
download=下載
|
||||||
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
|
||||||
download.failed=下載失敗
|
download.failed=下載失敗
|
||||||
download.failed.empty=沒有可供安裝的版本,點擊此處返回。
|
download.failed.empty=沒有可供安裝的版本,點擊此處返回。
|
||||||
download.failed.refresh=載入版本列表失敗,點擊此處重試。
|
download.failed.refresh=載入版本列表失敗,點擊此處重試。
|
||||||
download.mojang=官方伺服器(Mojang)
|
download.provider.bmclapi=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||||
|
download.provider.mojang=官方伺服器(Mojang)
|
||||||
|
|
||||||
extension.bat=Windows 腳本
|
extension.bat=Windows 腳本
|
||||||
extension.mod=模組檔案
|
extension.mod=模組檔案
|
||||||
|
@ -84,11 +84,11 @@ crash.NoClassDefFound=请确认 HMCL 本体是否完整,或更新您的 Java
|
|||||||
crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,请检查您的 Java 环境或您的电脑!可以尝试重新安装 Java。
|
crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,请检查您的 Java 环境或您的电脑!可以尝试重新安装 Java。
|
||||||
|
|
||||||
download=下载
|
download=下载
|
||||||
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
|
||||||
download.failed=下载失败
|
download.failed=下载失败
|
||||||
download.failed.empty=没有可供安装的版本,点击此处返回。
|
download.failed.empty=没有可供安装的版本,点击此处返回。
|
||||||
download.failed.refresh=加载版本列表失败,点击此处重试。
|
download.failed.refresh=加载版本列表失败,点击此处重试。
|
||||||
download.mojang=官方(Mojang)
|
download.provider.bmclapi=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||||
|
download.provider.mojang=官方(Mojang)
|
||||||
|
|
||||||
extension.bat=Windows 脚本
|
extension.bat=Windows 脚本
|
||||||
extension.mod=模组文件
|
extension.mod=模组文件
|
||||||
|
@ -28,11 +28,6 @@ import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
|
|||||||
*/
|
*/
|
||||||
public class BMCLAPIDownloadProvider implements DownloadProvider {
|
public class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||||
|
|
||||||
public static final BMCLAPIDownloadProvider INSTANCE = new BMCLAPIDownloadProvider();
|
|
||||||
|
|
||||||
private BMCLAPIDownloadProvider() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getVersionListURL() {
|
public String getVersionListURL() {
|
||||||
return "https://bmclapi2.bangbang93.com/mc/game/version_manifest.json";
|
return "https://bmclapi2.bangbang93.com/mc/game/version_manifest.json";
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher.
|
|
||||||
* Copyright (C) 2018 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.download;
|
|
||||||
|
|
||||||
public class CurseCDNDownloadProvider extends MojangDownloadProvider {
|
|
||||||
public static final CurseCDNDownloadProvider INSTANCE = new CurseCDNDownloadProvider();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String injectURL(String baseURL) {
|
|
||||||
return baseURL == null ? null : baseURL.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://ftb.cursecdn.com/FTB2/maven");
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,7 +23,7 @@ import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList;
|
|||||||
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
|
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see <a href="http://wiki.vg">http://wiki,vg</a>
|
* @see <a href="http://wiki.vg">http://wiki.vg</a>
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public class MojangDownloadProvider implements DownloadProvider {
|
public class MojangDownloadProvider implements DownloadProvider {
|
||||||
@ -56,6 +56,6 @@ public class MojangDownloadProvider implements DownloadProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String injectURL(String baseURL) {
|
public String injectURL(String baseURL) {
|
||||||
return baseURL;
|
return baseURL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2018 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.util;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.SelectionModel;
|
||||||
|
|
||||||
|
public class SelectionModelSelectedItemProperty<T> extends SimpleObjectProperty<T> {
|
||||||
|
|
||||||
|
public static <T> SelectionModelSelectedItemProperty<T> selectedItemPropertyFor(ComboBox<T> comboBox) {
|
||||||
|
return new SelectionModelSelectedItemProperty<>(comboBox.getSelectionModel());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SelectionModel<T> model;
|
||||||
|
|
||||||
|
public SelectionModelSelectedItemProperty(SelectionModel<T> model) {
|
||||||
|
this.model = model;
|
||||||
|
model.selectedItemProperty().addListener((observable, oldValue, newValue) -> set(newValue));
|
||||||
|
set(model.getSelectedItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void invalidated() {
|
||||||
|
model.select(get());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user