Compare commits

..

13 Commits

Author SHA1 Message Date
octopus yan
47b001cdf8
Release v1.0.1
Release v1.0.1
2024-09-23 02:58:34 +08:00
b9e842a80c docs: README.md 添加界面截图
Some checks are pending
Auto Alpha Tag / auto-tag (x64, x64) (push) Waiting to run
Release / meta (push) Waiting to run
Release / windows (x64, x64) (push) Blocked by required conditions
Release / release (push) Blocked by required conditions
2024-09-21 20:11:48 +08:00
899f286682 style: 默认关闭托盘 2024-09-21 19:39:18 +08:00
229662bd15 pref: 开始下载后跳转主界面查看下载进度 2024-09-21 19:37:51 +08:00
6294fdbf42 chore: 修改类名 WindowsUtil -> ViewUtil 2024-09-21 19:36:21 +08:00
fbdf6b3ba7 feat: 添加GUI检查更新功能,添加nojre打包方式 2024-09-21 19:34:32 +08:00
828a2fdb62 fix: 修复代理类型下拉框无法跟随语言切换的问题
Some checks are pending
Auto Alpha Tag / auto-tag (x64, x64) (push) Waiting to run
Release / meta (push) Waiting to run
Release / windows (x64, x64) (push) Blocked by required conditions
Release / release (push) Blocked by required conditions
2024-09-21 12:46:21 +08:00
4702396bbc fix: 密码弹窗 2024-09-21 10:13:43 +08:00
0660674f7f pref: I18n 自动绑定
Some checks failed
Auto Alpha Tag / auto-tag (x64, x64) (push) Has been cancelled
Release / meta (push) Has been cancelled
Release / windows (x64, x64) (push) Has been cancelled
Release / release (push) Has been cancelled
2024-09-20 22:06:59 +08:00
1b3a4f5569 fix: 切换语言时theme重复bind导致报错的问题
Some checks failed
Auto Alpha Tag / auto-tag (x64, x64) (push) Has been cancelled
Release / meta (push) Has been cancelled
Release / windows (x64, x64) (push) Has been cancelled
Release / release (push) Has been cancelled
2024-09-19 12:34:52 +08:00
be7e17665f chore: clean up 2024-09-19 12:33:24 +08:00
654b13e150 fix: 托盘初始化时与主界面状态同步 2024-09-19 12:25:22 +08:00
329c484f4c ci: fix Auto Alpha Tag
Some checks are pending
Auto Alpha Tag / auto-tag (x64, x64) (push) Waiting to run
Release / meta (push) Waiting to run
Release / windows (x64, x64) (push) Blocked by required conditions
Release / release (push) Blocked by required conditions
2024-09-19 01:13:45 +08:00
35 changed files with 448 additions and 260 deletions

View File

@ -4,6 +4,11 @@ on:
push: push:
branches: branches:
- "dev" - "dev"
paths:
- ".github/workflows/*.yml"
- "src/**"
- "pom.xml"
- "!**/*.md"
pull_request: pull_request:
branches: branches:
- "dev" - "dev"

View File

@ -18,6 +18,40 @@
### TODO ### TODO
### 截图
<details>
<summary> 主界面 </summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/909ac6ad-0021-47d7-a75c-7fb6505e8c15">
<img alt="main" src="https://github.com/user-attachments/assets/4984f7fb-acaa-4dbc-a322-8b6b89557cbf">
</picture>
</details>
<details>
<summary> 管理员信息 </summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/840dca69-e67d-4083-88f8-8e67c3e47141">
<img alt="main" src="https://github.com/user-attachments/assets/a93d5967-65b5-4185-8bfb-77e55d811532">
</picture>
</details>
<details>
<summary> 设置 </summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/8fc8c489-b9cd-4e34-ad32-4899ccc275e9">
<img alt="main" src="https://github.com/user-attachments/assets/f4cc78df-0718-4bac-9985-3761611f8f57">
</picture>
</details>
<details>
<summary> 关于 </summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/dbef2d66-4ca4-4e89-8292-dbdce3566f93">
<img alt="about" src="https://github.com/user-attachments/assets/0e474a5d-78f3-4475-a8a9-fca15c3ed515">
</picture>
</details>
#### 本地运行 #### 本地运行
1. 克隆代码 1. 克隆代码

19
pom.xml
View File

@ -258,7 +258,6 @@
<vmArgs> <vmArgs>
<arg>--enable-preview</arg> <arg>--enable-preview</arg>
<arg>-Xmx100m</arg> <arg>-Xmx100m</arg>
<!-- <arg>-Djava.awt.headless=false</arg>-->
</vmArgs> </vmArgs>
</configuration> </configuration>
<executions> <executions>
@ -270,6 +269,7 @@
</goals> </goals>
<configuration> <configuration>
<platform>windows</platform> <platform>windows</platform>
<zipballName>${project.name}-windows</zipballName>
<createZipball>true</createZipball> <createZipball>true</createZipball>
<winConfig> <winConfig>
<headerType>gui</headerType> <headerType>gui</headerType>
@ -277,6 +277,23 @@
</winConfig> </winConfig>
</configuration> </configuration>
</execution> </execution>
<execution>
<id>bundling-for-windows-nojre</id>
<phase>package</phase>
<goals>
<goal>package</goal>
</goals>
<configuration>
<zipballName>${project.name}-windows-nojre</zipballName>
<platform>windows</platform>
<createZipball>true</createZipball>
<bundleJre>false</bundleJre>
<winConfig>
<headerType>gui</headerType>
<generateMsi>false</generateMsi>
</winConfig>
</configuration>
</execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>

View File

@ -2,19 +2,28 @@ package cn.octopusyan.alistgui.base;
import cn.octopusyan.alistgui.Application; import cn.octopusyan.alistgui.Application;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.config.I18n;
import cn.octopusyan.alistgui.util.FxmlUtil; import cn.octopusyan.alistgui.util.FxmlUtil;
import cn.octopusyan.alistgui.util.WindowsUtil; import cn.octopusyan.alistgui.util.ViewUtil;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.Labeled;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.stage.Stage; import javafx.stage.Stage;
import lombok.Getter; import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle; import java.util.ResourceBundle;
/** /**
@ -43,6 +52,29 @@ public abstract class BaseController<VM extends BaseViewModel> implements Initia
} }
} }
viewModel = vm; viewModel = vm;
}
/**
* 国际化绑定
*/
private void bindI18n() {
// i18n 绑定
try {
for (Field field : getAllField(this.getClass())) {
I18n i18n = field.getAnnotation(I18n.class);
if (i18n != null && StringUtils.isNoneEmpty(i18n.key())) {
switch (field.get(this)) {
case Labeled labeled -> labeled.textProperty().bind(Context.getLanguageBinding(i18n.key()));
case Tab tab -> tab.textProperty().bind(Context.getLanguageBinding(i18n.key()));
case MenuItem mi -> mi.textProperty().bind(Context.getLanguageBinding(i18n.key()));
default -> {}
}
}
}
} catch (IllegalAccessException e) {
logger.error("获取属性失败", e);
}
} }
@FXML @FXML
@ -51,9 +83,12 @@ public abstract class BaseController<VM extends BaseViewModel> implements Initia
// 全局窗口拖拽 // 全局窗口拖拽
if (dragWindow() && getRootPanel() != null) { if (dragWindow() && getRootPanel() != null) {
// 窗口拖拽 // 窗口拖拽
WindowsUtil.bindDragged(getRootPanel()); ViewUtil.bindDragged(getRootPanel());
} }
// 国际化绑定
bindI18n();
// 初始化数据 // 初始化数据
initData(); initData();
@ -109,4 +144,16 @@ public abstract class BaseController<VM extends BaseViewModel> implements Initia
* 视图事件 * 视图事件
*/ */
public abstract void initViewAction(); public abstract void initViewAction();
private static List<Field> getAllField(Class<?> class1) {
List<Field> list = new ArrayList<>();
while (class1 != Object.class) {
list.addAll(Arrays.stream(class1.getDeclaredFields()).toList());
//获取父类
class1 = class1.getSuperclass();
}
return list;
}
} }

View File

@ -1,17 +1,12 @@
package cn.octopusyan.alistgui.config; package cn.octopusyan.alistgui.config;
import atlantafx.base.theme.Theme;
import cn.octopusyan.alistgui.Application; import cn.octopusyan.alistgui.Application;
import cn.octopusyan.alistgui.base.BaseController; import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.controller.AboutController; import cn.octopusyan.alistgui.controller.*;
import cn.octopusyan.alistgui.controller.MainController;
import cn.octopusyan.alistgui.controller.RootController;
import cn.octopusyan.alistgui.controller.SetupController;
import cn.octopusyan.alistgui.manager.ConfigManager; import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.manager.ConsoleLog; import cn.octopusyan.alistgui.manager.ConsoleLog;
import cn.octopusyan.alistgui.util.FxmlUtil; import cn.octopusyan.alistgui.util.FxmlUtil;
import cn.octopusyan.alistgui.util.ProcessesUtil; import cn.octopusyan.alistgui.util.ProcessesUtil;
import javafx.application.Platform;
import javafx.beans.binding.StringBinding; import javafx.beans.binding.StringBinding;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
@ -41,7 +36,6 @@ public class Context {
private static final Logger log = LoggerFactory.getLogger(Context.class); private static final Logger log = LoggerFactory.getLogger(Context.class);
private static Scene scene; private static Scene scene;
private static final IntegerProperty currentViewIndex = new SimpleIntegerProperty(0); private static final IntegerProperty currentViewIndex = new SimpleIntegerProperty(0);
private static final ObjectProperty<Theme> theme = new SimpleObjectProperty<>(ConfigManager.theme());
/** /**
* 控制器集合 * 控制器集合
@ -79,6 +73,7 @@ public class Context {
case MainController main -> main; case MainController main -> main;
case SetupController setup -> setup; case SetupController setup -> setup;
case AboutController about -> about; case AboutController about -> about;
case PasswordController passwod -> passwod;
default -> throw new IllegalStateException(STR."Unexpected value: \{type}"); default -> throw new IllegalStateException(STR."Unexpected value: \{type}");
}; };
} catch (Exception e) { } catch (Exception e) {
@ -92,10 +87,6 @@ public class Context {
Context.application = application; Context.application = application;
} }
public static ObjectProperty<Theme> themeProperty() {
return theme;
}
// 获取当前所选时区属性 // 获取当前所选时区属性
public static ObjectProperty<Locale> currentLocaleProperty() { public static ObjectProperty<Locale> currentLocaleProperty() {
return currentLocale; return currentLocale;
@ -150,12 +141,6 @@ public class Context {
return LANGUAGE_RESOURCE_FACTORY.getResourceBundleProperty(); return LANGUAGE_RESOURCE_FACTORY.getResourceBundleProperty();
} }
/**
* 初始化 语言
*/
private static void initI18n() {
}
/** /**
* 有此类所在路径决定相对路径 * 有此类所在路径决定相对路径
* *
@ -173,17 +158,8 @@ public class Context {
* @return Scene * @return Scene
*/ */
public static Scene initScene() { public static Scene initScene() {
// locale监听; 切换后重新加载界面
currentLocaleProperty().addListener((_, _, locale) -> Platform.runLater(Context::loadScene));
// 加载
loadScene();
return scene;
}
private static void loadScene() {
try { try {
FXMLLoader loader = FxmlUtil.load("root-view"); FXMLLoader loader = FxmlUtil.load("root-view");
loader.setControllerFactory(Context.getControlFactory());
//底层面板 //底层面板
Pane root = loader.load(); Pane root = loader.load();
Optional.ofNullable(scene).ifPresentOrElse( Optional.ofNullable(scene).ifPresentOrElse(
@ -198,6 +174,7 @@ public class Context {
} catch (Throwable e) { } catch (Throwable e) {
log.error("loadScene error", e); log.error("loadScene error", e);
} }
return scene;
} }
public static int currentViewIndex() { public static int currentViewIndex() {

View File

@ -0,0 +1,15 @@
package cn.octopusyan.alistgui.config;
import java.lang.annotation.*;
/**
* 显示文本绑定
*
* @author octopus_yan
*/
@Documented
@Target({ElementType.FIELD})//用此注解用在属性上
@Retention(RetentionPolicy.RUNTIME)
public @interface I18n {
String key() default "";
}

View File

@ -1,5 +1,6 @@
package cn.octopusyan.alistgui.config; package cn.octopusyan.alistgui.config;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding; import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
@ -26,15 +27,6 @@ public class ObservableResourceBundleFactory {
} }
public StringBinding getStringBinding(String key) { public StringBinding getStringBinding(String key) {
return new StringBinding() { return Bindings.createStringBinding(() -> getResourceBundle().getString(key), resourceBundleProperty);
{
bind(resourceBundleProperty);
}
@Override
protected String computeValue() {
return getResourceBundle().getString(key);
}
};
} }
} }

View File

@ -1,10 +1,10 @@
package cn.octopusyan.alistgui.controller; package cn.octopusyan.alistgui.controller;
import cn.octopusyan.alistgui.base.BaseController; import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.I18n;
import cn.octopusyan.alistgui.manager.ConfigManager; import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.view.alert.AlertUtil;
import cn.octopusyan.alistgui.viewModel.AboutViewModule; import cn.octopusyan.alistgui.viewModel.AboutViewModule;
import javafx.fxml.FXML; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -18,12 +18,22 @@ import org.slf4j.LoggerFactory;
public class AboutController extends BaseController<AboutViewModule> { public class AboutController extends BaseController<AboutViewModule> {
protected final Logger logger = LoggerFactory.getLogger(this.getClass()); protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@FXML
public VBox aboutView; public VBox aboutView;
@FXML
public Label aListVersion; public Label aListVersion;
@I18n(key = "about.alist.version")
public Label aListVersionLabel;
@I18n(key = "about.app.version")
public Label appVersionLabel;
@I18n(key = "about.app.update")
public Button checkAppVersion;
@I18n(key = "about.alist.update")
public Button checkAListVersion;
@Override @Override
public VBox getRootPanel() { public VBox getRootPanel() {
return aboutView; return aboutView;
@ -44,15 +54,12 @@ public class AboutController extends BaseController<AboutViewModule> {
aListVersion.textProperty().bindBidirectional(viewModel.aListVersionProperty()); aListVersion.textProperty().bindBidirectional(viewModel.aListVersionProperty());
} }
@FXML
public void checkAListUpdate() { public void checkAListUpdate() {
viewModel.checkUpdate(ConfigManager.aList()); viewModel.checkUpdate(ConfigManager.aList());
} }
@FXML
public void checkGuiUpdate() { public void checkGuiUpdate() {
// TODO 检查 gui 版本 viewModel.checkUpdate(ConfigManager.gui());
AlertUtil.info("待开发。。。").show();
} }
} }

View File

@ -2,13 +2,16 @@ package cn.octopusyan.alistgui.controller;
import cn.octopusyan.alistgui.base.BaseController; import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.config.I18n;
import cn.octopusyan.alistgui.manager.AListManager; import cn.octopusyan.alistgui.manager.AListManager;
import cn.octopusyan.alistgui.manager.ConsoleLog; import cn.octopusyan.alistgui.manager.ConsoleLog;
import cn.octopusyan.alistgui.util.FxmlUtil; import cn.octopusyan.alistgui.util.FxmlUtil;
import cn.octopusyan.alistgui.viewModel.MainViewModel; import cn.octopusyan.alistgui.viewModel.MainViewModel;
import javafx.fxml.FXML; import javafx.application.Platform;
import javafx.beans.binding.StringBinding;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
@ -25,19 +28,34 @@ import java.io.IOException;
public class MainController extends BaseController<MainViewModel> { public class MainController extends BaseController<MainViewModel> {
protected final Logger logger = LoggerFactory.getLogger(this.getClass()); protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@FXML
public VBox mainView; public VBox mainView;
@FXML
public VBox logArea; public VBox logArea;
@FXML
public ScrollPane logAreaSp; public ScrollPane logAreaSp;
@FXML
@I18n(key = "main.status.label-stop")
public Button statusLabel; public Button statusLabel;
@FXML
@I18n(key = "main.control.start")
public Button startButton; public Button startButton;
@FXML
@I18n(key = "main.control.password")
public Button passwordButton;
@I18n(key = "main.control.restart")
public Button restartButton;
@I18n(key = "main.control.more")
public MenuButton moreButton;
@I18n(key = "main.more.browser")
public MenuItem browserButton; public MenuItem browserButton;
@I18n(key = "main.more.open-config")
public MenuItem configButton;
@I18n(key = "main.more.open-log")
public MenuItem logButton;
private PasswordController controller; private PasswordController controller;
@Override @Override
@ -52,19 +70,18 @@ public class MainController extends BaseController<MainViewModel> {
@Override @Override
public void initViewStyle() { public void initViewStyle() {
// 运行状态监听
viewModel.runningProperty().addListener((_, _, running) -> {
resetStatus(running);
browserButton.disableProperty().set(!running);
});
} }
@Override @Override
public void initViewAction() { public void initViewAction() {
viewModel.startBtnStyleCssProperty().bindContentBidirectional(startButton.getStyleClass());
viewModel.statusLabelStyleCssProperty().bindContentBidirectional(statusLabel.getStyleClass());
viewModel.startBtnTextProperty().bindBidirectional(startButton.textProperty());
viewModel.statusLabelTextProperty().bindBidirectional(statusLabel.textProperty());
viewModel.browserButtonDisableProperty().bindBidirectional(browserButton.disableProperty());
} }
// start button // start button
@FXML
public void start() { public void start() {
if (AListManager.isRunning()) { if (AListManager.isRunning()) {
AListManager.stop(); AListManager.stop();
@ -74,7 +91,6 @@ public class MainController extends BaseController<MainViewModel> {
} }
// password button // password button
@FXML
public void adminPassword() throws IOException { public void adminPassword() throws IOException {
if (controller == null) { if (controller == null) {
FXMLLoader load = FxmlUtil.load("admin-panel"); FXMLLoader load = FxmlUtil.load("admin-panel");
@ -85,29 +101,43 @@ public class MainController extends BaseController<MainViewModel> {
} }
// restart button // restart button
@FXML
public void restart() { public void restart() {
AListManager.restart(); AListManager.restart();
} }
// more button // more button
@FXML
public void openInBrowser() { public void openInBrowser() {
AListManager.openScheme(); AListManager.openScheme();
} }
@FXML
public void openLogFolder() { public void openLogFolder() {
AListManager.openLogFolder(); AListManager.openLogFolder();
} }
@FXML
public void openConfig() { public void openConfig() {
AListManager.openConfig(); AListManager.openConfig();
} }
private String getText(String key) { /**
return Context.getLanguageBinding(key).get(); * 根据运行状态改变按钮样式
*
* @param running 运行状态
*/
private void resetStatus(boolean running) {
String removeStyle = running ? "success" : "danger";
String addStyle = running ? "danger" : "success";
StringBinding button = Context.getLanguageBinding(STR."main.control.\{running ? "stop" : "start"}");
StringBinding status = Context.getLanguageBinding(STR."main.status.label-\{running ? "running" : "stop"}");
Platform.runLater(() -> {
startButton.getStyleClass().remove(removeStyle);
startButton.getStyleClass().add(addStyle);
startButton.textProperty().bind(button);
statusLabel.getStyleClass().remove(addStyle);
statusLabel.getStyleClass().add(removeStyle);
statusLabel.textProperty().bind(status);
});
} }
} }

View File

@ -4,12 +4,14 @@ import atlantafx.base.controls.Popover;
import cn.hutool.core.swing.clipboard.ClipboardUtil; import cn.hutool.core.swing.clipboard.ClipboardUtil;
import cn.octopusyan.alistgui.base.BaseController; import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.config.I18n;
import cn.octopusyan.alistgui.manager.AListManager; import cn.octopusyan.alistgui.manager.AListManager;
import cn.octopusyan.alistgui.viewModel.AdminPanelViewModel; import cn.octopusyan.alistgui.viewModel.AdminPanelViewModel;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField; import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
@ -23,20 +25,20 @@ import org.apache.commons.lang3.StringUtils;
* @author octopus_yan * @author octopus_yan
*/ */
public class PasswordController extends BaseController<AdminPanelViewModel> { public class PasswordController extends BaseController<AdminPanelViewModel> {
@FXML public AnchorPane adminPanel;
private AnchorPane adminPanel;
@FXML @I18n(key = "admin.pwd.toptip")
public Label toptip;
@I18n(key = "admin.pwd.user-field")
public Label usernameLabel;
public TextField usernameField; public TextField usernameField;
@FXML @FXML
public Button copyUsername; public Button copyUsername;
@FXML @I18n(key = "admin.pwd.pwd-field")
public Label passwordLabel;
public PasswordField passwordField; public PasswordField passwordField;
@FXML
public Button refreshPassword; public Button refreshPassword;
@FXML
public Button savePassword; public Button savePassword;
@FXML
public Button copyPassword; public Button copyPassword;
private RootController root; private RootController root;

View File

@ -3,18 +3,19 @@ package cn.octopusyan.alistgui.controller;
import atlantafx.base.controls.ModalPane; import atlantafx.base.controls.ModalPane;
import cn.octopusyan.alistgui.base.BaseController; import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.config.I18n;
import cn.octopusyan.alistgui.manager.ConfigManager; import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.manager.SystemTrayManager; import cn.octopusyan.alistgui.manager.SystemTrayManager;
import cn.octopusyan.alistgui.util.WindowsUtil; import cn.octopusyan.alistgui.util.ViewUtil;
import cn.octopusyan.alistgui.viewModel.RootViewModel; import cn.octopusyan.alistgui.viewModel.RootViewModel;
import com.gluonhq.emoji.EmojiData; import com.gluonhq.emoji.EmojiData;
import com.gluonhq.emoji.util.EmojiImageUtils; import com.gluonhq.emoji.util.EmojiImageUtils;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.css.PseudoClass; import javafx.css.PseudoClass;
import javafx.fxml.FXML;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane; import javafx.scene.control.TabPane;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
@ -31,27 +32,28 @@ import java.util.Locale;
*/ */
public class RootController extends BaseController<RootViewModel> { public class RootController extends BaseController<RootViewModel> {
// 布局 // 布局
@FXML public StackPane rootPane;
private StackPane rootPane; public HBox windowHeader;
@FXML public FontIcon alwaysOnTopIcon;
private HBox windowHeader; public FontIcon minimizeIcon;
@FXML public FontIcon closeIcon;
private FontIcon alwaysOnTopIcon;
@FXML
private FontIcon minimizeIcon;
@FXML
private FontIcon closeIcon;
// 界面 // 界面
@FXML public TabPane tabPane;
private TabPane tabPane;
@I18n(key = "root.tab.main")
public Tab mainTab;
@I18n(key = "root.tab.setup")
public Tab setupTab;
@I18n(key = "root.tab.about")
public Tab aboutTab;
// footer // footer
@FXML @I18n(key = "root.foot.doc")
public Button document; public Button document;
@FXML @I18n(key = "root.foot.github")
public Button github; public Button github;
@FXML @I18n(key = "root.foot.sponsor")
public Button sponsor; public Button sponsor;
private final ModalPane modalPane = new ModalPane(); private final ModalPane modalPane = new ModalPane();
@ -94,6 +96,7 @@ public class RootController extends BaseController<RootViewModel> {
sponsor.setGraphic(juice); sponsor.setGraphic(juice);
}); });
// 遮罩
getRootPanel().getChildren().add(modalPane); getRootPanel().getChildren().add(modalPane);
modalPane.setId("modalPane"); modalPane.setId("modalPane");
// reset side and transition to reuse a single modal pane between different examples // reset side and transition to reuse a single modal pane between different examples
@ -126,22 +129,25 @@ public class RootController extends BaseController<RootViewModel> {
getWindow().setAlwaysOnTop(newVal); getWindow().setAlwaysOnTop(newVal);
}); });
WindowsUtil.bindDragged(windowHeader); ViewUtil.bindDragged(windowHeader);
viewModel.currentViewIndexProperty().bind(tabPane.getSelectionModel().selectedIndexProperty()); viewModel.currentViewIndexProperty().bind(tabPane.getSelectionModel().selectedIndexProperty());
} }
@FXML
public void openDocument() { public void openDocument() {
String locale = Context.getCurrentLocale().equals(Locale.ENGLISH) ? "" : "zh/"; String locale = Context.getCurrentLocale().equals(Locale.ENGLISH) ? "" : "zh/";
Context.openUrl(STR."https://alist.nn.ci/\{locale}"); Context.openUrl(STR."https://alist.nn.ci/\{locale}");
} }
@FXML
public void openGithub() { public void openGithub() {
Context.openUrl("https://github.com/alist-org/alist"); Context.openUrl("https://github.com/alist-org/alist");
} }
public void showTab(int index) {
if (index < 0 || index > 2) return;
tabPane.getSelectionModel().select(index);
}
public void showModal(Node node, boolean persistent) { public void showModal(Node node, boolean persistent) {
modalPane.show(node); modalPane.show(node);
modalPane.setPersistent(persistent); modalPane.setPersistent(persistent);

View File

@ -3,16 +3,15 @@ package cn.octopusyan.alistgui.controller;
import atlantafx.base.theme.Theme; import atlantafx.base.theme.Theme;
import cn.octopusyan.alistgui.base.BaseController; import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.config.I18n;
import cn.octopusyan.alistgui.enums.ProxySetup; import cn.octopusyan.alistgui.enums.ProxySetup;
import cn.octopusyan.alistgui.manager.ConfigManager; import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.view.ProxySetupCell;
import cn.octopusyan.alistgui.viewModel.SetupViewModel; import cn.octopusyan.alistgui.viewModel.SetupViewModel;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.Button; import javafx.scene.control.*;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.util.StringConverter; import javafx.util.StringConverter;
@ -32,26 +31,24 @@ public class SetupController extends BaseController<SetupViewModel> implements I
@FXML @FXML
public VBox setupView; public VBox setupView;
@FXML @I18n(key = "setup.auto-start.label")
public CheckBox autoStartCheckBox; public CheckBox autoStartCheckBox;
@FXML @I18n(key = "setup.silent-startup.label")
public CheckBox silentStartupCheckBox; public CheckBox silentStartupCheckBox;
@FXML @I18n(key = "setup.close-to-tray.label")
public CheckBox closeToTrayCheckBox; public CheckBox closeToTrayCheckBox;
@FXML
public ComboBox<Locale> languageComboBox; public ComboBox<Locale> languageComboBox;
@FXML
public ComboBox<Theme> themeComboBox; public ComboBox<Theme> themeComboBox;
@FXML
public ComboBox<ProxySetup> proxySetupComboBox; public ComboBox<ProxySetup> proxySetupComboBox;
@FXML
public Pane proxySetupPane; public Pane proxySetupPane;
@FXML @I18n(key = "setup.proxy.test")
public Button proxyCheck; public Button proxyCheck;
@FXML
public TextField proxyHost; public TextField proxyHost;
@FXML
public TextField proxyPort; public TextField proxyPort;
@I18n(key = "setup.proxy.host")
public Label hostLabel;
@I18n(key = "setup.proxy.port")
public Label portLabel;
@Override @Override
public VBox getRootPanel() { public VBox getRootPanel() {
@ -64,6 +61,9 @@ public class SetupController extends BaseController<SetupViewModel> implements I
themeComboBox.setItems(FXCollections.observableList(ConfigManager.THEME_LIST)); themeComboBox.setItems(FXCollections.observableList(ConfigManager.THEME_LIST));
proxySetupComboBox.setItems(FXCollections.observableList(List.of(ProxySetup.values()))); proxySetupComboBox.setItems(FXCollections.observableList(List.of(ProxySetup.values())));
proxySetupComboBox.setCellFactory(_ -> new ProxySetupCell());
proxySetupComboBox.setButtonCell(new ProxySetupCell());
themeComboBox.setConverter(new StringConverter<>() { themeComboBox.setConverter(new StringConverter<>() {
@Override @Override
public String toString(Theme object) { public String toString(Theme object) {
@ -82,6 +82,14 @@ public class SetupController extends BaseController<SetupViewModel> implements I
proxySetupComboBox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> { proxySetupComboBox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
proxySetupPane.setVisible(ProxySetup.MANUAL.equals(newValue)); proxySetupPane.setVisible(ProxySetup.MANUAL.equals(newValue));
proxyCheck.setVisible(!ProxySetup.NO_PROXY.equals(newValue)); proxyCheck.setVisible(!ProxySetup.NO_PROXY.equals(newValue));
// proxySetupComboBox.promptTextProperty().bind(
//// Bindings.createStringBinding(
//// () -> Context.getLanguageBinding(STR."proxy.setup.label.\{newValue.getName()}").get(),
//// Context.currentLocaleProperty()
//// )
// Context.getLanguageBinding(STR."proxy.setup.label.\{newValue.getName()}")
// );
}); });
languageComboBox.getSelectionModel().select(ConfigManager.language()); languageComboBox.getSelectionModel().select(ConfigManager.language());
@ -103,7 +111,6 @@ public class SetupController extends BaseController<SetupViewModel> implements I
viewModel.proxySetupProperty().bind(proxySetupComboBox.getSelectionModel().selectedItemProperty()); viewModel.proxySetupProperty().bind(proxySetupComboBox.getSelectionModel().selectedItemProperty());
} }
@FXML
public void proxyTest() { public void proxyTest() {
viewModel.proxyTest(); viewModel.proxyTest();
} }

View File

@ -1,6 +1,7 @@
package cn.octopusyan.alistgui.enums; package cn.octopusyan.alistgui.enums;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import javafx.beans.binding.StringBinding;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -20,6 +21,10 @@ public enum ProxySetup {
@Override @Override
public String toString() { public String toString() {
return Context.getLanguageBinding("proxy.setup.label." + getName()).getValue(); return getBinding().get();
}
public StringBinding getBinding() {
return Context.getLanguageBinding(STR."proxy.setup.label.\{getName()}");
} }
} }

View File

@ -205,7 +205,7 @@ public class AListManager {
} }
var task = new CheckUpdateTask(ConfigManager.aList()); var task = new CheckUpdateTask(ConfigManager.aList());
task.onListen(new TaskListener.UpgradeUpgradeListener(task) { task.onListen(new TaskListener.UpgradeListener(task) {
@Override @Override
public void onChecked(boolean hasUpgrade, String version) { public void onChecked(boolean hasUpgrade, String version) {
Platform.runLater(() -> showDownload(version)); Platform.runLater(() -> showDownload(version));

View File

@ -3,9 +3,10 @@ package cn.octopusyan.alistgui.manager;
import cn.octopusyan.alistgui.Application; import cn.octopusyan.alistgui.Application;
import cn.octopusyan.alistgui.config.Constants; import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.util.WindowsUtil; import cn.octopusyan.alistgui.util.ViewUtil;
import cn.octopusyan.alistgui.view.PopupMenu; import cn.octopusyan.alistgui.view.PopupMenu;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.StringBinding;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.stage.Stage; import javafx.stage.Stage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -47,7 +48,7 @@ public class SystemTrayManager {
public static void icon(String path) { public static void icon(String path) {
if (trayIcon == null) return; if (trayIcon == null) return;
icon(WindowsUtil.class.getResource(path)); icon(ViewUtil.class.getResource(path));
} }
public static void icon(URL url) { public static void icon(URL url) {
@ -76,7 +77,7 @@ public class SystemTrayManager {
return; return;
} }
initTrayIcon(); initTrayIcon(AListManager.isRunning());
try { try {
if (!isShowing()) if (!isShowing())
@ -95,11 +96,12 @@ public class SystemTrayManager {
//========================================={ private }=========================================== //========================================={ private }===========================================
private static void initTrayIcon() { private static void initTrayIcon(boolean running) {
if (trayIcon != null) return; if (trayIcon != null) return;
// 系统托盘图标 // 系统托盘图标
Image image = Toolkit.getDefaultToolkit().getImage(WindowsUtil.class.getResource("/assets/logo-disabled.png")); URL resource = ViewUtil.class.getResource(STR."/assets/logo\{running ? "" : "-disabled"}.png");
Image image = Toolkit.getDefaultToolkit().getImage(resource);
trayIcon = new TrayIcon(image); trayIcon = new TrayIcon(image);
// 设置图标尺寸自动适应 // 设置图标尺寸自动适应
@ -129,7 +131,7 @@ public class SystemTrayManager {
if (event.isPopupTrigger()) { if (event.isPopupTrigger()) {
// 弹出菜单 // 弹出菜单
Platform.runLater(() -> { Platform.runLater(() -> {
initPopupMenu(); initPopupMenu(running);
popupMenu.show(event); popupMenu.show(event);
}); });
} else if (event.getButton() == MouseEvent.BUTTON1) { } else if (event.getButton() == MouseEvent.BUTTON1) {
@ -143,15 +145,19 @@ public class SystemTrayManager {
/** /**
* 构建托盘菜单 * 构建托盘菜单
*/ */
private static void initPopupMenu() { private static void initPopupMenu(boolean running) {
if (popupMenu != null) return; if (popupMenu != null) return;
MenuItem start = PopupMenu.menuItem(getString("main.control.start"), _ -> AListManager.openScheme()); MenuItem start = PopupMenu.menuItem(
MenuItem browser = PopupMenu.menuItem(getString("main.more.browser"), _ -> AListManager.openScheme()); getStringBinding(STR."main.control.\{running ? "stop" : "start"}"),
browser.setDisable(true); _ -> AListManager.openScheme()
);
MenuItem browser = PopupMenu.menuItem(getStringBinding("main.more.browser"), _ -> AListManager.openScheme());
browser.setDisable(!running);
AListManager.runningProperty().addListener((_, _, newValue) -> { AListManager.runningProperty().addListener((_, _, newValue) -> {
start.setText(getString(STR."main.control.\{newValue ? "stop" : "start"}")); start.textProperty().unbind();
start.textProperty().bind(getStringBinding(STR."main.control.\{newValue ? "stop" : "start"}"));
browser.disableProperty().set(!newValue); browser.disableProperty().set(!newValue);
toolTip(STR."AList \{newValue ? "running" : "stopped"}"); toolTip(STR."AList \{newValue ? "running" : "stopped"}");
icon(STR."/assets/logo\{newValue ? "" : "-disabled"}.png"); icon(STR."/assets/logo\{newValue ? "" : "-disabled"}.png");
@ -168,19 +174,19 @@ public class SystemTrayManager {
AListManager.start(); AListManager.start();
} }
}) })
.addItem(getString("main.control.restart"), _ -> AListManager.restart()) .addItem(getStringBinding("main.control.restart"), _ -> AListManager.restart())
.addMenu(getString("main.control.more"), browser, .addMenu(getStringBinding("main.control.more"), browser,
PopupMenu.menuItem(getString("main.more.open-config"), _ -> AListManager.openConfig()), PopupMenu.menuItem(getStringBinding("main.more.open-config"), _ -> AListManager.openConfig()),
PopupMenu.menuItem(getString("main.more.open-log"), _ -> AListManager.openLogFolder())) PopupMenu.menuItem(getStringBinding("main.more.open-log"), _ -> AListManager.openLogFolder()))
.addSeparator() .addSeparator()
.addExitItem(); .addExitItem();
} }
private static String getString(String key) { private static StringBinding getStringBinding(String key) {
return Context.getLanguageBinding(key).get(); return Context.getLanguageBinding(key);
} }
private static Stage stage() { private static Stage stage() {
return WindowsUtil.getStage(); return ViewUtil.getStage();
} }
} }

View File

@ -18,7 +18,7 @@ public class GuiConfig {
private Boolean autoStart = false; private Boolean autoStart = false;
private Boolean silentStartup = false; private Boolean silentStartup = false;
private Boolean closeToTray = true; private Boolean closeToTray = false;
@JsonProperty("proxy") @JsonProperty("proxy")
private ProxyInfo proxyInfo; private ProxyInfo proxyInfo;
@JsonProperty("proxy.testUrl") @JsonProperty("proxy.testUrl")

View File

@ -10,18 +10,12 @@ import lombok.Data;
@Data @Data
public class Gui implements UpgradeApp { public class Gui implements UpgradeApp {
@JsonIgnore @JsonIgnore
private final String owner = "alist-org"; private final String owner = "octopusYan";
@JsonIgnore @JsonIgnore
private final String repo = "alist"; private final String repo = "alist-gui";
private String releaseFile = "alist-gui-windows-nojre.zip";
private String releaseFile = "alist-gui-{version}-windows.zip";
private String version = PropertiesUtils.getInstance().getProperty("app.version"); private String version = PropertiesUtils.getInstance().getProperty("app.version");
public String getReleaseFile() {
return getReleaseFile(version);
}
public String getReleaseFile(String version) {
return releaseFile.replace("{version}", version);
}
} }

View File

@ -1,7 +1,6 @@
package cn.octopusyan.alistgui.task; package cn.octopusyan.alistgui.task;
import cn.octopusyan.alistgui.base.BaseTask; import cn.octopusyan.alistgui.base.BaseTask;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.manager.http.HttpUtil; import cn.octopusyan.alistgui.manager.http.HttpUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -13,10 +12,12 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class DownloadTask extends BaseTask { public class DownloadTask extends BaseTask {
private final String downloadUrl; private final String downloadUrl;
private final String savePath;
public DownloadTask(String downloadUrl) { public DownloadTask(String downloadUrl, String savePath) {
super(STR."Download \{downloadUrl}"); super(STR."Download \{downloadUrl}");
this.downloadUrl = downloadUrl; this.downloadUrl = downloadUrl;
this.savePath = savePath;
} }
public void onListen(DownloadListener listener) { public void onListen(DownloadListener listener) {
@ -27,7 +28,7 @@ public class DownloadTask extends BaseTask {
protected void task() throws Exception { protected void task() throws Exception {
HttpUtil.getInstance().download( HttpUtil.getInstance().download(
downloadUrl, downloadUrl,
Constants.BIN_DIR_PATH, savePath,
listener instanceof DownloadListener ? ((DownloadListener) listener)::onProgress : null listener instanceof DownloadListener ? ((DownloadListener) listener)::onProgress : null
); );
} }

View File

@ -89,8 +89,8 @@ public abstract class TaskListener implements BaseTask.Listener {
/** /**
* 检查更新监听默认实现 * 检查更新监听默认实现
*/ */
public static abstract class UpgradeUpgradeListener extends TaskListener implements CheckUpdateTask.UpgradeListener { public static abstract class UpgradeListener extends TaskListener implements CheckUpdateTask.UpgradeListener {
public UpgradeUpgradeListener(BaseTask task) { public UpgradeListener(BaseTask task) {
super(task); super(task);
} }

View File

@ -5,8 +5,11 @@ import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil; import cn.hutool.core.util.ZipUtil;
import cn.octopusyan.alistgui.config.Constants; import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.controller.RootController;
import cn.octopusyan.alistgui.manager.ConsoleLog; import cn.octopusyan.alistgui.manager.ConsoleLog;
import cn.octopusyan.alistgui.model.upgrade.AList; import cn.octopusyan.alistgui.model.upgrade.AList;
import cn.octopusyan.alistgui.model.upgrade.Gui;
import cn.octopusyan.alistgui.model.upgrade.UpgradeApp; import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
import cn.octopusyan.alistgui.task.DownloadTask; import cn.octopusyan.alistgui.task.DownloadTask;
import cn.octopusyan.alistgui.task.listener.TaskListener; import cn.octopusyan.alistgui.task.listener.TaskListener;
@ -24,7 +27,6 @@ import java.util.zip.ZipFile;
@Slf4j @Slf4j
public class DownloadUtil { public class DownloadUtil {
/** /**
* 下载文件 * 下载文件
* *
@ -32,12 +34,19 @@ public class DownloadUtil {
* @param version 下载版本 * @param version 下载版本
*/ */
public static DownloadTask startDownload(UpgradeApp app, String version, Runnable runnable) { public static DownloadTask startDownload(UpgradeApp app, String version, Runnable runnable) {
var task = new DownloadTask(app.getDownloadUrl(version)); var parentPath = switch (app) {
case AList _ -> Constants.BIN_DIR_PATH;
case Gui _ -> Constants.DATA_DIR_PATH;
default -> throw new IllegalStateException(STR."Unexpected value: \{app}");
};
var task = new DownloadTask(app.getDownloadUrl(version), parentPath);
task.onListen(new TaskListener.DownloadListener(task) { task.onListen(new TaskListener.DownloadListener(task) {
@Override @Override
public void onRunning() { public void onRunning() {
// 不展示进度条 // 不展示进度条
RootController root = (RootController) Context.getControllers().get(RootController.class.getSimpleName());
root.showTab(0);
} }
@Override @Override
@ -64,6 +73,11 @@ public class DownloadUtil {
path = StrUtil.replace(path, "*", "_"); path = StrUtil.replace(path, "*", "_");
} }
// 打包后文件都在alist-gui文件夹下解压时去掉
if (app instanceof Gui) {
path = path.replaceFirst(Constants.APP_NAME, "");
}
final File outItemFile = FileUtil.file(parentPath, path); final File outItemFile = FileUtil.file(parentPath, path);
if (zipEntry.isDirectory()) { if (zipEntry.isDirectory()) {
// 目录 // 目录

View File

@ -25,7 +25,7 @@ public class FxmlUtil {
FxmlUtil.class.getResource(prefix + name + suffix), FxmlUtil.class.getResource(prefix + name + suffix),
bundle, bundle,
new JavaFXBuilderFactory(), new JavaFXBuilderFactory(),
null, Context.getControlFactory(),
StandardCharsets.UTF_8 StandardCharsets.UTF_8
); );
} }

View File

@ -43,7 +43,6 @@ public class Registry {
RESTORE, RESTORE,
SAVE, SAVE,
UNLOAD, UNLOAD,
;
} }
public enum Root { public enum Root {
@ -75,6 +74,5 @@ public class Registry {
REG_LINK, REG_LINK,
REG_FULL_RESOURCE_DESCRIPTOR, REG_FULL_RESOURCE_DESCRIPTOR,
REG_EXPAND_SZ, REG_EXPAND_SZ,
;
} }
} }

View File

@ -13,7 +13,7 @@ import java.util.Map;
* *
* @author octopus_yan * @author octopus_yan
*/ */
public class WindowsUtil { public class ViewUtil {
// 获取系统缩放比 // 获取系统缩放比
public static final double scaleX = Screen.getPrimary().getOutputScaleX(); public static final double scaleX = Screen.getPrimary().getOutputScaleX();
public static final double scaleY = Screen.getPrimary().getOutputScaleY(); public static final double scaleY = Screen.getPrimary().getOutputScaleY();

View File

@ -2,8 +2,9 @@ package cn.octopusyan.alistgui.view;
import atlantafx.base.controls.CaptionMenuItem; import atlantafx.base.controls.CaptionMenuItem;
import cn.octopusyan.alistgui.config.Constants; import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.util.WindowsUtil; import cn.octopusyan.alistgui.util.ViewUtil;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.StringBinding;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.scene.Scene; import javafx.scene.Scene;
@ -47,6 +48,12 @@ public class PopupMenu {
return addItem(new MenuItem(label), handler); return addItem(new MenuItem(label), handler);
} }
public PopupMenu addItem(StringBinding bind, EventHandler<ActionEvent> handler) {
MenuItem menuItem = new MenuItem();
menuItem.textProperty().bind(bind);
return addItem(menuItem, handler);
}
public PopupMenu addItem(MenuItem node, EventHandler<ActionEvent> handler) { public PopupMenu addItem(MenuItem node, EventHandler<ActionEvent> handler) {
node.setOnAction(handler); node.setOnAction(handler);
return addItem(node); return addItem(node);
@ -68,6 +75,12 @@ public class PopupMenu {
return addMenu(new Menu(label), items); return addMenu(new Menu(label), items);
} }
public PopupMenu addMenu(StringBinding label, MenuItem... items) {
Menu menu = new Menu();
menu.textProperty().bind(label);
return addMenu(menu, items);
}
public PopupMenu addMenu(Menu menu, MenuItem... items) { public PopupMenu addMenu(Menu menu, MenuItem... items) {
menu.getItems().addAll(items); menu.getItems().addAll(items);
return addItem(menu); return addItem(menu);
@ -102,8 +115,8 @@ public class PopupMenu {
root.hide(); root.hide();
root.show(utilityStage, root.show(utilityStage,
event.getX() / WindowsUtil.scaleX, event.getX() / ViewUtil.scaleX,
event.getY() / WindowsUtil.scaleY event.getY() / ViewUtil.scaleY
); );
// 获取焦点 (失去焦点隐藏自身) // 获取焦点 (失去焦点隐藏自身)
root.requestFocus(); root.requestFocus();
@ -114,4 +127,11 @@ public class PopupMenu {
menuItem.setOnAction(handler); menuItem.setOnAction(handler);
return menuItem; return menuItem;
} }
public static MenuItem menuItem(StringBinding stringBinding, EventHandler<ActionEvent> handler) {
MenuItem menuItem = new MenuItem();
menuItem.textProperty().bind(stringBinding);
menuItem.setOnAction(handler);
return menuItem;
}
} }

View File

@ -0,0 +1,23 @@
package cn.octopusyan.alistgui.view;
import cn.octopusyan.alistgui.enums.ProxySetup;
import javafx.scene.control.ListCell;
/**
* ProxySetup I18n Cell
*
* @author octopus_yan
*/
public class ProxySetupCell extends ListCell<ProxySetup> {
@Override
protected void updateItem(ProxySetup item, boolean empty) {
super.updateItem(item, empty);
textProperty().unbind();
if (empty || item == null) {
setText("");
} else {
textProperty().bind(item.getBinding());
}
}
}

View File

@ -2,7 +2,7 @@ package cn.octopusyan.alistgui.view.alert.builder;
import cn.octopusyan.alistgui.base.BaseBuilder; import cn.octopusyan.alistgui.base.BaseBuilder;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.util.WindowsUtil; import cn.octopusyan.alistgui.util.ViewUtil;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType; import javafx.scene.control.ButtonType;
@ -26,9 +26,9 @@ public class DefaultBuilder extends BaseBuilder<DefaultBuilder, Dialog<?>> {
DialogPane dialogPane = dialog.getDialogPane(); DialogPane dialogPane = dialog.getDialogPane();
dialogPane.getScene().setFill(Color.TRANSPARENT); dialogPane.getScene().setFill(Color.TRANSPARENT);
WindowsUtil.bindDragged(dialogPane); ViewUtil.bindDragged(dialogPane);
WindowsUtil.bindShadow(dialogPane); ViewUtil.bindShadow(dialogPane);
WindowsUtil.getStage(dialogPane).initStyle(StageStyle.TRANSPARENT); ViewUtil.getStage(dialogPane).initStyle(StageStyle.TRANSPARENT);
dialogPane.getButtonTypes().add(new ButtonType( dialogPane.getButtonTypes().add(new ButtonType(
Context.getLanguageBinding("label.cancel").get(), Context.getLanguageBinding("label.cancel").get(),

View File

@ -1,14 +1,19 @@
package cn.octopusyan.alistgui.viewModel; package cn.octopusyan.alistgui.viewModel;
import cn.octopusyan.alistgui.Application;
import cn.octopusyan.alistgui.base.BaseViewModel; import cn.octopusyan.alistgui.base.BaseViewModel;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.manager.AListManager;
import cn.octopusyan.alistgui.manager.ConfigManager; import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.manager.ConsoleLog; import cn.octopusyan.alistgui.manager.ConsoleLog;
import cn.octopusyan.alistgui.model.upgrade.AList; import cn.octopusyan.alistgui.model.upgrade.AList;
import cn.octopusyan.alistgui.model.upgrade.Gui;
import cn.octopusyan.alistgui.model.upgrade.UpgradeApp; import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
import cn.octopusyan.alistgui.task.CheckUpdateTask; import cn.octopusyan.alistgui.task.CheckUpdateTask;
import cn.octopusyan.alistgui.task.listener.TaskListener; import cn.octopusyan.alistgui.task.listener.TaskListener;
import cn.octopusyan.alistgui.util.DownloadUtil; import cn.octopusyan.alistgui.util.DownloadUtil;
import cn.octopusyan.alistgui.util.ProcessesUtil;
import cn.octopusyan.alistgui.view.alert.AlertUtil; import cn.octopusyan.alistgui.view.alert.AlertUtil;
import cn.octopusyan.alistgui.view.alert.builder.AlertBuilder; import cn.octopusyan.alistgui.view.alert.builder.AlertBuilder;
import javafx.application.Platform; import javafx.application.Platform;
@ -61,52 +66,22 @@ public class AboutViewModule extends BaseViewModel {
* 检查更新 * 检查更新
*/ */
public void checkUpdate(UpgradeApp app) { public void checkUpdate(UpgradeApp app) {
// 检查任务 // 检查任务
startUpgrade(app, () -> { startUpgrade(app, () -> onChecked(app));
// 判断 检查的应用
boolean tag = app instanceof AList;
boolean upgrade = tag ? aListUpgrade.get() : guiUpgrade.get();
String version = tag ? aListVersion.get() : guiVersion.get();
String newVersion = tag ? aListNewVersion.get() : guiNewVersion.get();
String title = Context.getLanguageBinding(STR."about.\{tag ? "alist" : "app"}.update").getValue();
String currentLabel = Context.getLanguageBinding("update.current").get();
String newLabel = Context.getLanguageBinding("update.remote").get();
String header = Context.getLanguageBinding(STR."update.upgrade.\{upgrade ? "new" : "not"}").get();
// 版本检查消息
String msg = STR."\{app.getRepo()}\{upgrade ? "" : STR." \{version}"} \{header} \{upgrade ? newVersion : ""}";
log.info(msg);
ConsoleLog.info(msg);
// 弹窗
AlertBuilder builder = upgrade ? AlertUtil.confirm() : AlertUtil.info();
builder.title(title)
.header(header)
.content(STR."""
\{currentLabel} : \{version}
\{newLabel} : \{newVersion}
""")
.show(() -> {
// 可升级且点击了确定后开始下载任务
if (upgrade)
DownloadUtil.startDownload(app, newVersion, () -> {
// 下载完成后解压并删除文件
DownloadUtil.unzip(app);
// 设置应用版本
Platform.runLater(() -> aListVersion.setValue(aListNewVersion.getValue()));
}).execute();
});
});
} }
/**
* 开始检查更新
*
* @param app 更新的应用
* @param runnable 检查后执行的任务
*/
private void startUpgrade(UpgradeApp app, Runnable runnable) { private void startUpgrade(UpgradeApp app, Runnable runnable) {
// 检查更新的任务 // 检查更新的任务
var task = new CheckUpdateTask(app); var task = new CheckUpdateTask(app);
// 任务监听 // 任务监听
task.onListen(new TaskListener.UpgradeUpgradeListener(task) { task.onListen(new TaskListener.UpgradeListener(task) {
@Override @Override
protected void onSucceed() { protected void onSucceed() {
@ -135,4 +110,62 @@ public class AboutViewModule extends BaseViewModel {
// 执行任务 // 执行任务
task.execute(); task.execute();
} }
private void onChecked(UpgradeApp app) {
// 判断 检查的应用
boolean tag = app instanceof AList;
boolean upgrade = tag ? aListUpgrade.get() : guiUpgrade.get();
String version = tag ? aListVersion.get() : guiVersion.get();
String newVersion = tag ? aListNewVersion.get() : guiNewVersion.get();
String title = Context.getLanguageBinding(STR."about.\{tag ? "alist" : "app"}.update").getValue();
String currentLabel = Context.getLanguageBinding("update.current").get();
String newLabel = Context.getLanguageBinding("update.remote").get();
String header = Context.getLanguageBinding(STR."update.upgrade.\{upgrade ? "new" : "not"}").get();
// 版本检查消息
String msg = STR."\{app.getRepo()}\{upgrade ? "" : STR." \{version}"} \{header} \{upgrade ? newVersion : ""}";
log.info(msg);
ConsoleLog.info(msg);
// 弹窗
AlertBuilder builder = upgrade ? AlertUtil.confirm() : AlertUtil.info();
builder.title(title)
.header(header)
.content(STR."""
\{currentLabel} : \{version}
\{newLabel} : \{newVersion}
""")
.show(() -> {
// 可升级且点击了确定后开始下载任务
if (upgrade)
DownloadUtil.startDownload(app, newVersion, () -> {
// 下载完成后解压并删除文件
DownloadUtil.unzip(app);
// 解压后
Platform.runLater(() -> {
switch (app) {
case AList _ -> {
// 设置应用版本
aListVersion.setValue(aListNewVersion.getValue());
AListManager.restart();
}
case Gui _ -> {
log.info(STR."guiNewVersion => \{guiNewVersion.get()}");
// 重启
Platform.setImplicitExit(true);
Application.getPrimaryStage().close();
ProcessesUtil.init(Constants.DATA_DIR_PATH).exec(STR."\{Constants.APP_NAME}.exe");
}
default -> throw new IllegalStateException(STR."Unexpected value: \{app}");
}
});
}).execute();
});
}
} }

View File

@ -1,11 +1,9 @@
package cn.octopusyan.alistgui.viewModel; package cn.octopusyan.alistgui.viewModel;
import cn.octopusyan.alistgui.base.BaseViewModel; import cn.octopusyan.alistgui.base.BaseViewModel;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.manager.AListManager; import cn.octopusyan.alistgui.manager.AListManager;
import javafx.application.Platform; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.*; import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
/** /**
* 主界面VM * 主界面VM
@ -13,56 +11,14 @@ import javafx.collections.FXCollections;
* @author octopus_yan * @author octopus_yan
*/ */
public class MainViewModel extends BaseViewModel { public class MainViewModel extends BaseViewModel {
private final ListProperty<String> startBtnStyleCss = new SimpleListProperty<>(FXCollections.observableArrayList());
private final StringProperty startBtnText = new SimpleStringProperty();
private final ListProperty<String> statusLabelStyleCss = new SimpleListProperty<>(FXCollections.observableArrayList());
private final StringProperty statusLabelText = new SimpleStringProperty();
private final BooleanProperty browserButtonDisable = new SimpleBooleanProperty();
private final BooleanProperty running = new SimpleBooleanProperty(); private final BooleanProperty running = new SimpleBooleanProperty();
public MainViewModel() { public MainViewModel() {
running.addListener((_, _, running) -> {
resetStatus(running);
browserButtonDisable.set(!running);
});
// 先添加监听再绑定解决切换locale后界面状态显示错误的问题 // 先添加监听再绑定解决切换locale后界面状态显示错误的问题
running.bind(AListManager.runningProperty()); running.bind(AListManager.runningProperty());
} }
public ListProperty<String> startBtnStyleCssProperty() { public BooleanProperty runningProperty() {
return startBtnStyleCss; return running;
}
public StringProperty startBtnTextProperty() {
return startBtnText;
}
public ListProperty<String> statusLabelStyleCssProperty() {
return statusLabelStyleCss;
}
public StringProperty statusLabelTextProperty() {
return statusLabelText;
}
public BooleanProperty browserButtonDisableProperty() {
return browserButtonDisable;
}
public void resetStatus(boolean running) {
String removeStyle = running ? "success" : "danger";
String addStyle = running ? "danger" : "success";
String button = Context.getLanguageBinding(STR."main.control.\{running ? "stop" : "start"}").get();
String status = Context.getLanguageBinding(STR."main.status.label-\{running ? "running" : "stop"}").get();
Platform.runLater(() -> {
startBtnStyleCss.remove(removeStyle);
startBtnStyleCss.add(addStyle);
startBtnText.set(button);
statusLabelStyleCss.remove(addStyle);
statusLabelStyleCss.add(removeStyle);
statusLabelText.set(status);
});
} }
} }

View File

@ -36,7 +36,6 @@ public class SetupViewModel extends BaseViewModel {
public SetupViewModel() { public SetupViewModel() {
theme.bindBidirectional(Context.themeProperty());
theme.addListener((_, _, newValue) -> ConfigManager.theme(newValue)); theme.addListener((_, _, newValue) -> ConfigManager.theme(newValue));
silentStartup.addListener((_, _, newValue) -> ConfigManager.silentStartup(newValue)); silentStartup.addListener((_, _, newValue) -> ConfigManager.silentStartup(newValue));
autoStart.addListener((_, _, newValue) -> { autoStart.addListener((_, _, newValue) -> {

View File

@ -1,3 +1,3 @@
app.name=${project.name} app.name=${project.name}
app.title=AList GUI app.title=AList GUI
app.version=${project.version} app.version=v${project.version}

View File

@ -22,11 +22,11 @@
</StackPane> </StackPane>
<HBox alignment="CENTER" styleClass="shield"> <HBox alignment="CENTER" styleClass="shield">
<Label styleClass="shield-name" text="%about.alist.version"/> <Label fx:id="aListVersionLabel" styleClass="shield-name" text="%about.alist.version"/>
<Label fx:id="aListVersion" styleClass="shield-version"/> <Label fx:id="aListVersion" styleClass="shield-version"/>
</HBox> </HBox>
<HBox alignment="CENTER" styleClass="shield"> <HBox alignment="CENTER" styleClass="shield">
<Label styleClass="shield-name" text="%about.app.version"/> <Label fx:id="appVersionLabel" styleClass="shield-name" text="%about.app.version"/>
<Label styleClass="shield-version" text="v${project.version}"/> <Label styleClass="shield-version" text="v${project.version}"/>
</HBox> </HBox>
<Button fx:id="checkAppVersion" onAction="#checkGuiUpdate" styleClass="flat" text="%about.app.update"/> <Button fx:id="checkAppVersion" onAction="#checkGuiUpdate" styleClass="flat" text="%about.app.update"/>

View File

@ -21,13 +21,13 @@
<VBox alignment="CENTER" spacing="20" <VBox alignment="CENTER" spacing="20"
AnchorPane.bottomAnchor="30" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> AnchorPane.bottomAnchor="30" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0">
<Label style="-fx-background-radius: 10;-fx-background-color: -color-button-bg-hover;" <Label fx:id="toptip" style="-fx-background-radius: 10;-fx-background-color: -color-button-bg-hover;"
styleClass="admin-toptip, button, flat, danger" text="%admin.pwd.toptip"/> styleClass="admin-toptip, button, flat, danger" text="%admin.pwd.toptip"/>
<Pane style="-fx-background-color: transparent"/> <Pane style="-fx-background-color: transparent"/>
<HBox alignment="CENTER" styleClass="admin-field"> <HBox alignment="CENTER" styleClass="admin-field">
<Label text="%admin.pwd.user-field"/> <Label fx:id="usernameLabel" text="%admin.pwd.user-field"/>
<InputGroup fx:id="userField" styleClass="admin-field-value"> <InputGroup fx:id="userField" styleClass="admin-field-value">
<TextField fx:id="usernameField" text="admin" editable="false"/> <TextField fx:id="usernameField" text="admin" editable="false"/>
<Button fx:id="copyUsername" onAction="#copyUsername"> <Button fx:id="copyUsername" onAction="#copyUsername">
@ -39,7 +39,7 @@
</HBox> </HBox>
<HBox alignment="CENTER" styleClass="admin-field"> <HBox alignment="CENTER" styleClass="admin-field">
<Label text="%admin.pwd.pwd-field"/> <Label fx:id="passwordLabel" text="%admin.pwd.pwd-field"/>
<InputGroup styleClass="admin-field-value"> <InputGroup styleClass="admin-field-value">
<PasswordField fx:id="passwordField" editable="false"/> <PasswordField fx:id="passwordField" editable="false"/>
<Button fx:id="refreshPassword" onAction="#savePassword" visible="false" managed="false"> <Button fx:id="refreshPassword" onAction="#savePassword" visible="false" managed="false">

View File

@ -34,8 +34,8 @@
<MenuButton fx:id="moreButton" styleClass="button-outlined, no-arrow" text="%main.control.more"> <MenuButton fx:id="moreButton" styleClass="button-outlined, no-arrow" text="%main.control.more">
<items> <items>
<MenuItem fx:id="browserButton" onAction="#openInBrowser" disable="true" text="%main.more.browser"/> <MenuItem fx:id="browserButton" onAction="#openInBrowser" disable="true" text="%main.more.browser"/>
<MenuItem onAction="#openConfig" text="%main.more.open-config"/> <MenuItem fx:id="configButton" onAction="#openConfig" text="%main.more.open-config"/>
<MenuItem onAction="#openLogFolder" text="%main.more.open-log"/> <MenuItem fx:id="logButton" onAction="#openLogFolder" text="%main.more.open-log"/>
</items> </items>
</MenuButton> </MenuButton>
</HBox> </HBox>

View File

@ -24,14 +24,14 @@
<padding> <padding>
<Insets left="20.0" right="20.0"/> <Insets left="20.0" right="20.0"/>
</padding> </padding>
<Tab text="%root.tab.main"> <Tab fx:id="mainTab" text="%root.tab.main">
<graphic> <graphic>
<FontIcon iconColor="white" iconLiteral="fa-th-large"/> <FontIcon iconColor="white" iconLiteral="fa-th-large"/>
</graphic> </graphic>
<!-- 引入主页 --> <!-- 引入主页 -->
<fx:include fx:id="mainController" source="main-view.fxml" prefWidth="Infinity" prefHeight="-Infinity"/> <fx:include fx:id="mainController" source="main-view.fxml" prefWidth="Infinity" prefHeight="-Infinity"/>
</Tab> </Tab>
<Tab text="%root.tab.setup"> <Tab fx:id="setupTab" text="%root.tab.setup">
<graphic> <graphic>
<FontIcon iconColor="white" iconLiteral="fa-cog"/> <FontIcon iconColor="white" iconLiteral="fa-cog"/>
</graphic> </graphic>
@ -39,7 +39,7 @@
<fx:include fx:id="setupController" source="setup-view.fxml" prefWidth="Infinity" <fx:include fx:id="setupController" source="setup-view.fxml" prefWidth="Infinity"
prefHeight="-Infinity"/> prefHeight="-Infinity"/>
</Tab> </Tab>
<Tab text="%root.tab.about"> <Tab fx:id="aboutTab" text="%root.tab.about">
<graphic> <graphic>
<FontIcon iconColor="white" iconLiteral="fa-info-circle"/> <FontIcon iconColor="white" iconLiteral="fa-info-circle"/>
</graphic> </graphic>

View File

@ -38,9 +38,9 @@
<padding> <padding>
<Insets left="30"/> <Insets left="30"/>
</padding> </padding>
<Label text="%setup.proxy.host"/> <Label fx:id="hostLabel" text="%setup.proxy.host"/>
<TextField fx:id="proxyHost" promptText="127.0.0.1" GridPane.columnIndex="1"/> <TextField fx:id="proxyHost" promptText="127.0.0.1" GridPane.columnIndex="1"/>
<Label text="%setup.proxy.port" GridPane.rowIndex="1"/> <Label fx:id="portLabel" text="%setup.proxy.port" GridPane.rowIndex="1"/>
<TextField fx:id="proxyPort" promptText="8080" GridPane.columnIndex="1" GridPane.rowIndex="1"/> <TextField fx:id="proxyPort" promptText="8080" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
</GridPane> </GridPane>
</VBox> </VBox>