新增统一通知中心
This commit is contained in:
@@ -0,0 +1,106 @@
|
|||||||
|
package com.example.workutils.system.ui.notification;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.scene.control.TextArea;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class FxNotificationCenter implements NotificationCenter {
|
||||||
|
|
||||||
|
private static final int LONG_MESSAGE_LENGTH = 240;
|
||||||
|
|
||||||
|
private final Window owner;
|
||||||
|
|
||||||
|
public FxNotificationCenter(Window owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show(NotificationMessage message) {
|
||||||
|
Objects.requireNonNull(message, "message");
|
||||||
|
if (Platform.isFxApplicationThread()) {
|
||||||
|
showNow(message);
|
||||||
|
} else {
|
||||||
|
Platform.runLater(() -> showNow(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirm(String title, String header, String message) {
|
||||||
|
if (!Platform.isFxApplicationThread()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"confirm must be called on the JavaFX Application Thread");
|
||||||
|
}
|
||||||
|
String normalizedMessage = requireMessage(message);
|
||||||
|
Alert alert = createAlert(Alert.AlertType.CONFIRMATION, title);
|
||||||
|
alert.setHeaderText(normalizeOptional(header));
|
||||||
|
setMessage(alert, normalizedMessage);
|
||||||
|
return alert.showAndWait().filter(ButtonType.OK::equals).isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showNow(NotificationMessage message) {
|
||||||
|
Alert alert = createAlert(alertType(message.level()), message.title());
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
setMessage(alert, message.message());
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Alert createAlert(Alert.AlertType type, String title) {
|
||||||
|
Alert alert = new Alert(type);
|
||||||
|
alert.setTitle(normalizeTitle(title));
|
||||||
|
if (owner != null) {
|
||||||
|
alert.initOwner(owner);
|
||||||
|
}
|
||||||
|
return alert;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMessage(Alert alert, String message) {
|
||||||
|
if (!message.contains("\n") && message.length() <= LONG_MESSAGE_LENGTH) {
|
||||||
|
alert.setContentText(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TextArea details = new TextArea(message);
|
||||||
|
details.setEditable(false);
|
||||||
|
details.setWrapText(true);
|
||||||
|
details.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
details.setMaxHeight(Double.MAX_VALUE);
|
||||||
|
GridPane.setVgrow(details, Priority.ALWAYS);
|
||||||
|
GridPane.setHgrow(details, Priority.ALWAYS);
|
||||||
|
alert.getDialogPane().setExpandableContent(details);
|
||||||
|
alert.getDialogPane().setExpanded(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Alert.AlertType alertType(NotificationLevel level) {
|
||||||
|
return switch (level) {
|
||||||
|
case INFO -> Alert.AlertType.INFORMATION;
|
||||||
|
case WARNING -> Alert.AlertType.WARNING;
|
||||||
|
case ERROR -> Alert.AlertType.ERROR;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeTitle(String title) {
|
||||||
|
String normalized = title == null ? "" : title.trim();
|
||||||
|
return normalized.isEmpty() ? "提示" : normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeOptional(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String normalized = value.trim();
|
||||||
|
return normalized.isEmpty() ? null : normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requireMessage(String message) {
|
||||||
|
String normalized = Objects.requireNonNull(message, "message").trim();
|
||||||
|
if (normalized.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("message must not be blank");
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.example.workutils.system.ui.notification;
|
||||||
|
|
||||||
|
import static com.example.workutils.system.ui.notification.NotificationLevel.ERROR;
|
||||||
|
import static com.example.workutils.system.ui.notification.NotificationLevel.INFO;
|
||||||
|
import static com.example.workutils.system.ui.notification.NotificationLevel.WARNING;
|
||||||
|
|
||||||
|
public interface NotificationCenter {
|
||||||
|
|
||||||
|
void show(NotificationMessage message);
|
||||||
|
|
||||||
|
boolean confirm(String title, String header, String message);
|
||||||
|
|
||||||
|
default void info(String title, String message) {
|
||||||
|
show(new NotificationMessage(INFO, title, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
default void warning(String title, String message) {
|
||||||
|
show(new NotificationMessage(WARNING, title, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
default void error(String title, String message) {
|
||||||
|
show(new NotificationMessage(ERROR, title, message));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.example.workutils.system.ui.notification;
|
||||||
|
|
||||||
|
public enum NotificationLevel {
|
||||||
|
INFO,
|
||||||
|
WARNING,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.example.workutils.system.ui.notification;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record NotificationMessage(
|
||||||
|
NotificationLevel level,
|
||||||
|
String title,
|
||||||
|
String message) {
|
||||||
|
|
||||||
|
public NotificationMessage {
|
||||||
|
level = Objects.requireNonNull(level, "level");
|
||||||
|
title = Objects.requireNonNull(title, "title").trim();
|
||||||
|
message = Objects.requireNonNull(message, "message").trim();
|
||||||
|
if (title.isEmpty()) {
|
||||||
|
title = "提示";
|
||||||
|
}
|
||||||
|
if (message.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("message must not be blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package com.example.workutils.system.ui.notification;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
class FxNotificationCenterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsSynchronousConfirmationOutsideFxThread() {
|
||||||
|
FxNotificationCenter center = new FxNotificationCenter(null);
|
||||||
|
|
||||||
|
IllegalStateException error = assertThrows(IllegalStateException.class,
|
||||||
|
() -> center.confirm("确认", "继续?", "操作不可撤销"));
|
||||||
|
|
||||||
|
assertEquals("confirm must be called on the JavaFX Application Thread",
|
||||||
|
error.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package com.example.workutils.system.ui.notification;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.example.workutils.system.ui.notification.NotificationLevel.ERROR;
|
||||||
|
import static com.example.workutils.system.ui.notification.NotificationLevel.INFO;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
class NotificationMessageTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void normalizesTitleAndMessageForDisplay() {
|
||||||
|
NotificationMessage message = new NotificationMessage(INFO, " ", " 完成\n详情 ");
|
||||||
|
|
||||||
|
assertEquals("提示", message.title());
|
||||||
|
assertEquals("完成\n详情", message.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsBlankMessage() {
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new NotificationMessage(ERROR, "失败", " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void convenienceMethodsCreateTypedMessages() {
|
||||||
|
List<NotificationMessage> shown = new ArrayList<>();
|
||||||
|
NotificationCenter center = new NotificationCenter() {
|
||||||
|
@Override
|
||||||
|
public void show(NotificationMessage message) {
|
||||||
|
shown.add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirm(String title, String header, String message) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
center.info("完成", "导出成功");
|
||||||
|
center.error("失败", "无法打开文件");
|
||||||
|
|
||||||
|
assertEquals(List.of(
|
||||||
|
new NotificationMessage(INFO, "完成", "导出成功"),
|
||||||
|
new NotificationMessage(ERROR, "失败", "无法打开文件")), shown);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user