My employer gave me a sit-stand desk, but I was forgetting to use it: I just sat almost all day. So for fun and learning I wrote a simple JavaFX reminder app to remind me to alternate between sitting and standing. The app is pictured below:
In the screenshot the app is telling me to stand, and it shows the time the reminder was posted. When a hardcoded amount of time passes (25 minutes), the app window comes to the foreground and displays the next reminder. The next reminder is automatically scheduled.
The "Switch" button skips to the next reminder. I added it so that I could skip reminders that I didn't feel like doing. The app runs and displays reminders whether or not you use the button. I should rename it to "Skip Reminder".
I have no experience with JavaFX (or Swing) and almost no experience with UIs in general except for JSF and a little web, which is why I'm asking for a code review. My code probably falls short of best practices and maybe there are some bugs.
The initial project structure was generated by Netbeans using "New > Maven > JavaFX Application".
Here is the code. The interesting code is in FXMLController.java.
Scene.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox id="AnchorPane" xmlns:fx="http://javafx.com/fxml" fx:controller="link.sharpe.reminders.FXMLController">
<children>
<Button text="Switch" onAction="#handleButtonAction" fx:id="button" />
<Label fx:id="label" />
<Label fx:id="time" />
</children>
</VBox>
MainApp.java
package link.sharpe.reminders;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainApp extends Application {
static Stage stage;
@Override
public void start(Stage stage) throws Exception {
this.stage = stage;
Parent root = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add("/styles/Styles.css");
stage.setTitle("Reminders");
stage.setScene(scene);
stage.show();
}
}
FXMLController.java
package link.sharpe.reminders;
import java.net.URL;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.util.Duration;
public class FXMLController implements Initializable {
@FXML
private Label label;
@FXML
private Label time;
private List<Reminder> reminders = Arrays.asList(
new Reminder("stand", 25),
new Reminder("short break", 5),
new Reminder("sit", 25),
new Reminder("long break", 15));
private Iterator<Reminder> reminderIterator = reminders.iterator();
private Timeline timeline;
@FXML
void handleButtonAction(ActionEvent event) {
scheduleNextReminder();
}
@Override
public void initialize(URL url, ResourceBundle rb) {
scheduleNextReminder();
}
private KeyValue[] scheduleNextReminder() {
if (timeline != null) {
timeline.stop();
timeline = null;
}
if (!reminderIterator.hasNext()) {
reminderIterator = reminders.iterator();
}
Reminder reminder = reminderIterator.next();
label.setText(reminder.message);
time.setText(LocalTime.now().format(DateTimeFormatter.ofPattern("h:mm a")));
MainApp.stage.setIconified(false);
MainApp.stage.toFront();
timeline = new Timeline(new KeyFrame(
Duration.minutes(reminder.duration),
ae -> scheduleNextReminder()));
timeline.play();
return null;
}
private static class Reminder {
String message;
int duration;
Reminder(String message, int duration) {
this.message = message;
this.duration = duration;
}
}
}
Styles.css
.button {
-fx-font-weight: bold;
}
.label {
-fx-font-size: 16pt;
}
(The whole project is hosted on this Git repo.)
