Compare commits

...
Author SHA1 Message Date
David Guler 1f049f86ab fix: fixed fxml version warning 2022-11-25 12:31:27 +01:00
David Guler bcb36b89c7 feature: added tutorial window 2022-11-23 21:55:35 +01:00
gulerdavandGitHub Enterprise f7105f041c Merge pull request #66 from schrom01/feature_guiOverhaul_M3
Feature gui overhaul m3
2022-11-21 12:56:57 +01:00
giavaphi b0369e3174 fix error edit task list 2022-11-21 12:53:28 +01:00
gulerdavandGitHub Enterprise 8c20dcaadb Merge pull request #65 from schrom01/refactor_task-id-semantics_M3
refactor: Made Task::id nullable/Optional for semantic reasons
2022-11-21 12:43:04 +01:00
David Guler 95b0f7e13d refactor: Made Task::id nullable/Optional for semantic reasons
better use of builder semantics in task and crop creation
2022-11-21 09:45:58 +01:00
giavaphiandGitHub Enterprise 2312149256 Merge pull request #64 from schrom01/fix_tasklist-not-updated_M3
fix: Properly saving tasks
2022-11-21 09:21:26 +01:00
David Guler ffc7f5174e fix: Properly saving tasks
Tasks were not properly saved by the JsonTaskList, resulting in the newly created tasks being discarded immediately. Fixed by putting them into the taskMap in saveTask()
2022-11-21 09:15:29 +01:00
giavaphi 8d3fbc06ad added task list editor date cell factory 2022-11-21 07:03:45 +01:00
giavaphi dcb97f1c55 add and edit task list 2022-11-21 06:47:27 +01:00
15 changed files with 585 additions and 147 deletions
@@ -1,5 +1,6 @@
package ch.zhaw.gartenverwaltung; package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import ch.zhaw.gartenverwaltung.bootstrap.Inject; import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import ch.zhaw.gartenverwaltung.io.PlantList; import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.models.Garden; import ch.zhaw.gartenverwaltung.models.Garden;
@@ -10,14 +11,18 @@ import ch.zhaw.gartenverwaltung.types.Crop;
import ch.zhaw.gartenverwaltung.types.Pest; import ch.zhaw.gartenverwaltung.types.Pest;
import ch.zhaw.gartenverwaltung.types.Plant; import ch.zhaw.gartenverwaltung.types.Plant;
import ch.zhaw.gartenverwaltung.types.Task; import ch.zhaw.gartenverwaltung.types.Task;
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; import javafx.fxml.FXML;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.Button; import javafx.scene.control.*;
import javafx.scene.control.Label;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox; import javafx.scene.layout.Priority;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.IOException; import java.io.IOException;
@@ -35,7 +40,12 @@ public class CropDetailController {
@Inject @Inject
private Garden garden; private Garden garden;
@Inject
AppLoader appLoader;
private static final Logger LOG = Logger.getLogger(CropDetailController.class.getName()); private static final Logger LOG = Logger.getLogger(CropDetailController.class.getName());
private final ListProperty<Task> taskListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
private final ListProperty<Pest> pestListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
@FXML @FXML
private ImageView imageView; private ImageView imageView;
@@ -52,9 +62,6 @@ public class CropDetailController {
@FXML @FXML
private Label description_label; private Label description_label;
@FXML
private VBox growthPhases_vbox;
@FXML @FXML
private Label location_label; private Label location_label;
@@ -64,9 +71,6 @@ public class CropDetailController {
@FXML @FXML
private Button location_button; private Button location_button;
@FXML
private VBox pests_vbox;
@FXML @FXML
private Label soil_label; private Label soil_label;
@@ -74,11 +78,17 @@ public class CropDetailController {
private Label spacing_label; private Label spacing_label;
@FXML @FXML
private Button editTaskList_button; private Button addTask_button;
@FXML @FXML
void editTaskList() { private ListView<Task> taskList_listView;
@FXML
private ListView<Pest> pests_listView;
@FXML
void addTask() throws IOException {
createTaskDialog(true, null);
} }
/** /**
@@ -128,25 +138,65 @@ public class CropDetailController {
} }
area_label.setText(""); area_label.setText("");
location_label.setText(""); location_label.setText("");
createTaskLists(crop);
createPestList(plant); setTaskListProperty(crop);
taskList_listView.itemsProperty().bind(taskListProperty);
pestListProperty.addAll(plant.pests());
pests_listView.itemsProperty().bind(pestListProperty);
} catch (HardinessZoneNotSetException | IOException e) { } catch (HardinessZoneNotSetException | IOException e) {
throw new PlantNotFoundException(); throw new PlantNotFoundException();
} }
setIconToButton(editTaskList_button, "editIcon.png"); setIconToButton(addTask_button, "addIcon.png");
setIconToButton(area_button, "areaIcon.png"); setIconToButton(area_button, "areaIcon.png");
setIconToButton(location_button, "locationIcon.png"); setIconToButton(location_button, "locationIcon.png");
setCellFactoryPests();
setCellFactoryTasks();
} }
private void createTaskLists(Crop crop) { private void setCellFactoryTasks() {
taskList_listView.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(Task task, boolean empty) {
super.updateItem(task, empty);
if (empty || task == null) {
setText(null);
setGraphic(null);
} else {
setText("");
setGraphic(createTaskHBox(task));
}
}
});
}
private void setCellFactoryPests() {
pests_listView.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(Pest pest, boolean empty) {
super.updateItem(pest, empty);
if (empty || pest == null) {
setText(null);
setGraphic(null);
} else {
setText("");
setGraphic(createPestHBox(pest));
}
}
});
}
private void setTaskListProperty(Crop crop) {
crop.getCropId().ifPresent(id -> { crop.getCropId().ifPresent(id -> {
List<Task> taskList; List<Task> taskList;
try { try {
taskList = gardenSchedule.getTaskListForCrop(id); taskList = gardenSchedule.getTaskListForCrop(id);
for (Task task : taskList) { taskListProperty.clear();
Label label = new Label(task.getDescription()); taskListProperty.addAll(taskList);
growthPhases_vbox.getChildren().add(label);
}
} catch (IOException e) { } catch (IOException e) {
// TODO: Alert // TODO: Alert
LOG.log(Level.SEVERE, "Could not get task list for crop", e.getCause()); LOG.log(Level.SEVERE, "Could not get task list for crop", e.getCause());
@@ -154,10 +204,27 @@ public class CropDetailController {
}); });
} }
private void createPestList(Plant plant) { private HBox createTaskHBox(Task task) {
List<Pest> pests = plant.pests(); HBox hBox = new HBox();
for (Pest pest : pests) { Label taskName = new Label(task.getName()+": ");
Label label = new Label(pest.name() + ":"); taskName.setStyle("-fx-font-weight: bold");
Label taskDescription = new Label(task.getDescription());
taskDescription.setWrapText(true);
taskDescription.setMaxWidth(2000);
HBox.setHgrow(taskDescription, Priority.ALWAYS);
Button edit = new Button();
Button delete = new Button();
setIconToButton(edit, "editIcon.png");
setIconToButton(delete, "deleteIcon.png");
edit.setOnAction(getEditTaskEvent(task));
hBox.getChildren().addAll(taskName, taskDescription, edit, delete);
return hBox;
}
private HBox createPestHBox(Pest pest) {
Label label = new Label(pest.name() + ": ");
label.setStyle("-fx-font-weight: bold"); label.setStyle("-fx-font-weight: bold");
HBox hBox = new HBox(); HBox hBox = new HBox();
hBox.fillHeightProperty(); hBox.fillHeightProperty();
@@ -165,11 +232,9 @@ public class CropDetailController {
label1.setAlignment(Pos.TOP_LEFT); label1.setAlignment(Pos.TOP_LEFT);
label1.setWrapText(true); label1.setWrapText(true);
label1.setMaxWidth(600); label1.setMaxWidth(600);
label1.setMaxHeight(100);
Button button = new Button("Get Counter Measures"); Button button = new Button("Get Counter Measures");
hBox.getChildren().addAll(label1, button); hBox.getChildren().addAll(label, label1, button);
pests_vbox.getChildren().addAll(label, hBox); return hBox;
}
} }
/** /**
@@ -184,4 +249,55 @@ public class CropDetailController {
imageView.setPreserveRatio(true); imageView.setPreserveRatio(true);
button.setGraphic(imageView); button.setGraphic(imageView);
} }
private EventHandler<ActionEvent> getEditTaskEvent(Task task) {
return (event) -> {
try {
createTaskDialog(false, task);
} catch (IOException e) {
e.printStackTrace();
}
};
}
private void createTaskDialog(boolean newTask, Task givenTask) throws IOException {
Dialog<Task> dialog = new Dialog<>();
dialog.setTitle("Set Task");
dialog.setHeaderText("Add/Edit Task:");
dialog.setResizable(false);
DialogPane dialogPane = dialog.getDialogPane();
ButtonType saveTask;
if(newTask) {
saveTask = new ButtonType("Add", ButtonBar.ButtonData.OK_DONE);
} else {
saveTask = new ButtonType("Save", ButtonBar.ButtonData.OK_DONE);
}
dialogPane.getButtonTypes().addAll(saveTask, ButtonType.CANCEL);
if (appLoader.loadPaneToDialog("TaskFormular.fxml", dialogPane) instanceof TaskFormularController controller) {
controller.setCorp(this.crop);
if (!newTask) {
controller.setTaskValue(givenTask);
}
dialog.setResultConverter(button -> button.equals(saveTask) ? controller.returnResult() : null);
dialog.showAndWait()
.ifPresent(task -> {
if (newTask) {
try {
gardenSchedule.addTask(task);
setTaskListProperty(this.crop);
} catch (IOException e) {
e.printStackTrace();
}
} else {
//ToDo method to edit task
setTaskListProperty(this.crop);
}
});
}
}
} }
@@ -11,6 +11,9 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
@@ -40,6 +43,9 @@ public class MainFXMLController {
@FXML @FXML
private Button tutorial_button; private Button tutorial_button;
private final Stage tutorialModal = new Stage();
/** /**
* go to home pane * go to home pane
*/ */
@@ -95,11 +101,23 @@ public class MainFXMLController {
} }
/** /**
* go to Tutorial pane * Show the tutorial window
*/ */
public void goToTutorial() { public void showTutorial() {
showPaneAsMainView("Tutorial.fxml"); if (!tutorialModal.isShowing()) {
styleChangeButton(tutorial_button); if (tutorialModal.getScene() == null) {
try {
appLoader.loadSceneToStage("Tutorial.fxml", tutorialModal);
tutorialModal.initModality(Modality.NONE);
tutorialModal.setResizable(false);
tutorialModal.sizeToScene();
} catch (IOException e) {
LOG.log(Level.SEVERE, "Could not load Tutorial");
}
}
tutorialModal.show();
}
tutorialModal.requestFocus();
} }
/** /**
@@ -132,7 +150,6 @@ public class MainFXMLController {
appLoader.loadAndCacheFxml("MyGarden.fxml"); appLoader.loadAndCacheFxml("MyGarden.fxml");
appLoader.loadAndCacheFxml("MySchedule.fxml"); appLoader.loadAndCacheFxml("MySchedule.fxml");
appLoader.loadAndCacheFxml("Plants.fxml"); appLoader.loadAndCacheFxml("Plants.fxml");
appLoader.loadAndCacheFxml("Tutorial.fxml");
} }
private void styleChangeButton(Button button) { private void styleChangeButton(Button button) {
@@ -153,12 +170,14 @@ public class MainFXMLController {
} catch (IOException e) { } catch (IOException e) {
LOG.log(Level.SEVERE, "Failed to load FXML-Pane!", e); LOG.log(Level.SEVERE, "Failed to load FXML-Pane!", e);
} }
mainPane.getScene().getWindow().setOnCloseRequest(this::closeWindowHandler);
setIconToButton(home_button, "homeIcon.png"); setIconToButton(home_button, "homeIcon.png");
setIconToButton(settings_button, "settingsIcon.png"); setIconToButton(settings_button, "settingsIcon.png");
Settings.getInstance().getShowTutorialProperty().addListener((observable, oldValue, newValue) -> { tutorial_button.visibleProperty().bind(Settings.getInstance().getShowTutorialProperty());
tutorial_button.setVisible(newValue); }
});
tutorial_button.setVisible(Settings.getInstance().getShowTutorial()); private void closeWindowHandler(WindowEvent windowEvent) {
tutorialModal.close();
} }
/** /**
@@ -173,7 +192,4 @@ public class MainFXMLController {
imageView.setPreserveRatio(true); imageView.setPreserveRatio(true);
button.setGraphic(imageView); button.setGraphic(imageView);
} }
} }
@@ -0,0 +1,86 @@
package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.types.Crop;
import ch.zhaw.gartenverwaltung.types.Task;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.DateCell;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.util.Callback;
import java.net.URL;
import java.time.LocalDate;
import java.util.ResourceBundle;
public class TaskFormularController implements Initializable {
private Crop crop;
@FXML
private TextArea description_area;
@FXML
private DatePicker end_datePicker;
@FXML
private TextField interval_field;
@FXML
private DatePicker start_datePicker;
@FXML
private TextField taskName_field;
public Task returnResult() {
Task task = new Task(taskName_field.getText(), description_area.getText(),
start_datePicker.getValue(), end_datePicker.getValue(),
Integer.parseInt(interval_field.getText()), crop.getCropId().get());
return task;
}
public void setCorp(Crop crop) {
this.crop = crop;
}
public void setTaskValue(Task task) {
taskName_field.setText(task.getName());
description_area.setText(task.getDescription());
start_datePicker.setValue(task.getStartDate());
end_datePicker.setValue(task.getEndDate().orElse(null));
if(task.getInterval().orElse(null)!=null) {
interval_field.setText(task.getInterval().get().toString());
} else {
interval_field.setText(null);
}
}
private Callback<DatePicker, DateCell> getDayCellFactory() {
return (datePicker) -> new DateCell() {
private final LocalDate today = LocalDate.now();
@Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
setDisable(true);
setStyle("-fx-background-color: #ffc0cb;");
if (item.compareTo(today) > 0 && item.compareTo(crop.getStartDate()) > 0) {
setDisable(false);
setStyle("-fx-background-color: #32CD32;");
}
}
};
}
@Override
public void initialize(URL location, ResourceBundle resources) {
start_datePicker.setDayCellFactory(getDayCellFactory());
start_datePicker.setEditable(false);
end_datePicker.setDayCellFactory(getDayCellFactory());
end_datePicker.setEditable(false);
}
}
@@ -1,6 +1,61 @@
package ch.zhaw.gartenverwaltung; package ch.zhaw.gartenverwaltung;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TutorialController { public class TutorialController {
@FXML
public Button previousPageButton;
@FXML
public Button nextPageButton;
@FXML
public StackPane tourPages;
public ImageView imgAddNewPlant;
public ImageView imgTaskList;
public ImageView imgSelectDate;
private int page = 0;
@FXML
public void initialize() {
switchViews();
setButtonAbilities();
Image placeholder = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
imgAddNewPlant.setImage(placeholder);
imgSelectDate.setImage(placeholder);
imgTaskList.setImage(placeholder);
}
public void viewNextPage() {
page++;
switchViews();
setButtonAbilities();
}
public void viewPreviousPage() {
page--;
switchViews();
setButtonAbilities();
}
private void setButtonAbilities() {
previousPageButton.setDisable(page <= 0);
nextPageButton.setDisable(page >= tourPages.getChildren().size() - 1);
}
private void switchViews() {
tourPages.getChildren().forEach(node -> node.setOpacity(0));
tourPages.getChildren().get(page).setOpacity(1);
}
public void closeTutorial() {
Stage root = (Stage) tourPages.getScene().getWindow();
root.close();
}
} }
@@ -103,9 +103,9 @@ public class JsonTaskList implements TaskList {
if(taskMap.isEmpty()) { if(taskMap.isEmpty()) {
loadTaskListFromFile(); loadTaskListFromFile();
} }
if(task.getId() == 0) { long id = task.getId().orElse(idProvider.incrementAndGet());
task.withId(idProvider.incrementAndGet());
} taskMap.put(id, task.withId(id));
writeTaskListToFile(); writeTaskListToFile();
} }
@@ -121,8 +121,9 @@ public class JsonTaskList implements TaskList {
if(taskMap.isEmpty()) { if(taskMap.isEmpty()) {
loadTaskListFromFile(); loadTaskListFromFile();
} }
if(taskMap.containsKey(task.getId())){ Long taskId = task.getId().orElseThrow(IOException::new);
taskMap.remove(task.getId()); if(taskMap.containsKey(taskId)){
taskMap.remove(taskId);
writeTaskListToFile(); writeTaskListToFile();
} }
} }
@@ -162,7 +163,7 @@ public class JsonTaskList implements TaskList {
taskMap = result.stream() taskMap = result.stream()
.collect(HashMap::new, .collect(HashMap::new,
(res, task) -> res.put(task.getId(), task), (res, task) -> res.put(task.getId().orElse(0L), task),
(existing, replacement) -> {}); (existing, replacement) -> {});
} }
@@ -9,7 +9,7 @@ import java.util.Optional;
* May be created using the builder pattern. * May be created using the builder pattern.
*/ */
public class Task { public class Task {
private long id; private Long id;
private final String name; private final String name;
private final String description; private final String description;
private final LocalDate startDate; private final LocalDate startDate;
@@ -62,7 +62,7 @@ public class Task {
} }
// Getters // Getters
public long getId() { return id; } public Optional<Long> getId() { return Optional.ofNullable(id); }
public String getName() { return name; } public String getName() { return name; }
public String getDescription() { return description; } public String getDescription() { return description; }
public LocalDate getStartDate() { return startDate; } public LocalDate getStartDate() { return startDate; }
@@ -34,6 +34,7 @@ public class TaskTemplate {
public void setRelativeEndDate(Integer relativeEndDate) { public void setRelativeEndDate(Integer relativeEndDate) {
this.relativeEndDate = relativeEndDate; this.relativeEndDate = relativeEndDate;
} }
public void setInterval(Integer interval) { public void setInterval(Integer interval) {
this.interval = interval; this.interval = interval;
} }
@@ -45,13 +46,10 @@ public class TaskTemplate {
} }
public Task generateTask(LocalDate realStartDate, long cropId) { public Task generateTask(LocalDate realStartDate, long cropId) {
Task task = new Task(name, description, realStartDate.plusDays(relativeStartDate), cropId); LocalDate endDate = relativeEndDate != null ? realStartDate.plusDays(relativeEndDate) : null;
if (relativeEndDate != null) {
task.withEndDate(realStartDate.plusDays(relativeEndDate)); return new Task(name, description, realStartDate.plusDays(relativeStartDate), cropId)
} .withInterval(interval)
if (interval != null) { .withEndDate(endDate);
task.withInterval(interval);
}
return task;
} }
} }
@@ -3,6 +3,7 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.ScrollPane?> <?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.image.ImageView?> <?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.AnchorPane?>
@@ -12,13 +13,11 @@
<?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="785.0" prefWidth="899.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.CropDetailController">
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="785.0" prefWidth="899.0"
xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.CropDetailController">
<children> <children>
<ScrollPane fitToWidth="true" prefHeight="759.0" prefWidth="664.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <ScrollPane fitToWidth="true" prefHeight="759.0" prefWidth="664.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<content> <content>
<VBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="503.0" prefWidth="897.0"> <VBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="781.0" prefWidth="897.0">
<padding> <padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding> </padding>
@@ -85,22 +84,22 @@
<Insets bottom="10.0" /> <Insets bottom="10.0" />
</VBox.margin> </VBox.margin>
</Label> </Label>
<VBox fx:id="growthPhases_vbox" prefHeight="135.0" prefWidth="879.0"> <ListView fx:id="taskList_listView" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="877.0" VBox.vgrow="ALWAYS">
<VBox.margin> <VBox.margin>
<Insets bottom="10.0" /> <Insets bottom="10.0" />
</VBox.margin> </VBox.margin>
</VBox> </ListView>
<Button fx:id="editTaskList_button" mnemonicParsing="false" onAction="#editTaskList" prefHeight="25.0" prefWidth="45.0"> <Button fx:id="addTask_button" mnemonicParsing="false" onAction="#addTask" prefHeight="25.0" prefWidth="45.0">
<VBox.margin> <VBox.margin>
<Insets bottom="10.0" /> <Insets bottom="10.0" />
</VBox.margin> </VBox.margin>
</Button> </Button>
<Label text="Pests:" /> <Label text="Pests:" />
<VBox fx:id="pests_vbox" prefHeight="200.0" prefWidth="100.0"> <ListView fx:id="pests_listView" maxHeight="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<VBox.margin> <VBox.margin>
<Insets bottom="10.0" /> <Insets bottom="10.0" />
</VBox.margin> </VBox.margin>
</VBox> </ListView>
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" VBox.vgrow="NEVER"> <HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" VBox.vgrow="NEVER">
<children> <children>
<Label text="Area:"> <Label text="Area:">
@@ -12,7 +12,7 @@
<Button fx:id="home_button" mnemonicParsing="false" onAction="#goToHome" prefHeight="38.0" prefWidth="40.0" HBox.hgrow="NEVER" /> <Button fx:id="home_button" mnemonicParsing="false" onAction="#goToHome" prefHeight="38.0" prefWidth="40.0" HBox.hgrow="NEVER" />
<Button fx:id="myGarden_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToMyPlants" prefHeight="38.0" prefWidth="121.0" text="My Garden" /> <Button fx:id="myGarden_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToMyPlants" prefHeight="38.0" prefWidth="121.0" text="My Garden" />
<Button fx:id="mySchedule_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToMySchedule" prefHeight="38.0" prefWidth="121.0" text="My Schedule" /> <Button fx:id="mySchedule_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToMySchedule" prefHeight="38.0" prefWidth="121.0" text="My Schedule" />
<Button fx:id="tutorial_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToTutorial" prefHeight="38.0" prefWidth="121.0" text="Tutorial" /> <Button fx:id="tutorial_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#showTutorial" prefHeight="38.0" prefWidth="121.0" text="Tutorial" />
<Pane maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" /> <Pane maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
<Button fx:id="settings_button" maxHeight="1.7976931348623157E308" mnemonicParsing="false" onAction="#openSettings" prefHeight="38.0" prefWidth="40.0" HBox.hgrow="NEVER" /> <Button fx:id="settings_button" maxHeight="1.7976931348623157E308" mnemonicParsing="false" onAction="#openSettings" prefHeight="38.0" prefWidth="40.0" HBox.hgrow="NEVER" />
</children> </children>
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="259.0" prefWidth="390.0" xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.TaskFormularController">
<children>
<VBox layoutX="14.0" layoutY="14.0" prefHeight="272.0" prefWidth="390.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<children>
<HBox alignment="CENTER_LEFT" prefHeight="35.0" prefWidth="560.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="Task name:" HBox.hgrow="ALWAYS" />
<TextField fx:id="taskName_field" promptText="Task Name" />
</children>
</HBox>
<HBox prefHeight="77.0" prefWidth="350.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="Description:" HBox.hgrow="ALWAYS" />
<TextArea fx:id="description_area" prefHeight="73.0" prefWidth="206.0" promptText="Description" />
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutX="30.0" layoutY="30.0" prefHeight="35.0" prefWidth="560.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="Start Date:" HBox.hgrow="ALWAYS" />
<DatePicker fx:id="start_datePicker" />
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutX="30.0" layoutY="143.0" prefHeight="35.0" prefWidth="560.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="End Date:" HBox.hgrow="ALWAYS" />
<DatePicker fx:id="end_datePicker" />
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutX="30.0" layoutY="30.0" prefHeight="35.0" prefWidth="560.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="Interval:" HBox.hgrow="ALWAYS" />
<TextField fx:id="interval_field" promptText="Interval (e.g. 0, 1, 3 ...)" />
</children>
</HBox>
</children>
</VBox>
</children>
</AnchorPane>
@@ -1,27 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.text.TextFlow?>
<BorderPane maxHeight="470.0" maxWidth="724.0" minHeight="470.0" minWidth="724.0" prefHeight="470.0" prefWidth="724.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.TutorialController">
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17" <bottom>
xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.TutorialController"> <HBox alignment="CENTER" prefHeight="0.0" prefWidth="724.0" BorderPane.alignment="CENTER">
<children> <children>
<VBox layoutX="7.0" layoutY="8.0" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <CheckBox mnemonicParsing="false" text="Don't show again" />
<children> <Separator prefWidth="50.0" visible="false" HBox.hgrow="ALWAYS" />
<Label text="Tutorial"> <ButtonBar prefHeight="40.0" prefWidth="200.0">
<font> <buttons>
<Font size="18.0" /> <Button cancelButton="true" contentDisplay="CENTER" graphicTextGap="5.0" mnemonicParsing="false" text="Close" onAction="#closeTutorial"/>
</font> <Button fx:id="previousPageButton" mnemonicParsing="false" text="Previous" onAction="#viewPreviousPage"/>
</Label> <Button fx:id="nextPageButton" defaultButton="true" mnemonicParsing="false" text="Next" onAction="#viewNextPage" />
<Label text="To be added" /> </buttons>
</ButtonBar>
</children> </children>
<padding> <padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> <Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</padding> </padding>
</HBox>
</bottom>
<center>
<StackPane fx:id="tourPages" prefHeight="150.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<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">
<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="In the &quot;My Garden&quot; tab, you can see all of the plants you're planning on growing.">
<VBox.margin>
<Insets bottom="20.0" />
</VBox.margin>
</Text>
<HBox prefHeight="100.0" prefWidth="200.0">
<children>
<TextFlow lineSpacing="4.0" prefHeight="90.0" prefWidth="500.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="To get started, click on the &quot;Add new Plant&quot; button in the bottom left. This will open a list of all the plants you can grow.&#10;Use the search bar at the top, the filters on the left, or simply browse the list until you've found a plant you want to grow in your garden." wrappingWidth="345.80316162109375" />
</children>
<HBox.margin>
<Insets bottom="20.0" />
</HBox.margin>
</TextFlow>
<Separator prefWidth="200.0" visible="false" HBox.hgrow="ALWAYS" />
<ImageView fx:id="imgAddNewPlant" fitHeight="98.0" fitWidth="200.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="Once you've found what you're looking for, click the &quot;Select Sow/Harvest Date&quot; button at the bottom of the window.&#10;In the subsequently shown dialog, you can select the date you'd like to sow or harvest the crop. The date selector shows possible dates in green. Confirm your selection with the &quot;Save&quot; button." />
</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" />
</children>
</HBox>
</children>
<opaqueInsets>
<Insets />
</opaqueInsets>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Managing Your Tasks">
<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="In the &quot;My Schedule&quot; tab, you can see all of the tasks you need to complete for a successful harvest.">
<VBox.margin>
<Insets bottom="20.0" />
</VBox.margin>
</Text>
<HBox prefHeight="100.0" prefWidth="200.0">
<children>
<TextFlow lineSpacing="4.0" prefHeight="90.0" prefWidth="500.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="You can view the tasks for a specific crop by selecting it from the left sidebar.&#10;To see all tasks again, click the &quot;Clear Filter&quot; button at the bottom." wrappingWidth="345.80316162109375" />
</children>
<HBox.margin>
<Insets bottom="20.0" />
</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" />
</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 &quot;Add Task&quot; button.&#10;In the subsequently shown dialog, you can enter the corresponding details.&#10;&#10;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 />
</opaqueInsets>
</VBox> </VBox>
</children> </children>
</AnchorPane> <BorderPane.margin>
<Insets />
</BorderPane.margin>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</StackPane>
</center>
</BorderPane>
@@ -184,15 +184,14 @@
"litersPerSqM": 15, "litersPerSqM": 15,
"interval": 4, "interval": 4,
"notes": [ "notes": [
] ]
}, },
"taskTemplates": [ "taskTemplates": [
{ {
"name": "hilling", "name": "Plant Sets",
"relativeStartDate": 0, "relativeStartDate": 0,
"relativeEndDate": 0, "relativeEndDate": 0,
"description": "Mound up the soil around the plant until just the top few leaves show above the soil. ", "description": "Plant the sets about 5cm deep into the soil.",
"interval": null, "interval": null,
"isOptional": false "isOptional": false
} }
@@ -232,7 +231,6 @@
"litersPerSqM": 0, "litersPerSqM": 0,
"interval": null, "interval": null,
"notes": [ "notes": [
] ]
}, },
"taskTemplates": [ "taskTemplates": [
@@ -1,56 +1,56 @@
[ [
{ {
"id" : 1, "id": 1,
"name" : "sow plant", "name": "sow plant",
"description": "Plant the seeds, crops in de bed.", "description": "Plant the seeds, crops in de bed.",
"startDate" : "2022-05-01", "startDate": "2022-05-01",
"endDate" : "2022-05-01", "endDate": "2022-05-01",
"interval" : 0, "interval": 0,
"cropId" : 0 "cropId": 0
}, },
{ {
"id" : 2, "id": 2,
"name" : "water plant", "name": "water plant",
"description": "water the plant, so that the soil is wet around the plant.", "description": "water the plant, so that the soil is wet around the plant.",
"startDate" : "2022-05-01", "startDate": "2022-05-01",
"endDate" : "2022-09-01", "endDate": "2022-09-01",
"interval" : 2, "interval": 2,
"cropId" : 0 "cropId": 0
}, },
{ {
"id" : 3, "id": 3,
"name" : "fertilize plant", "name": "fertilize plant",
"description": "The fertilizer has to be mixed with water. Then fertilize the plants soil with the mixture", "description": "The fertilizer has to be mixed with water. Then fertilize the plants soil with the mixture",
"startDate" : "2022-06-01", "startDate": "2022-06-01",
"endDate" : "2022-08-01", "endDate": "2022-08-01",
"interval" : 28, "interval": 28,
"cropId" : 0 "cropId": 0
}, },
{ {
"id" : 4, "id": 4,
"name" : "covering plant", "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", "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", "startDate": "2022-07-01",
"endDate" : "2022-07-01", "endDate": "2022-07-01",
"interval" : 0, "interval": 0,
"cropId" : 0 "cropId": 0
}, },
{ {
"id" : 5, "id": 5,
"name" : "look after plant", "name": "look after plant",
"description": "Look for pest or illness at the leaves of the plant. Check the soil around the plant, if the roots are enough covered with soil", "description": "Look for pest or illness at the leaves of the plant. Check the soil around the plant, if the roots are enough covered with soil",
"startDate" : "2022-05-01", "startDate": "2022-05-01",
"endDate" : "2022-09-01", "endDate": "2022-09-01",
"interval" : 5, "interval": 5,
"cropId" : 0 "cropId": 0
}, },
{ {
"id" : 6, "id": 6,
"name" : "harvest plant", "name": "harvest plant",
"description": "Pull the ripe vegetables out from the soil. Clean them with clear, fresh water. ", "description": "Pull the ripe vegetables out from the soil. Clean them with clear, fresh water. ",
"startDate" : "2022-09-01", "startDate": "2022-09-01",
"endDate" : "2022-09-01", "endDate": "2022-09-01",
"interval" : 0, "interval": 0,
"cropId" : 0 "cropId": 0
} }
] ]
@@ -72,7 +72,8 @@ public class JsonTaskListTest {
@Test @Test
@DisplayName("Remove task.") @DisplayName("Remove task.")
void removeTask() { void removeTask() {
Task task = new Task("Dummy", "Dummy", LocalDate.parse("2022-05-31", formatter), 1).withId(2); Task task = new Task("Dummy", "Dummy", LocalDate.parse("2022-05-31", formatter), 1)
.withId(2);
try { try {
testDatabase.removeTask(task); testDatabase.removeTask(task);
List<Task> taskList; List<Task> taskList;
@@ -55,8 +55,8 @@ public class GardenPlanModelTest {
new GrowthPhase(MonthDay.of(2, 8), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>()), new GrowthPhase(MonthDay.of(2, 8), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>()),
new GrowthPhase(MonthDay.of(10, 2), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>()))); new GrowthPhase(MonthDay.of(10, 2), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>())));
exampleCropOnion = new Crop(examplePlantOnion.id(), LocalDate.of(2023,3,1)); exampleCropOnion = new Crop(examplePlantOnion.id(), LocalDate.of(2023, 3, 1))
exampleCropOnion.withId(3); .withId(3);
examplePlantCarrot = new Plant( examplePlantCarrot = new Plant(
1, 1,
"Early Carrot", "Early Carrot",
@@ -67,18 +67,18 @@ public class GardenPlanModelTest {
"sandy to loamy, loose soil, free of stones", "sandy to loamy, loose soil, free of stones",
new ArrayList<>(), new ArrayList<>(),
List.of(new GrowthPhase(MonthDay.of(4, 4), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>()))); List.of(new GrowthPhase(MonthDay.of(4, 4), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>())));
exampleCropCarrot = new Crop(examplePlantCarrot.id(), LocalDate.now()); exampleCropCarrot = new Crop(examplePlantCarrot.id(), LocalDate.now())
exampleCropCarrot.withId(5); .withId(5);
exampleCrop1 = new Crop(1, LocalDate.of(2023,2,25)); exampleCrop1 = new Crop(1, LocalDate.of(2023, 2, 25))
exampleCrop1.withId(0); .withId(0)
exampleCrop1.withArea(0.5); .withArea(0.5);
exampleCrop2 = new Crop(1,LocalDate.of(2023,3,1)); exampleCrop2 = new Crop(1, LocalDate.of(2023, 3, 1))
exampleCrop2.withId(1); .withId(1)
exampleCrop2.withArea(0.5); .withArea(0.5);
exampleCrop3 = new Crop(0,LocalDate.of(2023,3,1)); exampleCrop3 = new Crop(0, LocalDate.of(2023, 3, 1))
exampleCrop3.withId(2); .withId(2)
exampleCrop3.withArea(1.0); .withArea(1.0);
exampleCrops = new ArrayList<>(); exampleCrops = new ArrayList<>();
exampleCrops.add(exampleCrop1); exampleCrops.add(exampleCrop1);
@@ -107,7 +107,7 @@ public class GardenPlanModelTest {
@Test @Test
void plantAsCrop() throws HardinessZoneNotSetException, IOException, PlantNotFoundException { void plantAsCrop() throws HardinessZoneNotSetException, IOException, PlantNotFoundException {
model.plantAsCrop(examplePlantOnion, LocalDate.of(2023,3,1)); model.plantAsCrop(examplePlantOnion, LocalDate.of(2023, 3, 1));
Optional<Crop> exampleCrop = model.getCrop(2L); Optional<Crop> exampleCrop = model.getCrop(2L);
assertTrue(exampleCrop.isPresent()); assertTrue(exampleCrop.isPresent());
assertEquals(model.getCrops().get(2), exampleCrop.get()); assertEquals(model.getCrops().get(2), exampleCrop.get());
@@ -119,6 +119,6 @@ public class GardenPlanModelTest {
exampleCrop1.withId(2); exampleCrop1.withId(2);
exampleCrop1.withArea(1.5); exampleCrop1.withArea(1.5);
model.removeCrop(exampleCrop1); model.removeCrop(exampleCrop1);
assertEquals(2,model.getCrops().size()); assertEquals(2, model.getCrops().size());
} }
} }