From cb3f0b0938f870cf44ee77f31238774c20747dd6 Mon Sep 17 00:00:00 2001 From: Nathan McRae Date: Sun, 16 Feb 2025 22:25:43 -0800 Subject: [PATCH] Add notifications and some basic usage Since I can't rely on platform specific notifications, I'll just have to open a custom window that stays until dismissed. --- .../name/nathanmcrae/numbersstation/Main.java | 64 ++++++++++---- .../NotificationController.java | 84 +++++++++++++++++++ .../numbersstation/NotificationView.fxml | 57 +++++++++++++ 3 files changed, 191 insertions(+), 14 deletions(-) create mode 100644 src/main/java/name/nathanmcrae/numbersstation/NotificationController.java create mode 100644 src/main/java/name/nathanmcrae/numbersstation/NotificationView.fxml diff --git a/src/main/java/name/nathanmcrae/numbersstation/Main.java b/src/main/java/name/nathanmcrae/numbersstation/Main.java index 7c4ccd7..50a3000 100644 --- a/src/main/java/name/nathanmcrae/numbersstation/Main.java +++ b/src/main/java/name/nathanmcrae/numbersstation/Main.java @@ -16,6 +16,7 @@ import java.util.Date; import java.util.logging.FileHandler; import java.util.logging.Logger; import java.util.logging.Level; +import java.util.Optional; import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; @@ -25,6 +26,8 @@ import javafx.stage.Stage; import org.apache.commons.cli.*; public class Main extends Application { + public record StartParameters (Optional notification) {} + private static final Logger logger = Logger.getLogger(Main.class.getName()); // TODO: get git info private static final String VERSION = "0.0.1"; @@ -44,14 +47,28 @@ public class Main extends Application { return statePath; } + private static StartParameters startParams = new StartParameters(Optional.empty()); + @Override public void start(Stage primaryStage) throws Exception { try { - Parent root = FXMLLoader.load(getClass().getResource("MainView.fxml")); - primaryStage.setTitle("Numbers Station"); - primaryStage.setScene(new Scene(root)); - primaryStage.show(); - logger.info("Application started"); + if (startParams.notification().isPresent()){ + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("NotificationView.fxml")); + Parent root = fxmlLoader.load(); + + NotificationController controller = fxmlLoader.getController(); + controller.setNotification(startParams.notification().get()); + + primaryStage.setTitle("Numbers Station"); + primaryStage.setScene(new Scene(root)); + primaryStage.show(); + } else { + Parent root = FXMLLoader.load(getClass().getResource("MainView.fxml")); + primaryStage.setTitle("Numbers Station"); + primaryStage.setScene(new Scene(root)); + primaryStage.show(); + logger.info("Application started"); + } } catch (IOException e) { logger.log(Level.SEVERE, "Failed to load main view", e); } @@ -98,18 +115,36 @@ public class Main extends Application { setupLogger(); if (parsedArgs.getStationName() != null) { - // TODO: errors in runStation should trigger a notification - runStation(parsedArgs); + try { + runStation(parsedArgs); + } catch (StationRunException e) { + // TODO: Add time of run + var notification = new NotificationController.NotificationParameters("Error running station '" + parsedArgs.getStationName() + "'", + e.getMessage(), + NotificationController.NotificationType.ERROR, + Optional.empty()); + startParams = new StartParameters(Optional.of(notification)); + + launch(args); + System.exit(1); + } } else { launch(args); } } - public static void runStation(MainParsedArguments parsedArgs) { + public static class StationRunException extends Exception { + public StationRunException(String message) { + super(message); + } + } + + public static void runStation(MainParsedArguments parsedArgs) throws StationRunException { if (parsedArgs.getStationName() == null || parsedArgs.getStationName() == "") { - logger.log(Level.SEVERE, "Station name must be provided and not empty"); - // TODO: Notification - System.exit(1); + String message = "Station name must be provided and not empty"; + logger.log(Level.SEVERE, message); + + throw new StationRunException(message); } Result result = MainSettings.load(); @@ -126,9 +161,10 @@ public class Main extends Application { .orElse(null); if (loadedStation == null) { - logger.log(Level.SEVERE, "Unable to find station " + parsedArgs.getStationName()); - // TODO: Notification - System.exit(1); + String message = "Unable to find station '" + parsedArgs.getStationName() + "'"; + logger.log(Level.SEVERE, message); + + throw new StationRunException(message); } logger.info("Loaded station " + parsedArgs.getStationName()); diff --git a/src/main/java/name/nathanmcrae/numbersstation/NotificationController.java b/src/main/java/name/nathanmcrae/numbersstation/NotificationController.java new file mode 100644 index 0000000..3c38437 --- /dev/null +++ b/src/main/java/name/nathanmcrae/numbersstation/NotificationController.java @@ -0,0 +1,84 @@ +package name.nathanmcrae.numbersstation; + +import java.util.logging.Logger; +import java.util.logging.Level; +import java.util.Optional; +import javafx.event.Event; +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.DialogPane; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.Node; +import javafx.scene.paint.Color; +import javafx.stage.Stage; + +public class NotificationController { + public enum NotificationType { + INFO, + ERROR + } + + private static final Logger logger = Logger.getLogger(Main.class.getName()); + + private static final String informationString = "🛈"; + private static final String errorString = "⚠"; + + private Runnable openCallback; + + @FXML + TextArea descriptionTextArea; + + @FXML + Label iconLabel; + + @FXML + Button openButton; + + @FXML + Label titleLabel; + + @FXML + private ImageView iconImageView; + + public void handleOpenButtonPressed() { + if (openCallback != null) { + openCallback.run(); + } + } + + public void handleDismissButtonPressed(Event event) { + Node node = (Node) event.getSource(); + Stage stage = (Stage) node.getScene().getWindow(); + stage.close(); + } + + public record NotificationParameters(String title, + String description, + NotificationType type, + Optional openAction) {} + + public void setNotification(NotificationParameters params) throws IllegalArgumentException { + titleLabel.setText(params.title()); + descriptionTextArea.setText(params.description()); + switch (params.type()) { + case INFO: + iconLabel.setText(informationString); + iconLabel.setTextFill(Color.color(0.2, 0.47, 0.73)); + break; + case ERROR: + iconLabel.setText(errorString); + break; + default: + throw new IllegalArgumentException("Invalid notification type: " + params.type()); + } + + if (params.openAction().isPresent()) { + openButton.setVisible(true); + this.openCallback = params.openAction().get(); + } + } +} diff --git a/src/main/java/name/nathanmcrae/numbersstation/NotificationView.fxml b/src/main/java/name/nathanmcrae/numbersstation/NotificationView.fxml new file mode 100644 index 0000000..9dbe705 --- /dev/null +++ b/src/main/java/name/nathanmcrae/numbersstation/NotificationView.fxml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + +