新增统一通知中心
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