diff --git a/config/gui.yaml b/config/gui.yaml
new file mode 100644
index 0000000..f4e2a49
--- /dev/null
+++ b/config/gui.yaml
@@ -0,0 +1,7 @@
+---
+autoStart: false
+silentStartup: false
+proxyInfo: {}
+proxySetup: "no_proxy"
+language: "zh"
+aListVersion: "unknown"
diff --git a/pom.xml b/pom.xml
index 590bfac..4612d77 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
cn.octopusyan
alist-gui
- 0.1.0
+ 0.1
alist-gui
@@ -15,7 +15,7 @@
2024
- alist windows gui
+ AList windows gui
17
@@ -28,11 +28,13 @@
17.0.6
2.0.16
1.4.14
- 2.0.52
- 5.8.25
+ 5.8.32
3.16.0
- 2.16.1
+
1.4.0
+ 1.18.32
+ 12.3.1
+ 1.0.1
@@ -48,6 +50,13 @@
${javafx.version}
+
+ io.github.mkpaz
+ atlantafx-base
+ 2.0.1
+
+
+
@@ -87,12 +96,6 @@
commons-lang3
${common-lang3.version}
-
-
- commons-io
- commons-io
- ${common-io.version}
-
org.apache.commons
@@ -100,24 +103,45 @@
${common-exec.version}
-
-
+
- com.alibaba.fastjson2
- fastjson2
- ${fastjson.version}
+ cn.hutool
+ hutool-core
+ ${hutool.version}
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+ 2.15.4
org.kordamp.ikonli
ikonli-javafx
- 12.3.1
+ ${ikonli.version}
+
+
+ org.kordamp.ikonli
+ ikonli-fontawesome-pack
+ ${ikonli.version}
org.kordamp.ikonli
ikonli-coreui-pack
- 12.3.1
+ ${ikonli.version}
+
+
+ com.gluonhq
+ emoji
+ ${gluonhq-emoji.version}
@@ -164,6 +188,7 @@
exe
dll
+ ttf
diff --git a/src/main/java/cn/octopusyan/alistgui/Application.java b/src/main/java/cn/octopusyan/alistgui/Application.java
index 1be5558..6bd56c3 100644
--- a/src/main/java/cn/octopusyan/alistgui/Application.java
+++ b/src/main/java/cn/octopusyan/alistgui/Application.java
@@ -1,79 +1,82 @@
package cn.octopusyan.alistgui;
+import atlantafx.base.theme.PrimerLight;
import cn.octopusyan.alistgui.config.AppConstant;
-import cn.octopusyan.alistgui.config.CustomConfig;
-import cn.octopusyan.alistgui.controller.MainController;
+import cn.octopusyan.alistgui.config.ConfigManager;
+import cn.octopusyan.alistgui.config.Context;
+import cn.octopusyan.alistgui.enums.ProxySetup;
import cn.octopusyan.alistgui.manager.http.HttpConfig;
import cn.octopusyan.alistgui.manager.http.HttpUtil;
import cn.octopusyan.alistgui.manager.thread.ThreadPoolManager;
import cn.octopusyan.alistgui.util.AlertUtil;
-import cn.octopusyan.alistgui.util.FxmlUtil;
-import javafx.fxml.FXMLLoader;
-import javafx.scene.Parent;
+import javafx.application.Platform;
import javafx.scene.Scene;
-import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
+import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
-import java.util.Objects;
public class Application extends javafx.application.Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
+ @Getter
+ private static Stage primaryStage;
@Override
public void init() throws Exception {
logger.info("application init ...");
// 初始化客户端配置
- CustomConfig.init();
+ ConfigManager.load();
}
@Override
public void start(Stage primaryStage) throws IOException {
+ Application.primaryStage = primaryStage;
logger.info("application start ...");
// 初始化弹窗工具
AlertUtil.initOwner(primaryStage);
- // http请求工具初始化
- HttpConfig httpConfig = new HttpConfig();
- if (CustomConfig.hasProxy()) {
- InetSocketAddress unresolved = InetSocketAddress.createUnresolved(CustomConfig.proxyHost(), CustomConfig.proxyPort());
- httpConfig.setProxySelector(ProxySelector.of(unresolved));
- }
- httpConfig.setConnectTimeout(10);
- HttpUtil.init(httpConfig);
-
// 全局异常处理
Thread.setDefaultUncaughtExceptionHandler(this::showErrorDialog);
Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog);
- // 启动主界面
- try {
- FXMLLoader loader = FxmlUtil.load("root-view");
- loader.setControllerFactory(c -> new MainController(primaryStage));
- Parent root = loader.load();//底层面板
+ // http请求工具初始化
+ HttpConfig httpConfig = new HttpConfig();
- Scene scene = new Scene(root);
- scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm());
- scene.setFill(Color.TRANSPARENT);
-
- primaryStage.setScene(scene);
- primaryStage.initStyle(StageStyle.TRANSPARENT);
- primaryStage.setTitle(String.format("%s v%s", AppConstant.APP_TITLE, AppConstant.APP_VERSION));
- primaryStage.show();
-
- MainController controller = loader.getController();
- controller.setApplication(this);
- } catch (Throwable t) {
- showErrorDialog(Thread.currentThread(), t);
+ if(!ProxySetup.NO_PROXY.equals(ConfigManager.proxySetup())) {
+ // 系统代理
+ if (ProxySetup.SYSTEM.equals(ConfigManager.proxySetup())) {
+ httpConfig.setProxySelector(ProxySelector.getDefault());
+ }
+ // 自定义代理
+ if (ProxySetup.MANUAL.equals(ConfigManager.proxySetup()) && ConfigManager.hasProxy()) {
+ InetSocketAddress unresolved = InetSocketAddress.createUnresolved(ConfigManager.proxyHost(), ConfigManager.getProxyPort());
+ httpConfig.setProxySelector(ProxySelector.of(unresolved));
+ }
}
+ httpConfig.setConnectTimeout(10);
+ HttpUtil.init(httpConfig);
+
+ // i18n
+ Context.setLanguage(ConfigManager.language());
+
+ // 主题样式
+ Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet());
+
+ // 启动主界面
+ Scene scene = Context.initScene();
+ primaryStage.setScene(scene);
+ primaryStage.initStyle(StageStyle.TRANSPARENT);
+ primaryStage.setTitle(String.format("%s v%s", AppConstant.APP_TITLE, AppConstant.APP_VERSION));
+ primaryStage.show();
+
logger.info("application start over ...");
}
@@ -85,9 +88,12 @@ public class Application extends javafx.application.Application {
@Override
public void stop() throws Exception {
logger.info("application stop ...");
+ // 保存应用数据
+ ConfigManager.save();
// 停止所有线程
ThreadPoolManager.getInstance().shutdown();
- // 保存应用数据
- CustomConfig.store();
+ // 关闭主界面
+ Platform.exit();
+ System.exit(0);
}
}
\ No newline at end of file
diff --git a/src/main/java/cn/octopusyan/alistgui/base/BaseController.java b/src/main/java/cn/octopusyan/alistgui/base/BaseController.java
index 4237148..1b414f2 100644
--- a/src/main/java/cn/octopusyan/alistgui/base/BaseController.java
+++ b/src/main/java/cn/octopusyan/alistgui/base/BaseController.java
@@ -1,26 +1,16 @@
package cn.octopusyan.alistgui.base;
-import cn.octopusyan.alistgui.config.AppConstant;
+import cn.octopusyan.alistgui.Application;
+import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.util.FxmlUtil;
-import javafx.application.Application;
-import javafx.application.Platform;
-import javafx.beans.value.ChangeListener;
-import javafx.beans.value.ObservableValue;
-import javafx.fxml.FXMLLoader;
+import javafx.fxml.FXML;
import javafx.fxml.Initializable;
-import javafx.scene.Parent;
-import javafx.scene.Scene;
-import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
-import javafx.stage.Modality;
import javafx.stage.Stage;
-import javafx.stage.Window;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
import java.net.URL;
-import java.util.Objects;
import java.util.ResourceBundle;
/**
@@ -30,58 +20,14 @@ import java.util.ResourceBundle;
*/
public abstract class BaseController
implements Initializable {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
- private Application application;
- private final Stage primaryStage;
-
private double xOffSet = 0, yOffSet = 0;
- protected BaseController(Stage primaryStage) {
- this.primaryStage = primaryStage;
- }
-
- public void jumpTo(BaseController
controller) throws IOException {
- FXMLLoader fxmlLoader = FxmlUtil.load(controller.getRootFxml());
-
- Scene scene = getRootPanel().getScene();
- double oldHeight = getRootPanel().getPrefHeight();
- double oldWidth = getRootPanel().getPrefWidth();
-
- Pane root = fxmlLoader.load();
- Stage stage = (Stage) scene.getWindow();
- // 窗口大小
- double newWidth = root.getPrefWidth();
- double newHeight = root.getPrefHeight();
- // 窗口位置
- double newX = stage.getX() - (newWidth - oldWidth) / 2;
- double newY = stage.getY() - (newHeight - oldHeight) / 2;
- scene.setRoot(root);
- stage.setX(newX < 0 ? 0 : newX);
- stage.setY(newY < 0 ? 0 : newY);
- stage.setWidth(newWidth);
- stage.setHeight(newHeight);
-
- controller = fxmlLoader.getController();
- controller.setApplication(getApplication());
- }
-
- protected void open(Class extends BaseController>> clazz, String title) {
- try {
- FXMLLoader load = FxmlUtil.load(clazz.getDeclaredConstructor().newInstance().getRootFxml());
- Parent root = load.load();
- Scene scene = new Scene(root);
- scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm());
- Stage stage = new Stage();
- stage.setScene(scene);
- stage.setTitle(title);
- stage.initOwner(getWindow());
- stage.initModality(Modality.WINDOW_MODAL);
- stage.show();
- load.getController();
- } catch (Exception e) {
- logger.error("", e);
- }
+ public BaseController() {
+ //初始化时保存当前Controller实例
+ Context.getControllers().put(this.getClass().getSimpleName(), this);
}
+ @FXML
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// 全局窗口拖拽
@@ -92,46 +38,19 @@ public abstract class BaseController
implements Initializable {
yOffSet = event.getSceneY();
});
getRootPanel().setOnMouseDragged(event -> {
- Stage stage = (Stage) getWindow();
- stage.setX(event.getScreenX() - xOffSet);
- stage.setY(event.getScreenY() - yOffSet);
+ getWindow().setX(event.getScreenX() - xOffSet);
+ getWindow().setY(event.getScreenY() - yOffSet);
});
}
- // 窗口初始化完成监听
- getRootPanel().sceneProperty().addListener((observable, oldValue, newValue) -> {
- newValue.windowProperty().addListener(new ChangeListener() {
- @Override
- public void changed(ObservableValue extends Window> observable, Window oldValue, Window newValue) {
- //关闭窗口监听
- getWindow().setOnCloseRequest(windowEvent -> onDestroy());
+ // 初始化数据
+ initData();
- // app 版本信息
- if (getAppVersionLabel() != null) getAppVersionLabel().setText("v" + AppConstant.APP_VERSION);
+ // 初始化视图样式
+ initViewStyle();
- // 初始化数据
- initData();
-
- // 初始化视图样式
- initViewStyle();
-
- // 初始化视图事件
- initViewAction();
- }
- });
- });
- }
-
- public void setApplication(Application application) {
- this.application = application;
- }
-
- public Application getApplication() {
- return application;
- }
-
- public Stage getPrimaryStage() {
- return primaryStage;
+ // 初始化视图事件
+ initViewAction();
}
/**
@@ -139,7 +58,9 @@ public abstract class BaseController implements Initializable {
*
* @return 是否启用
*/
- public abstract boolean dragWindow();
+ public boolean dragWindow() {
+ return false;
+ }
/**
* 获取根布局
@@ -150,25 +71,17 @@ public abstract class BaseController
implements Initializable {
/**
* 获取根布局
- *
搭配 FxmlUtil.load
使用
+ *
搭配 {@link FxmlUtil#load(String)} 使用
*
* @return 根布局对象
- * @see FxmlUtil#load(String)
*/
protected String getRootFxml() {
System.out.println(getClass().getSimpleName());
return "";
}
- protected Window getWindow() {
- return getRootPanel().getScene().getWindow();
- }
-
- /**
- * App版本信息标签
- */
- public Label getAppVersionLabel() {
- return null;
+ protected Stage getWindow() {
+ return Application.getPrimaryStage();
}
/**
@@ -185,19 +98,4 @@ public abstract class BaseController
implements Initializable {
* 视图事件
*/
public abstract void initViewAction();
-
- /**
- * 关闭窗口
- */
- public void onDestroy() {
- Stage stage = (Stage) getWindow();
- stage.hide();
- stage.close();
- try {
- Thread.sleep(1000);
- Platform.exit();
- } catch (InterruptedException e) {
- logger.error("", e);
- }
- }
}
diff --git a/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java b/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java
index 291a381..95ab185 100644
--- a/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java
+++ b/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java
@@ -1,9 +1,9 @@
package cn.octopusyan.alistgui.config;
import cn.octopusyan.alistgui.util.PropertiesUtils;
-import org.apache.commons.io.FileUtils;
import java.io.File;
+import java.nio.file.Paths;
/**
* 应用信息
@@ -14,8 +14,9 @@ public class AppConstant {
public static final String APP_TITLE = PropertiesUtils.getInstance().getProperty("app.title");
public static final String APP_NAME = PropertiesUtils.getInstance().getProperty("app.name");
public static final String APP_VERSION = PropertiesUtils.getInstance().getProperty("app.version");
- public static final String DATA_DIR_PATH = System.getProperty("user.home") + File.separator + "AppData" + File.separator + "Local" + File.separator + APP_NAME;
- public static final String TMP_DIR_PATH = FileUtils.getTempDirectoryPath() + APP_NAME;
- public static final String CUSTOM_CONFIG_PATH = DATA_DIR_PATH + File.separator + "config.properties";
+ public static final String DATA_DIR_PATH = Paths.get(".").toFile().getAbsolutePath();
+ public static final String TMP_DIR_PATH = System.getProperty("java.io.tmpdir") + APP_NAME;
+ public static final String CONFIG_DIR_PATH = DATA_DIR_PATH + File.separator + "config";
+ public static final String GUI_CONFIG_PATH = CONFIG_DIR_PATH + File.separator + "gui.yaml";
public static final String BAK_FILE_PATH = AppConstant.TMP_DIR_PATH + File.separator + "bak";
}
diff --git a/src/main/java/cn/octopusyan/alistgui/config/ConfigManager.java b/src/main/java/cn/octopusyan/alistgui/config/ConfigManager.java
new file mode 100644
index 0000000..4c155a6
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/config/ConfigManager.java
@@ -0,0 +1,127 @@
+package cn.octopusyan.alistgui.config;
+
+import cn.hutool.core.lang.PatternPool;
+import cn.hutool.core.util.NumberUtil;
+import cn.octopusyan.alistgui.enums.ProxySetup;
+import cn.octopusyan.alistgui.manager.http.HttpUtil;
+import cn.octopusyan.alistgui.model.GuiConfig;
+import cn.octopusyan.alistgui.model.ProxyInfo;
+import org.apache.commons.lang3.LocaleUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.regex.Matcher;
+
+/**
+ * 客户端设置
+ *
+ * @author octopus_yan@foxmail.com
+ */
+public class ConfigManager {
+ private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class);
+ public static final Locale DEFAULT_LANGUAGE = Locale.CHINESE;
+ private static GuiConfig guiConfig;
+
+ public static void load() {
+ try {
+ guiConfig = GuiConfig.getInstance();
+ } catch (IOException e) {
+ logger.error("load config error", e);
+ }
+ }
+
+ public static boolean hasProxy() {
+ if (guiConfig == null)
+ return false;
+ ProxyInfo proxyInfo = guiConfig.getProxyInfo();
+ return proxyInfo != null
+ && StringUtils.isNoneEmpty(proxyInfo.getHost())
+ && StringUtils.isNoneEmpty(proxyInfo.getPort())
+ && Integer.parseInt(proxyInfo.getPort()) > 0;
+ }
+
+ public static ProxyInfo getProxyInfo() {
+ return guiConfig.getProxyInfo();
+ }
+
+ public static String proxyHost() {
+ return guiConfig.getProxyInfo().getHost();
+ }
+
+ public static void proxyHost(String host) {
+ final Matcher matcher = PatternPool.IPV4.matcher(host);
+ if (matcher.matches()) {
+ guiConfig.getProxyInfo().setHost(host);
+ }
+ }
+
+ public static String proxyPort() {
+ return guiConfig.getProxyInfo().getPort();
+ }
+
+ public static int getProxyPort() {
+ return Integer.parseInt(guiConfig.getProxyInfo().getPort());
+ }
+
+ public static void proxyPort(String port) {
+ if (NumberUtil.isNumber(port))
+ guiConfig.getProxyInfo().setPort(port);
+ }
+
+ public static Locale language() {
+ String language = guiConfig.getLanguage();
+ return LocaleUtils.toLocale(Optional.ofNullable(language).orElse(DEFAULT_LANGUAGE.toString()));
+ }
+
+ public static void language(Locale locale) {
+ guiConfig.setLanguage(locale.toString());
+ }
+
+ public static boolean autoStart() {
+ return guiConfig.getAutoStart();
+ }
+
+ public static void autoStart(Boolean autoStart) {
+ guiConfig.setAutoStart(autoStart);
+ }
+
+ public static ProxySetup proxySetup() {
+ return ProxySetup.valueOf(StringUtils.upperCase(guiConfig.getProxySetup()));
+ }
+
+ public static void proxySetup(ProxySetup setup) {
+ guiConfig.setProxySetup(setup.getName());
+
+ if (!ProxySetup.NO_PROXY.equals(setup) && hasProxy()) {
+ HttpUtil.getInstance().proxy(setup, ConfigManager.getProxyInfo());
+ }
+ }
+
+ public static boolean silentStartup() {
+ return guiConfig.getSilentStartup();
+ }
+
+ public static void silentStartup(Boolean startup) {
+ guiConfig.setSilentStartup(startup);
+ }
+
+ public static String aListVersion() {
+ return guiConfig.getAListVersion();
+ }
+
+ public static void aListVersion(String version) {
+ guiConfig.setAListVersion(version);
+ }
+
+ public static void save() {
+ try {
+ guiConfig.save();
+ } catch (IOException e) {
+ logger.error("save config error", e);
+ }
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/config/Context.java b/src/main/java/cn/octopusyan/alistgui/config/Context.java
new file mode 100644
index 0000000..e906c74
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/config/Context.java
@@ -0,0 +1,209 @@
+package cn.octopusyan.alistgui.config;
+
+import cn.octopusyan.alistgui.base.BaseController;
+import cn.octopusyan.alistgui.controller.MainController;
+import cn.octopusyan.alistgui.controller.RootController;
+import cn.octopusyan.alistgui.controller.SetupController;
+import cn.octopusyan.alistgui.util.FxmlUtil;
+import javafx.application.Platform;
+import javafx.beans.binding.StringBinding;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Insets;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.effect.DropShadow;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.util.Callback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * test contect
+ *
+ * @author octopus_yan
+ */
+public class Context {
+ private static final Logger log = LoggerFactory.getLogger(Context.class);
+ private static Scene scene;
+ private static final IntegerProperty currentViewIndexProperty = new SimpleIntegerProperty(0);
+ /**
+ * 控制器集合
+ */
+ private static final Map> controllers = new HashMap<>();
+ /**
+ * 默认语言文件 Base Name
+ */
+ private static final String LANGUAGE_RESOURCE_NAME = "language/language";
+ /**
+ * 语言资源工厂
+ */
+ private static final ObservableResourceBundleFactory LANGUAGE_RESOURCE_FACTORY = new ObservableResourceBundleFactory();
+ /**
+ * 支持的语言集合,应与语言资源文件同步手动更新
+ */
+ public static final List SUPPORT_LANGUAGE_LIST = Arrays.asList(Locale.CHINESE, Locale.ENGLISH);
+ /**
+ * 记录当前所选时区
+ */
+ private static final ObjectProperty currentLocale = new SimpleObjectProperty<>();
+
+
+ private Context() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ // 获取控制器集合
+ public static Map> getControllers() {
+ return controllers;
+ }
+
+ // 获取控制工厂
+ public static Callback, Object> getControlFactory() {
+ return type -> {
+ if (type.equals(RootController.class)) {
+ return new RootController();
+ } else if (type.equals(MainController.class)) {
+ return new MainController();
+ } else if (type.equals(SetupController.class)) {
+ return new SetupController();
+ }
+ throw new IllegalStateException("Unexpected value: " + type);
+ };
+ }
+
+ // 获取当前所选时区属性
+ public static ObjectProperty currentLocaleProperty() {
+ return currentLocale;
+ }
+
+ // 设置当前所选时区
+ public static void setCurrentLocale(Locale locale) {
+ currentLocaleProperty().set(locale);
+ }
+
+ /**
+ * 更换语言的组件使用此方法初始化自己的值,调用 {@link Context#setLanguage(Locale)} 来更新界面语言
+ *
+ * @return 当前界面语言
+ */
+ // 获取当前界面语言
+ public static Locale getCurrentLocale() {
+ return currentLocaleProperty().get();
+ }
+
+ /**
+ * 更新界面语言
+ *
+ * @param locale 区域
+ */
+ // 更新界面语言
+ public static void setLanguage(Locale locale) {
+ setCurrentLocale(locale);
+ ConfigManager.language(locale);
+ LANGUAGE_RESOURCE_FACTORY.setResourceBundle(ResourceBundle.getBundle(LANGUAGE_RESOURCE_NAME, locale));
+ }
+
+ /**
+ * 获取指定标识的字符串绑定
+ *
+ * @param key 标识
+ * @return 对应该标识的字符串属性绑定
+ */
+ // 获取指定标识的字符串绑定
+ public static StringBinding getLanguageBinding(String key) {
+ return LANGUAGE_RESOURCE_FACTORY.getStringBinding(key);
+ }
+
+ /**
+ * 获取语言资源属性
+ */
+ public static ObjectProperty getLanguageResource() {
+ return LANGUAGE_RESOURCE_FACTORY.getResourceBundleProperty();
+ }
+
+ /**
+ * 初始化 语言
+ */
+ private static void initI18n() {
+ currentLocaleProperty().addListener((observable, oldValue, newValue) -> {
+ Platform.runLater(() -> {
+ try {
+ loadScene();
+ } catch (IOException e) {
+ log.error("", e);
+ }
+ });
+ });
+ }
+
+ /**
+ * 有此类所在路径决定相对路径
+ *
+ * @param path 资源文件相对路径
+ * @return 资源文件路径
+ */
+ // 加载资源文件
+ public static URL load(String path) {
+ return Context.class.getResource(path);
+ }
+
+ /**
+ * 初始化场景
+ *
+ * @return Scene
+ * @throws IOException 如果在加载过程中发生错误
+ */
+ public static Scene initScene() throws IOException {
+ initI18n();
+ loadScene();
+ return scene;
+ }
+
+ private static void loadScene() throws IOException {
+ FXMLLoader loader = FxmlUtil.load("root-view");
+ loader.setControllerFactory(Context.getControlFactory());
+ Parent root = loader.load();//底层面板
+// bindShadow((Pane) root);
+ Optional.ofNullable(scene).ifPresentOrElse(
+ s -> s.setRoot(root),
+ () -> {
+ scene = new Scene(root);
+ URL resource = Objects.requireNonNull(Context.class.getResource("/css/root.css"));
+ scene.getStylesheets().addAll(resource.toExternalForm());
+ scene.setFill(Color.TRANSPARENT);
+ }
+ );
+ }
+
+ // 设置当前展示的界面
+ public static void setCurrentViewIndex(Number newValue) {
+ currentViewIndexProperty.setValue(newValue);
+ }
+
+ // 获取当前展示的界面Index
+ public static Integer getCurrentViewIndex() {
+ return currentViewIndexProperty.get();
+ }
+
+ private static void bindShadow(Pane pane) {
+ Pane root = new Pane();
+ root.setPadding(new Insets(20, 20, 20, 20));
+ DropShadow dropshadow = new DropShadow();// 阴影向外
+ dropshadow.setRadius(10);// 颜色蔓延的距离
+ dropshadow.setOffsetX(0);// 水平方向,0则向左右两侧,正则向右,负则向左
+ dropshadow.setOffsetY(0);// 垂直方向,0则向上下两侧,正则向下,负则向上
+ dropshadow.setSpread(0.1);// 颜色变淡的程度
+ dropshadow.setColor(Color.BLACK);// 设置颜色
+ root.setEffect(dropshadow);// 绑定指定窗口控件
+ root.getChildren().addAll(pane);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java b/src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java
deleted file mode 100644
index dc2de54..0000000
--- a/src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package cn.octopusyan.alistgui.config;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.charset.StandardCharsets;
-import java.util.Objects;
-import java.util.Properties;
-
-/**
- * 客户端设置
- *
- * @author octopus_yan@foxmail.com
- */
-public class CustomConfig {
- private static final Logger logger = LoggerFactory.getLogger(CustomConfig.class);
- private static final Properties properties = new Properties();
- public static final String PROXY_HOST_KEY = "proxy.host";
- public static final String PROXY_PORT_KEY = "proxy.port";
-
- public static void init() {
- FileReader reader = null;
- try {
- File file = new File(AppConstant.CUSTOM_CONFIG_PATH);
- if (!file.exists()) {
- // 创建配置文件
- if (!file.getParentFile().exists()) {
- FileUtils.createParentDirectories(file);
- }
- boolean newFile = file.createNewFile();
- // 保存配置
- store();
- } else {
- reader = new FileReader(file);
- properties.load(reader);
- }
- } catch (Exception e) {
- logger.error("读取配置文件失败", e);
- } finally {
- try {
- if (reader != null) {
- reader.close();
- }
- } catch (IOException e) {
- logger.error("关闭配置文件流", e);
- }
- }
- }
-
- /**
- * 是否配置代理
- */
- public static boolean hasProxy() {
- String host = proxyHost();
- Integer port = proxyPort();
-
- return StringUtils.isNoneBlank(host) && Objects.nonNull(port);
- }
-
- /**
- * 代理地址
- */
- public static String proxyHost() {
- return properties.getProperty(PROXY_HOST_KEY);
- }
-
- /**
- * 代理地址
- */
- public static void proxyHost(String host) {
- properties.setProperty(PROXY_HOST_KEY, host);
- }
-
- /**
- * 代理端口
- */
- public static Integer proxyPort() {
- try {
- return Integer.parseInt(properties.getProperty(PROXY_PORT_KEY));
- } catch (Exception ignored) {
- }
- return 10809;
- }
-
- /**
- * 代理端口
- */
- public static void proxyPort(int port) {
- properties.setProperty(PROXY_PORT_KEY, String.valueOf(port));
- }
-
-
- /**
- * 保存配置
- */
- public static void store() {
- // 生成配置文件
- try {
- properties.store(new PrintStream(AppConstant.CUSTOM_CONFIG_PATH), String.valueOf(StandardCharsets.UTF_8));
- } catch (IOException e) {
- logger.error("保存客户端配置失败", e);
- }
- }
-}
diff --git a/src/main/java/cn/octopusyan/alistgui/config/ObservableResourceBundleFactory.java b/src/main/java/cn/octopusyan/alistgui/config/ObservableResourceBundleFactory.java
new file mode 100644
index 0000000..9b45a26
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/config/ObservableResourceBundleFactory.java
@@ -0,0 +1,40 @@
+package cn.octopusyan.alistgui.config;
+
+import javafx.beans.binding.StringBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import lombok.Getter;
+
+import java.util.ResourceBundle;
+
+/**
+ * 多国语言属性绑定
+ *
+ * @author octopus_yan
+ */
+@Getter
+public class ObservableResourceBundleFactory {
+
+ private final ObjectProperty resourceBundleProperty = new SimpleObjectProperty<>();
+
+ public ResourceBundle getResourceBundle() {
+ return getResourceBundleProperty().get();
+ }
+
+ public void setResourceBundle(ResourceBundle resourceBundle) {
+ getResourceBundleProperty().set(resourceBundle);
+ }
+
+ public StringBinding getStringBinding(String key) {
+ return new StringBinding() {
+ {
+ bind(resourceBundleProperty);
+ }
+
+ @Override
+ protected String computeValue() {
+ return getResourceBundle().getString(key);
+ }
+ };
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/controller/MainController.java b/src/main/java/cn/octopusyan/alistgui/controller/MainController.java
index 34f5e20..cc2ef60 100644
--- a/src/main/java/cn/octopusyan/alistgui/controller/MainController.java
+++ b/src/main/java/cn/octopusyan/alistgui/controller/MainController.java
@@ -1,102 +1,39 @@
package cn.octopusyan.alistgui.controller;
import cn.octopusyan.alistgui.base.BaseController;
-import javafx.css.PseudoClass;
import javafx.fxml.FXML;
-import javafx.fxml.Initializable;
-import javafx.scene.control.Button;
-import javafx.scene.control.TabPane;
-import javafx.scene.input.MouseEvent;
-import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
-import javafx.stage.Stage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * 主页面控制器
+ * 主界面控制器
*
- * @author octopus_yan@foxmail.com
+ * @author octopus_yan
*/
-public class MainController extends BaseController implements Initializable {
+public class MainController extends BaseController {
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
- private double xOffset;
- private double yOffset;
+ @FXML
+ public VBox mainView;
- // 布局
- @FXML
- public VBox rootPane;
- @FXML
- public HBox windowHeader;
- @FXML
- public Button alwaysOnTopIcon;
- @FXML
- public Button minimizeIcon;
- @FXML
- public Button closeIcon;
-
- // 界面
- @FXML
- public TabPane tabPane;
-
- public MainController(Stage primaryStage) {
- super(primaryStage);
- }
-
- /**
- * 窗口拖拽设置
- *
- * @return 是否启用
- */
- @Override
- public boolean dragWindow() {
- return false;
- }
-
- /**
- * 获取根布局
- *
- * @return 根布局对象
- */
@Override
public VBox getRootPanel() {
- return rootPane;
+ return mainView;
}
- /**
- * 初始化数据
- */
@Override
public void initData() {
}
- /**
- * 视图样式
- */
@Override
public void initViewStyle() {
}
- /**
- * 视图事件
- */
@Override
public void initViewAction() {
- closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> onDestroy());
- minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> ((Stage) rootPane.getScene().getWindow()).setIconified(true));
- alwaysOnTopIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
- boolean newVal = !getPrimaryStage().isAlwaysOnTop();
- alwaysOnTopIcon.pseudoClassStateChanged(PseudoClass.getPseudoClass("always-on-top"), newVal);
- getPrimaryStage().setAlwaysOnTop(newVal);
- });
- windowHeader.setOnMousePressed(event -> {
- xOffset = getPrimaryStage().getX() - event.getScreenX();
- yOffset = getPrimaryStage().getY() - event.getScreenY();
- });
- windowHeader.setOnMouseDragged(event -> {
- getPrimaryStage().setX(event.getScreenX() + xOffset);
- getPrimaryStage().setY(event.getScreenY() + yOffset);
- });
}
}
diff --git a/src/main/java/cn/octopusyan/alistgui/controller/RootController.java b/src/main/java/cn/octopusyan/alistgui/controller/RootController.java
new file mode 100644
index 0000000..ae4ff99
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/controller/RootController.java
@@ -0,0 +1,108 @@
+package cn.octopusyan.alistgui.controller;
+
+import cn.octopusyan.alistgui.base.BaseController;
+import cn.octopusyan.alistgui.config.Context;
+import com.gluonhq.emoji.EmojiData;
+import com.gluonhq.emoji.util.EmojiImageUtils;
+import javafx.css.PseudoClass;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.TabPane;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+/**
+ * 主页面控制器
+ *
+ * @author octopus_yan@foxmail.com
+ */
+public class RootController extends BaseController {
+
+ private double xOffset;
+ private double yOffset;
+
+ // 布局
+ @FXML
+ private VBox rootPane;
+ @FXML
+ private HBox windowHeader;
+ @FXML
+ private FontIcon alwaysOnTopIcon;
+ @FXML
+ private FontIcon minimizeIcon;
+ @FXML
+ private FontIcon closeIcon;
+
+ // 界面
+ @FXML
+ private TabPane tabPane;
+
+ // footer
+ @FXML
+ public Button document;
+ @FXML
+ public Button github;
+ @FXML
+ public Button sponsor;
+ /**
+ * 获取根布局
+ *
+ * @return 根布局对象
+ */
+ @Override
+ public VBox getRootPanel() {
+ return rootPane;
+ }
+
+ /**
+ * 初始化数据
+ */
+ @Override
+ public void initData() {
+ tabPane.getSelectionModel().select(Context.getCurrentViewIndex());
+ }
+
+ /**
+ * 视图样式
+ */
+ @Override
+ public void initViewStyle() {
+ // 设置图标
+ ImageView book = EmojiImageUtils.emojiView(EmojiData.emojiFromShortName("book").get(), 25);
+ document.setGraphic(book);
+ ImageView githubIcon = EmojiImageUtils.emojiView(EmojiData.emojiFromShortName("cat").get(), 25);
+ github.setGraphic(githubIcon);
+ ImageView juice = EmojiImageUtils.emojiView(EmojiData.emojiFromShortName("tropical_drink").get(), 25);
+ sponsor.setGraphic(juice);
+ }
+
+ /**
+ * 视图事件
+ */
+ @Override
+ public void initViewAction() {
+ closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> getWindow().close());
+ minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> getWindow().setIconified(true));
+ alwaysOnTopIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
+ boolean newVal = !getWindow().isAlwaysOnTop();
+ alwaysOnTopIcon.pseudoClassStateChanged(PseudoClass.getPseudoClass("always-on-top"), newVal);
+ getWindow().setAlwaysOnTop(newVal);
+ });
+
+ windowHeader.setOnMousePressed(event -> {
+ xOffset = getWindow().getX() - event.getScreenX();
+ yOffset = getWindow().getY() - event.getScreenY();
+ });
+ windowHeader.setOnMouseDragged(event -> {
+ getWindow().setX(event.getScreenX() + xOffset);
+ getWindow().setY(event.getScreenY() + yOffset);
+ });
+
+ tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> {
+ Context.setCurrentViewIndex(newValue);
+ });
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java b/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java
new file mode 100644
index 0000000..a8687a8
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java
@@ -0,0 +1,99 @@
+package cn.octopusyan.alistgui.controller;
+
+import cn.octopusyan.alistgui.base.BaseController;
+import cn.octopusyan.alistgui.config.ConfigManager;
+import cn.octopusyan.alistgui.config.Context;
+import cn.octopusyan.alistgui.enums.ProxySetup;
+import cn.octopusyan.alistgui.viewModel.SetupViewModel;
+import javafx.collections.FXCollections;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.*;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Pane;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * 设置页面控制器
+ *
+ * @author octopus_yan
+ */
+public class SetupController extends BaseController implements Initializable {
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @FXML
+ public GridPane setupView;
+ @FXML
+ public CheckBox autoStartCheckBox;
+ @FXML
+ public CheckBox silentStartupCheckBox;
+ @FXML
+ public ComboBox languageComboBox;
+ @FXML
+ public ComboBox proxySetupComboBox;
+// @FXML
+// public RadioButton noProxy;
+// @FXML
+// public RadioButton systemProxy;
+// @FXML
+// public RadioButton manualProxy;
+ @FXML
+ public Pane proxySetupPane;
+ @FXML
+ public TextField proxyHost;
+ @FXML
+ public TextField proxyPort;
+ @FXML
+ public Label alistVersion;
+
+ private final SetupViewModel setupViewModel = new SetupViewModel();
+ private final ToggleGroup proxySetupGroup = new ToggleGroup();
+
+ @Override
+ public GridPane getRootPanel() {
+ return setupView;
+ }
+
+ @Override
+ public void initData() {
+ languageComboBox.setItems(FXCollections.observableList(Context.SUPPORT_LANGUAGE_LIST));
+ proxySetupComboBox.setItems(FXCollections.observableList(List.of(ProxySetup.values())));
+
+// noProxy.setToggleGroup(proxySetupGroup);
+// systemProxy.setToggleGroup(proxySetupGroup);
+// manualProxy.setToggleGroup(proxySetupGroup);
+ }
+
+ @Override
+ public void initViewStyle() {
+ proxySetupComboBox.getSelectionModel().selectedItemProperty().addListener(observable -> {
+ proxySetupPane.setVisible(ProxySetup.MANUAL.equals(setupViewModel.proxySetupProperty().get()));
+ });
+ proxySetupComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
+ proxySetupPane.setVisible(ProxySetup.MANUAL.equals(newValue));
+ });
+
+ languageComboBox.getSelectionModel().select(ConfigManager.language());
+ proxySetupComboBox.getSelectionModel().select(ConfigManager.proxySetup());
+ }
+
+ @Override
+ public void initViewAction() {
+ autoStartCheckBox.selectedProperty().bindBidirectional(setupViewModel.autoStartProperty());
+ silentStartupCheckBox.selectedProperty().bindBidirectional(setupViewModel.silentStartupProperty());
+ proxySetupComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
+ setupViewModel.proxySetupProperty().set(newValue);
+ });
+ proxyHost.textProperty().bindBidirectional(setupViewModel.proxyHostProperty());
+ proxyPort.textProperty().bindBidirectional(setupViewModel.proxyPortProperty());
+ languageComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
+ setupViewModel.languageProperty().set(newValue);
+ logger.info("language changed to {}", newValue);
+ });
+ alistVersion.textProperty().bindBidirectional(setupViewModel.aListVersionProperty());
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/enums/ProxySetup.java b/src/main/java/cn/octopusyan/alistgui/enums/ProxySetup.java
new file mode 100644
index 0000000..d0e119a
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/enums/ProxySetup.java
@@ -0,0 +1,25 @@
+package cn.octopusyan.alistgui.enums;
+
+import cn.octopusyan.alistgui.config.Context;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 代理类型
+ *
+ * @author octopus_yan
+ */
+@Getter
+@RequiredArgsConstructor
+public enum ProxySetup {
+ NO_PROXY("no_proxy"),
+ SYSTEM("system"),
+ MANUAL("manual");
+
+ private final String name;
+
+ @Override
+ public String toString() {
+ return Context.getLanguageBinding("proxy.setup.label." + getName()).getValue();
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java
index fb9ad1a..31bc4e4 100644
--- a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java
+++ b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java
@@ -1,5 +1,6 @@
package cn.octopusyan.alistgui.manager.http;
+import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -23,6 +24,7 @@ import java.util.concurrent.Executor;
*
* @author octopus_yan@foxmail.com
*/
+@Data
public class HttpConfig {
private static final Logger logger = LoggerFactory.getLogger(HttpConfig.class);
/**
@@ -95,8 +97,7 @@ public class HttpConfig {
}};
sslParameters = new SSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("");
-
-
+ sslParameters.setProtocols(new String[]{"TLSv1.2"});
try {
sslContext = SSLContext.getInstance("TLSv1.2");
System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");//取消主机名验证
@@ -104,80 +105,5 @@ public class HttpConfig {
} catch (NoSuchAlgorithmException | KeyManagementException e) {
logger.error("", e);
}
-
- }
-
-
- public HttpClient.Version getVersion() {
- return version;
- }
-
- public void setVersion(HttpClient.Version version) {
- this.version = version;
- }
-
- public int getConnectTimeout() {
- return connectTimeout;
- }
-
- public void setConnectTimeout(int connectTimeout) {
- this.connectTimeout = connectTimeout;
- }
-
-
- public HttpClient.Redirect getRedirect() {
- return redirect;
- }
-
- public void setRedirect(HttpClient.Redirect redirect) {
- this.redirect = redirect;
- }
-
- public Executor getExecutor() {
- return executor;
- }
-
- public void setExecutor(Executor executor) {
- this.executor = executor;
- }
-
- public Authenticator getAuthenticator() {
- return authenticator;
- }
-
- public void setAuthenticator(Authenticator authenticator) {
- this.authenticator = authenticator;
- }
-
- public ProxySelector getProxySelector() {
- return proxySelector;
- }
-
- public void setProxySelector(ProxySelector proxySelector) {
- this.proxySelector = proxySelector;
- }
-
- public CookieHandler getCookieHandler() {
- return cookieHandler;
- }
-
- public void setCookieHandler(CookieHandler cookieHandler) {
- this.cookieHandler = cookieHandler;
- }
-
- public int getDefaultReadTimeout() {
- return defaultReadTimeout;
- }
-
- public void setDefaultReadTimeout(int defaultReadTimeout) {
- this.defaultReadTimeout = defaultReadTimeout;
- }
-
- public SSLContext getSslContext() {
- return sslContext;
- }
-
- public SSLParameters getSslParameters() {
- return sslParameters;
}
}
diff --git a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java
index a351a33..89557bd 100644
--- a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java
+++ b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java
@@ -1,6 +1,10 @@
package cn.octopusyan.alistgui.manager.http;
-import com.alibaba.fastjson2.JSONObject;
+import cn.octopusyan.alistgui.config.ConfigManager;
+import cn.octopusyan.alistgui.enums.ProxySetup;
+import cn.octopusyan.alistgui.model.ProxyInfo;
+import cn.octopusyan.alistgui.util.JsonUtil;
+import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.net.InetSocketAddress;
@@ -12,7 +16,7 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
-import java.util.List;
+import java.util.Map;
import java.util.Optional;
/**
@@ -57,15 +61,20 @@ public class HttpUtil {
return builder.build();
}
- public HttpUtil proxy(String host, int port) {
+ public void proxy(ProxySetup setup, ProxyInfo proxy) {
if (httpClient == null)
throw new RuntimeException("are you ready ?");
- InetSocketAddress unresolved = InetSocketAddress.createUnresolved(host, port);
- ProxySelector other = ProxySelector.of(unresolved);
- this.httpConfig.setProxySelector(other);
+ switch (setup) {
+ case NO_PROXY -> clearProxy();
+ case SYSTEM -> httpConfig.setProxySelector(ProxySelector.getDefault());
+ case MANUAL -> {
+ InetSocketAddress unresolved = InetSocketAddress.createUnresolved(ConfigManager.proxyHost(), ConfigManager.getProxyPort());
+ httpConfig.setProxySelector(ProxySelector.of(unresolved));
+ }
+ }
+
this.httpClient = createClient(httpConfig);
- return this;
}
public void clearProxy() {
@@ -76,24 +85,20 @@ public class HttpUtil {
httpClient = createClient(httpConfig);
}
- public HttpClient getHttpClient() {
- return httpClient;
- }
-
- public String get(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException {
+ public String get(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException {
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header).GET();
HttpResponse response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
return response.body();
}
- public String post(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException {
+ public String post(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException {
HttpRequest.Builder request = getRequest(uri, header)
- .POST(HttpRequest.BodyPublishers.ofString(param.toJSONString()));
+ .POST(HttpRequest.BodyPublishers.ofString(JsonUtil.toJsonString(param)));
HttpResponse response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
return response.body();
}
- public String postForm(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException {
+ public String postForm(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException {
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header)
.POST(HttpRequest.BodyPublishers.noBody());
@@ -101,35 +106,37 @@ public class HttpUtil {
return response.body();
}
- private HttpRequest.Builder getRequest(String uri, JSONObject header) {
+ private HttpRequest.Builder getRequest(String uri, JsonNode header) {
HttpRequest.Builder request = HttpRequest.newBuilder();
// 请求地址
request.uri(URI.create(uri));
// 请求头
if (header != null && !header.isEmpty()) {
- for (String key : header.keySet()) {
- request.header(key, header.getString(key));
+ for (Map.Entry property : header.properties()) {
+ String key = property.getKey();
+ request.header(key, JsonUtil.toJsonString(property.getValue()));
}
}
return request;
}
- private String createFormParams(JSONObject params) {
+ private String createFormParams(JsonNode params) {
StringBuilder formParams = new StringBuilder();
if (params == null) {
return formParams.toString();
}
- for (String key : params.keySet()) {
- Object value = params.get(key);
- if (value instanceof String) {
- value = URLEncoder.encode(String.valueOf(value), StandardCharsets.UTF_8);
+ for (Map.Entry property : params.properties()) {
+ String key = property.getKey();
+ JsonNode value = params.get(key);
+ if (value.isTextual()) {
+ String value_ = URLEncoder.encode(String.valueOf(value), StandardCharsets.UTF_8);
+ formParams.append("&").append(key).append("=").append(value_);
+ } else if (value.isNumber()) {
formParams.append("&").append(key).append("=").append(value);
- } else if (value instanceof Number) {
- formParams.append("&").append(key).append("=").append(value);
- } else if (value instanceof List) {
- formParams.append("&").append(key).append("=").append(params.getJSONArray(key));
+ } else if (value.isArray()) {
+ formParams.append("&").append(key).append("=").append(JsonUtil.toJsonString(value));
} else {
- formParams.append("&").append(key).append("=").append(params.getJSONObject(key));
+ formParams.append("&").append(key).append("=").append(JsonUtil.toJsonString(value));
}
}
if (!formParams.isEmpty()) {
diff --git a/src/main/java/cn/octopusyan/alistgui/model/GuiConfig.java b/src/main/java/cn/octopusyan/alistgui/model/GuiConfig.java
new file mode 100644
index 0000000..ac8fa1d
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/model/GuiConfig.java
@@ -0,0 +1,71 @@
+package cn.octopusyan.alistgui.model;
+
+import cn.hutool.core.io.FileUtil;
+import cn.octopusyan.alistgui.config.AppConstant;
+import cn.octopusyan.alistgui.config.ConfigManager;
+import cn.octopusyan.alistgui.enums.ProxySetup;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import lombok.Data;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * GUI配置信息
+ * 通过Jackson 写入yaml
+ *
+ * @author octopus_yan
+ */
+@Data
+public class GuiConfig {
+ private static final Logger log = LoggerFactory.getLogger(GuiConfig.class);
+ public static GuiConfig INSTANCE;
+ public static ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
+
+ private Boolean autoStart = false;
+ private Boolean silentStartup = false;
+ private ProxyInfo proxyInfo = new ProxyInfo();
+ private String proxySetup = ProxySetup.NO_PROXY.getName();
+ private String language = ConfigManager.DEFAULT_LANGUAGE.toString();
+ @JsonProperty("aListVersion")
+ private String aListVersion = "unknown";
+
+ static {
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+ }
+
+ private GuiConfig() {
+
+ }
+
+ public static GuiConfig getInstance() throws IOException {
+ File src = new File(AppConstant.GUI_CONFIG_PATH);
+ if (INSTANCE == null) {
+ if (!src.exists()) {
+ checkFile(src);
+ }
+ INSTANCE = objectMapper.readValue(src, GuiConfig.class);
+ }
+ return INSTANCE;
+ }
+
+ private static void checkFile(File src) throws IOException {
+ File parent = FileUtil.getParent(src, 1);
+ if (!parent.exists()) {
+ boolean wasSuccessful = parent.mkdirs();
+ objectMapper.writeValue(src, new GuiConfig());
+ if (!wasSuccessful)
+ log.error("{} 创建失败", src.getAbsolutePath());
+ }
+ }
+
+ public void save() throws IOException {
+ objectMapper.writeValue(new File(AppConstant.GUI_CONFIG_PATH), INSTANCE);
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/model/ProxyInfo.java b/src/main/java/cn/octopusyan/alistgui/model/ProxyInfo.java
new file mode 100644
index 0000000..f6e5d70
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/model/ProxyInfo.java
@@ -0,0 +1,16 @@
+package cn.octopusyan.alistgui.model;
+
+import lombok.Data;
+
+/**
+ * 代理信息
+ *
+ * @author octopus_yan
+ */
+@Data
+public class ProxyInfo {
+ private String host = "";
+ private String port = "";
+ private String username = "";
+ private String password = "";
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java b/src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java
deleted file mode 100644
index 03efe0a..0000000
--- a/src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package cn.octopusyan.alistgui.util;
-
-import javafx.scene.input.Clipboard;
-import javafx.scene.input.ClipboardContent;
-
-/**
- * 剪切板工具
- *
- * @author octopus_yan@foxmail.com
- */
-public class ClipUtil {
- //获取系统剪切板
- private static final Clipboard clipboard = Clipboard.getSystemClipboard();
-
- public static void setClip(String data) {
- clipboard.clear();
- // 设置剪切板内容
- ClipboardContent clipboardContent = new ClipboardContent();
- clipboardContent.putString(data);
- clipboard.setContent(clipboardContent);
-
- }
-
- public static String getString() {
- // javafx 从剪切板获取文本
- return clipboard.getString();
- }
-}
diff --git a/src/main/java/cn/octopusyan/alistgui/util/FileUtil.java b/src/main/java/cn/octopusyan/alistgui/util/FileUtil.java
deleted file mode 100644
index 1cc4ba3..0000000
--- a/src/main/java/cn/octopusyan/alistgui/util/FileUtil.java
+++ /dev/null
@@ -1,135 +0,0 @@
-package cn.octopusyan.alistgui.util;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.filefilter.CanReadFileFilter;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * 文件工具类
- *
- * @author octopus_yan@foxmail.com
- */
-public class FileUtil {
- private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);
-
- public static File[] ls(String path) {
- File dir = new File(path);
- if (!dir.exists())
- throw new RuntimeException(path + "不存在!");
-
- if (!dir.isDirectory())
- throw new RuntimeException(path + "不是一个文件夹!");
-
- return dir.listFiles();
- }
-
- public static void copyFilesFromDir(String path, String dest) throws IOException {
- if (StringUtils.isBlank(path) || StringUtils.isBlank(dest)) {
- logger.error("path is blank !");
- return;
- }
- File dir = new File(path);
- if (!dir.exists()) {
- logger.error("[" + path + "] 不存在!");
- return;
- }
- if (!dir.isDirectory()) {
- logger.error("[" + path + "] 不是一个文件夹!");
- }
-
- File[] files = dir.listFiles();
- if (files == null) return;
-
- File directory = new File(dest);
- if (directory.exists() && !directory.isDirectory()) {
- logger.error("[" + dest + "] 不是一个文件夹!");
- }
-
- FileUtils.forceMkdir(directory);
-
- for (File file : files) {
- copyFile(file, new File(dest + File.separator + file.getName()));
- }
- }
-
- public static void copyFile(File in, File out) throws IOException {
- copyFile(Files.newInputStream(in.toPath()), out);
- }
-
- public static void copyFile(InputStream input, File out) throws IOException {
- OutputStream output = null;
- try {
- output = Files.newOutputStream(out.toPath());
- byte[] buf = new byte[1024];
- int bytesRead;
- while ((bytesRead = input.read(buf)) > 0) {
- output.write(buf, 0, bytesRead);
- }
- } catch (IOException e) {
- logger.error("", e);
- } finally {
- if (output != null) input.close();
- if (output != null) output.close();
- }
- }
-
- /**
- * 获取文件主名称
- *
- * @param file 文件对象
- * @return 文件名称
- */
- public static String mainName(File file) {
- //忽略判断
- String fileName = file.getName();
- return fileName.substring(0, fileName.lastIndexOf("."));
- }
-
- public static List listFileNames(String path) {
- Collection files = FileUtils.listFiles(new File(path), CanReadFileFilter.CAN_READ, null);
- return files.stream().map(File::getName).collect(Collectors.toList());
- }
-
- /**
- * 返回被查找到的文件的绝对路径(匹配到一个就返回)
- *
- * @param root 根目录文件
- * @param fileName 要找的文件名
- * @return 绝对路径
- */
- private static String findFiles(File root, String fileName) {
- //定义一个返回值
- String path = null;
- //如果传进来的是目录,并且存在
- if (root.exists() && root.isDirectory()) {
- //遍历文件夹中的各个文件
- File[] files = root.listFiles();
- if (files != null) {
- for (File file : files) {
- //如果path的值没有变化
- if (path == null) {
- if (file.isFile() && file.getName().contains(fileName)) {
- path = file.getAbsolutePath();
- } else {
- path = findFiles(file, fileName);
- }
- } else {
- break;//跳出循环,增加性能
- }
- }
- }
- }
- return path;
- }
-}
diff --git a/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java b/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java
index 404d3a6..de5c07d 100644
--- a/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java
+++ b/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java
@@ -1,9 +1,11 @@
package cn.octopusyan.alistgui.util;
+import cn.octopusyan.alistgui.config.Context;
import javafx.fxml.FXMLLoader;
import javafx.fxml.JavaFXBuilderFactory;
import java.nio.charset.StandardCharsets;
+import java.util.ResourceBundle;
/**
* FXML 工具
@@ -13,11 +15,15 @@ import java.nio.charset.StandardCharsets;
public class FxmlUtil {
public static FXMLLoader load(String name) {
+ return load(name, Context.getLanguageResource().get());
+ }
+
+ public static FXMLLoader load(String name, ResourceBundle bundle) {
String prefix = "/fxml/";
String suffix = ".fxml";
return new FXMLLoader(
FxmlUtil.class.getResource(prefix + name + suffix),
- null,
+ bundle,
new JavaFXBuilderFactory(),
null,
StandardCharsets.UTF_8
diff --git a/src/main/java/cn/octopusyan/alistgui/util/JsonUtil.java b/src/main/java/cn/octopusyan/alistgui/util/JsonUtil.java
new file mode 100644
index 0000000..716139e
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/util/JsonUtil.java
@@ -0,0 +1,187 @@
+package cn.octopusyan.alistgui.util;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+
+/**
+ * Jackson 封装工具类
+ *
+ * @author octopus_yan
+ */
+public class JsonUtil {
+ private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * 时间日期格式
+ */
+ private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ static {
+ //对象的所有字段全部列入序列化
+ objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
+ //取消默认转换timestamps形式
+ objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ //忽略空Bean转json的错误
+ objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+ //所有的日期格式都统一为以下的格式,即yyyy-MM-dd HH:mm:ss
+ objectMapper.setDateFormat(new SimpleDateFormat(STANDARD_FORMAT));
+ //忽略 在json字符串中存在,但在java对象中不存在对应属性的情况。防止错误
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ /**
+ * Json字符串 转 JavaBean
+ *
+ * @param jsonString Json字符串
+ * @param clazz Java类对象
+ * @param Java类
+ * @return JavaBean
+ */
+ public static T parseObject(String jsonString, Class clazz) {
+ T t = null;
+ try {
+ t = objectMapper.readValue(jsonString, clazz);
+ } catch (JsonProcessingException e) {
+ log.error("失败:{}", e.getMessage());
+ }
+ return t;
+ }
+
+ /**
+ * 读取Json文件 转 JavaBean
+ *
+ * @param file Json文件
+ * @param clazz Java类对象
+ * @param Java类
+ * @return JavaBean
+ */
+ public static T parseObject(File file, Class clazz) {
+ T t = null;
+ try {
+ t = objectMapper.readValue(file, clazz);
+ } catch (IOException e) {
+ log.error("失败:{}", e.getMessage());
+ }
+ return t;
+ }
+
+ /**
+ * 读取Json字符串 转 JavaBean集合
+ *
+ * @param jsonArray Json字符串
+ * @param reference 类型
+ * @param JavaBean类型
+ * @return JavaBean集合
+ */
+ public static T parseJsonArray(String jsonArray, TypeReference reference) {
+ T t = null;
+ try {
+ t = objectMapper.readValue(jsonArray, reference);
+ } catch (JsonProcessingException e) {
+ log.error("失败:{}", e.getMessage());
+ }
+ return t;
+ }
+
+
+ /**
+ * JavaBean 转 Json字符串
+ *
+ * @param object JavaBean
+ * @return Json字符串
+ */
+ public static String toJsonString(Object object) {
+ String jsonString = null;
+ try {
+ jsonString = objectMapper.writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ log.error("失败:{}", e.getMessage());
+ }
+ return jsonString;
+ }
+
+ /**
+ * JavaBean 转 字节数组
+ *
+ * @param object JavaBean
+ * @return 字节数组
+ */
+ public static byte[] toByteArray(Object object) {
+ byte[] bytes = null;
+ try {
+ bytes = objectMapper.writeValueAsBytes(object);
+ } catch (JsonProcessingException e) {
+ log.error("失败:{}", e.getMessage());
+ }
+ return bytes;
+ }
+
+ /**
+ * JavaBean序列化到文件
+ *
+ * @param file 写入文件对象
+ * @param object JavaBean
+ */
+ public static void objectToFile(File file, Object object) {
+ try {
+ objectMapper.writeValue(file, object);
+ } catch (Exception e) {
+ log.error("失败:{}", e.getMessage());
+ }
+ }
+
+
+ /**
+ * Json字符串 转 JsonNode
+ *
+ * @param jsonString Json字符串
+ * @return JsonNode
+ */
+ public static JsonNode parseJsonObject(String jsonString) {
+ JsonNode jsonNode = null;
+ try {
+ jsonNode = objectMapper.readTree(jsonString);
+ } catch (JsonProcessingException e) {
+ log.error("失败:{}", e.getMessage());
+ }
+ return jsonNode;
+ }
+
+ /**
+ * JavaBean 转 JsonNode
+ *
+ * @param object JavaBean
+ * @return JsonNode
+ */
+ public static JsonNode parseJsonObject(Object object) {
+ return objectMapper.valueToTree(object);
+ }
+
+ /**
+ * JsonNode 转 Json字符串
+ *
+ * @param jsonNode JsonNode
+ * @return Json字符串
+ */
+ public static String toJsonString(JsonNode jsonNode) {
+ String jsonString = null;
+ try {
+ jsonString = objectMapper.writeValueAsString(jsonNode);
+ } catch (JsonProcessingException e) {
+ log.error("失败:{}", e.getMessage());
+ }
+ return jsonString;
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java b/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java
index 1aec45b..0cd12bf 100644
--- a/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java
+++ b/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java
@@ -12,33 +12,14 @@ import java.io.IOException;
* @author octopus_yan@foxmail.com
*/
public class ProcessesUtil {
+
private static final Logger logger = LoggerFactory.getLogger(ProcessesUtil.class);
- private static final String NEWLINE = System.lineSeparator();
+ private static final String NEW_LINE = System.lineSeparator();
private static final DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
public static boolean exec(String command) {
try {
- exec(command, new OnExecuteListener() {
- @Override
- public void onExecute(String msg) {
-
- }
-
- @Override
- public void onExecuteSuccess(int exitValue) {
-
- }
-
- @Override
- public void onExecuteError(Exception e) {
-
- }
-
- @Override
- public void onExecuteOver() {
-
- }
- });
+ exec(command, msg -> {});
handler.waitFor();
} catch (Exception e) {
logger.error("", e);
@@ -50,7 +31,7 @@ public class ProcessesUtil {
LogOutputStream logout = new LogOutputStream() {
@Override
protected void processLine(String line, int logLevel) {
- if (listener != null) listener.onExecute(line + NEWLINE);
+ if (listener != null) listener.onExecute(line + NEW_LINE);
}
};
@@ -81,11 +62,8 @@ public class ProcessesUtil {
public interface OnExecuteListener {
void onExecute(String msg);
-
- void onExecuteSuccess(int exitValue);
-
- void onExecuteError(Exception e);
- void onExecuteOver();
+ default void onExecuteSuccess(int exitValue){}
+ default void onExecuteError(Exception e){}
}
/**
diff --git a/src/main/java/cn/octopusyan/alistgui/viewModel/SetupViewModel.java b/src/main/java/cn/octopusyan/alistgui/viewModel/SetupViewModel.java
new file mode 100644
index 0000000..b931ae9
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/viewModel/SetupViewModel.java
@@ -0,0 +1,87 @@
+package cn.octopusyan.alistgui.viewModel;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.net.url.UrlBuilder;
+import cn.octopusyan.alistgui.config.ConfigManager;
+import cn.octopusyan.alistgui.config.Context;
+import cn.octopusyan.alistgui.enums.ProxySetup;
+import javafx.beans.property.*;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Locale;
+
+/**
+ * 设置视图数据
+ *
+ * @author octopus_yan
+ */
+public class SetupViewModel {
+ private final BooleanProperty autoStart = new SimpleBooleanProperty(ConfigManager.autoStart());
+ private final BooleanProperty silentStartup = new SimpleBooleanProperty(ConfigManager.silentStartup());
+ private final StringProperty proxyHost = new SimpleStringProperty(ConfigManager.proxyHost());
+ private final StringProperty proxyPort = new SimpleStringProperty(ConfigManager.proxyPort());
+ private final BooleanProperty proxyVerify = new SimpleBooleanProperty(false);
+ private final ObjectProperty language = new SimpleObjectProperty<>(ConfigManager.language());
+ private final ObjectProperty proxySetup = new SimpleObjectProperty<>(ConfigManager.proxySetup());
+ private final SimpleStringProperty aListVersion = new SimpleStringProperty(ConfigManager.aListVersion());
+
+ public SetupViewModel() {
+ aListVersion.addListener((observable, oldValue, newValue) -> ConfigManager.aListVersion(newValue));
+ autoStart.addListener((observable, oldValue, newValue) -> ConfigManager.autoStart(newValue));
+ silentStartup.addListener((observable, oldValue, newValue) -> ConfigManager.silentStartup(newValue));
+ proxySetup.addListener((observable, oldValue, newValue) -> ConfigManager.proxySetup(newValue));
+ proxyHost.addListener((observable, oldValue, newValue) -> ConfigManager.proxyHost(newValue));
+ proxyPort.addListener((observable, oldValue, newValue) -> ConfigManager.proxyPort(newValue));
+ language.addListener((observable, oldValue, newValue) -> Context.setLanguage(newValue));
+ }
+
+ public BooleanProperty autoStartProperty() {
+ return autoStart;
+ }
+
+ public BooleanProperty silentStartupProperty() {
+ return silentStartup;
+ }
+
+ public ObjectProperty languageProperty() {
+ return language;
+ }
+
+ public ObjectProperty proxySetupProperty() {
+ return proxySetup;
+ }
+
+ public StringProperty proxyHostProperty() {
+ return proxyHost;
+ }
+
+ public StringProperty proxyPortProperty() {
+ return proxyPort;
+ }
+
+ public Property aListVersionProperty() {
+ return aListVersion;
+ }
+
+ /**
+ * 验证代理地址
+ *
+ * @param address 新的代理地址
+ * @return UrlBuilder, 验证失败时为null
+ */
+ public UrlBuilder validateAddress(String address) {
+ if (StringUtils.isEmpty(address))
+ return null;
+
+ // 验证 URL 的可用性
+ UrlBuilder ub = UrlBuilder.of(address);
+ boolean isUrlValid = Validator.isUrl(address) && ub.getPort() > 0;
+
+ // 设置 proxyVerify 和 proxyVerifyMsg
+ proxyVerify.setValue(isUrlValid);
+
+ if (isUrlValid)
+ return ub;
+ else return null;
+ }
+}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index a09a857..b6e3dd2 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -3,15 +3,21 @@ module cn.octopusyan.alistgui {
requires javafx.controls;
requires javafx.fxml;
requires javafx.graphics;
- requires org.apache.commons.io;
requires org.apache.commons.lang3;
requires org.apache.commons.exec;
requires org.slf4j;
requires ch.qos.logback.core;
requires ch.qos.logback.classic;
- requires com.alibaba.fastjson2;
+ requires cn.hutool.core;
+ requires org.kordamp.ikonli.javafx;
+ requires com.gluonhq.emoji;
+ requires static lombok;
+ requires com.fasterxml.jackson.databind;
+ requires com.fasterxml.jackson.dataformat.yaml;
+ requires atlantafx.base;
exports cn.octopusyan.alistgui;
opens cn.octopusyan.alistgui to javafx.fxml;
+ opens cn.octopusyan.alistgui.model to com.fasterxml.jackson.databind;
opens cn.octopusyan.alistgui.controller to javafx.fxml;
}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 7553f33..a63aa58 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,3 +1,3 @@
app.name=${project.name}
-app.title=alist gui
+app.title=AList GUI
app.version=${project.version}
\ No newline at end of file
diff --git a/src/main/resources/css/main-view.css b/src/main/resources/css/main-view.css
new file mode 100644
index 0000000..584b99a
--- /dev/null
+++ b/src/main/resources/css/main-view.css
@@ -0,0 +1,87 @@
+/**************************************************
+ * Main View
+**************************************************/
+#homeLabel {
+ -fx-font-size: 35;
+ -fx-font-weight: bold;
+}
+
+#statusLabel {
+ -fx-padding: 2 5 2 5;
+ -fx-text-fill: white;
+ -fx-font-size: 15;
+ -fx-background-color: #1bc964;
+ -fx-background-radius: 10;
+ -fx-text-alignment: CENTER;
+ -fx-border-radius: 10;
+}
+
+.control-menu {
+ -fx-font-size: 15;
+ -fx-background-radius: 15;
+ -fx-padding: 10 40 10 40;
+ -fx-border-radius: 15;
+ -fx-border-width: 2;
+ -fx-opacity: 0.9;
+}
+.control-menu:hover {
+ -fx-opacity: 1;
+}
+
+#startButton {
+ -fx-background-color: #fa6057;
+ -fx-border-color: #fa6057;
+ -fx-text-fill: white;
+}
+
+#passwordButton {
+ -fx-background-color: #1bc964;
+ -fx-border-color: #1bc964;
+}
+
+#restartButton {
+ -fx-background-color: linear-gradient(#57b4f2, #9198e5);
+ -fx-border-color: linear-gradient(#57b4f2, #9198e5);
+ -fx-text-fill: white;
+}
+
+#moreButton {
+ -fx-background-color: transparent;
+ -fx-text-fill: #9254d1;
+ -fx-border-color: #9254d1;
+ -fx-border-width: 2;
+}
+
+#logArea {
+ -fx-font-family: "Lucida Console";
+ -fx-font-size: 20;
+ -fx-background-radius: 15;
+ -fx-border-radius: 15;
+ -fx-border-color: transparent;
+ -fx-background-insets: 0;
+ -fx-background-color: #e9e9e9;
+}
+#logArea:focused {
+ -fx-background-radius: 15;
+ -fx-border-radius: 15;
+ -fx-border-color: transparent;
+ -fx-background-color: #e9e9e9;
+}
+#logArea .content {
+ -fx-padding: 0 15 0 15;
+ -fx-background-radius: 15;
+ -fx-background-color: #e9e9e9;
+ -fx-border-radius: 15;
+ -fx-border-color: transparent;
+}
+#logArea .content:focused {
+ -fx-background-color: #e9e9e9;
+}
+#logArea .scroll-pane {
+ -fx-background-color: transparent;
+}
+#logArea .scroll-pane .viewport {
+ -fx-background-color: transparent;
+}
+
+/*# sourceMappingURL=main-view.css.map */
diff --git a/src/main/resources/css/main-view.css.map b/src/main/resources/css/main-view.css.map
new file mode 100644
index 0000000..7f57c04
--- /dev/null
+++ b/src/main/resources/css/main-view.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["main-view.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAGA;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE","file":"main-view.css"}
\ No newline at end of file
diff --git a/src/main/resources/css/main-view.scss b/src/main/resources/css/main-view.scss
new file mode 100644
index 0000000..6b1db7d
--- /dev/null
+++ b/src/main/resources/css/main-view.scss
@@ -0,0 +1,91 @@
+/**************************************************
+ * Main View
+**************************************************/
+#homeLabel {
+ -fx-font-size: 35;
+ -fx-font-weight: bold;
+}
+
+#statusLabel {
+ -fx-padding: 2 5 2 5;
+ -fx-text-fill: white;
+ -fx-font-size: 15;
+ -fx-background-color: #1bc964;
+ -fx-background-radius: 10;
+ -fx-text-alignment: CENTER;
+ -fx-border-radius: 10;
+}
+
+.control-menu {
+ -fx-font-size: 15;
+ -fx-background-radius: 15;
+ -fx-padding: 10 40 10 40;
+ -fx-border-radius: 15;
+ -fx-border-width: 2;
+ -fx-opacity: 0.9;
+
+ &:hover {
+ -fx-opacity: 1.0;
+ }
+}
+
+#startButton {
+ -fx-background-color: #fa6057;
+ -fx-border-color: #fa6057;
+ -fx-text-fill: white;
+}
+
+#passwordButton {
+ -fx-background-color: #1bc964;
+ -fx-border-color: #1bc964;
+}
+
+#restartButton {
+ -fx-background-color: linear-gradient(#57b4f2, #9198e5);
+ -fx-border-color: linear-gradient(#57b4f2, #9198e5);
+ -fx-text-fill: white;
+}
+
+#moreButton {
+ -fx-background-color: transparent;
+ -fx-text-fill: #9254d1;
+ -fx-border-color: #9254d1;
+ -fx-border-width: 2;
+}
+
+#logArea {
+ -fx-font-family: "Lucida Console";
+ -fx-font-size: 20;
+ -fx-background-radius: 15;
+ -fx-border-radius: 15;
+ -fx-border-color: transparent;
+ -fx-background-insets: 0;
+ -fx-background-color: #e9e9e9;
+
+ &:focused {
+ -fx-background-radius: 15;
+ -fx-border-radius: 15;
+ -fx-border-color: transparent;
+ -fx-background-color: #e9e9e9;
+ }
+
+ .content {
+ -fx-padding: 0 15 0 15;
+ -fx-background-radius: 15;
+ -fx-background-color: #e9e9e9;
+ -fx-border-radius: 15;
+ -fx-border-color: transparent;
+
+ &:focused {
+ -fx-background-color: #e9e9e9;
+ }
+ }
+
+ .scroll-pane {
+ -fx-background-color: transparent;
+
+ .viewport {
+ -fx-background-color: transparent;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/css/root-view.css b/src/main/resources/css/root-view.css
index 13fc80f..5464151 100644
--- a/src/main/resources/css/root-view.css
+++ b/src/main/resources/css/root-view.css
@@ -1,176 +1,99 @@
@import "root.css";
-
/**************************************************
* Window Header
**************************************************/
-#windowHeader .iconButton {
- /*-fx-max-height: 5px;*/
- /*-fx-max-width: 5px;*/
- -fx-border-radius: 15;
+#windowHeader .icon-button {
+ -fx-icon-code: fa-circle;
+ -fx-opacity: 0.5;
}
-
-#rootPane #closeIcon {
- -fx-color: #fa6057;
- -fx-opacity: 0.5;
+#windowHeader .icon-button:hover {
+ -fx-opacity: 1;
}
-
-#rootPane #closeIcon:hover {
- -fx-opacity: 1.0;
+#windowHeader #closeIcon {
+ -fx-color: #fa6057;
+ -fx-background-color: #fa6057;
+ -fx-icon-color: #fa6057;
}
-
-#rootPane #minimizeIcon {
- -fx-color: #fbbc2e;
- -fx-opacity: 0.5;
+#windowHeader #minimizeIcon {
+ -fx-color: #fbbc2e;
+ -fx-background-color: #fbbc2e;
+ -fx-icon-color: #fbbc2e;
}
-
-#rootPane #minimizeIcon:hover {
- -fx-opacity: 1.0;
+#windowHeader #alwaysOnTopIcon {
+ -fx-color: #27c940;
+ -fx-background-color: #27c940;
+ -fx-icon-color: #27c940;
}
-
-#rootPane #alwaysOnTopIcon {
- -fx-color: #27c940;
- -fx-opacity: 0.5;
-}
-
-#rootPane #alwaysOnTopIcon:hover {
- -fx-opacity: 1.0;
-}
-
-#rootPane #alwaysOnTopIcon:always-on-top {
- -fx-opacity: 1.0;
+#windowHeader #alwaysOnTopIcon:always-on-top {
+ -fx-opacity: 1;
}
/**************************************************
* Tab label
**************************************************/
-
#tabPane .tab-header-area {
- -fx-background-radius: 10;
- -fx-background-color: #0000;
+ -fx-background-radius: 0 0 0 0;
+ -fx-background-color: transparent;
}
-
#tabPane .headers-region {
- -fx-alignment: TOP_CENTER;
- -fx-background-color: #18181a;
- -fx-background-radius: 10;
- -fx-padding: 5 0 5 0;
+ -fx-alignment: TOP_CENTER;
+ -fx-background-color: #e9e9e9;
+ -fx-background-radius: 15;
+ -fx-padding: 5 0 5 0;
}
-
#tabPane .tab-header-background {
- -fx-background-color: #0000;
+ -fx-background-color: transparent;
}
-
#tabPane .tab {
- -fx-text-fill: white;
- -fx-padding: 10 20 10 20;
- -fx-background-radius: 10;
- -fx-background-color: #0000;
- -fx-border-width: 0;
+ -fx-padding: 10 20 10 20;
+ -fx-background-radius: 15;
+ -fx-background-color: transparent;
+ -fx-border-width: 0;
}
-
-#tabPane .tab-label {
- -fx-font-size: 15px;
- -fx-text-fill: #707079;
+#tabPane .tab .ikonli-font-icon {
+ -fx-icon-color: black;
+}
+#tabPane .tab .tab-label {
+ -fx-text-alignment: CENTER;
+ -fx-alignment: CENTER;
}
-
#tabPane .tab:selected {
- -fx-background-color: #2c69e0;
+ -fx-background-color: #2c69e0;
+}
+#tabPane .tab:selected .ikonli-font-icon {
+ -fx-icon-color: white;
}
-
#tabPane .tab:selected .tab-label {
- -fx-text-fill: white;
+ -fx-text-fill: white;
+}
+#tabPane .tab:selected .focus-indicator {
+ -fx-border-width: 0;
+ -fx-border-color: transparent;
+ -fx-border-insets: 0;
+}
+#tabPane .tab-label {
+ -fx-font-size: 15px;
}
/**************************************************
- * Main View
+ * Window Footer
**************************************************/
-#homeLabel {
- -fx-font-size: 35px;
- -fx-font-weight: bold;
- -fx-text-fill: white;
- -fx-font-family: 'JetBrains Mono';
+#windowFooter .button {
+ -fx-font-size: 15;
+ -fx-background-color: transparent;
+ -fx-text-alignment: CENTER;
+}
+#windowFooter #document {
+ -fx-text-fill: #1bc964;
+}
+#windowFooter #github {
+ -fx-text-fill: #1f4ca6;
+}
+#windowFooter #sponsor {
+ -fx-text-fill: #f22760;
+}
+#windowFooter .ikonli-font-icon {
+ -fx-font-size: 15;
}
-#statusLabel {
- -fx-padding: 2 5 2 5;
- -fx-text-fill: black;
- -fx-background-color: #1bc964;
- -fx-background-radius: 10;
- -fx-text-alignment: CENTER;
- -fx-border-radius: 10;
- -fx-border-color: black;
-}
-
-.controlMenu {
- -fx-font-size: 15;
- -fx-background-radius: 10px;
- -fx-padding: 10 40 10 40;
- -fx-border-radius: 10;
-}
-
-.controlMenu:focused {
- -fx-opacity: 0.5;
-}
-
-#startButton {
- -fx-background-color: #fa6057;
- -fx-text-fill: white;
- -fx-opacity: 1.0;
-}
-
-#passwordButton {
- -fx-background-color: #1bc964;
- -fx-opacity: 1.0;
-}
-
-#restartButton {
- -fx-background-color: linear-gradient(#57b4f2, #9198e5);
- -fx-text-fill: white;
- -fx-opacity: 1.0;
-}
-
-#moreButton {
- -fx-background-color: #0000;
- -fx-text-fill: #9254d1;
- -fx-border-color: #9254d1;
- -fx-border-width: 2px;
- -fx-opacity: 1.0;
-}
-
-#tabPane .tab:selected .focus-indicator {
- -fx-border-width: 0;
- -fx-border-color: #0000;
-}
-
-#logArea {
- -fx-text-fill: #e3e4e4;
- -fx-font-family: 'JetBrains Mono';
- -fx-font-size: 20;
- -fx-background-radius: 15;
- -fx-border-radius: 15;
- -fx-border-color: transparent;
- -fx-background-insets: 0;
- -fx-background-color: #18181c;
-}
-
-#logArea .content {
- -fx-padding: 15;
- -fx-background-radius: 15;
- -fx-background-color: #18181c;
- -fx-border-radius: 15;
- -fx-border-color: transparent;
-}
-
-#logArea .scroll-pane {
- -fx-background-color: transparent;
- }
-#logArea .scroll-pane .viewport{
- -fx-background-color: transparent;
-}
-
-#logArea:focused {
- -fx-background-radius: 15;
- -fx-border-radius: 15;
- -fx-border-color: transparent;
- -fx-background-color: #18181c;
-}
\ No newline at end of file
+/*# sourceMappingURL=root-view.css.map */
diff --git a/src/main/resources/css/root-view.css.map b/src/main/resources/css/root-view.css.map
new file mode 100644
index 0000000..a55b2cc
--- /dev/null
+++ b/src/main/resources/css/root-view.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["root-view.scss"],"names":[],"mappings":"AAAQ;AACR;AAAA;AAAA;AAIE;EACE;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;;AAMN;AAAA;AAAA;AAME;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAKN;EACE;;;AAIJ;AAAA;AAAA;AAKE;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE","file":"root-view.css"}
\ No newline at end of file
diff --git a/src/main/resources/css/root-view.scss b/src/main/resources/css/root-view.scss
new file mode 100644
index 0000000..a703d73
--- /dev/null
+++ b/src/main/resources/css/root-view.scss
@@ -0,0 +1,126 @@
+@import "root.css";
+/**************************************************
+ * Window Header
+**************************************************/
+#windowHeader {
+ .icon-button {
+ -fx-icon-code: fa-circle;
+ -fx-opacity: 0.5;
+
+ &:hover {
+ -fx-opacity: 1.0;
+ }
+ }
+
+ #closeIcon {
+ -fx-color: #fa6057;
+ -fx-background-color: #fa6057;
+ -fx-icon-color: #fa6057;
+ }
+
+ #minimizeIcon {
+ -fx-color: #fbbc2e;
+ -fx-background-color: #fbbc2e;
+ -fx-icon-color: #fbbc2e;
+ }
+
+ #alwaysOnTopIcon {
+ -fx-color: #27c940;
+ -fx-background-color: #27c940;
+ -fx-icon-color: #27c940;
+
+ &:always-on-top {
+ -fx-opacity: 1.0;
+ }
+ }
+}
+
+
+/**************************************************
+ * Tab label
+**************************************************/
+
+#tabPane {
+
+ .tab-header-area {
+ -fx-background-radius: 0 0 0 0;
+ -fx-background-color: transparent;
+ }
+
+ .headers-region {
+ -fx-alignment: TOP_CENTER;
+ -fx-background-color: #e9e9e9;
+ -fx-background-radius: 15;
+ -fx-padding: 5 0 5 0;
+ }
+
+ .tab-header-background {
+ -fx-background-color: transparent;
+ }
+
+ .tab {
+ -fx-padding: 10 20 10 20;
+ -fx-background-radius: 15;
+ -fx-background-color: transparent;
+ -fx-border-width: 0;
+
+ .ikonli-font-icon {
+ -fx-icon-color: black;
+ }
+
+ .tab-label {
+ -fx-text-alignment: CENTER;
+ -fx-alignment: CENTER;
+ }
+
+ &:selected {
+ -fx-background-color: #2c69e0;
+
+ .ikonli-font-icon {
+ -fx-icon-color: white;
+ }
+
+ .tab-label {
+ -fx-text-fill: white;
+ }
+
+ .focus-indicator {
+ -fx-border-width: 0;
+ -fx-border-color: transparent;
+ -fx-border-insets: 0;
+ }
+ }
+ }
+
+ .tab-label {
+ -fx-font-size: 15px;
+ }
+}
+
+/**************************************************
+ * Window Footer
+**************************************************/
+
+#windowFooter {
+ .button {
+ -fx-font-size: 15;
+ -fx-background-color: transparent;
+ -fx-text-alignment: CENTER;
+ }
+
+ #document {
+ -fx-text-fill: #1bc964;
+ }
+
+ #github {
+ -fx-text-fill: #1f4ca6;
+ }
+
+ #sponsor {
+ -fx-text-fill: #f22760;
+ }
+
+ .ikonli-font-icon {
+ -fx-font-size: 15;
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/css/root.css b/src/main/resources/css/root.css
index 8fa808f..2f9d683 100644
--- a/src/main/resources/css/root.css
+++ b/src/main/resources/css/root.css
@@ -1,15 +1,14 @@
-
/**************************************************
* Root
**************************************************/
-
-.rootPane {
- -fx-background-color: black;
- -fx-background-radius: 10;
- -fx-border-radius: 10;
-}
-
.root {
- -fx-font-family: "Comic Sans MS";
+ -fx-font-size: 15;
+ -fx-font-weight: normal;
}
+.root-pane {
+ -fx-background-radius: 15;
+ -fx-border-radius: 15;
+}
+
+/*# sourceMappingURL=root.css.map */
diff --git a/src/main/resources/css/root.css.map b/src/main/resources/css/root.css.map
new file mode 100644
index 0000000..32c7e3f
--- /dev/null
+++ b/src/main/resources/css/root.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["root.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAIA;EACE;EACA;;;AAGF;EACE;EACA","file":"root.css"}
\ No newline at end of file
diff --git a/src/main/resources/css/root.scss b/src/main/resources/css/root.scss
new file mode 100644
index 0000000..d11c35a
--- /dev/null
+++ b/src/main/resources/css/root.scss
@@ -0,0 +1,14 @@
+/**************************************************
+ * Root
+**************************************************/
+
+.root {
+ -fx-font-size: 15;
+ -fx-font-weight: normal;
+}
+
+.root-pane {
+ -fx-background-radius: 15;
+ -fx-border-radius: 15;
+}
+
diff --git a/src/main/resources/css/setup-view.css b/src/main/resources/css/setup-view.css
new file mode 100644
index 0000000..a7aecf5
--- /dev/null
+++ b/src/main/resources/css/setup-view.css
@@ -0,0 +1,34 @@
+/**************************************************
+ * Setup View
+**************************************************/
+#setupView .check-box {
+ -fx-font-size: 15;
+}
+#setupView .proxy-panel {
+ -fx-background-color: #e9e9e9;
+ -fx-background-radius: 15;
+ -fx-border-radius: 15;
+ -fx-border-width: 5;
+}
+#setupView .proxy-panel .radio-button {
+ -fx-background-color: transparent;
+}
+#setupView .proxy-label {
+ -fx-font-size: 15;
+ -fx-text-fill: #2c69e0;
+ -fx-text-alignment: CENTER;
+}
+#setupView .shield .label {
+ -fx-text-fill: white;
+ -fx-label-padding: 3 5 3 5;
+}
+#setupView .shield .shield-name {
+ -fx-background-color: #555555;
+ -fx-background-radius: 5 0 0 5;
+}
+#setupView .shield .shield-version {
+ -fx-background-color: #6969AA;
+ -fx-background-radius: 0 5 5 0;
+}
+
+/*# sourceMappingURL=setup-view.css.map */
diff --git a/src/main/resources/css/setup-view.css.map b/src/main/resources/css/setup-view.css.map
new file mode 100644
index 0000000..6828792
--- /dev/null
+++ b/src/main/resources/css/setup-view.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["setup-view.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAKE;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAKA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA","file":"setup-view.css"}
\ No newline at end of file
diff --git a/src/main/resources/css/setup-view.scss b/src/main/resources/css/setup-view.scss
new file mode 100644
index 0000000..fe2a1ea
--- /dev/null
+++ b/src/main/resources/css/setup-view.scss
@@ -0,0 +1,45 @@
+/**************************************************
+ * Setup View
+**************************************************/
+
+#setupView {
+ .check-box {
+ -fx-font-size: 15;
+ }
+
+ .proxy-panel {
+ -fx-background-color: #e9e9e9;
+ -fx-background-radius: 15;
+ -fx-border-radius: 15;
+ -fx-border-width: 5;
+
+ .radio-button {
+ -fx-background-color: transparent;
+ }
+ }
+
+ .proxy-label {
+ -fx-font-size: 15;
+ -fx-text-fill: #2c69e0;
+ -fx-text-alignment: CENTER;
+ }
+
+ .shield {
+
+ .label {
+ -fx-text-fill: white;
+ -fx-label-padding: 3 5 3 5;
+ }
+
+ .shield-name {
+ -fx-background-color: #555555;
+ -fx-background-radius: 5 0 0 5;
+ }
+
+ .shield-version {
+ -fx-background-color: #6969AA;
+ -fx-background-radius: 0 5 5 0;
+ }
+ }
+}
+
diff --git a/src/main/resources/fxml/main-view.fxml b/src/main/resources/fxml/main-view.fxml
new file mode 100644
index 0000000..3f82c29
--- /dev/null
+++ b/src/main/resources/fxml/main-view.fxml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/root-view.fxml b/src/main/resources/fxml/root-view.fxml
index 8ff9682..da0ef1f 100644
--- a/src/main/resources/fxml/root-view.fxml
+++ b/src/main/resources/fxml/root-view.fxml
@@ -1,90 +1,49 @@
-
+
-
+
+
-
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/setup-view.fxml b/src/main/resources/fxml/setup-view.fxml
new file mode 100644
index 0000000..809822d
--- /dev/null
+++ b/src/main/resources/fxml/setup-view.fxml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/language/language.properties b/src/main/resources/language/language.properties
new file mode 100644
index 0000000..08ea9ad
--- /dev/null
+++ b/src/main/resources/language/language.properties
@@ -0,0 +1,27 @@
+root.tab.main=\u4E3B\u9875
+root.tab.setup=\u8BBE\u7F6E
+root.foot.doc=\u6587\u6863
+root.foot.github=GitHub
+root.foot.sponsor=\u8D5E\u52A9
+main.control.start=\u5F00\u59CB
+main.control.password=\u5BC6\u7801
+main.control.restart=\u91CD\u542F
+main.control.more=\u66F4\u591A
+main.status.label-running=\u8FD0\u884C\u4E2D
+main.status.label-stop=\u5DF2\u505C\u6B62
+main.more.browser=\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00
+main.more.open-log=\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6\u5939
+setup.proxyInfo=HTTP\u4EE3\u7406
+setup.auto-start.label=\u5F00\u673A\u81EA\u542F
+setup.silent-startup.label=\u9759\u9ED8\u542F\u52A8
+setup.language=\u8BED\u8A00
+alist.version=AList \u7248\u672C
+app.version=GUI \u7248\u672C
+alist.update=\u68C0\u67E5 AList \u7248\u672C
+app.update=\u68C0\u67E5 GUI \u7248\u672C
+setup.proxyInfo.host=\u4E3B\u673A
+setup.proxyInfo.port=\u7AEF\u53E3
+proxy.setup.label.no_proxy=\u4E0D\u4EE3\u7406
+proxy.setup.label.system=\u7CFB\u7EDF\u4EE3\u7406
+proxy.setup.label.manual=\u624B\u52A8\u8BBE\u7F6E
+
diff --git a/src/main/resources/language/language_en.properties b/src/main/resources/language/language_en.properties
new file mode 100644
index 0000000..54f2d1f
--- /dev/null
+++ b/src/main/resources/language/language_en.properties
@@ -0,0 +1,27 @@
+root.tab.main=Home
+root.tab.setup=Setup
+root.foot.doc=Document
+root.foot.github=GitHub
+root.foot.sponsor=Sponsor
+main.control.start=Start
+main.control.password=Password
+main.control.restart=Restart
+main.control.more=More
+main.status.label-running=Running
+main.status.label-stop=Stoped
+main.more.browser=Open in browser
+main.more.open-log=Open the log folder
+setup.proxyInfo=HTTP PROXY
+setup.auto-start.label=Auto start with PC
+setup.silent-startup.label=Silent startup
+setup.language=language
+alist.version=AList Version
+app.version=GUI Version
+alist.update=Check AList Version
+app.update=Check GUI Version
+setup.proxyInfo.host=Host
+setup.proxyInfo.port=Port
+proxy.setup.label.no_proxy=No Proxy
+proxy.setup.label.system=System Proxy
+proxy.setup.label.manual=Manual Config
+
diff --git a/src/main/resources/language/language_zh.properties b/src/main/resources/language/language_zh.properties
new file mode 100644
index 0000000..08ea9ad
--- /dev/null
+++ b/src/main/resources/language/language_zh.properties
@@ -0,0 +1,27 @@
+root.tab.main=\u4E3B\u9875
+root.tab.setup=\u8BBE\u7F6E
+root.foot.doc=\u6587\u6863
+root.foot.github=GitHub
+root.foot.sponsor=\u8D5E\u52A9
+main.control.start=\u5F00\u59CB
+main.control.password=\u5BC6\u7801
+main.control.restart=\u91CD\u542F
+main.control.more=\u66F4\u591A
+main.status.label-running=\u8FD0\u884C\u4E2D
+main.status.label-stop=\u5DF2\u505C\u6B62
+main.more.browser=\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00
+main.more.open-log=\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6\u5939
+setup.proxyInfo=HTTP\u4EE3\u7406
+setup.auto-start.label=\u5F00\u673A\u81EA\u542F
+setup.silent-startup.label=\u9759\u9ED8\u542F\u52A8
+setup.language=\u8BED\u8A00
+alist.version=AList \u7248\u672C
+app.version=GUI \u7248\u672C
+alist.update=\u68C0\u67E5 AList \u7248\u672C
+app.update=\u68C0\u67E5 GUI \u7248\u672C
+setup.proxyInfo.host=\u4E3B\u673A
+setup.proxyInfo.port=\u7AEF\u53E3
+proxy.setup.label.no_proxy=\u4E0D\u4EE3\u7406
+proxy.setup.label.system=\u7CFB\u7EDF\u4EE3\u7406
+proxy.setup.label.manual=\u624B\u52A8\u8BBE\u7F6E
+
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
index 8ee2621..c9386bf 100644
--- a/src/main/resources/logback.xml
+++ b/src/main/resources/logback.xml
@@ -5,7 +5,7 @@
-
+