Compare commits

...
Author SHA1 Message Date
giavaphiandGitHub Enterprise 7a6a0eb66f Merge pull request #45 from schrom01/feature_controllerPlantList_M2
Plant Controller Filter to hardiness zone and season
2022-10-31 13:07:49 +01:00
gulerdavandGitHub Enterprise 3afeb8a22d Merge branch 'dev' into feature_controllerPlantList_M2 2022-10-31 13:05:49 +01:00
gulerdavandGitHub Enterprise 88f9bf7990 Merge pull request #44 from schrom01/feature_plantlist-gui_M2
Display images in gui and fix image deserialise with spaces
2022-10-31 12:59:06 +01:00
giavaphi 5f35d99839 fixed merge conflict PlantsController 2022-10-31 12:52:27 +01:00
schrom01 d0cef1fe82 fixed problems with file Path
#43
2022-10-31 12:43:13 +01:00
David Guler 7a060be84a case insensitive serarch, images 2022-10-31 10:06:17 +01:00
giavaphi 5b039eb762 #12 small changes and java doc 2022-10-31 09:23:44 +01:00
gulerdavandGitHub Enterprise 6c00b7f182 Merge pull request #41 from schrom01/feature_plantList_M2
fixed Method getFilteredPlantListByString
2022-10-31 08:01:43 +01:00
giavaphi c83b8695ab #12 PlantsController filter list view 2022-10-30 23:23:19 +01:00
giavaphi f22ef61d3c #42 bug resize window fix 2022-10-30 18:06:18 +01:00
gulerdavandGitHub Enterprise f452b73233 Merge pull request #40 from schrom01/feature_plantList_M2
Feature plant list m2
2022-10-30 10:36:07 +01:00
Roman SchenkandGitHub Enterprise 691d2a6345 Merge pull request #38 from schrom01/feature_skeletonGUI_M2
#16 create MainFXML with skeletal structure
2022-10-30 09:23:21 +01:00
Roman SchenkandGitHub Enterprise 246fed7826 Merge pull request #39 from schrom01/feature_json-plant-db_M2
#36 Added images to Plant database
2022-10-29 11:36:14 +02:00
David Guler 5b860e0ff4 #10 doc: layer-diagram 2022-10-29 10:17:51 +02:00
gulerdavandGitHub Enterprise 554c560832 Merge pull request #37 from schrom01/feature_json-task-db_M2
Feature json task db m2
2022-10-29 09:47:17 +02:00
gulerdavandGitHub Enterprise 8120071348 Merge branch 'dev' into feature_json-task-db_M2 2022-10-29 09:46:50 +02:00
David Guler bfbda8d753 #36 Added images to Plant database 2022-10-29 09:44:02 +02:00
Gian-Andrea Hutter 51e8f27a20 #17 implementation objectmapper writeFile 2022-10-28 12:24:55 +02:00
Gian-Andrea Hutter bd2aa60128 Merge remote-tracking branch 'origin/feature_json-gardenplan_M2' into feature_json-task-db_M2
# Conflicts:
#	build.gradle
2022-10-28 11:52:49 +02:00
Gian-Andrea Hutter 63f0501397 Merge branch 'dev' into feature_json-task-db_M2 2022-10-25 18:15:26 +02:00
Gian-Andrea Hutter 6c75fcd0ec #17 taskdb.json changed to the right variable types, JsonTaskDatabase.java fully implemented and documented 2022-10-25 18:11:29 +02:00
Gian-Andrea Hutter 8fd57d91f2 Merge remote-tracking branch 'origin/feature_json-task-db_M2' into feature_json-task-db_M2 2022-10-25 10:11:02 +02:00
Gian-Andrea Hutter 23d87f7a85 #17 taskdb.json changed to the right variable types, ad isIntimePeriode to , JasonTaskDatabase 2022-10-25 10:10:40 +02:00
Elias Csomor af0e73c007 Prepare for tests 2022-10-24 14:20:59 +02:00
Gian-Andrea Hutter e550f5549a Merge remote-tracking branch 'origin/feature_json-gardenplan_M2' into feature_json-task-db_M2 2022-10-24 13:54:43 +02:00
Gian-Andrea Hutter 694da97cd6 #17 implementation JsonTaskDatabase.java, writeTasklistToFile 2022-10-24 13:54:28 +02:00
Gian-Andrea Hutter 0ca381f8cc Merge branch 'dev' into feature_json-task-db_M2
# Conflicts:
#	src/main/java/module-info.java
2022-10-24 12:53:09 +02:00
Gian-Andrea Hutter 904041afc0 #17 implementation JsonTaskDatabase.java, gradle import for jackson 2022-10-24 12:43:50 +02:00
Gian-Andrea Hutter e228b9019d #17 implementation JsonTaskDatabase.java, gradle import for jackson 2022-10-22 11:52:06 +02:00
Gian-Andrea Hutter a2008450e6 #17 implementation of an example task database 2022-10-21 21:18:29 +02:00
Gian-Andrea Hutter 96b5dba36c #17 first version of tasks 2022-10-20 21:59:18 +02:00
26 changed files with 637 additions and 157 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+7
View File
@@ -0,0 +1,7 @@
<diagram program="umletino" version="15.0.0"><zoom_level>10</zoom_level><element><id>UMLPackage</id><coordinates><x>390</x><y>60</y><w>340</w><h>100</h></coordinates><panel_attributes>UI</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>390</x><y>180</y><w>460</w><h>120</h></coordinates><panel_attributes>Domain</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>390</x><y>330</y><w>460</w><h>120</h></coordinates><panel_attributes>Technical Services</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>400</x><y>90</y><w>100</w><h>60</h></coordinates><panel_attributes>Views (JFX)</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>400</x><y>220</y><w>100</w><h>60</h></coordinates><panel_attributes>IO</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>400</x><y>370</y><w>100</w><h>60</h></coordinates><panel_attributes>Jackson</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>510</x><y>220</y><w>100</w><h>60</h></coordinates><panel_attributes>Types</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>620</x><y>220</y><w>100</w><h>60</h></coordinates><panel_attributes>Models</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>510</x><y>90</y><w>110</w><h>60</h></coordinates><panel_attributes>Controllers (JFX)</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>510</x><y>370</y><w>100</w><h>60</h></coordinates><panel_attributes>Logging</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>620</x><y>370</y><w>100</w><h>60</h></coordinates><panel_attributes>JavaFX</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>730</x><y>220</y><w>110</w><h>60</h></coordinates><panel_attributes>Services</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>730</x><y>370</y><w>110</w><h>60</h></coordinates><panel_attributes>HTTP/API</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>570</x><y>150</y><w>90</w><h>70</h></coordinates><panel_attributes>lt=.&gt;
</panel_attributes><additional_attributes>70;10;10;50</additional_attributes></element><element><id>Relation</id><coordinates><x>570</x><y>290</y><w>110</w><h>80</h></coordinates><panel_attributes>lt=.&gt;
</panel_attributes><additional_attributes>90;10;10;60</additional_attributes></element><element><id>Relation</id><coordinates><x>340</x><y>120</y><w>350</w><h>400</h></coordinates><panel_attributes>lt=.&gt;
</panel_attributes><additional_attributes>60;10;10;10;10;380;330;380;330;310</additional_attributes></element><element><id>Relation</id><coordinates><x>770</x><y>270</y><w>30</w><h>120</h></coordinates><panel_attributes>lt=.&gt;
</panel_attributes><additional_attributes>10;10;10;100</additional_attributes></element><element><id>Relation</id><coordinates><x>360</x><y>250</y><w>60</w><h>180</h></coordinates><panel_attributes>lt=.&gt;
</panel_attributes><additional_attributes>40;10;10;10;10;160;40;160</additional_attributes></element><element><id>Relation</id><coordinates><x>610</x><y>120</y><w>70</w><h>120</h></coordinates><panel_attributes>lt=.&gt;
</panel_attributes><additional_attributes>10;10;50;10;50;100</additional_attributes></element></diagram>
@@ -10,10 +10,16 @@ import javafx.scene.layout.AnchorPane;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.ResourceBundle;
public class MainFXMLController implements Initializable {
/**
* Caching the panes
*/
private final Map<String, AnchorPane> panes = new HashMap<>();
@FXML
private Button home_button;
@@ -62,15 +68,22 @@ public class MainFXMLController implements Initializable {
* @throws IOException exception when file does not exist
*/
public void loadPane(String fxmlFile) throws IOException {
//ToDo HGrow and VGrow of new node
Node node;
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
node = (Node)loader.load();
if(fxmlFile.equals("MyPlants.fxml")) {
MyPlantsController myPlantsController = loader.getController();
myPlantsController.getMainController(this);
AnchorPane anchorPane = panes.get(fxmlFile);
if (anchorPane == null) {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
anchorPane = loader.load();
panes.put(fxmlFile, anchorPane);
if(fxmlFile.equals("MyPlants.fxml")) {
MyPlantsController myPlantsController = loader.getController();
myPlantsController.getMainController(this);
}
}
mainPane.getChildren().setAll(node);
mainPane.getChildren().setAll(anchorPane);
anchorPane.prefWidthProperty().bind(mainPane.widthProperty());
anchorPane.prefHeightProperty().bind(mainPane.heightProperty());
}
private void styleChangeButton(Button button) {
@@ -4,19 +4,26 @@ import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
import ch.zhaw.gartenverwaltung.plantList.PlantListModel;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import ch.zhaw.gartenverwaltung.types.Plant;
import ch.zhaw.gartenverwaltung.types.Seasons;
import javafx.application.Platform;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.geometry.Bounds;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.InputMethodEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import java.io.IOException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.ResourceBundle;
@@ -25,8 +32,12 @@ public class PlantsController implements Initializable {
private Plant selectedPlant = null;
private final HardinessZone DEFAULT_HARDINESS_ZONE = HardinessZone.ZONE_8A;
// TODO: move to model
private final ListProperty<Plant> plantListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
@FXML
private CheckBox autum_filter;
private VBox seasons;
@FXML
private VBox climate_zones;
@@ -46,79 +57,45 @@ public class PlantsController implements Initializable {
@FXML
private TextField search_plants;
@FXML
private CheckBox sommer_filter;
@FXML
private CheckBox spring_filter;
@FXML
private CheckBox winter_filter;
@FXML
void filterAutum(ActionEvent event) {
//ToDo
}
@FXML
void filterSommer(ActionEvent event) {
//ToDo
}
@FXML
void filterSpring(ActionEvent event) {
//ToDo
}
@FXML
void filterWinter(ActionEvent event) {
//ToDo
}
/**
* saves the current selected plant in new JSON database
* @param event event
*/
@FXML
void saveToMyPlant(ActionEvent event) {
//ToDo model save selectedPlant to mySelectedPlant(IO)
}
@FXML
void searchForPlant(InputMethodEvent event) {
viewFilteredListBySearch(search_plants.getText());
}
/**
* fill list view with current hardiness zone
* set default values
* create filter of season and hardiness zone
* create event listener for selected list entry and search by query
* {@inheritDoc}
*/
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
List<Plant> plantList = new LinkedList<>();
try {
plantList = plantListModel.getPlantList(DEFAULT_HARDINESS_ZONE);
} catch (HardinessZoneNotSetException | IOException e) {
e.printStackTrace();
}
fillListViewWithData(plantList);
setListCellFactory();
fillPlantListWithHardinessZone();
list_plants.itemsProperty().bind(plantListProperty);
description_plant.setText("");
saveToMyPlant_button.setDisable(true);
createFilterSeasons();
createFilterHardinessZone();
lookForSelectedListEntry();
try {
viewFilteredListBySearch();
} catch (HardinessZoneNotSetException | IOException e) {
e.printStackTrace();
}
}
/**
* update the ListView according to the plant list provided
* Entry in ListView is plant name
* @param list plantList which fill the ListView
* set text of list view to plant name
*/
private void fillListViewWithData(List<Plant> list) {
clearListView();
for (Plant plant : list) {
list_plants.getItems().add(plant);
}
private void setListCellFactory() {
list_plants.setCellFactory(param -> new ListCell<Plant>() {
@Override
protected void updateItem(Plant plant, boolean empty) {
@@ -133,44 +110,139 @@ public class PlantsController implements Initializable {
});
}
private void viewFilteredListByFilters() {
boolean springValue = spring_filter.isSelected();
boolean sommerValue = sommer_filter.isSelected();
boolean autumValue = autum_filter.isSelected();
boolean winterValue = winter_filter.isSelected();
//ToDo getFilteredPlantList with (plantListModel.getFilteredPlantList(DEFAULT_HARDINESS_ZONE, <predicate>))
//List<Plant> plantList = new LinkedList<>();
//fillListViewWithData(plantList);
}
private void viewFilteredListBySearch(String query) {
//ToDo getFilteredPlantList with (plantListModel.getFilteredPlantList(DEFAULT_HARDINESS_ZONE, <predicate>))
//List<Plant> plantList = new LinkedList<>();
//fillListViewWithData(plantList);
}
private void createFilterHardinessZone() {
//ToDo create radioList of hardinessZone in VBox climate_zones
/**
* get plant list according to param season and hardiness zone
* fill list view with plant list
* @param season enum of seasons
* @throws HardinessZoneNotSetException throws exception
* @throws IOException throws exception
*/
private void viewFilteredListBySeason(Seasons season) throws HardinessZoneNotSetException, IOException {
clearListView();
plantListProperty.addAll(plantListModel.getFilteredPlantListBySaisonWithoutGrowthPhase(plantListModel.getCurrentZone(), season.getStartDate(), season.getEndDate()));
}
/**
* observes changes in the selectedProperty of ListView and updates the description label
* get plant list filtered by search plant entry and hardiness zone
* fill list view with plant list
* @throws HardinessZoneNotSetException throws exception when no hardiness zone is defined
* @throws IOException throws exception
*/
private void viewFilteredListBySearch() throws HardinessZoneNotSetException, IOException {
search_plants.textProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.isEmpty()) {
fillPlantListWithHardinessZone();
}else {
try {
List<Plant> filteredPlants = plantListModel.getFilteredPlantListByString(DEFAULT_HARDINESS_ZONE, newValue);
clearListView();
plantListProperty.addAll(filteredPlants);
} catch (HardinessZoneNotSetException | IOException e) {
e.printStackTrace();
}
}
});
}
/**
* get plant list of current hardiness zone
* fill list view with plant list
*/
private void fillPlantListWithHardinessZone() {
try {
clearListView();
plantListProperty.addAll(plantListModel.getPlantList(plantListModel.getCurrentZone()));
} catch (HardinessZoneNotSetException | IOException e) {
e.printStackTrace();
}
}
/**
* creates radio buttons for the hardiness zones defined in enum HardinessZone
* defines default value as selected
* when selected filter viewList according to hardiness zone
*/
private void createFilterHardinessZone() {
ToggleGroup hardinessGroup = new ToggleGroup();
for (HardinessZone zone : HardinessZone.values()) {
RadioButton radioButton = new RadioButton(zone.name());
radioButton.setToggleGroup(hardinessGroup);
radioButton.setPadding(new Insets(0,0,10,0));
if (zone.equals(DEFAULT_HARDINESS_ZONE)) {
radioButton.setSelected(true);
}
radioButton.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
plantListModel.setCurrentZone(zone);
fillPlantListWithHardinessZone();
}
});
climate_zones.getChildren().add(radioButton);
}
}
/**
* creates radio buttons for the seasons defined in enum Seasons
* defines default value as selected
* when selected filter viewList according to seasons
*/
private void createFilterSeasons() {
ToggleGroup seasonGroup = new ToggleGroup();
for (Seasons season : Seasons.values()) {
RadioButton radioButton = new RadioButton(season.name());
radioButton.setToggleGroup(seasonGroup);
radioButton.setPadding(new Insets(0,0,10,0));
if (season.equals(Seasons.AllSEASONS)) {
radioButton.setSelected(true);
}
radioButton.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (season.equals(Seasons.AllSEASONS)) {
fillPlantListWithHardinessZone();
} else {
try {
viewFilteredListBySeason(season);
} catch (HardinessZoneNotSetException | IOException e) {
e.printStackTrace();
}
}
}
});
seasons.getChildren().add(radioButton);
}
}
/**
* observes changes in the selectedProperty of ListView and updates:
* the description label
* image of the plant
*/
private void lookForSelectedListEntry() {
list_plants.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Plant>() {
@Override
public void changed(ObservableValue<? extends Plant> observable, Plant oldValue, Plant newValue) {
//ToDo
if(newValue != null) {
selectedPlant = newValue;
description_plant.setText(selectedPlant.description());
saveToMyPlant_button.setDisable(false);
//update img plant
Image img;
if(selectedPlant.image() != null) {
img = selectedPlant.image();
} else {
img = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
}
img_plant.setImage(img);
} else {
selectedPlant = null;
description_plant.setText("");
saveToMyPlant_button.setDisable(true);
//update img when null placeholder PNG
Image img = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
img_plant.setImage(img);
}
}
});
@@ -181,6 +253,6 @@ public class PlantsController implements Initializable {
* clears the ListView of entries
*/
private void clearListView() {
list_plants.getItems().clear();
plantListProperty.clear();
}
}
@@ -0,0 +1,8 @@
package ch.zhaw.gartenverwaltung.gardenplan;
public class Gardenplanmodel {
// private JasonGardenplan gardenplan;
// liste von crops
//task generieren
//plant holen, task template mit tasktemplateklasse tasks erstellen
}
@@ -1,10 +1,13 @@
package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.json.PlantImageDeserializer;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import ch.zhaw.gartenverwaltung.types.Plant;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.MonthDayDeserializer;
import javafx.scene.image.Image;
import java.io.IOException;
import java.net.URL;
@@ -31,10 +34,14 @@ public class JsonPlantDatabase implements PlantDatabase {
* Creating constant objects required to deserialize the {@link MonthDay} classes
*/
private final static JavaTimeModule timeModule = new JavaTimeModule();
private final static SimpleModule imageModule = new SimpleModule();
static {
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM-dd");
MonthDayDeserializer dateDeserializer = new MonthDayDeserializer(dateFormat);
timeModule.addDeserializer(MonthDay.class, dateDeserializer);
imageModule.addDeserializer(Image.class, new PlantImageDeserializer());
}
/**
@@ -75,8 +82,9 @@ public class JsonPlantDatabase implements PlantDatabase {
}
if (dataSource != null) {
currentZone = zone;
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(timeModule);
ObjectMapper mapper = new ObjectMapper()
.registerModule(timeModule)
.registerModule(imageModule);
List<Plant> result;
result = mapper.readerForListOf(Plant.class).readValue(dataSource);
@@ -0,0 +1,126 @@
package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.types.Task;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Implements the {@link TaskDatabase} interface for loading and writing {@link Task} objects
* from and to a JSON file.
* The reads are cached to minimize file-io operations.
*/
public class JsonTaskDatabase implements TaskDatabase{
IdProvider idProvider;
private final URL dataSource = getClass().getResource("taskdb.json");
private Map<Long, Task> taskMap = Collections.emptyMap();
/**
* Creating constant objects required to deserialize the {@link LocalDate} classes
*/
private final static JavaTimeModule timeModule = new JavaTimeModule();
static {
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDateDeserializer dateDeserializer = new LocalDateDeserializer(dateFormat);
timeModule.addDeserializer(LocalDate.class, dateDeserializer);
}
/**
* If no data is currently loaded, data is loaded from {@link #dataSource}.
* In any case, the values of {@link #taskMap} are returned.
*
* @see TaskDatabase#getTaskList(LocalDate, LocalDate)
*/
@Override
public List<Task> getTaskList(LocalDate start, LocalDate end) throws IOException{
if(taskMap.isEmpty()) {
loadTaskListFromFile();
}
return taskMap.values().stream().filter(task -> task.isInTimePeriode(start, end)).toList();
}
/**
* If no data is currently loaded, data is loaded from {@link #dataSource}.
* If the {@link Task} has an id than the task is added to the {@link #taskMap}
* otherwise the id is generated with the {@link IdProvider} before adding
* it to the {@link #taskMap}. In any case, the {@link #taskMap} is written
* to the {@link #dataSource}.
*
* @see TaskDatabase#saveTask(Task)
*/
@Override
public void saveTask(Task task) throws IOException {
if(taskMap.isEmpty()) {
loadTaskListFromFile();
}
if(task.getId() == 0) {
task.withId(idProvider.incrementAndGet());
}
writeTaskListToFile();
}
/**
* If no data is currently loaded, data is loaded from {@link #dataSource}.
* If the {@link Task}s id is found in the {@link #taskMap}, the Task is removed
* from the {@link #taskMap}. Then the Task are written to the {@link #dataSource}.
*
* @see TaskDatabase#removeTask(Task)
*/
@Override
public void removeTask(Task task) throws IOException {
if(taskMap.isEmpty()) {
loadTaskListFromFile();
}
if(taskMap.containsKey(task.getId())){
taskMap.remove(task.getId());
writeTaskListToFile();
}
}
/**
* Writes cached data to the {@link #dataSource}.
*
* @throws IOException If the database cannot be accessed
*/
private void writeTaskListToFile() throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(timeModule)
.registerModule(new Jdk8Module());
if(dataSource != null) {
mapper.writeValue(new File(dataSource.getFile()), taskMap);
}
}
/**
* Loads the database from {@link #dataSource} and updates the cached data.
*
* @throws IOException If the database cannot be accessed
*/
private void loadTaskListFromFile() throws IOException {
if (dataSource != null) {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(timeModule);
List<Task> result;
result = mapper.readerForListOf(Task.class).readValue(dataSource);
taskMap = result.stream()
.collect(HashMap::new,
(res, task) -> res.put(task.getId(), task),
(existing, replacement) -> {});
}
}
}
@@ -1,13 +1,42 @@
package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import ch.zhaw.gartenverwaltung.types.Plant;
import ch.zhaw.gartenverwaltung.types.Task;
import java.io.IOException;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
/**
* A database of {@link Task}s.
* The interface specifies the minimal required operations.
*/
public interface TaskDatabase {
List<Task> getTaskList(Date start, Date end);
/**
* Yields a list of all {@link Task}s in the database with the start and end date of a period of time.
*
* @param start The start date of the wanted time period
* @param end The end date of the wanted time period
* @return A list of {@link Task}s planned in the specified time period
* @throws IOException If the database cannot be accessed
*/
List<Task> getTaskList(LocalDate start, LocalDate end) throws IOException;
/**
* Saves the {@link Task} in the Cache.
*
* @param task The {@link Task} which is wanted to be saved in the TaskList
* @throws IOException If the database cannot be accessed
*/
void saveTask(Task task) throws IOException;
/**
* Removes the {@link Task}from the Cache.
*
* @param task The {@link Task} which has to be removed from the {@link Task} list.
* @throws IOException If the database cannot be accessed
*/
void removeTask(Task task) throws IOException;
}
@@ -17,11 +17,12 @@ public class GrowthPhaseTypeDeserializer extends StdDeserializer<GrowthPhaseType
@Override
public GrowthPhaseType deserialize(JsonParser parser, DeserializationContext context) throws IOException {
GrowthPhaseType result = null;
String token = parser.getText();
try {
result = GrowthPhaseType.valueOf(parser.getText().toUpperCase());
result = GrowthPhaseType.valueOf(token.toUpperCase());
} catch (IllegalArgumentException e) {
// TODO: Log
System.err.println("bad growth phase type");
System.err.printf("Bad growth phase type \"%s\"\n", token);
}
return result;
}
@@ -18,11 +18,12 @@ public class HardinessZoneDeserializer extends StdDeserializer<HardinessZone> {
@Override
public HardinessZone deserialize(JsonParser parser, DeserializationContext context) throws IOException {
HardinessZone result = null;
String token = parser.getText();
try {
result = HardinessZone.valueOf(parser.getText().toUpperCase());
result = HardinessZone.valueOf(token.toUpperCase());
} catch (IllegalArgumentException e) {
// TODO: Log
System.err.println("bad growth phase type");
System.err.printf("Unknown Hardiness Zone \"%s\"\n", token);
}
return result;
}
@@ -0,0 +1,33 @@
package ch.zhaw.gartenverwaltung.json;
import ch.zhaw.gartenverwaltung.io.PlantDatabase;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import javafx.scene.image.Image;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
public class PlantImageDeserializer extends JsonDeserializer<Image> {
@Override
public Image deserialize(JsonParser parser, DeserializationContext context) throws IOException {
Image result = null;
URL imageUrl = PlantDatabase.class.getResource(String.format("images/%s", parser.getText()));
if (imageUrl != null) {
try (InputStream is = new FileInputStream(new File(imageUrl.toURI()))) {
result = new Image(is);
} catch (IllegalArgumentException | URISyntaxException e) {
// TODO: Log
e.printStackTrace();
System.err.printf("Cannot find Image \"%s\"\n", imageUrl.getFile());
}
}
return result;
}
}
@@ -33,12 +33,12 @@ public class PlantListModel {
setDefaultZone();
}
public PlantListModel(PlantDatabase plantDatabase){
public PlantListModel(PlantDatabase plantDatabase) {
this.plantDatabase = plantDatabase;
setDefaultZone();
}
private void setDefaultZone(){
private void setDefaultZone() {
currentZone = HardinessZone.ZONE_8A; // TODO: get Default Zone from Config
}
@@ -52,6 +52,7 @@ public class PlantListModel {
/**
* Method to get actual Plant List in alphabetic Order
*
* @return actual Plant List in alphabetic Order
*/
public List<Plant> getPlantList(HardinessZone zone) throws HardinessZoneNotSetException, IOException {
@@ -62,10 +63,11 @@ public class PlantListModel {
/**
* Method to get the actual Plant list in custom Order
* @param zone selected hardiness zone
*
* @param zone selected hardiness zone
* @param comparator comparator to sort the list
* @return sorted list with plants in the given hardiness zone
* @throws IOException If the database cannot be accessed
* @throws IOException If the database cannot be accessed
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
*/
public List<Plant> getSortedPlantList(HardinessZone zone, Comparator<Plant> comparator) throws HardinessZoneNotSetException, IOException {
@@ -75,10 +77,11 @@ public class PlantListModel {
/**
* Method to get Filtered plant list
*
* @param predicate predicate to filter the list
* @param zone selected hardiness zone
* @param zone selected hardiness zone
* @return filterd list with plants in the hardinness zone
* @throws IOException If the database cannot be accessed
* @throws IOException If the database cannot be accessed
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
*/
public List<Plant> getFilteredPlantList(HardinessZone zone, Predicate<Plant> predicate) throws HardinessZoneNotSetException, IOException {
@@ -88,10 +91,11 @@ public class PlantListModel {
/**
* Method to get Filtered plant list by id by exact match
*
* @param zone selected hardiness zone
* @param id id of plant
* @param id id of plant
* @return if id doesn't exist: empty List, else list with 1 plant entry.
* @throws IOException If the database cannot be accessed
* @throws IOException If the database cannot be accessed
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
*/
public List<Plant> getFilteredPlantListById(HardinessZone zone, Long id) throws HardinessZoneNotSetException, IOException {
@@ -102,54 +106,68 @@ public class PlantListModel {
}
/**
*
* @param zone selected hardiness zone
* @param zone selected hardiness zone
* @param searchString the string to search plant List, set '#' as first char the search by id.
* @return List of plants found in Plant List which contain the search String in the name or description
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
* @throws IOException If the database cannot be accessed
* @throws IOException If the database cannot be accessed
*/
public List<Plant> getFilteredPlantListByString(HardinessZone zone, String searchString) throws HardinessZoneNotSetException, IOException {
if(searchString.length() == 0){
if (searchString.length() == 0) {
return getPlantList(zone);
} else if(searchString.charAt(0) == '#') {
} else if (searchString.charAt(0) == '#') {
try {
return getFilteredPlantListById(zone, Long.parseLong(searchString.substring(1)));
} catch (NumberFormatException e) {
return new ArrayList<>();
}
} else {
return getFilteredPlantList(zone, plant -> plant.name().contains(searchString) || plant.description().contains(searchString));
String caseInsensitiveSearchString = searchString.toLowerCase();
return getFilteredPlantList(zone, plant ->
plant.name().toLowerCase().contains(caseInsensitiveSearchString) ||
plant.description().toLowerCase().contains(caseInsensitiveSearchString)
);
}
}
/**
*
* @param type GrowPhaseType to filter
* @param zone selected hardiness zone
* @param from the earliest date to for the filter
* @param to the lastest date for the filter
* @param to the lastest date for the filter
* @return List of Plants with selected saison
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
* @throws IOException If the database cannot be accessed
* @throws IOException If the database cannot be accessed
*/
private List<Plant> getFilteredPlantListBySaison(GrowthPhaseType type, HardinessZone zone, MonthDay from, MonthDay to) throws HardinessZoneNotSetException, IOException {
return getFilteredPlantList(zone, plant -> plant.lifecycle().stream().anyMatch(growthPhase -> growthPhase.startDate().compareTo(from) >= 0 && (growthPhase.startDate().compareTo(to) <= 0) && growthPhase.type() == type));
}
/**
*
* @param zone selected hardiness zone
* @param from the earliest date to for the filter
* @param to the lastest date for the filter
* @param to the lastest date for the filter
* @return List of Plants with selected saison
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
* @throws IOException If the database cannot be accessed
* @throws IOException If the database cannot be accessed
*/
public List<Plant> getFilteredPlantListByPlantingSaison(HardinessZone zone, MonthDay from, MonthDay to) throws HardinessZoneNotSetException, IOException {
return getFilteredPlantListBySaison(GrowthPhaseType.PLANT, zone, from, to);
}
/**
* @param zone selected hardiness zone
* @param from the earliest date to for the filter
* @param to the lastest date for the filter
* @return List of Plants with selected saison
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
* @throws IOException If the database cannot be accessed
*/
public List<Plant> getFilteredPlantListByHarvestSaison(HardinessZone zone, MonthDay from, MonthDay to) throws HardinessZoneNotSetException, IOException {
return getFilteredPlantListBySaison(GrowthPhaseType.HARVEST, zone, from, to);
}
/**
*
* @param zone selected hardiness zone
@@ -159,7 +177,7 @@ public class PlantListModel {
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
* @throws IOException If the database cannot be accessed
*/
public List<Plant> getFilteredPlantListByHarvestSaison(HardinessZone zone, MonthDay from, MonthDay to) throws HardinessZoneNotSetException, IOException {
return getFilteredPlantListBySaison(GrowthPhaseType.HARVEST, zone, from, to);
public List<Plant> getFilteredPlantListBySaisonWithoutGrowthPhase(HardinessZone zone, MonthDay from, MonthDay to) throws HardinessZoneNotSetException, IOException {
return getFilteredPlantList(zone, plant -> plant.lifecycle().stream().anyMatch(growthPhase -> growthPhase.startDate().compareTo(from) >= 0 && (growthPhase.startDate().compareTo(to) <= 0)));
}
}
@@ -1,5 +1,7 @@
package ch.zhaw.gartenverwaltung.types;
import javafx.scene.image.Image;
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
@@ -10,6 +12,7 @@ public record Plant(
long id,
String name,
String description,
Image image,
String spacing,
int light,
String soil,
@@ -0,0 +1,26 @@
package ch.zhaw.gartenverwaltung.types;
import java.time.MonthDay;
public enum Seasons {
AllSEASONS("--01-01", "--12-31"),
SPRING("--03-01", "--05-30"),
SOMMER("--06-01", "--08-30"),
AUTUM("--09-01", "--11-30"),
WINTER("--12-01", "--02-28");
public final String startDate;
public final String endDate;
Seasons(String startDate, String endDate) {
this.startDate = startDate;
this.endDate = endDate;
}
public MonthDay getStartDate() {
return MonthDay.parse(this.startDate);
}
public MonthDay getEndDate() {
return MonthDay.parse(this.endDate);
}
}
@@ -16,11 +16,22 @@ public class Task {
private Integer interval;
private LocalDate endDate;
/**
* default constructor
* (used by Json deserializer)
*/
public Task(){
name= "";
description= "";
startDate = LocalDate.now();
}
public Task(String name, String description, LocalDate startDate) {
this.name = name;
this.description = description;
this.startDate = startDate;
}
public Task(String name, String description, LocalDate startDate, LocalDate endDate, int interval) {
this.name = name;
this.description = description;
@@ -43,6 +54,13 @@ public class Task {
return this;
}
public boolean isInTimePeriode(LocalDate searchStartDate, LocalDate searchEndDate){
if(startDate.isAfter(searchStartDate) &&startDate.isBefore(searchEndDate)){
return true;
}
return false;
}
// Getters
public long getId() { return id; }
public String getName() { return name; }
@@ -3,7 +3,6 @@
<?import javafx.geometry.Insets?>
<?import javafx.geometry.Rectangle2D?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.SplitPane?>
@@ -16,8 +15,9 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="1000.0" prefHeight="853.0" prefWidth="1219.0"
xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.PlantsController">
<AnchorPane maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="1000.0" prefHeight="853.0"
prefWidth="1219.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ch.zhaw.gartenverwaltung.PlantsController">
<children>
<SplitPane dividerPositions="0.7377363661277062" layoutX="539.0" layoutY="266.0" prefHeight="853.0" prefWidth="1219.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
@@ -30,7 +30,7 @@
<Font name="System Bold" size="30.0" />
</font>
</Label>
<TextField fx:id="search_plants" onInputMethodTextChanged="#searchForPlant" promptText="Search for Plant Name" />
<TextField fx:id="search_plants" promptText="Search for Plant Name" />
<HBox alignment="CENTER_LEFT" prefHeight="480.0" prefWidth="881.0" VBox.vgrow="ALWAYS">
<children>
<ListView fx:id="list_plants" maxWidth="1.7976931348623157E308" prefHeight="497.0" prefWidth="580.0" HBox.hgrow="ALWAYS" />
@@ -75,30 +75,7 @@
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<VBox layoutX="37.0" layoutY="-19.0" prefHeight="180.66666666666666" prefWidth="310.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<CheckBox fx:id="spring_filter" mnemonicParsing="false" onAction="#filterSpring" text="Spring">
<padding>
<Insets top="10.0" />
</padding>
</CheckBox>
<CheckBox fx:id="sommer_filter" mnemonicParsing="false" onAction="#filterSommer" text="Sommer">
<padding>
<Insets top="10.0" />
</padding>
</CheckBox>
<CheckBox fx:id="autum_filter" mnemonicParsing="false" onAction="#filterAutum" text="Autum">
<padding>
<Insets top="10.0" />
</padding>
</CheckBox>
<CheckBox fx:id="winter_filter" mnemonicParsing="false" onAction="#filterWinter" text="Winter">
<padding>
<Insets top="10.0" />
</padding>
</CheckBox>
</children>
</VBox>
<VBox fx:id="seasons" layoutX="37.0" layoutY="-19.0" prefHeight="180.66666666666666" prefWidth="310.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>
@@ -0,0 +1,5 @@
Potato, Onion
Photos by Lars Blankers: https://unsplash.com/@lmablankers?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
Carrot
Photo by Maja Vujic: https://unsplash.com/@majavujic87?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@@ -6,6 +6,7 @@
"light": 6,
"spacing": "35",
"soil": "sandy",
"image": "potato.jpg",
"pests": [
{
"name": "Rot",
@@ -86,6 +87,7 @@
"id": 1,
"name": "Early Carrot",
"description": "Carrot, (Daucus carota), herbaceous, generally biennial plant of the Apiaceae family that produces an edible taproot. Among common varieties root shapes range from globular to long, with lower ends blunt to pointed. Besides the orange-coloured roots, white-, yellow-, and purple-fleshed varieties are known.",
"image": "carrot.jpg",
"lifecycle": [
{
"startDate": "02-20",
@@ -167,6 +169,7 @@
"id": 2,
"name": "Summertime Onion",
"description": "Onion, (Allium cepa), herbaceous biennial plant in the amaryllis family (Amaryllidaceae) grown for its edible bulb. The onion is likely native to southwestern Asia but is now grown throughout the world, chiefly in the temperate zones. Onions are low in nutrients but are valued for their flavour and are used widely in cooking. They add flavour to such dishes as stews, roasts, soups, and salads and are also served as a cooked vegetable.",
"image": "onion.jpg",
"lifecycle": [
{
"startDate": "03-15",
@@ -0,0 +1,50 @@
[
{
"id" : 1,
"name" : "sow plant",
"description": "Plant the seeds, crops in de bed.",
"startDate" : "2022-05-01",
"endDate" : "2022-05-01",
"interval" : 0
},
{
"id" : 2,
"name" : "water plant",
"description": "water the plant, so that the soil is wet around the plant.",
"startDate" : "2022-05-01",
"endDate" : "2022-09-01",
"interval" : 2
},
{
"id" : 3,
"name" : "fertilize plant",
"description": "The fertilizer has to be mixed with water. Then fertilize the plants soil with the mixture",
"startDate" : "2022-06-01",
"endDate" : "2022-08-01",
"interval" : 28
},
{
"id" : 4,
"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",
"endDate" : "2022-07-01",
"interval" : 0
},
{
"id" : 5,
"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",
"startDate" : "2022-05-01",
"endDate" : "2022-09-01",
"interval" : 5
},
{
"id" : 6,
"name" : "harvest plant",
"description": "Pull the ripe vegetables out from the soil. Clean them with clear, fresh water. ",
"startDate" : "2022-09-01",
"endDate" : "2022-09-01",
"interval" : 0
}
]
@@ -30,9 +30,7 @@ public class JsonPlantDatabaseTest {
List<Plant> testList;
try {
testList = testDatabase.getPlantList(HardinessZone.ZONE_8A);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (HardinessZoneNotSetException e) {
} catch (IOException | HardinessZoneNotSetException e) {
throw new RuntimeException(e);
}
Assertions.assertEquals(3, testList.size());
@@ -47,11 +45,8 @@ public class JsonPlantDatabaseTest {
void getPlantById() {
Optional<Plant> testPlant;
try {
testDatabase.getPlantList(HardinessZone.ZONE_8A);
testPlant = testDatabase.getPlantById(1);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (HardinessZoneNotSetException e) {
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A,1);
} catch (IOException | HardinessZoneNotSetException e) {
throw new RuntimeException(e);
}
Assertions.assertTrue(testPlant.isPresent());
@@ -63,11 +58,8 @@ public class JsonPlantDatabaseTest {
void getPlantByIdMustFail() {
Optional<Plant> testPlant;
try {
testDatabase.getPlantList(HardinessZone.ZONE_8A);
testPlant = testDatabase.getPlantById(99);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (HardinessZoneNotSetException e) {
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A,99);
} catch (IOException | HardinessZoneNotSetException e) {
throw new RuntimeException(e);
}
Assertions.assertFalse(testPlant.isPresent());
@@ -0,0 +1,37 @@
package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.types.Task;
import org.junit.jupiter.api.*;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class JsonTaskDatabaseTest {
TaskDatabase testDatabase;
SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy");
@BeforeEach
void connectToDb() {
// testDatabase = new JsonTaskDatabase();
}
@Test
@DisplayName("Check if results are retrieved completely")
void getTasks(){
/*
List<Task> taskList=null;
try {
taskList = testDatabase.getTaskList(formatter.parse("01.05.2022"), formatter.parse("01.08.2022"));
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ParseException e) {
throw new RuntimeException(e);
}
Assertions.assertTrue(taskList.size()>0);
*/
}
}
@@ -43,6 +43,7 @@ class PlantListModelTest {
20,
"summertime onion",
"Onion, (Allium cepa), herbaceous biennial plant in the amaryllis family (Amaryllidaceae) grown for its edible bulb. The onion is likely native to southwestern Asia but is now grown throughout the world, chiefly in the temperate zones. Onions are low in nutrients but are valued for their flavour and are used widely in cooking. They add flavour to such dishes as stews, roasts, soups, and salads and are also served as a cooked vegetable.",
null,
"15,30,2",
0,
"sandy to loamy, loose soil, free of stones",
@@ -57,6 +58,7 @@ class PlantListModelTest {
0,
"Potato",
"The potato is a tuber, round or oval, with small white roots called 'eyes', that are growth buds. The size varies depending on the variety; the colour of the skin can be white, yellow or even purple.",
null,
"35",
6,
"sandy",
@@ -68,6 +70,7 @@ class PlantListModelTest {
1,
"Early Carrot",
"Carrot, (Daucus carota), herbaceous, generally biennial plant of the Apiaceae family that produces an edible taproot. Among common varieties root shapes range from globular to long, with lower ends blunt to pointed. Besides the orange-coloured roots, white-, yellow-, and purple-fleshed varieties are known.",
null,
"5,35,2.5",
0,
"sandy to loamy, loose soil, free of stones",
@@ -0,0 +1,50 @@
[
{
"id" : "1",
"name" : "sow plant",
"description": "Plant the seeds/ crops in de bed.",
"startDate" : "01.05.2022",
"endDate" : "01.05.2022",
"interval" : "null"
},
{
"id" : "2",
"name" : "water plant",
"description": "water the plant, so that the soil is wet around the plant.",
"startDate" : "01.05.2022",
"endDate" : "01.09.2022",
"interval" : "2"
},
{
"id" : "3",
"name" : "fertilize plant",
"description": "The fertilizer has to be mixed with water. Then fertilize the plant's soil with the mixture",
"startDate" : "01.06.2022",
"endDate" : "01.08.2022",
"interval" : "28"
},
{
"id" : "4",
"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" : "15.07.2022",
"endDate" : "15.07.2022",
"interval" : "null"
},
{
"id" : "5",
"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",
"startDate" : "01.05.2022",
"endDate" : "01.09.2022",
"interval" : "5"
},
{
"id" : "6",
"name" : "harvest plant",
"description": "Pull the ripe vegetables out from the soil. Clean them with clear, fresh water. ",
"startDate" : "01.09.2022",
"endDate" : "01.09.2022",
"interval" : "null"
}
]