Compare commits
28 Commits
3bda390708
...
2864c54c72
Author | SHA1 | Date |
---|---|---|
Gian-Andrea Hutter | 2864c54c72 | |
Gian-Andrea Hutter | a20edae4b8 | |
giavaphi | 2dd8cffda2 | |
giavaphi | 3f51adcbd0 | |
giavaphi | 4acd328286 | |
Gian-Andrea Hutter | dbba4e2662 | |
schrom01 | 6aeda395c3 | |
schrom01 | 7741569659 | |
schrom01 | 27b8d1754e | |
David Guler | 06c4c47e44 | |
giavaphi | dc0830120f | |
giavaphi | 39bff805ac | |
giavaphi | 3ebbb0e0e3 | |
Roman Schenk | 8a2119028c | |
giavaphi | 083d934472 | |
schrom01 | 1b096035a7 | |
schrom01 | 0074d43364 | |
Gian-Andrea Hutter | 08d40d8b80 | |
Roman Schenk | c6952aba55 | |
Roman Schenk | 807a9017ad | |
Roman Schenk | c134025408 | |
Roman Schenk | 1e95e0ff30 | |
David Guler | fb21797040 | |
Elias Csomor | 76197a19df | |
schrom01 | 5f7d690875 | |
giavaphi | 96dc0ad827 | |
Elias Csomor | 56020a7529 | |
Elias Csomor | 5830ee5180 |
|
@ -12,6 +12,7 @@ import ch.zhaw.gartenverwaltung.types.Crop;
|
|||
import ch.zhaw.gartenverwaltung.types.Pest;
|
||||
import ch.zhaw.gartenverwaltung.types.Plant;
|
||||
import ch.zhaw.gartenverwaltung.types.Task;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -133,8 +134,14 @@ public class CropDetailController {
|
|||
initializeTaskListProperty(crop);
|
||||
|
||||
TaskList.TaskListObserver taskListObserver = newTaskList -> {
|
||||
taskListProperty.clear();
|
||||
taskListProperty.addAll(gardenSchedule.getTaskListForCrop(crop.getCropId().get()));
|
||||
Platform.runLater(() -> {
|
||||
taskListProperty.clear();
|
||||
try {
|
||||
taskListProperty.addAll(gardenSchedule.getTaskListForCrop(crop.getCropId().get()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
};
|
||||
gardenSchedule.setTaskListObserver(taskListObserver);
|
||||
|
||||
|
|
|
@ -13,30 +13,46 @@ import javafx.stage.Stage;
|
|||
import java.io.IOException;
|
||||
import java.util.Timer;
|
||||
|
||||
/**
|
||||
* Main class of the Application
|
||||
*/
|
||||
public class Main extends Application {
|
||||
Timer backGroundTaskTimer = new Timer();
|
||||
BackgroundTasks backgroundTasks;
|
||||
|
||||
/**
|
||||
* Method which is automatically called if Application is starting. It loads the scenes to stage and shows the stage.
|
||||
* It creates a new Instance of BackgroundTasks and schedules them with a Timer instance to execute them every minute.
|
||||
* @param stage Stage to show
|
||||
* @throws IOException If loading Scenes can not access the fxml Resource File
|
||||
*/
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
AppLoader appLoader = new AppLoader();
|
||||
|
||||
backgroundTasks = new BackgroundTasks((TaskList) appLoader.getAppDependency(TaskList.class),(CropList) appLoader.getAppDependency(CropList.class), (PlantList) appLoader.getAppDependency(PlantList.class));
|
||||
|
||||
appLoader.loadSceneToStage("MainFXML.fxml", stage);
|
||||
|
||||
stage.setTitle("Gartenverwaltung");
|
||||
stage.show();
|
||||
|
||||
backgroundTasks = new BackgroundTasks((TaskList) appLoader.getAppDependency(TaskList.class),(CropList) appLoader.getAppDependency(CropList.class), (PlantList) appLoader.getAppDependency(PlantList.class));
|
||||
backGroundTaskTimer.scheduleAtFixedRate(backgroundTasks, 0, 60000);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method which is automatically called when application is stopped.
|
||||
* It cancels the timer to not execute the background tasks anymore.
|
||||
*/
|
||||
@Override
|
||||
public void stop(){
|
||||
backGroundTaskTimer.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Main method launches the application
|
||||
* @param args There are no arguments needed.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
launch();
|
||||
}
|
||||
|
|
|
@ -3,15 +3,15 @@ package ch.zhaw.gartenverwaltung;
|
|||
import ch.zhaw.gartenverwaltung.bootstrap.AfterInject;
|
||||
import ch.zhaw.gartenverwaltung.bootstrap.Inject;
|
||||
import ch.zhaw.gartenverwaltung.io.PlantList;
|
||||
import ch.zhaw.gartenverwaltung.io.TaskList;
|
||||
import ch.zhaw.gartenverwaltung.models.Garden;
|
||||
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
|
||||
import ch.zhaw.gartenverwaltung.models.GardenSchedule;
|
||||
import ch.zhaw.gartenverwaltung.types.Crop;
|
||||
import ch.zhaw.gartenverwaltung.types.Plant;
|
||||
import ch.zhaw.gartenverwaltung.types.Task;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
|
@ -24,6 +24,7 @@ import javafx.scene.layout.VBox;
|
|||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
@ -51,9 +52,15 @@ public class MyScheduleController {
|
|||
@FXML
|
||||
private ListView<Crop> scheduledPlants_listview;
|
||||
|
||||
@FXML
|
||||
private void showAllTasks(ActionEvent actionEvent) throws IOException {
|
||||
gardenSchedule.getTasksUpcomingWeek();
|
||||
scheduledPlants_listview.getSelectionModel().clearSelection();
|
||||
}
|
||||
|
||||
@AfterInject
|
||||
@SuppressWarnings("unused")
|
||||
public void init() {
|
||||
public void init() throws IOException {
|
||||
setCellFactoryCropListView();
|
||||
setCellFactoryTaskListView();
|
||||
scheduledPlants_listview.itemsProperty().bind(garden.getPlantedCrops());
|
||||
|
@ -61,6 +68,19 @@ public class MyScheduleController {
|
|||
week_listView.itemsProperty().bind(taskListProperty);
|
||||
lookForSelectedListEntries();
|
||||
information_label.setText("");
|
||||
|
||||
gardenSchedule.getTasksUpcomingWeek();
|
||||
TaskList.TaskListObserver taskListObserver = newTaskList -> {
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
gardenSchedule.getTasksUpcomingWeek();
|
||||
scheduledPlants_listview.getSelectionModel().clearSelection();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
gardenSchedule.setTaskListObserver(taskListObserver);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,6 +101,8 @@ public class MyScheduleController {
|
|||
* set cellFactory for the crops.
|
||||
*/
|
||||
private void setCellFactoryCropListView() {
|
||||
MultipleSelectionModel<Crop> selectionModel = scheduledPlants_listview.getSelectionModel();
|
||||
selectionModel.setSelectionMode(SelectionMode.MULTIPLE);
|
||||
scheduledPlants_listview.setCellFactory(param -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(Crop crop, boolean empty) {
|
||||
|
@ -133,8 +155,6 @@ public class MyScheduleController {
|
|||
} else {
|
||||
gardenSchedule.getTasksUpcomingWeek();
|
||||
}
|
||||
//taskListProperty.clear();
|
||||
//taskListProperty.addAll(taskLists);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -189,6 +209,12 @@ public class MyScheduleController {
|
|||
alert.setHeaderText("Are you sure you have completed this task?");
|
||||
alert.setContentText("Confirming that you have completed the task will remove it from the schedule.");
|
||||
|
||||
DialogPane dialogPane = alert.getDialogPane();
|
||||
|
||||
dialogPane.getStylesheets().add(
|
||||
Objects.requireNonNull(getClass().getResource("bootstrap/dialogStyle.css")).toExternalForm());
|
||||
dialogPane.getStyleClass().add("myDialog");
|
||||
|
||||
alert.showAndWait()
|
||||
.ifPresent(buttonType -> {
|
||||
if (buttonType == ButtonType.OK) {
|
||||
|
@ -202,5 +228,4 @@ public class MyScheduleController {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -6,21 +6,58 @@ import javafx.beans.property.BooleanProperty;
|
|||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Singleton Class to store default Settings and User Settings
|
||||
*/
|
||||
public class Settings {
|
||||
private static final Settings instance;
|
||||
private HardinessZone currentHardinessZone = HardinessZone.ZONE_8A;
|
||||
private final BooleanProperty showTutorial = new SimpleBooleanProperty(false);
|
||||
private SmtpCredentials smtpCredentials = new SmtpCredentials("imap.gmail.com", "587", "pm3.hs22.it21b.win.team1@gmail.com", "pm3.hs22.it21b.win.team1@gmail.com", "bisefhhjtrrhtoqr", true);
|
||||
// Gmail Address: pm3.hs22.it21b.win.team1@gmail.com
|
||||
// Gmail Passwort: Gartenverwaltung.PM3.2022
|
||||
// E-Mail Inbox: https://www.mailinator.com/v4/public/inboxes.jsp?to=pm3.hs22.it21b.win.team1
|
||||
private String mailNotificationReceivers = "pm3.hs22.it21b.win.team1@mailinator.com";
|
||||
private String mailNotificationSubjectTemplate = "Task %s is due!"; // {0} = Task Name
|
||||
private String mailNotificationTextTemplate = "Dear user\nYour gardentask %s for plant %s is due at %tF. Don't forget to confirm in your application if the task is done.\nTask description:\n%s"; // {0} = Task Name, {1} = plantname, {2} = nextExecution, {3} = Task description
|
||||
/*
|
||||
* The Class instance to use everywhere
|
||||
*/
|
||||
|
||||
private static final Settings instance;
|
||||
/**
|
||||
* The current Hardiness zone initialized as default value Zone_8A
|
||||
*/
|
||||
private HardinessZone currentHardinessZone = HardinessZone.ZONE_8A;
|
||||
/**
|
||||
* Setting to show or hide the Tutorial in Main Menu
|
||||
*/
|
||||
private final BooleanProperty showTutorial = new SimpleBooleanProperty(false);
|
||||
/**
|
||||
* The SMTP Credentials which are used to send E-Mail Notifications
|
||||
* The following Google Account is created for Testing:
|
||||
* E-Mail Address: pm3.hs22.it21b.win.team1@gmail.com
|
||||
* Account Password: Gartenverwaltung.PM3.2022
|
||||
* SMTP Password: bisefhhjtrrhtoqr
|
||||
*/
|
||||
private SmtpCredentials smtpCredentials = new SmtpCredentials("imap.gmail.com", "587", "pm3.hs22.it21b.win.team1@gmail.com", "pm3.hs22.it21b.win.team1@gmail.com", "bisefhhjtrrhtoqr", true);
|
||||
/**
|
||||
* List of Receivers for E-Mailnotifications. Multiple E-Mailadresses can be separated by ";"
|
||||
* Following Public E-mail address was used for Testing: pm3.hs22.it21b.win.team1@mailinator.com
|
||||
* The E-Mail inbox of this address can be found here: https://www.mailinator.com/v4/public/inboxes.jsp?to=pm3.hs22.it21b.win.team1
|
||||
*/
|
||||
private String mailNotificationReceivers = "pm3.hs22.it21b.win.team1@mailinator.com";
|
||||
/**
|
||||
* String Template to create E-Mail Subject for Notifications
|
||||
* Variables: Task Name
|
||||
*/
|
||||
private String mailNotificationSubjectTemplate = "Task %s is due!";
|
||||
/**
|
||||
* String Template to create E-Mail Text for Notifications
|
||||
* Variables: Task Name, plant Name, nextExecution, Task description
|
||||
*/
|
||||
private String mailNotificationTextTemplate = "Dear user\nYour gardentask %s for plant %s is due at %tF. Don't forget to confirm in your application if the task is done.\nTask description:\n%s";
|
||||
|
||||
/**
|
||||
* Location of the garden can be set by user (Used for Weather Service)
|
||||
*/
|
||||
private String location = "";
|
||||
|
||||
/*
|
||||
Create Instance of Settings
|
||||
*/
|
||||
static {
|
||||
instance = new Settings();
|
||||
}
|
||||
|
@ -29,14 +66,21 @@ public class Settings {
|
|||
return Settings.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor to prevent Classes from creating more instances
|
||||
*/
|
||||
private Settings() {}
|
||||
|
||||
public HardinessZone getCurrentHardinessZone() {
|
||||
return currentHardinessZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set the current Hardiness Zone. If no Hardiness Zone is given the default zone (ZONE_8A) will be used.
|
||||
* @param currentHardinessZone the new Hardiness Zone
|
||||
*/
|
||||
public void setCurrentHardinessZone(HardinessZone currentHardinessZone) {
|
||||
this.currentHardinessZone = currentHardinessZone;
|
||||
this.currentHardinessZone = Objects.requireNonNullElse(currentHardinessZone, HardinessZone.ZONE_8A);
|
||||
}
|
||||
|
||||
public void setShowTutorial (boolean showTutorial) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import javafx.scene.image.Image;
|
|||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Controller class for the Tutorial.fxml file
|
||||
|
@ -22,6 +23,8 @@ public class TutorialController {
|
|||
public ImageView imgAddNewPlant;
|
||||
public ImageView imgTaskList;
|
||||
public ImageView imgSelectDate;
|
||||
public ImageView imgAddTaskButton;
|
||||
public ImageView imgDetailDeleteButtons;
|
||||
|
||||
private int page = 0;
|
||||
|
||||
|
@ -30,10 +33,27 @@ public class TutorialController {
|
|||
switchViews();
|
||||
setButtonAbilities();
|
||||
|
||||
Image placeholder = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
|
||||
imgAddNewPlant.setImage(placeholder);
|
||||
imgSelectDate.setImage(placeholder);
|
||||
imgTaskList.setImage(placeholder);
|
||||
setImageView(imgAddNewPlant, "add-new-plant.png");
|
||||
setImageView(imgSelectDate, "select-sow-harvest.png");
|
||||
setImageView(imgDetailDeleteButtons, "details-delete.png");
|
||||
setImageView(imgTaskList, "schedule.png");
|
||||
setImageView(imgAddTaskButton, "add-task.png");
|
||||
}
|
||||
|
||||
/**
|
||||
* update the given image view with screenshot or placeholder image.
|
||||
* @param imageView the image view to update
|
||||
* @param fileName the file name of the source
|
||||
*/
|
||||
private void setImageView(ImageView imageView, String fileName) {
|
||||
InputStream is = PlantsController.class.getResourceAsStream("screenshots/" + fileName);
|
||||
Image image;
|
||||
if (is != null) {
|
||||
image = new Image(String.valueOf(PlantsController.class.getResource("screenshots/" + fileName)));
|
||||
} else {
|
||||
image = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
|
||||
}
|
||||
imageView.setImage(image);
|
||||
}
|
||||
|
||||
public void viewNextPage() {
|
||||
|
|
|
@ -17,14 +17,27 @@ import java.util.TimerTask;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Class with tasks which must be executed periodic and not triggered by user. Method run must be called in a fix interval.
|
||||
*/
|
||||
public class BackgroundTasks extends TimerTask {
|
||||
private static final Logger LOG = Logger.getLogger(CropDetailController.class.getName());
|
||||
private final TaskList taskList;
|
||||
private final Notifier notifier;
|
||||
private final WeatherGradenTaskPlanner weatherGardenTaskPlaner;
|
||||
|
||||
public BackgroundTasks(TaskList taskList, CropList cropList, PlantList plantList) {
|
||||
this.taskList = taskList;
|
||||
notifier = new Notifier(taskList, plantList, cropList);
|
||||
weatherGardenTaskPlaner = new WeatherGradenTaskPlanner(taskList, plantList, cropList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the field "nextExecution" of all tasks if it's in the past to the actual date as long as they are not done.
|
||||
* @throws IOException if the taskList can't be read.
|
||||
*/
|
||||
private void movePastTasks() throws IOException {
|
||||
List<Task> taskList = this.taskList.getTaskList(LocalDate.MIN, LocalDate.now().minusDays(1));
|
||||
List<Task> taskList = this.taskList.getTaskList(LocalDate.MIN.plusDays(1), LocalDate.now().minusDays(1));
|
||||
for (Task task : taskList) {
|
||||
if (!task.isDone()) {
|
||||
task.setNextExecution(LocalDate.now());
|
||||
|
@ -33,31 +46,28 @@ public class BackgroundTasks extends TimerTask {
|
|||
}
|
||||
}
|
||||
|
||||
public BackgroundTasks(TaskList taskList, CropList cropList, PlantList plantList) {
|
||||
this.taskList = taskList;
|
||||
notifier = new Notifier(taskList, plantList, cropList);
|
||||
weatherGardenTaskPlaner = new WeatherGradenTaskPlanner(taskList, plantList, cropList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to call if Tasks should be executed. It calls all Background tasks after each other.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
movePastTasks();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOG.log(Level.SEVERE, "Could not execute Background Task: move past Tasks ", e.getCause());
|
||||
LOG.log(Level.WARNING, "Could not execute Background Task: move past Tasks ", e.getCause());
|
||||
}
|
||||
try {
|
||||
weatherGardenTaskPlaner.refreshTasks();
|
||||
} catch (IOException | HardinessZoneNotSetException | PlantNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
LOG.log(Level.SEVERE, "Could not execute Background Task: Refresh Tasks by WeatherGardenTaskPlaner ", e.getCause());
|
||||
LOG.log(Level.WARNING, "Could not execute Background Task: Refresh Tasks by WeatherGardenTaskPlaner ", e.getCause());
|
||||
}
|
||||
try {
|
||||
notifier.sendNotifications();
|
||||
} catch (IOException | MessagingException | HardinessZoneNotSetException e) {
|
||||
e.printStackTrace();
|
||||
LOG.log(Level.SEVERE, "Could not execute Background Task: send Notification for due Tasks", e.getCause());
|
||||
LOG.log(Level.WARNING, "Could not execute Background Task: send Notification for due Tasks", e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,9 @@ import javax.mail.MessagingException;
|
|||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Class to send Notifications to the user
|
||||
*/
|
||||
public class Notifier {
|
||||
private final TaskList taskList;
|
||||
private final CropList cropList;
|
||||
|
@ -25,6 +28,13 @@ public class Notifier {
|
|||
this.plantList = plantList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to send a Notification to the user for e specific Task
|
||||
* @param task The task to use for notification
|
||||
* @throws IOException if tasklist can not be read
|
||||
* @throws MessagingException if E-Mail can not be sent for any reason
|
||||
* @throws HardinessZoneNotSetException if hardiness zone is not set in plant list.
|
||||
*/
|
||||
private void sendNotification(Task task) throws IOException, MessagingException, HardinessZoneNotSetException {
|
||||
String plantName = "unkown plant";
|
||||
if(cropList.getCropById((task.getCropId())).isPresent()){
|
||||
|
@ -38,6 +48,12 @@ public class Notifier {
|
|||
eMailSender.sendMails(Settings.getInstance().getMailNotificationReceivers(), messageSubject, messageText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a notification to the user for each task which is due and not done every day as long as it's not done.
|
||||
* @throws IOException if tasklist can not be read
|
||||
* @throws MessagingException if E-Mail can not be sent for any reason
|
||||
* @throws HardinessZoneNotSetException if hardiness zone is not set in plant list.
|
||||
*/
|
||||
public void sendNotifications() throws IOException, MessagingException, HardinessZoneNotSetException {
|
||||
for (Task task : taskList.getTaskList(LocalDate.MIN, LocalDate.MAX)) {
|
||||
if (task.getNextNotification() != null && task.getNextNotification().isBefore(LocalDate.now().minusDays(1))) {
|
||||
|
|
|
@ -10,16 +10,20 @@ import java.util.Arrays;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class to send E-Mails
|
||||
*/
|
||||
public class EMailSender {
|
||||
|
||||
|
||||
public EMailSender(){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to send E-Mail to one or multiple recipients
|
||||
* @param recipients recipients E-Mail addresses separated by ";"
|
||||
* @param subject Subject of the E-Mail
|
||||
* @param text E-Mail message Text as rear text or html
|
||||
* @throws MessagingException If sending the E-Mail fails for any reason
|
||||
*/
|
||||
public void sendMails(String recipients, String subject, String text) throws MessagingException {
|
||||
// TODO replace printMail with implementation
|
||||
printMail(recipients, subject, text); // TODO Remove Printing E-Mail to console to test it
|
||||
printMail(recipients, subject, text);
|
||||
MimeMessage message = new MimeMessage(Settings.getInstance().getSmtpCredentials().getSession());
|
||||
message.addHeader("Content-type", "text/HTML; charset=UTF-8");
|
||||
message.addHeader("format", "flowed");
|
||||
|
|
|
@ -5,6 +5,10 @@ import javax.mail.Session;
|
|||
import java.net.Authenticator;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Class to store SMTP Credentials to send E-Mails and create
|
||||
* corresponding Session Object
|
||||
*/
|
||||
public record SmtpCredentials(
|
||||
String host,
|
||||
String port,
|
||||
|
@ -14,6 +18,10 @@ public record SmtpCredentials(
|
|||
boolean startTLS
|
||||
) {
|
||||
|
||||
/**
|
||||
* Creates a Properties Object with SMTP Server Information
|
||||
* @return the created Properties Object
|
||||
*/
|
||||
private Properties getProperties() {
|
||||
Properties properties = new Properties();
|
||||
properties.put("mail.smtp.host", host);
|
||||
|
@ -23,6 +31,10 @@ public record SmtpCredentials(
|
|||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a javax.mail.Authenticator Object with username and password for SMTP Server
|
||||
* @return the created javax.mail.Authenticator Object
|
||||
*/
|
||||
private javax.mail.Authenticator getAuthenticator() {
|
||||
return new javax.mail.Authenticator() {
|
||||
@Override
|
||||
|
@ -32,10 +44,11 @@ public record SmtpCredentials(
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the Session Instance with the given SMTP Credentials
|
||||
* @return the Session Instance
|
||||
*/
|
||||
public Session getSession() {
|
||||
return Session.getInstance(getProperties(), getAuthenticator());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package ch.zhaw.gartenverwaltung.backgroundtasks.weather;
|
||||
|
||||
/**
|
||||
* Enum of possible Weather events
|
||||
*/
|
||||
public enum SevereWeather {
|
||||
FROST,SNOW,HAIL,NO_SEVERE_WEATHER,RAIN
|
||||
}
|
||||
|
|
|
@ -10,14 +10,10 @@ import ch.zhaw.gartenverwaltung.types.*;
|
|||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
/**
|
||||
* The WeatherGardenTaskPlanner creates Tasks based on weather events and the rain amount from the last days
|
||||
*
|
||||
*/
|
||||
public class WeatherGradenTaskPlanner {
|
||||
private final TaskList taskList;
|
||||
|
@ -46,12 +42,19 @@ public class WeatherGradenTaskPlanner {
|
|||
|
||||
private void getSevereWeatherEvents() throws IOException {
|
||||
SevereWeather actualWeather = weatherService.causeSevereWeather(1);
|
||||
if (SevereWeather.HAIL.equals(actualWeather)) {
|
||||
createPreHailTask();
|
||||
} else if (SevereWeather.FROST.equals(actualWeather)) {
|
||||
createPreFrostTask();
|
||||
} else if (SevereWeather.SNOW.equals(actualWeather)) {
|
||||
createPreSnowTask();
|
||||
|
||||
List<Crop> actualCrops = cropList.getCrops();
|
||||
for (Crop crop : actualCrops) {
|
||||
|
||||
List<Task> actualCropTasks = taskList.getTaskForCrop(crop.getCropId().orElse(-1L));
|
||||
|
||||
if (SevereWeather.HAIL.equals(actualWeather)) {
|
||||
createPreHailTask(crop, actualCropTasks);
|
||||
} else if (SevereWeather.FROST.equals(actualWeather)) {
|
||||
createPreFrostTask(crop, actualCropTasks);
|
||||
} else if (SevereWeather.SNOW.equals(actualWeather)) {
|
||||
createPreSnowTask(crop, actualCropTasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,87 +64,69 @@ public class WeatherGradenTaskPlanner {
|
|||
}
|
||||
|
||||
/**
|
||||
* Method to create a PreHailTask
|
||||
* Method to create a PreHailTask and saves it in the tasklist
|
||||
* @throws IOException If the database cannot be accessed
|
||||
*/
|
||||
private void createPreHailTask() throws IOException {
|
||||
private void createPreHailTask(Crop crop, List<Task> actualTasksForCrop) throws IOException {
|
||||
Task preHailTask = new Task("Hail",
|
||||
"During a summer Thunderstorm it could hail heavily. THe Hail could damage the crops. To prevent damage cover the plants with a strong tarpaulin",
|
||||
dateSevereWeather,dateSevereWeather.plusDays(1L),crop.getCropId().orElse(-1L));
|
||||
|
||||
List<Crop> actualCrops = cropList.getCrops();
|
||||
List<Task> hailTasks = actualTasksForCrop.stream().filter(task -> task.getName().equals("Hail")).toList();
|
||||
|
||||
for (Crop crop : actualCrops) {
|
||||
Task preHailTask = new Task("Hail",
|
||||
"During a summer Thunderstorm it could hail heavily. THe Hail could damage the crops. To prevent damage cover the plants with a strong tarpaulin",
|
||||
dateSevereWeather,dateSevereWeather.plusDays(1L),crop.getCropId().orElse(-1L));
|
||||
|
||||
List<Task> actualCropTaskList = taskList.getTaskForCrop(crop.getCropId().orElse(-1L));
|
||||
List<Task> hailTasklist = actualCropTaskList.stream().filter(task -> task.getName().equals("Hail")).toList();
|
||||
|
||||
List<Task> hailTaskListAtDate = new ArrayList<>();
|
||||
for (Task task : hailTasklist) {
|
||||
if (task.getStartDate() == (preHailTask.getStartDate())) {
|
||||
hailTaskListAtDate.add(task);
|
||||
}
|
||||
}
|
||||
|
||||
if(hailTaskListAtDate.isEmpty() && hailTasklist.isEmpty()){
|
||||
taskList.saveTask(preHailTask);
|
||||
}
|
||||
if(isNoSevereWeatherTaskAtDate(preHailTask, hailTasks) && hailTasks.isEmpty()){
|
||||
taskList.saveTask(preHailTask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create a PreFrosttask
|
||||
* Method to create a PreFrosttask and saves it in the tasklist
|
||||
* @throws IOException If the database cannot be accessed
|
||||
*/
|
||||
private void createPreFrostTask() throws IOException {
|
||||
List<Crop> actualCrops = cropList.getCrops();
|
||||
for (Crop crop : actualCrops) {
|
||||
|
||||
Task preFrostTask = new Task("Frost",
|
||||
private void createPreFrostTask(Crop crop, List<Task> actualTasksForCrop) throws IOException {
|
||||
Task preFrostTask = new Task("Frost",
|
||||
"The temperatur falls below zero degrees, cover especially the root with wool",
|
||||
dateSevereWeather,dateSevereWeather.plusDays(1L),crop.getCropId().orElse(-1L));
|
||||
|
||||
List<Task> actualCropTaskList = taskList.getTaskForCrop(crop.getCropId().orElse(-1L));
|
||||
List<Task> frostTasklist = actualCropTaskList.stream().filter(task -> task.getName().equals("Frost")).toList();
|
||||
List<Task> frostTasks = actualTasksForCrop.stream().filter(task -> task.getName().equals("Frost")).toList();
|
||||
|
||||
List<Task> frostTaskListAtDate = new ArrayList<>();
|
||||
for (Task task : frostTasklist) {
|
||||
if (task.getStartDate() == preFrostTask.getStartDate()) {
|
||||
frostTaskListAtDate.add(task);
|
||||
}
|
||||
}
|
||||
if(frostTaskListAtDate.isEmpty() && frostTasklist.isEmpty()){
|
||||
taskList.saveTask(preFrostTask);
|
||||
}
|
||||
if(isNoSevereWeatherTaskAtDate(preFrostTask, frostTasks) && frostTasks.isEmpty()){
|
||||
taskList.saveTask(preFrostTask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create a PreSnowTask and saves it in the tasklist
|
||||
* @throws IOException If the database cannot be accessed
|
||||
*/
|
||||
private void createPreSnowTask(Crop crop, List<Task> actualTasksForCrop) throws IOException {
|
||||
Task preSnowTask = new Task("Snow",
|
||||
"The weather brings little snowfall. Cover your crops",
|
||||
dateSevereWeather, dateSevereWeather.plusDays(1L), crop.getCropId().orElse(-1L));
|
||||
|
||||
List<Task> snowTasklist = actualTasksForCrop.stream().filter(task -> task.getName().equals("Snow")).toList();
|
||||
|
||||
if(isNoSevereWeatherTaskAtDate(preSnowTask, snowTasklist) && snowTasklist.isEmpty()){
|
||||
taskList.saveTask(preSnowTask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create a PreSnowTask
|
||||
* @throws IOException If the database cannot be accessed
|
||||
* Method to create a PreSnowTask and saves it in the tasklist
|
||||
* @param preSevereWeatherTask the Task which would be added if there is not already
|
||||
* the same Type of severe weather task
|
||||
* @param severeWeatherTasks List of severe weather tasks from e specific severe weather
|
||||
* @return true If there is not already a severe weather task of the same type of preSevereWeatherTask
|
||||
* task at the date of the preSevereWeatherTask
|
||||
*/
|
||||
private void createPreSnowTask() throws IOException {
|
||||
List<Crop> actualCrops = cropList.getCrops();
|
||||
for (Crop crop : actualCrops) {
|
||||
|
||||
Task preSnowTask = new Task("Snow",
|
||||
"The weather brings little snowfall. Cover your crops",
|
||||
dateSevereWeather, dateSevereWeather.plusDays(1L), crop.getCropId().orElse(-1L));
|
||||
|
||||
List<Task> actualCropTaskList = taskList.getTaskForCrop(crop.getCropId().orElse(-1L));
|
||||
List<Task> snowTasklist = actualCropTaskList.stream().filter(task -> task.getName().equals("Snow")).toList();
|
||||
|
||||
List<Task> snowTaskListAtDate = new ArrayList<>();
|
||||
for (Task task : snowTasklist) {
|
||||
if (task.getStartDate() == preSnowTask.getStartDate()) {
|
||||
snowTaskListAtDate.add(task);
|
||||
}
|
||||
private boolean isNoSevereWeatherTaskAtDate(Task preSevereWeatherTask, List<Task> severeWeatherTasks) {
|
||||
List<Task> severeWeatherTasksAtDate = new ArrayList<>();
|
||||
for (Task task : severeWeatherTasks) {
|
||||
if (task.getStartDate() == preSevereWeatherTask.getStartDate()) {
|
||||
severeWeatherTasksAtDate.add(task);
|
||||
}
|
||||
if(snowTaskListAtDate .isEmpty() && snowTasklist.isEmpty()){
|
||||
taskList.saveTask(preSnowTask);
|
||||
}
|
||||
|
||||
}
|
||||
return severeWeatherTasksAtDate.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ch.zhaw.gartenverwaltung.backgroundtasks.weather;
|
||||
/**
|
||||
* The WeatherService is a class to cause weather events for the WeatherGardenTaskPlanner
|
||||
*
|
||||
*/
|
||||
public class WeatherService {
|
||||
private static final int NO_RAIN = 0;
|
||||
|
@ -9,6 +8,11 @@ public class WeatherService {
|
|||
private static final int RAIN = 25;
|
||||
private static final int HEAVY_RAIN = 50;
|
||||
|
||||
/**
|
||||
* Method to simmulate a Weather Service for testing
|
||||
* @param randomWeather random int. Range: 1-3
|
||||
* @return the selected SevereWeather
|
||||
*/
|
||||
public SevereWeather causeSevereWeather(int randomWeather) {
|
||||
return switch (randomWeather) {
|
||||
case 1 -> SevereWeather.HAIL;
|
||||
|
@ -19,6 +23,11 @@ public class WeatherService {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to simulate a Weather Service for testing
|
||||
* @param randomRainAmount random int. Range: 1-4
|
||||
* @return the selected rain amount
|
||||
*/
|
||||
public int causeRainAmount(int randomRainAmount) {
|
||||
return switch (randomRainAmount) {
|
||||
case 1 -> NO_RAIN;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ch.zhaw.gartenverwaltung.bootstrap;
|
||||
|
||||
import ch.zhaw.gartenverwaltung.CropDetailController;
|
||||
import ch.zhaw.gartenverwaltung.Main;
|
||||
import ch.zhaw.gartenverwaltung.io.*;
|
||||
import ch.zhaw.gartenverwaltung.models.Garden;
|
||||
|
@ -17,12 +18,15 @@ import java.util.Arrays;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Class responsible for bootstrapping the application wide dependencies
|
||||
* and injecting them into JavaFX Controllers.
|
||||
*/
|
||||
public class AppLoader {
|
||||
private static final Logger LOG = Logger.getLogger(CropDetailController.class.getName());
|
||||
/**
|
||||
* Caching the panes
|
||||
*/
|
||||
|
@ -146,12 +150,17 @@ public class AppLoader {
|
|||
try {
|
||||
afterInjectMethod.invoke(controller);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
// TODO: Log
|
||||
LOG.log(Level.SEVERE, "Could not invoke afterInjectMethod", e.getCause());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get any AppDependency
|
||||
* @param type Class of Dependency
|
||||
* @return the App dependency
|
||||
*/
|
||||
public Object getAppDependency(Class<?> type) {
|
||||
return dependencies.get(type.getSimpleName());
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package ch.zhaw.gartenverwaltung.io;
|
||||
|
||||
/**
|
||||
* Exceptionm which is thrown if Hardiness zone is not set in plant list.
|
||||
*/
|
||||
public class HardinessZoneNotSetException extends Exception {
|
||||
public HardinessZoneNotSetException() {
|
||||
super("HardinessZone must be set to retrieve plants!");
|
||||
|
|
|
@ -2,6 +2,9 @@ package ch.zhaw.gartenverwaltung.io;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Excption which is thrown if a JSON file has a invalid File format.
|
||||
*/
|
||||
class InvalidJsonException extends IOException {
|
||||
public InvalidJsonException(String reason) {
|
||||
super(reason);
|
||||
|
|
|
@ -32,16 +32,6 @@ public class GardenSchedule {
|
|||
public GardenSchedule(TaskList taskList, PlantList plantList) throws IOException {
|
||||
this.taskList = taskList;
|
||||
this.plantList = plantList;
|
||||
TaskList.TaskListObserver taskListObserver = newTaskList -> {
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
getTasksUpcomingWeek();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
setTaskListObserver(taskListObserver);
|
||||
}
|
||||
|
||||
public ListProperty<List<Task>> getWeeklyTaskListProperty() {
|
||||
|
@ -176,14 +166,12 @@ public class GardenSchedule {
|
|||
dayTaskList.add(new ArrayList<>());
|
||||
final int finalI = i;
|
||||
weekTasks.forEach(task -> {
|
||||
if (task.getNextExecution() == null) {
|
||||
task.isDone();
|
||||
} else {
|
||||
if(task.getNextExecution() != null) {
|
||||
LocalDate checkDate = task.getNextExecution();
|
||||
|
||||
do {
|
||||
if (date.equals(checkDate) && !date.isAfter(task.getEndDate().orElse(LocalDate.MIN))) {
|
||||
if (date.equals(task.getNextExecution()) || (date.equals(checkDate) && !date.isAfter(task.getEndDate().orElse(LocalDate.MIN)))) {
|
||||
dayTaskList.get(finalI).add(task);
|
||||
break;
|
||||
}
|
||||
checkDate = checkDate.plusDays(task.getInterval().orElse(0));
|
||||
} while (!(task.getInterval().orElse(0) == 0) && checkDate.isBefore(LocalDate.now().plusDays(listLength)));
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ch.zhaw.gartenverwaltung.models;
|
||||
|
||||
import ch.zhaw.gartenverwaltung.Settings;
|
||||
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
|
||||
import ch.zhaw.gartenverwaltung.io.PlantList;
|
||||
import ch.zhaw.gartenverwaltung.types.GrowthPhaseType;
|
||||
|
@ -16,7 +17,6 @@ import java.util.stream.Collectors;
|
|||
|
||||
public class PlantListModel {
|
||||
private final PlantList plantList;
|
||||
private HardinessZone currentZone;
|
||||
|
||||
/**
|
||||
* Comparators to create sorted Plant List
|
||||
|
@ -33,15 +33,15 @@ public class PlantListModel {
|
|||
}
|
||||
|
||||
private void setDefaultZone() {
|
||||
currentZone = HardinessZone.ZONE_8A; // TODO: get Default Zone from Settings
|
||||
Settings.getInstance().setCurrentHardinessZone(null);
|
||||
}
|
||||
|
||||
public void setCurrentZone(HardinessZone currentZone) {
|
||||
this.currentZone = currentZone;
|
||||
Settings.getInstance().setCurrentHardinessZone(currentZone);
|
||||
}
|
||||
|
||||
public HardinessZone getCurrentZone() {
|
||||
return currentZone;
|
||||
return Settings.getInstance().getCurrentHardinessZone();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package ch.zhaw.gartenverwaltung.models;
|
||||
|
||||
/**
|
||||
* Exception which is thrown if there is no plant in plant Database with the given id.
|
||||
*/
|
||||
public class PlantNotFoundException extends Exception {
|
||||
public PlantNotFoundException() {
|
||||
super("The selected Plant was not found in Database!");
|
||||
|
|
|
@ -40,7 +40,6 @@ public class Crop {
|
|||
public Optional<Long> getCropId() {
|
||||
return Optional.ofNullable(cropId);
|
||||
}
|
||||
|
||||
public long getPlantId() { return plantId; }
|
||||
public LocalDate getStartDate() { return startDate; }
|
||||
public double getArea() { return area; }
|
||||
|
|
|
@ -68,7 +68,7 @@ public record Plant(
|
|||
return growthPhase.group();
|
||||
}
|
||||
}
|
||||
return 0; // TODO implement
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -108,7 +108,7 @@ public class Task {
|
|||
* @return Whether the Task is within the given range
|
||||
*/
|
||||
public boolean isInTimePeriod(LocalDate searchStartDate, LocalDate searchEndDate) {
|
||||
return endDate.isAfter(searchStartDate) && startDate.isBefore(searchEndDate) || (nextExecution != null && nextExecution.isBefore(searchEndDate) && nextExecution.isAfter(searchStartDate));
|
||||
return (endDate.isAfter(searchStartDate) && startDate.isBefore(searchEndDate)) || ((nextExecution != null && nextExecution.isBefore(searchEndDate.plusDays(1)) && nextExecution.isAfter(searchStartDate.minusDays(1))));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Class which represents a Task if the start and enddate is not known yet.
|
||||
*/
|
||||
public class TaskTemplate {
|
||||
@JsonProperty
|
||||
private final String name;
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
<Insets bottom="10.0" />
|
||||
</VBox.margin>
|
||||
</ListView>
|
||||
<Button styleClass="button-class" fx:id="addTask_button" mnemonicParsing="false" onAction="#addTask" prefHeight="25.0" prefWidth="45.0" VBox.vgrow="NEVER">
|
||||
<Button styleClass="button-class" fx:id="addTask_button" mnemonicParsing="false" onAction="#addTask" prefHeight="25.0" prefWidth="120.0" VBox.vgrow="NEVER" text="Add Task">
|
||||
<VBox.margin>
|
||||
<Insets />
|
||||
</VBox.margin>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
|
@ -19,11 +20,12 @@
|
|||
</Label>
|
||||
<HBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="541.0" prefWidth="867.0" spacing="10.0" VBox.vgrow="ALWAYS">
|
||||
<children>
|
||||
<ListView fx:id="scheduledPlants_listview" maxWidth="1.7976931348623157E308" prefHeight="522.0" prefWidth="271.0" HBox.hgrow="NEVER">
|
||||
<HBox.margin>
|
||||
<Insets />
|
||||
</HBox.margin>
|
||||
</ListView>
|
||||
<VBox prefHeight="497.0" prefWidth="237.0" spacing="10.0" HBox.hgrow="NEVER">
|
||||
<children>
|
||||
<ListView fx:id="scheduledPlants_listview" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="522.0" prefWidth="271.0" VBox.vgrow="ALWAYS" />
|
||||
<Button maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#showAllTasks" styleClass="button-class" text="Show All Tasks" VBox.vgrow="NEVER" />
|
||||
</children>
|
||||
</VBox>
|
||||
<VBox maxWidth="1.7976931348623157E308" prefHeight="537.0" prefWidth="650.0" spacing="10.0" HBox.hgrow="ALWAYS">
|
||||
<children>
|
||||
<ListView fx:id="week_listView" maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
<Separator prefWidth="50.0" visible="false" HBox.hgrow="ALWAYS" />
|
||||
<ButtonBar prefHeight="40.0" prefWidth="200.0">
|
||||
<buttons>
|
||||
<Button styleClass="button-class" cancelButton="true" contentDisplay="CENTER" graphicTextGap="5.0" mnemonicParsing="false" text="Close" onAction="#closeTutorial"/>
|
||||
<Button styleClass="button-class" fx:id="previousPageButton" mnemonicParsing="false" text="Previous" onAction="#viewPreviousPage"/>
|
||||
<Button styleClass="button-class" fx:id="nextPageButton" defaultButton="true" mnemonicParsing="false" text="Next" onAction="#viewNextPage" />
|
||||
<Button cancelButton="true" contentDisplay="CENTER" graphicTextGap="5.0" mnemonicParsing="false" onAction="#closeTutorial" styleClass="button-class" text="Close" />
|
||||
<Button fx:id="previousPageButton" mnemonicParsing="false" onAction="#viewPreviousPage" styleClass="button-class" text="Previous" />
|
||||
<Button fx:id="nextPageButton" defaultButton="true" mnemonicParsing="false" onAction="#viewNextPage" styleClass="button-class" text="Next" />
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</children>
|
||||
|
@ -38,7 +38,7 @@
|
|||
<children>
|
||||
<VBox layoutX="30.0" layoutY="26.0" opacity="0.0" prefHeight="200.0" prefWidth="100.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Managing Your Crops">
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Adding Crops">
|
||||
<font>
|
||||
<Font size="24.0" />
|
||||
</font>
|
||||
|
@ -75,7 +75,7 @@
|
|||
</children>
|
||||
</TextFlow>
|
||||
<Separator prefWidth="200.0" visible="false" HBox.hgrow="ALWAYS" />
|
||||
<ImageView fx:id="imgSelectDate" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" />
|
||||
<ImageView fx:id="imgSelectDate" fitHeight="150.0" fitWidth="400.0" pickOnBounds="true" preserveRatio="true" />
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
|
@ -83,7 +83,50 @@
|
|||
<Insets />
|
||||
</opaqueInsets>
|
||||
</VBox>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0">
|
||||
<VBox layoutX="30.0" layoutY="30.0" prefHeight="200.0" prefWidth="100.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Managing Your Crops">
|
||||
<font>
|
||||
<Font size="24.0" />
|
||||
</font>
|
||||
</Text>
|
||||
<Separator prefWidth="200.0">
|
||||
<VBox.margin>
|
||||
<Insets bottom="15.0" top="10.0" />
|
||||
</VBox.margin>
|
||||
</Separator>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Once you've added a crop to your garden, it will be displayed in the "My Garden" tab.">
|
||||
<VBox.margin>
|
||||
<Insets bottom="20.0" />
|
||||
</VBox.margin>
|
||||
</Text>
|
||||
<HBox prefHeight="100.0" prefWidth="200.0">
|
||||
<children>
|
||||
<TextFlow lineSpacing="4.0" prefHeight="200.0" prefWidth="500.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="On the right hand side of the listing, you'll see two buttons. Button with the "trash can" icon lets you remove a crop from your garden plan. This will also delete all associated tasks. The button on the left will display the details of the crop in a new window." />
|
||||
</children>
|
||||
</TextFlow>
|
||||
<Separator prefWidth="200.0" visible="false" HBox.hgrow="ALWAYS" />
|
||||
<ImageView fitHeight="70.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true" fx:id="imgDetailDeleteButtons"/>
|
||||
</children></HBox>
|
||||
<HBox layoutX="10.0" layoutY="105.0" prefHeight="100.0" prefWidth="200.0">
|
||||
<children>
|
||||
<TextFlow lineSpacing="4.0" prefHeight="200.0" prefWidth="500.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Here, you can also add your own custom tasks, by clicking the "Add Task" button. In the subsequently shown dialog, you can enter the corresponding details. Note: If you want to make a task recurring, you need to set both an interval (in days) AND an end date, so the task won't repeat for all eternity." />
|
||||
</children>
|
||||
</TextFlow>
|
||||
<Separator prefWidth="200.0" visible="false" HBox.hgrow="ALWAYS" />
|
||||
<ImageView fitHeight="150.0" fitWidth="400.0" pickOnBounds="true" preserveRatio="true" fx:id="imgAddTaskButton"/>
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
</opaqueInsets>
|
||||
</VBox>
|
||||
<VBox opacity="0.0" prefHeight="200.0" prefWidth="100.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Managing Your Tasks">
|
||||
<font>
|
||||
|
@ -111,19 +154,9 @@
|
|||
</HBox.margin>
|
||||
</TextFlow>
|
||||
<Separator prefWidth="200.0" visible="false" HBox.hgrow="ALWAYS" />
|
||||
<ImageView fx:id="imgTaskList" fitHeight="98.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" />
|
||||
<ImageView fx:id="imgTaskList" fitHeight="200.0" fitWidth="400.0" pickOnBounds="true" preserveRatio="true" />
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox prefHeight="100.0" prefWidth="200.0">
|
||||
<children>
|
||||
<TextFlow lineSpacing="4.0" prefHeight="200.0" prefWidth="500.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="You can also add your own custom tasks, by clicking the "Add Task" button. In the subsequently shown dialog, you can enter the corresponding details. Note: If you want to make a task recurring, you need to set both an interval (in days) AND an end date, so the task won't repeat for all eternity." />
|
||||
</children>
|
||||
</TextFlow>
|
||||
<Separator prefWidth="200.0" visible="false" HBox.hgrow="ALWAYS" />
|
||||
<ImageView fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" />
|
||||
</children></HBox>
|
||||
</children>
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
|
@ -1,7 +1,12 @@
|
|||
package ch.zhaw.gartenverwaltung.io;
|
||||
|
||||
import ch.zhaw.gartenverwaltung.models.GardenSchedule;
|
||||
import ch.zhaw.gartenverwaltung.models.PlantNotFoundException;
|
||||
import ch.zhaw.gartenverwaltung.types.Crop;
|
||||
import ch.zhaw.gartenverwaltung.types.Task;
|
||||
import javafx.application.Platform;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
|
@ -13,6 +18,7 @@ import java.nio.file.Path;
|
|||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
@ -27,6 +33,22 @@ public class JsonTaskListTest {
|
|||
private final URL dbDataSource = this.getClass().getResource("test-taskdb.json");
|
||||
private final URL testFile = this.getClass().getResource("template-taskdb.json");
|
||||
|
||||
|
||||
@BeforeAll
|
||||
static void setUpAll() {
|
||||
try{
|
||||
Platform.startup(()->{});
|
||||
}catch (IllegalStateException ise){
|
||||
//ignore double launches
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDownAll() {
|
||||
//Dont do: Platform.exit();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void connectToDb() throws URISyntaxException, IOException {
|
||||
assertNotNull(testFile);
|
||||
|
@ -35,6 +57,10 @@ public class JsonTaskListTest {
|
|||
testDatabase = new JsonTaskList(dbDataSource);
|
||||
}
|
||||
|
||||
private void reloadDb() {
|
||||
testDatabase = new JsonTaskList(dbDataSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Check if results are retrieved completely")
|
||||
void getTasks() {
|
||||
|
@ -47,7 +73,8 @@ public class JsonTaskListTest {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Assertions.assertEquals(3, taskList.size());
|
||||
List<Long> ids = taskList.stream().map(t -> t.getId().orElse(0L)).toList();
|
||||
Assertions.assertEquals(Arrays.asList(1L, 2L, 5L), ids);
|
||||
|
||||
}
|
||||
|
||||
|
@ -65,7 +92,8 @@ public class JsonTaskListTest {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Assertions.assertEquals(4, taskList.size());
|
||||
List<Long> ids = taskList.stream().map(t -> t.getId().orElse(0L)).toList();
|
||||
Assertions.assertEquals(Arrays.asList(1L, 2L, 5L, 9L), ids);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -83,7 +111,8 @@ public class JsonTaskListTest {
|
|||
taskList = testDatabase.getTaskList(LocalDate.parse("2022-04-30", formatter),
|
||||
LocalDate.parse("2022-05-31", formatter));
|
||||
|
||||
Assertions.assertEquals(2, taskList.size());
|
||||
List<Long> ids = taskList.stream().map(t -> t.getId().orElse(0L)).toList();
|
||||
Assertions.assertEquals(Arrays.asList(1L, 5L), ids);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@ -100,8 +129,8 @@ public class JsonTaskListTest {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Assertions.assertEquals(6, taskList.size());
|
||||
|
||||
List<Long> ids = taskList.stream().map(t -> t.getId().orElse(0L)).toList();
|
||||
Assertions.assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L), ids);
|
||||
}
|
||||
|
||||
|
||||
|
@ -116,10 +145,20 @@ public class JsonTaskListTest {
|
|||
}
|
||||
|
||||
Assertions.assertEquals(0, taskList.size());
|
||||
|
||||
try {
|
||||
taskList = testDatabase.getTaskForCrop(1);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
List<Long> ids = (taskList.stream().map( task -> task.getId().orElse(0L)).toList());
|
||||
Assertions.assertEquals(Arrays.asList(7L, 8L), ids);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultConstructor(){
|
||||
void testDefaultConstructor() {
|
||||
JsonTaskList db = new JsonTaskList();
|
||||
try {
|
||||
assertNotNull(db.getTaskForCrop(0));
|
||||
|
@ -138,6 +177,43 @@ public class JsonTaskListTest {
|
|||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
verify(mockObs, times(1)).onChange(ArgumentMatchers.anyList());
|
||||
|
||||
ArgumentCaptor<List<Task>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(mockObs, times(1)).onChange(captor.capture());
|
||||
List<Long> ids = captor.getValue().stream().map(t -> t.getId().orElse(0L)).toList();
|
||||
Assertions.assertEquals(Arrays.asList(7L, 8L), ids);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("IntegrationTest")
|
||||
void testComplete() {
|
||||
JsonPlantList plantList = new JsonPlantList();
|
||||
try {
|
||||
testDatabase.removeTasksForCrop(0);
|
||||
testDatabase.removeTasksForCrop(1);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
GardenSchedule gardenSchedule = null;
|
||||
try {
|
||||
gardenSchedule = new GardenSchedule(testDatabase, plantList);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Crop crop = new Crop(3L, LocalDate.parse("2022-12-01", formatter));
|
||||
|
||||
try {
|
||||
gardenSchedule.planTasksForCrop(crop);
|
||||
|
||||
reloadDb();
|
||||
|
||||
List<Task> tasks = gardenSchedule.getTaskList();
|
||||
List<String> tasknames = (tasks.stream().map( task -> task.getName().substring(0,3).toLowerCase()).toList());
|
||||
Assertions.assertEquals(Arrays.asList("ger","wat","hil","har"), tasknames);
|
||||
} catch (IOException | PlantNotFoundException | HardinessZoneNotSetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -3,6 +3,9 @@ package ch.zhaw.gartenverwaltung.models;
|
|||
import ch.zhaw.gartenverwaltung.io.*;
|
||||
import ch.zhaw.gartenverwaltung.models.GardenSchedule;
|
||||
import ch.zhaw.gartenverwaltung.types.*;
|
||||
import javafx.application.Platform;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -29,6 +32,21 @@ class GardenScheduleTest {
|
|||
List<TaskTemplate> exampleTaskTemplateList;
|
||||
GardenSchedule model;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpAll() {
|
||||
try{
|
||||
Platform.startup(()->{});
|
||||
}catch (IllegalStateException ise){
|
||||
//ignore double launches
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDownAll() {
|
||||
//Dont do: Platform.exit();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException, HardinessZoneNotSetException {
|
||||
createExampleTaskList();
|
||||
|
@ -221,5 +239,8 @@ class GardenScheduleTest {
|
|||
testTaskList.getTaskList(LocalDate.MIN,LocalDate.MAX).get(0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testPlantNotFoundException() throws HardinessZoneNotSetException, IOException {
|
||||
assertThrowsExactly(PlantNotFoundException.class, () -> { model.planTasksForCrop(new Crop(0, exampleStartDate)); });
|
||||
}
|
||||
}
|
|
@ -101,7 +101,7 @@ class PlantListModelTest {
|
|||
|
||||
@Test
|
||||
void setCurrentZone() {
|
||||
checkCurrentZone(HardinessZone.ZONE_8A); // TODO change to get default zone from config
|
||||
checkCurrentZone(HardinessZone.ZONE_8A);
|
||||
model.setCurrentZone(HardinessZone.ZONE_1A);
|
||||
checkCurrentZone(HardinessZone.ZONE_1A);
|
||||
model.setCurrentZone(HardinessZone.ZONE_8A);
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
"name" : "fertilize plant",
|
||||
"description": "The fertilizer has to be mixed with water. Then fertilize the plants soil with the mixture",
|
||||
"startDate" : "2022-06-01",
|
||||
"nextExecution": "2022-05-01",
|
||||
"nextNotification": "2022-05-01",
|
||||
"nextExecution": "2022-06-01",
|
||||
"nextNotification": "2022-06-01",
|
||||
"endDate" : "2022-08-01",
|
||||
"interval" : 28,
|
||||
"cropId" : 0
|
||||
|
@ -37,8 +37,8 @@
|
|||
"name" : "covering plant",
|
||||
"description": "Take a big enough coverage for the plants. Cover the whole plant with a bit space between the plant and the coverage",
|
||||
"startDate" : "2022-07-01",
|
||||
"nextExecution": "2022-05-01",
|
||||
"nextNotification": "2022-05-01",
|
||||
"nextExecution": "2022-07-01",
|
||||
"nextNotification": "2022-07-01",
|
||||
"endDate" : "2022-07-01",
|
||||
"interval" : 0,
|
||||
"cropId" : 0
|
||||
|
@ -59,10 +59,32 @@
|
|||
"name" : "harvest plant",
|
||||
"description": "Pull the ripe vegetables out from the soil. Clean them with clear, fresh water. ",
|
||||
"startDate" : "2022-09-01",
|
||||
"nextExecution": "2022-05-01",
|
||||
"nextNotification": "2022-05-01",
|
||||
"nextExecution": "2022-09-01",
|
||||
"nextNotification": "2022-09-01",
|
||||
"endDate" : "2022-09-01",
|
||||
"interval" : 0,
|
||||
"cropId" : 0
|
||||
},
|
||||
{
|
||||
"id" : 7,
|
||||
"name" : "sow plant",
|
||||
"description": "Plant the seeds, crops in de bed.",
|
||||
"startDate" : "2022-11-01",
|
||||
"nextExecution": "2022-11-01",
|
||||
"nextNotification": "2022-11-01",
|
||||
"endDate" : "2022-11-01",
|
||||
"interval" : 0,
|
||||
"cropId" : 1
|
||||
},
|
||||
{
|
||||
"id" : 8,
|
||||
"name" : "harvest plant",
|
||||
"description": "Pull the ripe vegetables out from the soil. Clean them with clear, fresh water. ",
|
||||
"startDate" : "2022-12-01",
|
||||
"nextExecution": "2022-12-01",
|
||||
"nextNotification": "2022-12-01",
|
||||
"endDate" : "2022-12-01",
|
||||
"interval" : 0,
|
||||
"cropId" : 1
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue