Compare commits

...

41 Commits

Author SHA1 Message Date
gulerdav fb0c50a715 Merge branch 'dev' into feature_json-gardenplan_M2 2022-11-03 14:31:23 +01:00
David Guler 83bc011870 #26 added fixed test files, made Crop class testable 2022-11-03 14:28:26 +01:00
David Guler ce93531ab8 Made tests pass for plants not in zone 2022-11-03 14:25:25 +01:00
giavaphi 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
gulerdav 3afeb8a22d Merge branch 'dev' into feature_controllerPlantList_M2 2022-10-31 13:05:49 +01:00
gulerdav 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
gulerdav 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
schrom01 98ff259d95 fixed Method getFilteredPlantListByString for case if SearchString is empty. 2022-10-30 10:38:46 +01:00
gulerdav f452b73233 Merge pull request #40 from schrom01/feature_plantList_M2
Feature plant list m2
2022-10-30 10:36:07 +01:00
schrom01 4e794a8a93 implemented Methods and tests:
getFilteredPlantListByHarvestSaison
getFilteredPlantListByPlantingSaison
#12
2022-10-30 10:31:09 +01:00
schrom01 da7a31f512 java doc and readme update 2022-10-30 09:37:27 +01:00
schrom01 3e586093ba implemented Method getFilteredPlantListByString in PlantListModel 2022-10-30 09:32:55 +01:00
Roman Schenk 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 Schenk 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
David Guler b6b5138e9f fix: adjust fxml-api version to remove warnings 2022-10-29 09:54:55 +02:00
gulerdav 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
gulerdav 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
giavaphi 7fd18f3830 #16 create MainFXML with skeletal structure 2022-10-28 15:36:33 +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
Roman Schenk f5fcfa78f6 Merge pull request #35 from schrom01/feature_json-gardenplan_M2
Feature json gardenplan m2
2022-10-28 11:52:30 +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
41 changed files with 1503 additions and 106 deletions

View File

@ -27,3 +27,6 @@ These branches are for bugfixes.
These branches are for javadoc and project documentation (such as the readme, class diagrams etc.). These branches are for javadoc and project documentation (such as the readme, class diagrams etc.).
## User Manual
- Search Plant List: if first Char is '#': only exact match in ID.

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

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>

View File

@ -10,9 +10,9 @@ import java.io.IOException;
public class HelloApplication extends Application { public class HelloApplication extends Application {
@Override @Override
public void start(Stage stage) throws IOException { public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml")); FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("MainFXML.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240); Scene scene = new Scene(fxmlLoader.load());
stage.setTitle("Hello!"); stage.setTitle("Gartenverwaltung");
stage.setScene(scene); stage.setScene(scene);
stage.show(); stage.show();
} }

View File

@ -0,0 +1,5 @@
package ch.zhaw.gartenverwaltung;
public class HomeController
{
}

View File

@ -0,0 +1,107 @@
package ch.zhaw.gartenverwaltung;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Button;
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;
@FXML
private AnchorPane mainPane;
@FXML
private Button myPlants_button;
@FXML
private Button mySchedule_button;
@FXML
private Button plants_button;
@FXML
void goToHome(ActionEvent event) throws IOException {
loadPane("Home.fxml");
styleChangeButton(home_button);
}
@FXML
void goToMyPlants(ActionEvent event) throws IOException {
loadPane("MyPlants.fxml");
styleChangeButton(myPlants_button);
}
@FXML
void goToMySchedule(ActionEvent event) throws IOException {
loadPane("MySchedule.fxml");
styleChangeButton(mySchedule_button);
}
@FXML
void goToPlants(ActionEvent event) throws IOException {
loadPane("Plants.fxml");
styleChangeButton(plants_button);
}
/**
* Updates the mainPane with the selected fxml file.
* set HGrow and VGrow to parent AnchorPane.
* Sends MainController to other Controllers.
* @param fxmlFile string of fxml file
* @throws IOException exception when file does not exist
*/
public void loadPane(String fxmlFile) throws IOException {
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(anchorPane);
anchorPane.prefWidthProperty().bind(mainPane.widthProperty());
anchorPane.prefHeightProperty().bind(mainPane.heightProperty());
}
private void styleChangeButton(Button button) {
//ToDo changeStyle of the menu buttons
}
/**
* loads the default FXML File
* {@inheritDoc}
*/
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
try {
loadPane("Home.fxml");
styleChangeButton(home_button);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,58 @@
package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.types.Plant;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
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;
public class MyPlantsController implements Initializable {
MainFXMLController mainController;
@FXML
private Button addPlant_button;
@FXML
private VBox myPlants_vbox;
@FXML
void addPlant(ActionEvent event) throws IOException {
mainController.loadPane("Plants.fxml");
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
//ToDo
List<Plant> myPlants = getMyPlants();
createPlantView(myPlants);
}
private void createPlantView(List<Plant> myPlants) {
//ToDo
for(Plant plant : myPlants) {
createPlantView();
}
}
public void getMainController(MainFXMLController controller) {
mainController = controller;
}
private List<Plant> getMyPlants() {
//ToDo method to get myPlantList(scheduled)
//Method to call all Plants saved
List<Plant> myPlantList = new LinkedList<>();
return myPlantList;
}
private void createPlantView() {
//ToDo FXML Panel with Plant data
}
}

View File

@ -0,0 +1,124 @@
package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.types.Plant;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.Pane;
import java.net.URL;
import java.time.LocalDate;
import java.util.LinkedList;
import java.util.List;
import java.util.ResourceBundle;
public class MyScheduleController implements Initializable {
private Plant selectedPlant = null;
@FXML
private Label day1_label;
@FXML
private Pane day1_pane;
@FXML
private Label day2_label;
@FXML
private Pane day2_pane;
@FXML
private Label day3_label;
@FXML
private Pane day3_pane;
@FXML
private Label day4_label;
@FXML
private Pane day4_pane;
@FXML
private Label day5_label;
@FXML
private Pane day5_pane;
@FXML
private Label day6_label;
@FXML
private Pane day6_pane;
@FXML
private Label day7_label;
@FXML
private Pane day7_pane;
@FXML
private Label information_label;
@FXML
private ListView<Plant> scheduledPlants_listview;
@Override
public void initialize(URL location, ResourceBundle resources) {
List<Plant> plantList = new LinkedList<>();
fillListViewMyPlantsInSchedule(plantList);
getSelectedPlantTask();
setDayLabels();
information_label.setText("");
}
private void getSelectedPlantTask() {
scheduledPlants_listview.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Plant>() {
@Override
public void changed(ObservableValue<? extends Plant> observable, Plant oldValue, Plant newValue) {
if(newValue != null) {
selectedPlant = newValue;
//ToDo update day<x>_panel with task for the day
} else {
selectedPlant = null;
//ToDo update day<x>_panel with task for the day (all plants)
}
}
});
}
private void setDayLabels() {
LocalDate today = LocalDate.now();
day1_label.setText(today.getDayOfWeek().toString());
day2_label.setText(today.plusDays(1).getDayOfWeek().toString());
day3_label.setText(today.plusDays(2).getDayOfWeek().toString());
day4_label.setText(today.plusDays(3).getDayOfWeek().toString());
day5_label.setText(today.plusDays(4).getDayOfWeek().toString());
day6_label.setText(today.plusDays(5).getDayOfWeek().toString());
day7_label.setText(today.plusDays(6).getDayOfWeek().toString());
}
private void fillListViewMyPlantsInSchedule(List<Plant> list) {
for (Plant plant : list) {
scheduledPlants_listview.getItems().add(plant);
}
scheduledPlants_listview.setCellFactory(param -> new ListCell<Plant>() {
@Override
protected void updateItem(Plant plant, boolean empty) {
super.updateItem(plant, empty);
if (empty || plant == null || plant.name() == null) {
setText(null);
} else {
setText(plant.name());
}
}
});
}
}

View File

@ -0,0 +1,258 @@
package ch.zhaw.gartenverwaltung;
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.KeyEvent;
import javafx.scene.layout.VBox;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
public class PlantsController implements Initializable {
private final PlantListModel plantListModel = new PlantListModel();
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 VBox seasons;
@FXML
private VBox climate_zones;
@FXML
private Label description_plant;
@FXML
private ImageView img_plant;
@FXML
private ListView<Plant> list_plants;
@FXML
private Button saveToMyPlant_button;
@FXML
private TextField search_plants;
/**
* saves the current selected plant in new JSON database
* @param event event
*/
@FXML
void saveToMyPlant(ActionEvent event) {
//ToDo model save selectedPlant to mySelectedPlant(IO)
}
/**
* 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) {
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();
}
}
/**
* set text of list view to plant name
*/
private void setListCellFactory() {
list_plants.setCellFactory(param -> new ListCell<Plant>() {
@Override
protected void updateItem(Plant plant, boolean empty) {
super.updateItem(plant, empty);
if (empty || plant == null || plant.name() == null) {
setText(null);
} else {
setText(plant.name());
}
}
});
}
/**
* 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()));
}
/**
* 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) {
if(newValue != null) {
selectedPlant = newValue;
description_plant.setText(selectedPlant.description());
saveToMyPlant_button.setDisable(false);
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);
Image img = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
img_plant.setImage(img);
}
}
});
}
/**
* clears the ListView of entries
*/
private void clearListView() {
plantListProperty.clear();
}
}

View File

@ -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
}

View File

@ -20,7 +20,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class JsonGardenPlan implements GardenPlan { public class JsonGardenPlan implements GardenPlan {
private final URL dataSource = getClass().getResource("user-crops.json"); private final URL dataSource;
private IdProvider idProvider; private IdProvider idProvider;
private Map<Long, Crop> cropMap = Collections.emptyMap(); private Map<Long, Crop> cropMap = Collections.emptyMap();
@ -40,7 +40,21 @@ public class JsonGardenPlan implements GardenPlan {
} }
/** /**
* @see GardenPlan#getCrops() * Default constructor
*/
public JsonGardenPlan() {
this.dataSource = getClass().getResource("user-crops.json");
}
/**
* Constructor to use a specified {@link URL} as a {@link #dataSource}
* @param dataSource A {@link URL} to the file to be used as a data source
*/
public JsonGardenPlan(URL dataSource) {
this.dataSource = dataSource;
}
/**
* {@inheritDoc}
*/ */
@Override @Override
public List<Crop> getCrops() throws IOException { public List<Crop> getCrops() throws IOException {
@ -51,7 +65,7 @@ public class JsonGardenPlan implements GardenPlan {
} }
/** /**
* @see GardenPlan#getCropById(long) * {@inheritDoc}
*/ */
@Override @Override
public Optional<Crop> getCropById(long id) throws IOException { public Optional<Crop> getCropById(long id) throws IOException {
@ -62,7 +76,7 @@ public class JsonGardenPlan implements GardenPlan {
} }
/** /**
* @see GardenPlan#saveCrop(Crop) * {@inheritDoc}
* *
* Saves a crop to the database. * Saves a crop to the database.
* If no {@link Crop#cropId} is set, one will be generated and * If no {@link Crop#cropId} is set, one will be generated and
@ -79,7 +93,7 @@ public class JsonGardenPlan implements GardenPlan {
} }
/** /**
* @see GardenPlan#removeCrop(Crop) * {@inheritDoc}
*/ */
@Override @Override
public void removeCrop(Crop crop) throws IOException { public void removeCrop(Crop crop) throws IOException {

View File

@ -1,11 +1,13 @@
package ch.zhaw.gartenverwaltung.io; package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.types.GrowthPhase; import ch.zhaw.gartenverwaltung.json.PlantImageDeserializer;
import ch.zhaw.gartenverwaltung.types.HardinessZone; import ch.zhaw.gartenverwaltung.types.HardinessZone;
import ch.zhaw.gartenverwaltung.types.Plant; import ch.zhaw.gartenverwaltung.types.Plant;
import com.fasterxml.jackson.databind.ObjectMapper; 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.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.MonthDayDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.MonthDayDeserializer;
import javafx.scene.image.Image;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
@ -32,10 +34,14 @@ public class JsonPlantDatabase implements PlantDatabase {
* Creating constant objects required to deserialize the {@link MonthDay} classes * Creating constant objects required to deserialize the {@link MonthDay} classes
*/ */
private final static JavaTimeModule timeModule = new JavaTimeModule(); private final static JavaTimeModule timeModule = new JavaTimeModule();
private final static SimpleModule imageModule = new SimpleModule();
static { static {
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM-dd"); DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM-dd");
MonthDayDeserializer dateDeserializer = new MonthDayDeserializer(dateFormat); MonthDayDeserializer dateDeserializer = new MonthDayDeserializer(dateFormat);
timeModule.addDeserializer(MonthDay.class, dateDeserializer); timeModule.addDeserializer(MonthDay.class, dateDeserializer);
imageModule.addDeserializer(Image.class, new PlantImageDeserializer());
} }
/** /**
@ -76,27 +82,23 @@ public class JsonPlantDatabase implements PlantDatabase {
} }
if (dataSource != null) { if (dataSource != null) {
currentZone = zone; currentZone = zone;
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper()
mapper.registerModule(timeModule); .registerModule(timeModule)
.registerModule(imageModule);
List<Plant> result; List<Plant> result;
result = mapper.readerForListOf(Plant.class).readValue(dataSource); result = mapper.readerForListOf(Plant.class).readValue(dataSource);
for (Plant plant : result) {
// for discussion because of failing tests:
// for(GrowthPhase growthPhase: plant.lifecycle()) {
// if(growthPhase.zone()==zone) {
// keep in result, remove if no match
// }
// }
plant.inZone(currentZone);
}
// Turn list into a HashMap with structure id => Plant
plantMap = result.stream() plantMap = result.stream()
// Remove plants not in the current zone
.filter(plant -> {
plant.inZone(currentZone);
return !plant.lifecycle().isEmpty();
})
// Create Hashmap from results
.collect(HashMap::new, .collect(HashMap::new,
(res, plant) -> res.put(plant.id(), plant), (res, plant) -> res.put(plant.id(), plant),
(existing, replacement) -> { }); (existing, replacement) -> {});
} }
} }
} }

View File

@ -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) -> {});
}
}
}

View File

@ -1,13 +1,42 @@
package ch.zhaw.gartenverwaltung.io; package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import ch.zhaw.gartenverwaltung.types.Plant;
import ch.zhaw.gartenverwaltung.types.Task; import ch.zhaw.gartenverwaltung.types.Task;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDate;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
/**
* A database of {@link Task}s.
* The interface specifies the minimal required operations.
*/
public interface TaskDatabase { 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; 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; void removeTask(Task task) throws IOException;
} }

View File

@ -17,11 +17,12 @@ public class GrowthPhaseTypeDeserializer extends StdDeserializer<GrowthPhaseType
@Override @Override
public GrowthPhaseType deserialize(JsonParser parser, DeserializationContext context) throws IOException { public GrowthPhaseType deserialize(JsonParser parser, DeserializationContext context) throws IOException {
GrowthPhaseType result = null; GrowthPhaseType result = null;
String token = parser.getText();
try { try {
result = GrowthPhaseType.valueOf(parser.getText().toUpperCase()); result = GrowthPhaseType.valueOf(token.toUpperCase());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// TODO: Log // TODO: Log
System.err.println("bad growth phase type"); System.err.printf("Bad growth phase type \"%s\"\n", token);
} }
return result; return result;
} }

View File

@ -18,11 +18,12 @@ public class HardinessZoneDeserializer extends StdDeserializer<HardinessZone> {
@Override @Override
public HardinessZone deserialize(JsonParser parser, DeserializationContext context) throws IOException { public HardinessZone deserialize(JsonParser parser, DeserializationContext context) throws IOException {
HardinessZone result = null; HardinessZone result = null;
String token = parser.getText();
try { try {
result = HardinessZone.valueOf(parser.getText().toUpperCase()); result = HardinessZone.valueOf(token.toUpperCase());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// TODO: Log // TODO: Log
System.err.println("bad growth phase type"); System.err.printf("Unknown Hardiness Zone \"%s\"\n", token);
} }
return result; return result;
} }

View File

@ -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;
}
}

View File

@ -3,14 +3,17 @@ package ch.zhaw.gartenverwaltung.plantList;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
import ch.zhaw.gartenverwaltung.io.JsonPlantDatabase; import ch.zhaw.gartenverwaltung.io.JsonPlantDatabase;
import ch.zhaw.gartenverwaltung.io.PlantDatabase; import ch.zhaw.gartenverwaltung.io.PlantDatabase;
import ch.zhaw.gartenverwaltung.types.GrowthPhaseType;
import ch.zhaw.gartenverwaltung.types.HardinessZone; import ch.zhaw.gartenverwaltung.types.HardinessZone;
import ch.zhaw.gartenverwaltung.types.Plant; import ch.zhaw.gartenverwaltung.types.Plant;
import java.io.IOException; import java.io.IOException;
import java.time.MonthDay;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
public class PlantListModel { public class PlantListModel {
private PlantDatabase plantDatabase; private PlantDatabase plantDatabase;
@ -19,8 +22,8 @@ public class PlantListModel {
/** /**
* Comparators to create sorted Plant List * Comparators to create sorted Plant List
*/ */
static final Comparator<Plant> sortByName = (Plant o1, Plant o2) -> o1.name().compareTo(o2.name()); static final Comparator<Plant> sortByName = Comparator.comparing(Plant::name);
static final Comparator<Plant> SortById = (Plant o1, Plant o2) -> Long.compare(o1.id(), o2.id()); static final Comparator<Plant> SortById = Comparator.comparingLong(Plant::id);
/** /**
* Constructor to create Database Object. * Constructor to create Database Object.
@ -30,12 +33,12 @@ public class PlantListModel {
setDefaultZone(); setDefaultZone();
} }
public PlantListModel(PlantDatabase plantDatabase){ public PlantListModel(PlantDatabase plantDatabase) {
this.plantDatabase = plantDatabase; this.plantDatabase = plantDatabase;
setDefaultZone(); setDefaultZone();
} }
private void setDefaultZone(){ private void setDefaultZone() {
currentZone = HardinessZone.ZONE_8A; // TODO: get Default Zone from Config currentZone = HardinessZone.ZONE_8A; // TODO: get Default Zone from Config
} }
@ -49,6 +52,7 @@ public class PlantListModel {
/** /**
* Method to get actual Plant List in alphabetic Order * Method to get actual Plant List in alphabetic Order
*
* @return actual Plant List in alphabetic Order * @return actual Plant List in alphabetic Order
*/ */
public List<Plant> getPlantList(HardinessZone zone) throws HardinessZoneNotSetException, IOException { public List<Plant> getPlantList(HardinessZone zone) throws HardinessZoneNotSetException, IOException {
@ -59,36 +63,39 @@ public class PlantListModel {
/** /**
* Method to get the actual Plant list in custom Order * 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 * @param comparator comparator to sort the list
* @return sorted list with plants in the given hardiness zone * @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 * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
*/ */
public List<Plant> getSortedPlantList(HardinessZone zone, Comparator<Plant> comparator) throws HardinessZoneNotSetException, IOException { public List<Plant> getSortedPlantList(HardinessZone zone, Comparator<Plant> comparator) throws HardinessZoneNotSetException, IOException {
setCurrentZone(zone); setCurrentZone(zone);
return plantDatabase.getPlantList(zone).stream().sorted(comparator).toList(); return plantDatabase.getPlantList(zone).stream().sorted(comparator).collect(Collectors.toList());
} }
/** /**
* Method to get Filtered plant list * Method to get Filtered plant list
*
* @param predicate predicate to filter the 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 * @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 * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
*/ */
public List<Plant> getFilteredPlantList(HardinessZone zone, Predicate<Plant> predicate) throws HardinessZoneNotSetException, IOException { public List<Plant> getFilteredPlantList(HardinessZone zone, Predicate<Plant> predicate) throws HardinessZoneNotSetException, IOException {
setCurrentZone(zone); setCurrentZone(zone);
return getPlantList(zone).stream().filter(predicate).toList(); return getPlantList(zone).stream().filter(predicate).collect(Collectors.toList());
} }
/** /**
* Method to get Filtered plant list by id by exact match * Method to get Filtered plant list by id by exact match
*
* @param zone selected hardiness zone * @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. * @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 * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
*/ */
public List<Plant> getFilteredPlantListById(HardinessZone zone, Long id) throws HardinessZoneNotSetException, IOException { public List<Plant> getFilteredPlantListById(HardinessZone zone, Long id) throws HardinessZoneNotSetException, IOException {
@ -97,4 +104,80 @@ public class PlantListModel {
plantDatabase.getPlantById(zone, id).ifPresent(plantList::add); plantDatabase.getPlantById(zone, id).ifPresent(plantList::add);
return plantList; return plantList;
} }
/**
* @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
*/
public List<Plant> getFilteredPlantListByString(HardinessZone zone, String searchString) throws HardinessZoneNotSetException, IOException {
if (searchString.length() == 0) {
return getPlantList(zone);
} else if (searchString.charAt(0) == '#') {
try {
return getFilteredPlantListById(zone, Long.parseLong(searchString.substring(1)));
} catch (NumberFormatException e) {
return new ArrayList<>();
}
} else {
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
* @return List of Plants with selected saison
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
* @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
* @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> 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
* @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> 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)));
}
} }

View File

@ -1,6 +1,7 @@
package ch.zhaw.gartenverwaltung.types; package ch.zhaw.gartenverwaltung.types;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
public class Crop { public class Crop {
@ -41,6 +42,24 @@ public class Crop {
public LocalDate getStartDate() { return startDate; } public LocalDate getStartDate() { return startDate; }
public double getArea() { return area; } public double getArea() { return area; }
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other instanceof Crop otherCrop) {
return Objects.equals(this.cropId, otherCrop.cropId) &&
plantId == otherCrop.plantId &&
startDate != null && startDate.equals(otherCrop.startDate) &&
area == otherCrop.area;
}
return false;
}
@Override
public int hashCode() {
int startCode = startDate != null ? startDate.hashCode() : 0;
return (int) plantId ^ (startCode << 16);
}
@Override @Override
public String toString() { public String toString() {
return String.format("Crop [ cropId: %d, plantId: %d, startDate: %s, area: %f ]", return String.format("Crop [ cropId: %d, plantId: %d, startDate: %s, area: %f ]",

View File

@ -1,7 +1,10 @@
package ch.zhaw.gartenverwaltung.types; package ch.zhaw.gartenverwaltung.types;
import javafx.scene.image.Image;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import static java.time.temporal.ChronoUnit.DAYS; import static java.time.temporal.ChronoUnit.DAYS;
@ -9,6 +12,7 @@ public record Plant(
long id, long id,
String name, String name,
String description, String description,
Image image,
String spacing, String spacing,
int light, int light,
String soil, String soil,
@ -22,7 +26,7 @@ public record Plant(
public List<GrowthPhase> lifecycleForGroup(int group) { public List<GrowthPhase> lifecycleForGroup(int group) {
return lifecycle.stream() return lifecycle.stream()
.filter(growthPhase -> growthPhase.group() != group) .filter(growthPhase -> growthPhase.group() != group)
.toList(); .collect(Collectors.toList());
} }
public LocalDate sowDateFromHarvestDate(LocalDate harvestDate, int group) { public LocalDate sowDateFromHarvestDate(LocalDate harvestDate, int group) {

View File

@ -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);
}
}

View File

@ -16,11 +16,22 @@ public class Task {
private Integer interval; private Integer interval;
private LocalDate endDate; private LocalDate endDate;
/**
* default constructor
* (used by Json deserializer)
*/
public Task(){
name= "";
description= "";
startDate = LocalDate.now();
}
public Task(String name, String description, LocalDate startDate) { public Task(String name, String description, LocalDate startDate) {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.startDate = startDate; this.startDate = startDate;
} }
public Task(String name, String description, LocalDate startDate, LocalDate endDate, int interval) { public Task(String name, String description, LocalDate startDate, LocalDate endDate, int interval) {
this.name = name; this.name = name;
this.description = description; this.description = description;
@ -43,6 +54,13 @@ public class Task {
return this; return this;
} }
public boolean isInTimePeriode(LocalDate searchStartDate, LocalDate searchEndDate){
if(startDate.isAfter(searchStartDate) &&startDate.isBefore(searchEndDate)){
return true;
}
return false;
}
// Getters // Getters
public long getId() { return id; } public long getId() { return id; }
public String getName() { return name; } public String getName() { return name; }

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="729.0" prefWidth="1060.0"
xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.HomeController">
<children>
<VBox layoutX="75.0" layoutY="73.0" prefHeight="729.0" prefWidth="1060.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label text="Gartenverwaltung">
<font>
<Font size="34.0" />
</font>
</Label>
<Label prefHeight="106.0" prefWidth="1040.0" text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." wrapText="true" />
<Label text="Tutorial">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
<Pane prefHeight="200.0" prefWidth="200.0">
<children>
<VBox prefHeight="200.0" prefWidth="1040.0">
<children>
<Label text="Task 1" wrapText="true" />
<Label text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." wrapText="true" />
</children>
</VBox>
</children>
</Pane>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</VBox>
</children>
</AnchorPane>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="938.0" prefWidth="1110.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.MainFXMLController">
<children>
<HBox maxWidth="35.0" minHeight="35.0" prefHeight="38.0" prefWidth="1110.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button fx:id="home_button" mnemonicParsing="false" onAction="#goToHome" prefHeight="38.0" prefWidth="121.0" text="Home" HBox.hgrow="NEVER" />
<Button fx:id="plants_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToPlants" prefHeight="38.0" prefWidth="121.0" text="Plants" />
<Button fx:id="myPlants_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToMyPlants" prefHeight="38.0" prefWidth="121.0" text="MyPlants" />
<Button fx:id="mySchedule_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToMySchedule" prefHeight="38.0" prefWidth="121.0" text="MySchedule" />
<Pane maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
</children>
</HBox>
<AnchorPane fx:id="mainPane" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="35.0" />
</children>
</AnchorPane>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="655.0" prefWidth="1175.0" xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.MyPlantsController">
<children>
<VBox layoutY="49.0" prefHeight="655.0" prefWidth="1175.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label text="MyPlants">
<font>
<Font name="System Bold" size="28.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<VBox fx:id="myPlants_vbox" prefHeight="200.0" prefWidth="100.0" VBox.vgrow="ALWAYS" />
<Button fx:id="addPlant_button" mnemonicParsing="false" onAction="#addPlant" prefHeight="45.0" prefWidth="155.0" text="Add new Plant">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</Button>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>
</children>
</AnchorPane>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="572.0" prefWidth="867.0"
xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.MyScheduleController">
<children>
<Label layoutX="14.0" layoutY="14.0" text="MySchedule">
<font>
<Font size="24.0" />
</font>
</Label>
<HBox layoutY="31.0" prefHeight="541.0" prefWidth="867.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="50.0">
<children>
<ListView fx:id="scheduledPlants_listview" maxWidth="1.7976931348623157E308" prefHeight="522.0" prefWidth="365.0" HBox.hgrow="NEVER" />
<VBox maxWidth="1.7976931348623157E308" prefHeight="537.0" prefWidth="650.0" HBox.hgrow="ALWAYS">
<children>
<GridPane alignment="CENTER_LEFT" gridLinesVisible="true" maxWidth="1.7976931348623157E308" prefHeight="403.0" prefWidth="575.0" VBox.vgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints fillWidth="false" hgrow="NEVER" maxWidth="278.0" minWidth="100.0" prefWidth="173.0" />
<ColumnConstraints hgrow="ALWAYS" maxWidth="1.7976931348623157E308" minWidth="10.0" prefWidth="402.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label fx:id="day1_label" alignment="CENTER" prefHeight="32.0" prefWidth="173.0" text="Label" />
<Label fx:id="day2_label" alignment="CENTER" prefHeight="29.0" prefWidth="173.0" text="Label" GridPane.rowIndex="1" />
<Label fx:id="day3_label" alignment="CENTER" prefHeight="32.0" prefWidth="172.0" text="Label" GridPane.rowIndex="2" />
<Label fx:id="day4_label" alignment="CENTER" prefHeight="35.0" prefWidth="173.0" text="Label" GridPane.rowIndex="3" />
<Label fx:id="day5_label" alignment="CENTER" prefHeight="31.0" prefWidth="173.0" text="Label" GridPane.rowIndex="4" />
<Label fx:id="day6_label" alignment="CENTER" prefHeight="31.0" prefWidth="173.0" text="Label" GridPane.rowIndex="5" />
<Label fx:id="day7_label" alignment="CENTER" prefHeight="35.0" prefWidth="173.0" text="Label" GridPane.rowIndex="6" />
<Pane fx:id="day1_pane" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" />
<Pane fx:id="day2_pane" maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Pane fx:id="day3_pane" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Pane fx:id="day4_pane" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Pane fx:id="day5_pane" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<Pane fx:id="day6_pane" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="5" />
<Pane fx:id="day7_pane" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="6" />
</children>
</GridPane>
<Pane prefHeight="119.0" prefWidth="575.0" VBox.vgrow="NEVER">
<children>
<Label alignment="TOP_LEFT" layoutX="14.0" layoutY="14.0" prefHeight="17.0" prefWidth="550.0" text="Importants Information:" wrapText="true">
<font>
<Font name="System Bold" size="12.0" />
</font>
</Label>
<Label fx:id="information_label" alignment="TOP_LEFT" layoutX="14.0" layoutY="31.0" maxWidth="1.7976931348623157E308" prefHeight="82.0" prefWidth="550.0" text="Label" wrapText="true" />
</children>
</Pane>
</children>
</VBox>
</children>
</HBox>
</children>
</AnchorPane>

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.geometry.Rectangle2D?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?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">
<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>
<AnchorPane maxWidth="1.7976931348623157E308" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<VBox maxWidth="1.7976931348623157E308" prefHeight="850.6666666666666" prefWidth="894.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label prefHeight="45.0" prefWidth="903.0" text="Plants">
<font>
<Font name="System Bold" size="30.0" />
</font>
</Label>
<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" />
<ImageView fx:id="img_plant" fitHeight="322.0" fitWidth="861.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER">
<viewport>
<Rectangle2D height="300.0" width="300.0" />
</viewport>
<image>
<Image url="@placeholder.png" />
</image>
</ImageView>
</children>
</HBox>
<Label prefHeight="33.0" prefWidth="919.0" text="Plant Information:">
<font>
<Font name="System Bold" size="12.0" />
</font>
<padding>
<Insets top="15.0" />
</padding>
</Label>
<Label fx:id="description_plant" alignment="TOP_LEFT" maxWidth="1.7976931348623157E308" prefHeight="194.0" prefWidth="893.0" text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." textAlignment="JUSTIFY" wrapText="true">
<padding>
<Insets bottom="10.0" top="10.0" />
</padding>
</Label>
<Button fx:id="saveToMyPlant_button" alignment="CENTER" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#saveToMyPlant" prefHeight="38.0" prefWidth="917.0" text="Save to MyPlants" />
</children>
</VBox>
</children>
</AnchorPane>
<AnchorPane maxWidth="300.0" minHeight="0.0" minWidth="300.0" prefHeight="160.0" prefWidth="100.0">
<children>
<VBox layoutX="38.0" layoutY="100.0" prefHeight="850.6666666666666" prefWidth="316.6666666666667" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label text="Filter">
<font>
<Font name="System Bold" size="17.0" />
</font>
</Label>
<TitledPane animated="false" text="Saison">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<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>
</TitledPane>
<TitledPane animated="false" text="Climate Zones">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<VBox fx:id="climate_zones" layoutX="36.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>
</TitledPane>
</children>
</VBox>
</children>
</AnchorPane>
</items>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</SplitPane>
</children>
</AnchorPane>

View File

@ -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

View File

@ -6,6 +6,7 @@
"light": 6, "light": 6,
"spacing": "35", "spacing": "35",
"soil": "sandy", "soil": "sandy",
"image": "potato.jpg",
"pests": [ "pests": [
{ {
"name": "Rot", "name": "Rot",
@ -86,6 +87,7 @@
"id": 1, "id": 1,
"name": "Early Carrot", "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.", "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": [ "lifecycle": [
{ {
"startDate": "02-20", "startDate": "02-20",
@ -167,6 +169,7 @@
"id": 2, "id": 2,
"name": "Summertime Onion", "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.", "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": [ "lifecycle": [
{ {
"startDate": "03-15", "startDate": "03-15",

View File

@ -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
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -1,16 +1,17 @@
package ch.zhaw.gartenverwaltung.io; package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.types.Crop; import ch.zhaw.gartenverwaltung.types.Crop;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import ch.zhaw.gartenverwaltung.types.Plant;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException; import java.net.URISyntaxException;
import java.text.SimpleDateFormat; import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Arrays; import java.util.Arrays;
@ -18,15 +19,24 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
public class JsonGardenPlanTest { public class JsonGardenPlanTest {
GardenPlan testDatabase; private GardenPlan testDatabase;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy"); private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* Files to isolate the test-units
*/
private final URL dbDataSource = this.getClass().getResource("user-crops.json");
private final URL testFile = this.getClass().getResource("test-user-crops.json");
@BeforeEach @BeforeEach
void connectToDb() { void connectToDb() throws URISyntaxException, IOException {
testDatabase = new JsonGardenPlan(); assertNotNull(testFile);
assertNotNull(dbDataSource);
Files.copy(Path.of(testFile.toURI()), Path.of(dbDataSource.toURI()), StandardCopyOption.REPLACE_EXISTING);
testDatabase = new JsonGardenPlan(dbDataSource);
} }
@ -42,19 +52,14 @@ public class JsonGardenPlanTest {
Assertions.assertEquals(3, testList.size()); Assertions.assertEquals(3, testList.size());
List<Long> plantIds = testList.stream().map(Crop::getPlantId).collect(Collectors.toList()); List<Long> plantIds = testList.stream().map(Crop::getPlantId).collect(Collectors.toList());
List<Long> expected = Arrays.asList(1l, 1l, 0l); List<Long> expected = Arrays.asList(1L, 1L, 0L);
Assertions.assertEquals(expected, plantIds); Assertions.assertEquals(expected, plantIds);
} }
@Test @Test
@DisplayName("Check whether single access works.") @DisplayName("Check whether single access works.")
void getCropById() { void getCropById() throws IOException {
Optional<Crop> testCrop; Optional<Crop> testCrop = testDatabase.getCropById(1);
try {
testCrop = testDatabase.getCropById(1);
} catch (IOException e) {
throw new RuntimeException(e);
}
assertTrue(testCrop.isPresent()); assertTrue(testCrop.isPresent());
Assertions.assertEquals(1, testCrop.get().getPlantId()); Assertions.assertEquals(1, testCrop.get().getPlantId());
} }
@ -62,31 +67,22 @@ public class JsonGardenPlanTest {
@Test @Test
@DisplayName("Check for a nonexisting crop.") @DisplayName("Check for a nonexisting crop.")
void getCropByIdMustFail() { void getCropByIdMustFail() throws IOException {
Optional<Crop> testCrop; Optional<Crop> testCrop = testDatabase.getCropById(99);
try {
testCrop = testDatabase.getCropById(99);
} catch (IOException e) {
throw new RuntimeException(e);
}
Assertions.assertFalse(testCrop.isPresent()); Assertions.assertFalse(testCrop.isPresent());
} }
@Test @Test
@DisplayName("Add new Crop.") @DisplayName("Add new Crop.")
void addNewCrop() { void addNewCrop() {
Crop crop = new Crop(2l, LocalDate.parse("22.02.2023", formatter)); Crop crop = new Crop(3L, LocalDate.parse("2023-02-22", formatter));
try { try {
testDatabase.saveCrop(crop); testDatabase.saveCrop(crop);
assertTrue(crop.getCropId().isPresent()); assertTrue(crop.getCropId().isPresent());
Optional<Crop> testCrop; Optional<Crop> testCrop = testDatabase.getCropById(crop.getCropId().get());
try {
testCrop = testDatabase.getCropById(crop.getCropId().get());
} catch (IOException e) {
throw new RuntimeException(e);
}
assertTrue(testCrop.isPresent()); assertTrue(testCrop.isPresent());
Assertions.assertEquals(2l, testCrop.get().getPlantId()); Assertions.assertEquals(crop, testCrop.get());
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -96,10 +92,10 @@ public class JsonGardenPlanTest {
@DisplayName("Remove crop") @DisplayName("Remove crop")
void removeCrop(){ void removeCrop(){
try { try {
Optional<Crop> crop = testDatabase.getCropById(2l); Optional<Crop> crop = testDatabase.getCropById(2L);
Assertions.assertTrue(crop.isPresent()); Assertions.assertTrue(crop.isPresent());
testDatabase.removeCrop(crop.get()); testDatabase.removeCrop(crop.get());
crop = testDatabase.getCropById(2l); crop = testDatabase.getCropById(2L);
Assertions.assertFalse(crop.isPresent()); Assertions.assertFalse(crop.isPresent());
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);

View File

@ -8,7 +8,6 @@ import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -16,7 +15,6 @@ import java.util.stream.Collectors;
public class JsonPlantDatabaseTest { public class JsonPlantDatabaseTest {
PlantDatabase testDatabase; PlantDatabase testDatabase;
SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy");
@BeforeEach @BeforeEach
void connectToDb() { void connectToDb() {
@ -30,9 +28,7 @@ public class JsonPlantDatabaseTest {
List<Plant> testList; List<Plant> testList;
try { try {
testList = testDatabase.getPlantList(HardinessZone.ZONE_8A); testList = testDatabase.getPlantList(HardinessZone.ZONE_8A);
} catch (IOException e) { } catch (IOException | HardinessZoneNotSetException e) {
throw new RuntimeException(e);
} catch (HardinessZoneNotSetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
Assertions.assertEquals(3, testList.size()); Assertions.assertEquals(3, testList.size());
@ -48,9 +44,7 @@ public class JsonPlantDatabaseTest {
List<Plant> testList; List<Plant> testList;
try { try {
testList = testDatabase.getPlantList(HardinessZone.ZONE_1A); testList = testDatabase.getPlantList(HardinessZone.ZONE_1A);
} catch (IOException e) { } catch (IOException | HardinessZoneNotSetException e) {
throw new RuntimeException(e);
} catch (HardinessZoneNotSetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
Assertions.assertEquals(0, testList.size()); Assertions.assertEquals(0, testList.size());
@ -64,9 +58,7 @@ public class JsonPlantDatabaseTest {
Optional<Plant> testPlant; Optional<Plant> testPlant;
try { try {
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A, 1); testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A, 1);
} catch (IOException e) { } catch (IOException | HardinessZoneNotSetException e) {
throw new RuntimeException(e);
} catch (HardinessZoneNotSetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
Assertions.assertTrue(testPlant.isPresent()); Assertions.assertTrue(testPlant.isPresent());
@ -75,20 +67,12 @@ public class JsonPlantDatabaseTest {
@Test @Test
@DisplayName("Check whether single access respects zone correctly.") @DisplayName("Check whether single access respects zone correctly.")
void getPlantByIdAndWrongZone() { void getPlantByIdAndWrongZone() throws HardinessZoneNotSetException, IOException {
Optional<Plant> testPlant; Optional<Plant> testPlant = testDatabase.getPlantById(HardinessZone.ZONE_1A, 1);
try { Assertions.assertFalse(testPlant.isPresent());
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_1A, 1); testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A, 1);
Assertions.assertFalse(testPlant.isPresent());
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A, 1);
Assertions.assertTrue(testPlant.isPresent());
} catch (IOException e) {
throw new RuntimeException(e);
} catch (HardinessZoneNotSetException e) {
throw new RuntimeException(e);
}
Assertions.assertTrue(testPlant.isPresent()); Assertions.assertTrue(testPlant.isPresent());
Assertions.assertEquals("Early Carrot", testPlant.get().name()); Assertions.assertEquals("Early Carrot", testPlant.get().name());
} }
@ -98,9 +82,7 @@ public class JsonPlantDatabaseTest {
Optional<Plant> testPlant; Optional<Plant> testPlant;
try { try {
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A, 99); testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A, 99);
} catch (IOException e) { } catch (IOException | HardinessZoneNotSetException e) {
throw new RuntimeException(e);
} catch (HardinessZoneNotSetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
Assertions.assertFalse(testPlant.isPresent()); Assertions.assertFalse(testPlant.isPresent());

View File

@ -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);
*/
}
}

View File

@ -3,14 +3,14 @@ package ch.zhaw.gartenverwaltung.plantList;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
import ch.zhaw.gartenverwaltung.io.JsonPlantDatabase; import ch.zhaw.gartenverwaltung.io.JsonPlantDatabase;
import ch.zhaw.gartenverwaltung.io.PlantDatabase; import ch.zhaw.gartenverwaltung.io.PlantDatabase;
import ch.zhaw.gartenverwaltung.types.HardinessZone; import ch.zhaw.gartenverwaltung.types.*;
import ch.zhaw.gartenverwaltung.types.Plant;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import java.time.MonthDay;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -43,31 +43,39 @@ class PlantListModelTest {
20, 20,
"summertime onion", "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.", "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", "15,30,2",
0, 0,
"sandy to loamy, loose soil, free of stones", "sandy to loamy, loose soil, free of stones",
new ArrayList<>(), new ArrayList<>(),
new ArrayList<>()) List.of(new GrowthPhase(MonthDay.of(6, 4), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.HARVEST, HardinessZone.ZONE_8A, new ArrayList<>()),
new GrowthPhase(MonthDay.of(4, 3), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>()),
new GrowthPhase(MonthDay.of(8, 5), 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<>())))
); );
examplePlantList.add(new Plant( examplePlantList.add(new Plant(
0, 0,
"Potato", "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.", "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", "35",
6, 6,
"sandy", "sandy",
new ArrayList<>(), new ArrayList<>(),
new ArrayList<>()) List.of(new GrowthPhase(MonthDay.of(6, 4), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.HARVEST, HardinessZone.ZONE_8A, new ArrayList<>()),
new GrowthPhase(MonthDay.of(6, 4), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>())))
); );
examplePlantList.add(new Plant( examplePlantList.add(new Plant(
1, 1,
"Early Carrot", "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.", "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", "5,35,2.5",
0, 0,
"sandy to loamy, loose soil, free of stones", "sandy to loamy, loose soil, free of stones",
new ArrayList<>(), 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<>())))
); );
} }
@ -146,4 +154,46 @@ class PlantListModelTest {
assertEquals(1, plantListResult.size()); assertEquals(1, plantListResult.size());
assertEquals(examplePlantList.get(0), plantListResult.get(0)); assertEquals(examplePlantList.get(0), plantListResult.get(0));
} }
@Test
void getFilteredPlantListByString() throws HardinessZoneNotSetException, IOException {
model.setCurrentZone(HardinessZone.ZONE_1A);
List<Plant> plantListResult = model.getFilteredPlantListByString(HardinessZone.ZONE_8A, "#2");
assertEquals(0, plantListResult.size());
plantListResult = model.getFilteredPlantListByString(HardinessZone.ZONE_8A, "#20");
assertEquals(1, plantListResult.size());
assertEquals(examplePlantList.get(0), plantListResult.get(0));
plantListResult = model.getFilteredPlantListByString(HardinessZone.ZONE_8A, "#a2");
assertEquals(0, plantListResult.size());
plantListResult = model.getFilteredPlantListByString(HardinessZone.ZONE_8A, "onion");
assertEquals(1, plantListResult.size());
assertEquals(examplePlantList.get(0), plantListResult.get(0));
plantListResult = model.getFilteredPlantListByString(HardinessZone.ZONE_8A, "white roots");
assertEquals(1, plantListResult.size());
assertEquals(examplePlantList.get(1), plantListResult.get(0));
plantListResult = model.getFilteredPlantListByString(HardinessZone.ZONE_8A, "apple");
assertEquals(0, plantListResult.size());
plantListResult = model.getFilteredPlantListByString(HardinessZone.ZONE_8A, "");
assertEquals(3, plantListResult.size());
}
@Test
void getFilteredPlantListByPlantingSaison() throws HardinessZoneNotSetException, IOException {
model.setCurrentZone(HardinessZone.ZONE_1A);
List<Plant> plantListResult = model.getFilteredPlantListByPlantingSaison(HardinessZone.ZONE_8A, MonthDay.of(4, 4), MonthDay.of(8, 4));
assertEquals(2, plantListResult.size());
assertEquals(examplePlantList.get(2), plantListResult.get(0));
assertEquals(examplePlantList.get(1), plantListResult.get(1));
}
@Test
void getFilteredPlantListByHarvestSaison() throws HardinessZoneNotSetException, IOException {
model.setCurrentZone(HardinessZone.ZONE_1A);
List<Plant> plantListResult = model.getFilteredPlantListByHarvestSaison(HardinessZone.ZONE_8A, MonthDay.of(4, 4), MonthDay.of(8, 4));
assertEquals(2, plantListResult.size());
assertEquals(examplePlantList.get(1), plantListResult.get(0));
assertEquals(examplePlantList.get(0), plantListResult.get(1));
}
} }

View File

@ -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"
}
]

View File

@ -0,0 +1,20 @@
[
{
"cropId": 0,
"plantId": 1,
"startDate": "2023-02-25",
"area": 0.5
},
{
"cropId": 1,
"plantId": 1,
"startDate": "2023-03-01",
"area": 0.5
},
{
"cropId": 2,
"plantId": 0,
"startDate": "2023-03-25",
"area": 1.5
}
]

View File

@ -0,0 +1,2 @@
[
]