Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c653005652 | ||
|
|
bee517317a | ||
|
|
2be9df6094 | ||
|
|
5faf61089a | ||
|
|
8e23124c6b | ||
|
|
78a27499a8 | ||
|
|
0e40bc6304 | ||
|
|
ced2645bd7 | ||
|
|
802f238d69 | ||
|
|
096abfd148 | ||
|
|
590049b9cf | ||
|
|
ad05e9e95a | ||
|
|
60c6dcd0d9 | ||
|
|
52ae2b02bc | ||
|
|
00db602904 | ||
|
|
5e206ace39 | ||
|
|
ea4a1ecc6a | ||
|
|
b7d08944a6 | ||
|
|
3077e02b32 | ||
|
|
96fdc64105 | ||
|
|
e92538fbb4 | ||
|
|
2cf7f55215 | ||
|
|
f6ec411b7a | ||
|
|
38288f8561 | ||
|
|
a43a23427c | ||
|
|
fb0c50a715 | ||
|
|
83bc011870 | ||
|
|
ce93531ab8 | ||
|
|
efc95dbcc6 | ||
|
|
eea530931e | ||
|
|
82eab6d5cd | ||
|
|
5411ac69ae | ||
|
|
146c43d5d9 | ||
|
|
160880d4f5 | ||
|
|
ceb448c1ff | ||
|
|
8c651aec2b | ||
|
|
56fac84e12 | ||
|
|
8b3a0c1570 | ||
|
|
516a185546 |
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to document bugs.
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Class / Methods**
|
||||||
|
List of classes or methods which contains the bug. (If known)
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Features to implement
|
||||||
|
title: "[FEATURE]"
|
||||||
|
labels: feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Description of Usecase**
|
||||||
|
A clear and concise description of what the feature allows to do.
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**List of Classes and Methods to implement**
|
||||||
|
List all Classes and Methods which have to be implemented.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Tests
|
||||||
|
about: Tests to implement
|
||||||
|
title: "[TEST]"
|
||||||
|
labels: Tests
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Feature description**
|
||||||
|
A clear and concise description of the functionality which have to be tested.
|
||||||
|
|
||||||
|
**Test cases**
|
||||||
|
A list of cases which have to be tested.
|
||||||
|
|
||||||
|
**List of affected Classes and Methods**
|
||||||
|
A clear and concise list of the classes and methods which have to be tested.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
@@ -0,0 +1,144 @@
|
|||||||
|
<diagram program="umletino" version="15.0.0"><zoom_level>10</zoom_level><element><id>UMLClass</id><coordinates><x>482</x><y>360</y><w>100</w><h>30</h></coordinates><panel_attributes>Main</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>1082</x><y>380</y><w>370</w><h>100</h></coordinates><panel_attributes><<Interface>>
|
||||||
|
PlantDatabase
|
||||||
|
--
|
||||||
|
+ getPlantList(zone: HardinessZone): List<Plant>
|
||||||
|
+ getPlantById(zone: HardinessZone id: long): Optional<Plant></panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>672</x><y>710</y><w>220</w><h>40</h></coordinates><panel_attributes>TaskListController</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>1132</x><y>110</y><w>300</w><h>180</h></coordinates><panel_attributes><<Record>>
|
||||||
|
Plant
|
||||||
|
--
|
||||||
|
+ id: long
|
||||||
|
+ name: String
|
||||||
|
+ description: String
|
||||||
|
+ spacing: int
|
||||||
|
+ lifecycle: List<GrowthPhase>
|
||||||
|
--
|
||||||
|
+ calculateStartDate(harvestDate: Date): Date
|
||||||
|
+ generateTasks()
|
||||||
|
</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>942</x><y>710</y><w>210</w><h>120</h></coordinates><panel_attributes>TaskListModel
|
||||||
|
--
|
||||||
|
- tasks: ListProperty<Task>
|
||||||
|
- taskDb: TaskDatabase
|
||||||
|
--
|
||||||
|
+ getTask(id: long): Optional<Task>
|
||||||
|
+ saveTask(task: Task)
|
||||||
|
+ removeTask(task: Task)</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>322</x><y>690</y><w>250</w><h>120</h></coordinates><panel_attributes>GardenPlanModel
|
||||||
|
--
|
||||||
|
- tasks: ListProperty<Crop>
|
||||||
|
- gardenPlan: GardenPlan
|
||||||
|
--
|
||||||
|
+ plantAsCrop(planting: UserPlanting)
|
||||||
|
+ removePlanting(planting: UserPlanting)</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>72</x><y>680</y><w>180</w><h>80</h></coordinates><panel_attributes>GardenPlanController</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>1362</x><y>830</y><w>490</w><h>240</h></coordinates><panel_attributes>Task
|
||||||
|
--
|
||||||
|
+ id: long
|
||||||
|
+ name: String {readOnly}
|
||||||
|
+ description: String {readOnly}
|
||||||
|
+ startDate: Date {readOnly}
|
||||||
|
+ isOptional: boolean {readOnly}
|
||||||
|
- interval: int
|
||||||
|
- endDate: Date
|
||||||
|
--
|
||||||
|
+ Task(name: String, description: String, startDate: String, isReadOnly: boolean): Task
|
||||||
|
+ withInterval(interval: int): Task
|
||||||
|
+ withEndDate(endDate: Date): Task
|
||||||
|
+ withId(id: long): Task
|
||||||
|
--
|
||||||
|
+ getInterval(): Optional<int>
|
||||||
|
+ getEndDate(): Optional<Date></panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>902</x><y>930</y><w>280</w><h>140</h></coordinates><panel_attributes><<Interface>>
|
||||||
|
TaskDatabase
|
||||||
|
--
|
||||||
|
+ getTaskList(start: Date, end: Date): List<Task>
|
||||||
|
+ saveTask(Task task) throws ??Exception
|
||||||
|
+ removeTask(Task task) throws ??Exception</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>442</x><y>440</y><w>170</w><h>80</h></coordinates><panel_attributes>MainWindowController</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>962</x><y>1310</y><w>210</w><h>70</h></coordinates><panel_attributes>NotificationService
|
||||||
|
--
|
||||||
|
- taskDb: TaskDatabase
|
||||||
|
--
|
||||||
|
+ tick()</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>1412</x><y>1310</y><w>240</w><h>90</h></coordinates><panel_attributes><<Interface>>
|
||||||
|
WeatherProvider
|
||||||
|
--
|
||||||
|
+ getWeatherForecast: WeatherForecast</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>1392</x><y>1150</y><w>400</w><h>100</h></coordinates><panel_attributes>WeatherService
|
||||||
|
--
|
||||||
|
- weatherPovider: WeatherProvider
|
||||||
|
- taskDb: TaskDatabase
|
||||||
|
--
|
||||||
|
+ WeatherService(provider: WeatherProvider, taskDb: TaskDatabase)
|
||||||
|
- updateTasks()</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>1502</x><y>110</y><w>260</w><h>150</h></coordinates><panel_attributes><<Record>>
|
||||||
|
GrowthPhase
|
||||||
|
--
|
||||||
|
+ startDate: MonthDay
|
||||||
|
+ endDate: MonthDay
|
||||||
|
+ type: GrowthPhaseType
|
||||||
|
|
||||||
|
</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>1422</x><y>180</y><w>100</w><h>40</h></coordinates><panel_attributes>lt=<->>>>>
|
||||||
|
m1=*
|
||||||
|
m2=1</panel_attributes><additional_attributes>80;10;10;10</additional_attributes></element><element><id>Text</id><coordinates><x>152</x><y>130</y><w>190</w><h>70</h></coordinates><panel_attributes>Note:
|
||||||
|
--
|
||||||
|
{final, readOnly} omitted on public data fields in <<Record>> Entities for clarity
|
||||||
|
style=wordwrap</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>1172</x><y>970</y><w>210</w><h>50</h></coordinates><panel_attributes>lt=<.
|
||||||
|
m1=*
|
||||||
|
m2=1
|
||||||
|
returns ></panel_attributes><additional_attributes>190;20;10;20</additional_attributes></element><element><id>Relation</id><coordinates><x>1032</x><y>820</y><w>140</w><h>130</h></coordinates><panel_attributes>lt=<-
|
||||||
|
m1=1 external database
|
||||||
|
m2=1 internal list
|
||||||
|
accesses</panel_attributes><additional_attributes>10;110;10;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>322</x><y>1120</y><w>240</w><h>150</h></coordinates><panel_attributes>Crop
|
||||||
|
--
|
||||||
|
- cropId: Long
|
||||||
|
+ plantId: long {final, readOnly}
|
||||||
|
+ startDate: LocalDate {final, readOnly}
|
||||||
|
+ area: Double
|
||||||
|
--
|
||||||
|
+ withId(long): Crop
|
||||||
|
+ withArea(double): Crop
|
||||||
|
--
|
||||||
|
+ getCropId(): Optional<Long></panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>1262</x><y>280</y><w>70</w><h>120</h></coordinates><panel_attributes>lt=<.
|
||||||
|
m1=*
|
||||||
|
m2=1
|
||||||
|
returns</panel_attributes><additional_attributes>10;10;10;100</additional_attributes></element><element><id>UMLClass</id><coordinates><x>1632</x><y>290</y><w>130</w><h>100</h></coordinates><panel_attributes><<Enumeration>>
|
||||||
|
GrowthPhaseType
|
||||||
|
--
|
||||||
|
SOW
|
||||||
|
PLANT
|
||||||
|
HARVEST</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>1582</x><y>250</y><w>70</w><h>120</h></coordinates><panel_attributes>lt=<->>>>
|
||||||
|
m1=1
|
||||||
|
m2=*</panel_attributes><additional_attributes>50;90;10;90;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>882</x><y>720</y><w>80</w><h>30</h></coordinates><panel_attributes>lt=<->>>></panel_attributes><additional_attributes>60;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>432</x><y>1020</y><w>110</w><h>120</h></coordinates><panel_attributes>lt=<.
|
||||||
|
m1=*
|
||||||
|
m2=1
|
||||||
|
returns / saves</panel_attributes><additional_attributes>10;100;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>242</x><y>710</y><w>100</w><h>30</h></coordinates><panel_attributes>lt=<->>>></panel_attributes><additional_attributes>80;10;10;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>302</x><y>890</y><w>280</w><h>140</h></coordinates><panel_attributes><<Interface>>
|
||||||
|
GardenPlan
|
||||||
|
--
|
||||||
|
+ getPlantings(): List<UserPlanting>
|
||||||
|
+ addPlanting(plantId: long, startDate)
|
||||||
|
+ savePlanting(planting: UserPlanting)
|
||||||
|
</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>422</x><y>800</y><w>80</w><h>110</h></coordinates><panel_attributes>lt=<.
|
||||||
|
m1=1
|
||||||
|
m2=1
|
||||||
|
accesses</panel_attributes><additional_attributes>10;90;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>1522</x><y>1240</y><w>30</w><h>90</h></coordinates><panel_attributes>lt=<.</panel_attributes><additional_attributes>10;70;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>1062</x><y>1060</y><w>80</w><h>270</h></coordinates><panel_attributes>lt=<-
|
||||||
|
m1=1
|
||||||
|
m2=1
|
||||||
|
accesses</panel_attributes><additional_attributes>10;10;10;250</additional_attributes></element><element><id>Relation</id><coordinates><x>1142</x><y>1060</y><w>270</w><h>170</h></coordinates><panel_attributes>lt=<-
|
||||||
|
m1=1
|
||||||
|
m2=1
|
||||||
|
adds Tasks</panel_attributes><additional_attributes>10;10;10;140;250;140</additional_attributes></element><element><id>UMLClass</id><coordinates><x>822</x><y>1140</y><w>200</w><h>50</h></coordinates><panel_attributes>JsonTaskDatabase</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>912</x><y>1060</y><w>30</w><h>100</h></coordinates><panel_attributes>lt=<<-</panel_attributes><additional_attributes>10;10;10;80</additional_attributes></element><element><id>UMLClass</id><coordinates><x>1562</x><y>430</y><w>200</w><h>50</h></coordinates><panel_attributes>JsonPlantDatabase</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>1442</x><y>440</y><w>140</w><h>30</h></coordinates><panel_attributes>lt=<<-</panel_attributes><additional_attributes>10;10;120;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>32</x><y>930</y><w>200</w><h>50</h></coordinates><panel_attributes>JsonGardenPlan</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>222</x><y>940</y><w>100</w><h>30</h></coordinates><panel_attributes>lt=<<-</panel_attributes><additional_attributes>80;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>582</x><y>510</y><w>220</w><h>220</h></coordinates><panel_attributes>lt=<.</panel_attributes><additional_attributes>200;200;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>172</x><y>510</y><w>340</w><h>190</h></coordinates><panel_attributes>lt=<.</panel_attributes><additional_attributes>10;170;320;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>582</x><y>250</y><w>160</w><h>60</h></coordinates><panel_attributes>PlantListController</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>522</x><y>380</y><w>30</w><h>80</h></coordinates><panel_attributes>lt=<.</panel_attributes><additional_attributes>10;60;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>602</x><y>300</y><w>80</w><h>210</h></coordinates><panel_attributes>lt=<.</panel_attributes><additional_attributes>60;10;10;190</additional_attributes></element><element><id>UMLClass</id><coordinates><x>732</x><y>390</y><w>160</w><h>60</h></coordinates><panel_attributes>PlantListModel</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>732</x><y>280</y><w>90</w><h>130</h></coordinates><panel_attributes>lt=<.</panel_attributes><additional_attributes>70;110;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>882</x><y>400</y><w>220</w><h>70</h></coordinates><panel_attributes>lt=<-
|
||||||
|
m1=1\nexternal\ndatabase
|
||||||
|
m2=1\ninternal\nlist
|
||||||
|
accesses ></panel_attributes><additional_attributes>200;20;10;20</additional_attributes></element><element><id>Relation</id><coordinates><x>582</x><y>0</y><w>190</w><h>50</h></coordinates><panel_attributes>lt=-
|
||||||
|
m1=0..n
|
||||||
|
m2=0..1
|
||||||
|
teaches to ></panel_attributes><additional_attributes>10;20;170;20</additional_attributes></element><element><id>UMLClass</id><coordinates><x>1412</x><y>530</y><w>420</w><h>200</h></coordinates><panel_attributes>TaskTemplate
|
||||||
|
--
|
||||||
|
+ name: String {readOnly}
|
||||||
|
+ description: String {readOnly}
|
||||||
|
+ relativeStartDate: int {readOnly}
|
||||||
|
- interval: Integer
|
||||||
|
- relativeEndDate: Integer
|
||||||
|
- isOptional: boolean
|
||||||
|
--
|
||||||
|
+ Task(name: String, description: String, startDate: String): TaskTemplate
|
||||||
|
+ setRelativeEndDate(relativeEndDate: int)
|
||||||
|
+ setInterval(interval: Integer)
|
||||||
|
+ generateTask(realStartDate: LocalDate): Task
|
||||||
|
</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>1752</x><y>180</y><w>130</w><h>490</h></coordinates><panel_attributes>lt=<->>>>>
|
||||||
|
m1=*
|
||||||
|
m2=1</panel_attributes><additional_attributes>80;460;110;460;110;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>1602</x><y>720</y><w>90</w><h>130</h></coordinates><panel_attributes>lt=<.
|
||||||
|
m1=*
|
||||||
|
m2=1
|
||||||
|
generates</panel_attributes><additional_attributes>10;110;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>562</x><y>770</y><w>400</w><h>40</h></coordinates><panel_attributes>lt=<-
|
||||||
|
delegates generating Tasks</panel_attributes><additional_attributes>380;20;10;20</additional_attributes></element></diagram>
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package ch.zhaw.gartenverwaltung;
|
||||||
|
|
||||||
|
import ch.zhaw.gartenverwaltung.gardenplan.Gardenplanmodel;
|
||||||
|
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
|
||||||
|
import ch.zhaw.gartenverwaltung.plantList.PlantListModel;
|
||||||
|
import ch.zhaw.gartenverwaltung.taskList.TaskListModel;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Crop;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Pest;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Plant;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Task;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CropDetailController {
|
||||||
|
private Crop crop = null;
|
||||||
|
private final PlantListModel plantListModel = new PlantListModel();
|
||||||
|
private final TaskListModel taskListModel = new TaskListModel();
|
||||||
|
private final Gardenplanmodel gardenplanmodel = new Gardenplanmodel(taskListModel);
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ImageView imageView;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button area_button;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label area_label;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label cropName_label;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label description_label;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private VBox growthPhases_vbox;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label location_label;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label light_label;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button location_button;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private VBox pests_vbox;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label soil_label;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label spacing_label;
|
||||||
|
|
||||||
|
public CropDetailController() throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void editTaskList(ActionEvent event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void goBack(ActionEvent event) {
|
||||||
|
Stage stage = (Stage) imageView.getScene().getWindow();
|
||||||
|
stage.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void setArea(ActionEvent event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void setLocation(ActionEvent event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlantFromCrop(Crop crop) throws HardinessZoneNotSetException, IOException {
|
||||||
|
this.crop = crop;
|
||||||
|
Plant plant = plantListModel.getFilteredPlantListById(Config.getCurrentHardinessZone(), crop.getPlantId()).get(0);
|
||||||
|
cropName_label.setText(plant.name());
|
||||||
|
description_label.setText(plant.description());
|
||||||
|
light_label.setText(String.valueOf(plant.light()));
|
||||||
|
soil_label.setText(plant.soil());
|
||||||
|
spacing_label.setText(plant.spacing());
|
||||||
|
if (plant.image() != null) {
|
||||||
|
imageView.setImage(plant.image());
|
||||||
|
}
|
||||||
|
area_label.setText("");
|
||||||
|
location_label.setText("");
|
||||||
|
createTaskLists(crop);
|
||||||
|
createPestList(plant);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTaskLists(Crop crop) throws IOException {
|
||||||
|
List<Task> taskList = taskListModel.getTaskListForCrop(crop.getCropId().get());
|
||||||
|
for (Task task : taskList) {
|
||||||
|
Label label = new Label(task.getDescription());
|
||||||
|
growthPhases_vbox.getChildren().add(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createPestList(Plant plant) {
|
||||||
|
List<Pest> pests = plant.pests();
|
||||||
|
for (Pest pest : pests) {
|
||||||
|
Label label = new Label(pest.name() + ":");
|
||||||
|
label.setStyle("-fx-font-weight: bold");
|
||||||
|
HBox hBox = new HBox();
|
||||||
|
hBox.fillHeightProperty();
|
||||||
|
Label label1 = new Label(pest.description());
|
||||||
|
label1.setAlignment(Pos.TOP_LEFT);
|
||||||
|
label1.setWrapText(true);
|
||||||
|
label1.setMaxWidth(600);
|
||||||
|
label1.setMaxHeight(100);
|
||||||
|
Button button = new Button("Get Counter Measures");
|
||||||
|
hBox.getChildren().addAll(label1, button);
|
||||||
|
pests_vbox.getChildren().addAll(label, hBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import javafx.event.ActionEvent;
|
|||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
|
||||||
@@ -14,12 +13,15 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class MainFXMLController implements Initializable {
|
public class MainFXMLController implements Initializable {
|
||||||
/**
|
/**
|
||||||
* Caching the panes
|
* Caching the panes
|
||||||
*/
|
*/
|
||||||
private final Map<String, AnchorPane> panes = new HashMap<>();
|
private final Map<String, AnchorPane> panes = new HashMap<>();
|
||||||
|
private static final Logger LOG = Logger.getLogger(MainFXMLController.class.getName());
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button home_button;
|
private Button home_button;
|
||||||
@@ -100,7 +102,7 @@ public class MainFXMLController implements Initializable {
|
|||||||
loadPane("Home.fxml");
|
loadPane("Home.fxml");
|
||||||
styleChangeButton(home_button);
|
styleChangeButton(home_button);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
LOG.log(Level.SEVERE, "Failed to load FXML-Pane!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,45 @@
|
|||||||
package ch.zhaw.gartenverwaltung;
|
package ch.zhaw.gartenverwaltung;
|
||||||
|
|
||||||
|
import ch.zhaw.gartenverwaltung.gardenplan.Gardenplanmodel;
|
||||||
|
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
|
||||||
|
import ch.zhaw.gartenverwaltung.plantList.PlantListModel;
|
||||||
|
import ch.zhaw.gartenverwaltung.taskList.TaskListModel;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Crop;
|
||||||
import ch.zhaw.gartenverwaltung.types.Plant;
|
import ch.zhaw.gartenverwaltung.types.Plant;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.LinkedList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
|
|
||||||
public class MyPlantsController implements Initializable {
|
public class MyPlantsController implements Initializable {
|
||||||
MainFXMLController mainController;
|
MainFXMLController mainController;
|
||||||
|
private final TaskListModel taskListModel = new TaskListModel();
|
||||||
@FXML
|
private final Gardenplanmodel gardenplanmodel = new Gardenplanmodel(taskListModel);
|
||||||
private Button addPlant_button;
|
private final PlantListModel plantListModel = new PlantListModel();
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private VBox myPlants_vbox;
|
private VBox myPlants_vbox;
|
||||||
|
|
||||||
|
public MyPlantsController() throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void addPlant(ActionEvent event) throws IOException {
|
void addPlant(ActionEvent event) throws IOException {
|
||||||
mainController.loadPane("Plants.fxml");
|
mainController.loadPane("Plants.fxml");
|
||||||
@@ -29,15 +47,29 @@ public class MyPlantsController implements Initializable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
//ToDo
|
//ToDo update, when new crops are added
|
||||||
List<Plant> myPlants = getMyPlants();
|
try {
|
||||||
createPlantView(myPlants);
|
loadCropList();
|
||||||
|
} catch (HardinessZoneNotSetException | IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createPlantView(List<Plant> myPlants) {
|
private void loadCropList() throws HardinessZoneNotSetException, IOException {
|
||||||
//ToDo
|
List<Crop> cropList = new LinkedList<>();
|
||||||
for(Plant plant : myPlants) {
|
try {
|
||||||
createPlantView();
|
cropList = getCropList();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
createPlantView(cropList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createPlantView(List<Crop> crops) throws HardinessZoneNotSetException, IOException {
|
||||||
|
myPlants_vbox.getChildren().clear();
|
||||||
|
for(Crop crop : crops) {
|
||||||
|
HBox hBox = createPlantView(crop);
|
||||||
|
myPlants_vbox.getChildren().add(hBox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,14 +77,84 @@ public class MyPlantsController implements Initializable {
|
|||||||
mainController = controller;
|
mainController = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Plant> getMyPlants() {
|
private List<Crop> getCropList() throws IOException {
|
||||||
//ToDo method to get myPlantList(scheduled)
|
List<Crop> cropList;
|
||||||
//Method to call all Plants saved
|
cropList = gardenplanmodel.getCrops();
|
||||||
List<Plant> myPlantList = new LinkedList<>();
|
return cropList;
|
||||||
return myPlantList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createPlantView() {
|
private HBox createPlantView(Crop crop) throws HardinessZoneNotSetException, IOException {
|
||||||
//ToDo FXML Panel with Plant data
|
//ToDo add better design
|
||||||
|
Plant plant = plantListModel.getFilteredPlantListById(Config.getCurrentHardinessZone(), crop.getPlantId()).get(0);
|
||||||
|
HBox hBox = new HBox(10);
|
||||||
|
ImageView imageView = new ImageView();
|
||||||
|
imageView.setPreserveRatio(false);
|
||||||
|
imageView.setFitHeight(100);
|
||||||
|
imageView.setFitWidth(100);
|
||||||
|
imageView.maxHeight(100);
|
||||||
|
if (plant.image() != null) {
|
||||||
|
imageView.setImage(plant.image());
|
||||||
|
}
|
||||||
|
hBox.setMinHeight(100);
|
||||||
|
Label label = new Label(plant.name());
|
||||||
|
label.setMaxWidth(2000);
|
||||||
|
HBox.setHgrow(label, Priority.ALWAYS);
|
||||||
|
Button details = new Button("Details");
|
||||||
|
Button delete = new Button("delete");
|
||||||
|
details.setOnAction(getGoToCropDetailEvent(crop));
|
||||||
|
delete.setOnAction(getDeleteCropEvent(crop));
|
||||||
|
hBox.getChildren().addAll(imageView, label, details, delete);
|
||||||
|
return hBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventHandler<ActionEvent> getGoToCropDetailEvent(Crop crop) {
|
||||||
|
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
|
||||||
|
@Override
|
||||||
|
public void handle(ActionEvent event) {
|
||||||
|
Parent root;
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader(Objects.requireNonNull(getClass().getResource("CropDetail.fxml")));
|
||||||
|
try {
|
||||||
|
root = fxmlLoader.load();
|
||||||
|
CropDetailController controller = fxmlLoader.getController();
|
||||||
|
controller.setPlantFromCrop(crop);
|
||||||
|
Stage stage = new Stage();
|
||||||
|
stage.setScene(new Scene(root));
|
||||||
|
stage.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
stage.setResizable(true);
|
||||||
|
stage.showAndWait();
|
||||||
|
} catch (IOException | HardinessZoneNotSetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventHandler<ActionEvent> getDeleteCropEvent(Crop crop) {
|
||||||
|
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
|
||||||
|
@Override
|
||||||
|
public void handle(ActionEvent event) {
|
||||||
|
try {
|
||||||
|
showConfirmation(crop);
|
||||||
|
} catch (IOException | HardinessZoneNotSetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showConfirmation(Crop crop) throws IOException, HardinessZoneNotSetException {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
|
alert.setTitle("Delete Crop");
|
||||||
|
alert.setHeaderText("Are you sure want to delete this Crop?");
|
||||||
|
alert.setContentText("placeholder");
|
||||||
|
|
||||||
|
Optional<ButtonType> option = alert.showAndWait();
|
||||||
|
|
||||||
|
if (option.get() == ButtonType.OK) {
|
||||||
|
gardenplanmodel.removeCrop(crop);
|
||||||
|
loadCropList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
package ch.zhaw.gartenverwaltung;
|
package ch.zhaw.gartenverwaltung;
|
||||||
|
|
||||||
import ch.zhaw.gartenverwaltung.types.Plant;
|
import ch.zhaw.gartenverwaltung.gardenplan.Gardenplanmodel;
|
||||||
|
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
|
||||||
|
import ch.zhaw.gartenverwaltung.plantList.PlantListModel;
|
||||||
|
import ch.zhaw.gartenverwaltung.taskList.TaskListModel;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Crop;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Task;
|
||||||
|
import javafx.beans.property.ListProperty;
|
||||||
|
import javafx.beans.property.SimpleListProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ListCell;
|
import javafx.scene.control.ListCell;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@@ -17,7 +27,12 @@ import java.util.List;
|
|||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
public class MyScheduleController implements Initializable {
|
public class MyScheduleController implements Initializable {
|
||||||
private Plant selectedPlant = null;
|
private Crop selectedCrop = null;
|
||||||
|
private final TaskListModel taskListModel = new TaskListModel();
|
||||||
|
private final Gardenplanmodel gardenplanmodel = new Gardenplanmodel(taskListModel);
|
||||||
|
private final PlantListModel plantListModel = new PlantListModel();
|
||||||
|
|
||||||
|
private final ListProperty<Crop> cropListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label day1_label;
|
private Label day1_label;
|
||||||
@@ -65,27 +80,41 @@ public class MyScheduleController implements Initializable {
|
|||||||
private Label information_label;
|
private Label information_label;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ListView<Plant> scheduledPlants_listview;
|
private ListView<Crop> scheduledPlants_listview;
|
||||||
|
|
||||||
|
public MyScheduleController() throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
List<Plant> plantList = new LinkedList<>();
|
List<Crop> cropList;
|
||||||
fillListViewMyPlantsInSchedule(plantList);
|
try {
|
||||||
getSelectedPlantTask();
|
cropList = gardenplanmodel.getCrops();
|
||||||
|
cropListProperty.addAll(cropList);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
setCellFactoryListView();
|
||||||
|
scheduledPlants_listview.itemsProperty().bind(cropListProperty);
|
||||||
|
lookForSelectedListEntries();
|
||||||
setDayLabels();
|
setDayLabels();
|
||||||
information_label.setText("");
|
information_label.setText("");
|
||||||
|
try {
|
||||||
|
loadTaskList();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getSelectedPlantTask() {
|
private void lookForSelectedListEntries() {
|
||||||
scheduledPlants_listview.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Plant>() {
|
scheduledPlants_listview.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Crop>() {
|
||||||
@Override
|
@Override
|
||||||
public void changed(ObservableValue<? extends Plant> observable, Plant oldValue, Plant newValue) {
|
public void changed(ObservableValue<? extends Crop> observable, Crop oldValue, Crop newValue) {
|
||||||
if(newValue != null) {
|
selectedCrop = newValue;
|
||||||
selectedPlant = newValue;
|
try {
|
||||||
//ToDo update day<x>_panel with task for the day
|
loadTaskList();
|
||||||
} else {
|
} catch (IOException e) {
|
||||||
selectedPlant = null;
|
e.printStackTrace();
|
||||||
//ToDo update day<x>_panel with task for the day (all plants)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -102,23 +131,52 @@ public class MyScheduleController implements Initializable {
|
|||||||
day7_label.setText(today.plusDays(6).getDayOfWeek().toString());
|
day7_label.setText(today.plusDays(6).getDayOfWeek().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillListViewMyPlantsInSchedule(List<Plant> list) {
|
private void setCellFactoryListView() {
|
||||||
for (Plant plant : list) {
|
scheduledPlants_listview.setCellFactory(param -> new ListCell<Crop>() {
|
||||||
scheduledPlants_listview.getItems().add(plant);
|
|
||||||
}
|
|
||||||
scheduledPlants_listview.setCellFactory(param -> new ListCell<Plant>() {
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateItem(Plant plant, boolean empty) {
|
protected void updateItem(Crop crop, boolean empty) {
|
||||||
super.updateItem(plant, empty);
|
super.updateItem(crop, empty);
|
||||||
|
|
||||||
if (empty || plant == null || plant.name() == null) {
|
if (empty || crop == null) {
|
||||||
setText(null);
|
setText(null);
|
||||||
} else {
|
} else {
|
||||||
setText(plant.name());
|
try {
|
||||||
|
setText(plantListModel.getFilteredPlantListById(Config.getCurrentHardinessZone(), crop.getPlantId()).get(0).name());
|
||||||
|
} catch (HardinessZoneNotSetException | IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadTaskList() throws IOException {
|
||||||
|
List<List<Task>> taskLists = new LinkedList<>();
|
||||||
|
if (selectedCrop != null) {
|
||||||
|
taskLists = taskListModel.getTasksUpcomingWeekForCrop(selectedCrop.getCropId().get());
|
||||||
|
} else {
|
||||||
|
taskLists = taskListModel.getTasksUpcomingWeek();
|
||||||
|
}
|
||||||
|
if (!taskLists.isEmpty()) {
|
||||||
|
viewTaskListOfDay(day1_pane, taskLists.get(0));
|
||||||
|
viewTaskListOfDay(day2_pane, taskLists.get(1));
|
||||||
|
viewTaskListOfDay(day3_pane, taskLists.get(2));
|
||||||
|
viewTaskListOfDay(day4_pane, taskLists.get(3));
|
||||||
|
viewTaskListOfDay(day5_pane, taskLists.get(4));
|
||||||
|
viewTaskListOfDay(day6_pane, taskLists.get(5));
|
||||||
|
viewTaskListOfDay(day7_pane, taskLists.get(6));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void viewTaskListOfDay(Pane pane, List<Task> tasks) {
|
||||||
|
//ToDo update pane with task list
|
||||||
|
VBox vBox = new VBox();
|
||||||
|
for (Task task : tasks) {
|
||||||
|
Label label = new Label(task.getDescription());
|
||||||
|
vBox.getChildren().add(label);
|
||||||
|
}
|
||||||
|
pane.getChildren().add(vBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,35 +5,38 @@ import ch.zhaw.gartenverwaltung.plantList.PlantListModel;
|
|||||||
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 ch.zhaw.gartenverwaltung.types.Seasons;
|
import ch.zhaw.gartenverwaltung.types.Seasons;
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.property.ListProperty;
|
import javafx.beans.property.ListProperty;
|
||||||
import javafx.beans.property.SimpleListProperty;
|
import javafx.beans.property.SimpleListProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Bounds;
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.input.KeyEvent;
|
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class PlantsController implements Initializable {
|
public class PlantsController implements Initializable {
|
||||||
|
private static final Logger LOG = Logger.getLogger(PlantsController.class.getName());
|
||||||
private final PlantListModel plantListModel = new PlantListModel();
|
private final PlantListModel plantListModel = new PlantListModel();
|
||||||
private Plant selectedPlant = null;
|
private Plant selectedPlant = null;
|
||||||
private final HardinessZone DEFAULT_HARDINESS_ZONE = HardinessZone.ZONE_8A;
|
private final HardinessZone DEFAULT_HARDINESS_ZONE = HardinessZone.ZONE_8A;
|
||||||
|
|
||||||
// TODO: move to model
|
// TODO: move to model
|
||||||
|
|
||||||
private final ListProperty<Plant> plantListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
|
private final ListProperty<Plant> plantListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -52,18 +55,27 @@ public class PlantsController implements Initializable {
|
|||||||
private ListView<Plant> list_plants;
|
private ListView<Plant> list_plants;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button saveToMyPlant_button;
|
private Button selectSowDay_button;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField search_plants;
|
private TextField search_plants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* saves the current selected plant in new JSON database
|
* open new window to select sow or harvest day to save the crop
|
||||||
* @param event event
|
* @param event event
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
void saveToMyPlant(ActionEvent event) {
|
void selectSowDate(ActionEvent event) throws IOException {
|
||||||
//ToDo model save selectedPlant to mySelectedPlant(IO)
|
Parent root;
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader(Objects.requireNonNull(getClass().getResource("SelectSowDay.fxml")));
|
||||||
|
root = fxmlLoader.load();
|
||||||
|
SelectSowDayController controller = fxmlLoader.getController();
|
||||||
|
controller.getSelectedPlant(selectedPlant);
|
||||||
|
Stage stage = new Stage();
|
||||||
|
stage.setScene(new Scene(root));
|
||||||
|
stage.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
stage.setResizable(false);
|
||||||
|
stage.showAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,15 +92,17 @@ public class PlantsController implements Initializable {
|
|||||||
list_plants.itemsProperty().bind(plantListProperty);
|
list_plants.itemsProperty().bind(plantListProperty);
|
||||||
|
|
||||||
description_plant.setText("");
|
description_plant.setText("");
|
||||||
saveToMyPlant_button.setDisable(true);
|
selectSowDay_button.setDisable(true);
|
||||||
|
|
||||||
createFilterSeasons();
|
createFilterSeasons();
|
||||||
createFilterHardinessZone();
|
createFilterHardinessZone();
|
||||||
lookForSelectedListEntry();
|
lookForSelectedListEntry();
|
||||||
try {
|
try {
|
||||||
viewFilteredListBySearch();
|
viewFilteredListBySearch();
|
||||||
} catch (HardinessZoneNotSetException | IOException e) {
|
} catch (HardinessZoneNotSetException e) {
|
||||||
e.printStackTrace();
|
LOG.log(Level.WARNING, "Hardiness Zone not set!");
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.WARNING, "Could not retrieve data!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +110,7 @@ public class PlantsController implements Initializable {
|
|||||||
* set text of list view to plant name
|
* set text of list view to plant name
|
||||||
*/
|
*/
|
||||||
private void setListCellFactory() {
|
private void setListCellFactory() {
|
||||||
list_plants.setCellFactory(param -> new ListCell<Plant>() {
|
list_plants.setCellFactory(param -> new ListCell<>() {
|
||||||
@Override
|
@Override
|
||||||
protected void updateItem(Plant plant, boolean empty) {
|
protected void updateItem(Plant plant, boolean empty) {
|
||||||
super.updateItem(plant, empty);
|
super.updateItem(plant, empty);
|
||||||
@@ -132,13 +146,15 @@ public class PlantsController implements Initializable {
|
|||||||
search_plants.textProperty().addListener((observable, oldValue, newValue) -> {
|
search_plants.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (newValue.isEmpty()) {
|
if (newValue.isEmpty()) {
|
||||||
fillPlantListWithHardinessZone();
|
fillPlantListWithHardinessZone();
|
||||||
}else {
|
} else {
|
||||||
try {
|
try {
|
||||||
List<Plant> filteredPlants = plantListModel.getFilteredPlantListByString(DEFAULT_HARDINESS_ZONE, newValue);
|
List<Plant> filteredPlants = plantListModel.getFilteredPlantListByString(DEFAULT_HARDINESS_ZONE, newValue);
|
||||||
clearListView();
|
clearListView();
|
||||||
plantListProperty.addAll(filteredPlants);
|
plantListProperty.addAll(filteredPlants);
|
||||||
} catch (HardinessZoneNotSetException | IOException e) {
|
} catch (HardinessZoneNotSetException e) {
|
||||||
e.printStackTrace();
|
LOG.log(Level.WARNING, "Hardiness Zone not set!");
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.WARNING, "Could not retrieve data!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -152,8 +168,10 @@ public class PlantsController implements Initializable {
|
|||||||
try {
|
try {
|
||||||
clearListView();
|
clearListView();
|
||||||
plantListProperty.addAll(plantListModel.getPlantList(plantListModel.getCurrentZone()));
|
plantListProperty.addAll(plantListModel.getPlantList(plantListModel.getCurrentZone()));
|
||||||
} catch (HardinessZoneNotSetException | IOException e) {
|
} catch (HardinessZoneNotSetException e) {
|
||||||
e.printStackTrace();
|
LOG.log(Level.WARNING, "Hardiness Zone not set!");
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.WARNING, "Could not retrieve data!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,12 +189,9 @@ public class PlantsController implements Initializable {
|
|||||||
if (zone.equals(DEFAULT_HARDINESS_ZONE)) {
|
if (zone.equals(DEFAULT_HARDINESS_ZONE)) {
|
||||||
radioButton.setSelected(true);
|
radioButton.setSelected(true);
|
||||||
}
|
}
|
||||||
radioButton.selectedProperty().addListener(new ChangeListener<Boolean>() {
|
radioButton.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
@Override
|
plantListModel.setCurrentZone(zone);
|
||||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
fillPlantListWithHardinessZone();
|
||||||
plantListModel.setCurrentZone(zone);
|
|
||||||
fillPlantListWithHardinessZone();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
climate_zones.getChildren().add(radioButton);
|
climate_zones.getChildren().add(radioButton);
|
||||||
}
|
}
|
||||||
@@ -190,26 +205,25 @@ public class PlantsController implements Initializable {
|
|||||||
private void createFilterSeasons() {
|
private void createFilterSeasons() {
|
||||||
ToggleGroup seasonGroup = new ToggleGroup();
|
ToggleGroup seasonGroup = new ToggleGroup();
|
||||||
for (Seasons season : Seasons.values()) {
|
for (Seasons season : Seasons.values()) {
|
||||||
RadioButton radioButton = new RadioButton(season.name());
|
RadioButton radioButton = new RadioButton(season.getName());
|
||||||
radioButton.setToggleGroup(seasonGroup);
|
radioButton.setToggleGroup(seasonGroup);
|
||||||
radioButton.setPadding(new Insets(0,0,10,0));
|
radioButton.setPadding(new Insets(0,0,10,0));
|
||||||
if (season.equals(Seasons.AllSEASONS)) {
|
if (season.equals(Seasons.AllSEASONS)) {
|
||||||
radioButton.setSelected(true);
|
radioButton.setSelected(true);
|
||||||
}
|
}
|
||||||
radioButton.selectedProperty().addListener(new ChangeListener<Boolean>() {
|
radioButton.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
@Override
|
if (season.equals(Seasons.AllSEASONS)) {
|
||||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
fillPlantListWithHardinessZone();
|
||||||
if (season.equals(Seasons.AllSEASONS)) {
|
} else {
|
||||||
fillPlantListWithHardinessZone();
|
try {
|
||||||
} else {
|
viewFilteredListBySeason(season);
|
||||||
try {
|
} catch (HardinessZoneNotSetException e) {
|
||||||
viewFilteredListBySeason(season);
|
LOG.log(Level.WARNING, "Hardiness Zone not set!");
|
||||||
} catch (HardinessZoneNotSetException | IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
LOG.log(Level.WARNING, "Could not retrieve data!", e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
seasons.getChildren().add(radioButton);
|
seasons.getChildren().add(radioButton);
|
||||||
}
|
}
|
||||||
@@ -221,29 +235,29 @@ public class PlantsController implements Initializable {
|
|||||||
* image of the plant
|
* image of the plant
|
||||||
*/
|
*/
|
||||||
private void lookForSelectedListEntry() {
|
private void lookForSelectedListEntry() {
|
||||||
list_plants.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Plant>() {
|
selectedPlant = null;
|
||||||
@Override
|
description_plant.setText("");
|
||||||
public void changed(ObservableValue<? extends Plant> observable, Plant oldValue, Plant newValue) {
|
selectSowDay_button.setDisable(true);
|
||||||
if(newValue != null) {
|
Image img = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
|
||||||
selectedPlant = newValue;
|
img_plant.setImage(img);
|
||||||
description_plant.setText(selectedPlant.description());
|
list_plants.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
saveToMyPlant_button.setDisable(false);
|
if(newValue != null) {
|
||||||
Image img;
|
selectedPlant = newValue;
|
||||||
if(selectedPlant.image() != null) {
|
description_plant.setText(selectedPlant.description());
|
||||||
img = selectedPlant.image();
|
selectSowDay_button.setDisable(false);
|
||||||
} else {
|
Image img1;
|
||||||
img = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
|
if(selectedPlant.image() != null) {
|
||||||
}
|
img1 = selectedPlant.image();
|
||||||
img_plant.setImage(img);
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
selectedPlant = null;
|
img1 = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
|
||||||
description_plant.setText("");
|
|
||||||
saveToMyPlant_button.setDisable(true);
|
|
||||||
Image img = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
|
|
||||||
img_plant.setImage(img);
|
|
||||||
}
|
}
|
||||||
|
img_plant.setImage(img1);
|
||||||
|
} else {
|
||||||
|
selectedPlant = null;
|
||||||
|
description_plant.setText("");
|
||||||
|
selectSowDay_button.setDisable(true);
|
||||||
|
Image img1 = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png")));
|
||||||
|
img_plant.setImage(img1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package ch.zhaw.gartenverwaltung;
|
||||||
|
|
||||||
|
import ch.zhaw.gartenverwaltung.gardenplan.Gardenplanmodel;
|
||||||
|
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
|
||||||
|
import ch.zhaw.gartenverwaltung.taskList.PlantNotFoundException;
|
||||||
|
import ch.zhaw.gartenverwaltung.taskList.TaskListModel;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.GrowthPhaseType;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Plant;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class SelectSowDayController implements Initializable {
|
||||||
|
private Plant selectedPlant = null;
|
||||||
|
private final TaskListModel taskListModel = new TaskListModel();
|
||||||
|
private final Gardenplanmodel gardenplanmodel = new Gardenplanmodel(taskListModel);
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private DatePicker datepicker;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label popup_label;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button save_button;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private RadioButton sow_radio;
|
||||||
|
|
||||||
|
public SelectSowDayController() throws IOException {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close the date selector window
|
||||||
|
* @param event event
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
void cancel(ActionEvent event) {
|
||||||
|
closeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get sow date from datePicker or calculate sow date from harvest date
|
||||||
|
* save selected plant and sow date
|
||||||
|
* @param event event
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
void save(ActionEvent event) throws HardinessZoneNotSetException, IOException, PlantNotFoundException {
|
||||||
|
LocalDate sowDate;
|
||||||
|
if (sow_radio.isSelected()) {
|
||||||
|
sowDate = datepicker.getValue();
|
||||||
|
} else {
|
||||||
|
//ToDo method to get current lifecycle group in plant
|
||||||
|
sowDate = selectedPlant.sowDateFromHarvestDate(datepicker.getValue(), 0);
|
||||||
|
}
|
||||||
|
gardenplanmodel.plantAsCrop(selectedPlant, sowDate);
|
||||||
|
closeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save the plant which will be planted and update label
|
||||||
|
* @param plant Plant
|
||||||
|
*/
|
||||||
|
public void getSelectedPlant(Plant plant) {
|
||||||
|
selectedPlant = plant;
|
||||||
|
popup_label.setText("Select Harvest/Sow Date for" + selectedPlant.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add listener and set default values
|
||||||
|
* {@inheritDoc}
|
||||||
|
* @param location location
|
||||||
|
* @param resources resources
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
clearDatePickerEntries();
|
||||||
|
|
||||||
|
Callback<DatePicker, DateCell> dayCellFactory= getDayCellFactory();
|
||||||
|
datepicker.setDayCellFactory(dayCellFactory);
|
||||||
|
datepicker.getEditor().setEditable(false);
|
||||||
|
|
||||||
|
enableDisableSaveButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear date picker editor when radio button is changed
|
||||||
|
*/
|
||||||
|
private void clearDatePickerEntries() {
|
||||||
|
sow_radio.selectedProperty().addListener(new ChangeListener<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean isNowSelected) {
|
||||||
|
datepicker.getEditor().clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* date picker disable/enable dates according to selected plant: sow or harvest day
|
||||||
|
* @return cellFactory of datePicker
|
||||||
|
*/
|
||||||
|
private Callback<DatePicker, DateCell> getDayCellFactory() {
|
||||||
|
|
||||||
|
final Callback<DatePicker, DateCell> dayCellFactory = new Callback<DatePicker, DateCell>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DateCell call(final DatePicker datePicker) {
|
||||||
|
return new DateCell() {
|
||||||
|
@Override
|
||||||
|
public void updateItem(LocalDate item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
setDisable(true);
|
||||||
|
setStyle("-fx-background-color: #ffc0cb;");
|
||||||
|
List<LocalDate> dates;
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
if (sow_radio.isSelected()) {
|
||||||
|
dates = selectedPlant.getDateListOfGrowthPhase(GrowthPhaseType.SOW);
|
||||||
|
} else {
|
||||||
|
dates = selectedPlant.getDateListOfGrowthPhase(GrowthPhaseType.HARVEST);
|
||||||
|
}
|
||||||
|
for (LocalDate date : dates) {
|
||||||
|
if (item.getMonth() == date.getMonth()
|
||||||
|
&& item.getDayOfMonth() == date.getDayOfMonth()
|
||||||
|
&& item.compareTo(today) > 0) {
|
||||||
|
setDisable(false);
|
||||||
|
setStyle("-fx-background-color: #32CD32;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((!sow_radio.isSelected() && selectedPlant.sowDateFromHarvestDate(item, 0).compareTo(today) < 0)) {
|
||||||
|
setDisable(true);
|
||||||
|
setStyle("-fx-background-color: #ffc0cb;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return dayCellFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close date picker window
|
||||||
|
*/
|
||||||
|
private void closeWindow() {
|
||||||
|
Stage stage = (Stage) save_button.getScene().getWindow();
|
||||||
|
stage.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* disable save button, when there is no date selected in date picker
|
||||||
|
*/
|
||||||
|
private void enableDisableSaveButton() {
|
||||||
|
save_button.setDisable(true);
|
||||||
|
datepicker.getEditor().textProperty().addListener(new ChangeListener<String>() {
|
||||||
|
@Override
|
||||||
|
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
|
||||||
|
if (newValue == null || newValue.equals("")) {
|
||||||
|
save_button.setDisable(true);
|
||||||
|
} else {
|
||||||
|
save_button.setDisable(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
@@ -131,7 +145,6 @@ public class JsonGardenPlan implements GardenPlan {
|
|||||||
.registerModule(new Jdk8Module())
|
.registerModule(new Jdk8Module())
|
||||||
.writeValue(new File(dataSource.toURI()), cropMap.values());
|
.writeValue(new File(dataSource.toURI()), cropMap.values());
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
// TODO: Log
|
|
||||||
throw new IOException(INVALID_DATASOURCE_MSG, e);
|
throw new IOException(INVALID_DATASOURCE_MSG, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,15 +89,16 @@ public class JsonPlantDatabase implements PlantDatabase {
|
|||||||
List<Plant> result;
|
List<Plant> result;
|
||||||
result = mapper.readerForListOf(Plant.class).readValue(dataSource);
|
result = mapper.readerForListOf(Plant.class).readValue(dataSource);
|
||||||
|
|
||||||
for (Plant plant : result) {
|
|
||||||
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) -> {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,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;
|
||||||
String token = parser.getText();
|
String token = parser.getText();
|
||||||
try {
|
try {
|
||||||
result = GrowthPhaseType.valueOf(token.toUpperCase());
|
result = GrowthPhaseType.valueOf(token.toUpperCase());
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
// TODO: Log
|
throw new IOException(String.format("Bad growth phase type \"%s\"\n", token));
|
||||||
System.err.printf("Bad growth phase type \"%s\"\n", token);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,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;
|
||||||
String token = parser.getText();
|
String token = parser.getText();
|
||||||
try {
|
try {
|
||||||
result = HardinessZone.valueOf(token.toUpperCase());
|
result = HardinessZone.valueOf(token.toUpperCase());
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
// TODO: Log
|
throw new IOException(String.format("Unknown Hardiness Zone \"%s\"\n", token), e);
|
||||||
System.err.printf("Unknown Hardiness Zone \"%s\"\n", token);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ public class PlantImageDeserializer extends JsonDeserializer<Image> {
|
|||||||
try (InputStream is = new FileInputStream(new File(imageUrl.toURI()))) {
|
try (InputStream is = new FileInputStream(new File(imageUrl.toURI()))) {
|
||||||
result = new Image(is);
|
result = new Image(is);
|
||||||
} catch (IllegalArgumentException | URISyntaxException e) {
|
} catch (IllegalArgumentException | URISyntaxException e) {
|
||||||
// TODO: Log
|
throw new IOException(String.format("Cannot find Image \"%s\"\n", imageUrl.getFile()));
|
||||||
e.printStackTrace();
|
|
||||||
System.err.printf("Cannot find Image \"%s\"\n", imageUrl.getFile());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -116,16 +116,17 @@ public class PlantListModel {
|
|||||||
if (searchString.length() == 0) {
|
if (searchString.length() == 0) {
|
||||||
return getPlantList(zone);
|
return getPlantList(zone);
|
||||||
} else if (searchString.charAt(0) == '#') {
|
} else if (searchString.charAt(0) == '#') {
|
||||||
try {
|
if (isPositiveIntegral(searchString.substring(1))) {
|
||||||
return getFilteredPlantListById(zone, Long.parseLong(searchString.substring(1)));
|
Long searchId = Long.parseLong(searchString.substring(1));
|
||||||
} catch (NumberFormatException e) {
|
return getFilteredPlantListById(zone, searchId);
|
||||||
|
} else {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String caseInsensitiveSearchString = searchString.toLowerCase();
|
String caseInsensitiveSearchString = searchString.toLowerCase();
|
||||||
return getFilteredPlantList(zone, plant ->
|
return getFilteredPlantList(zone, plant ->
|
||||||
plant.name().toLowerCase().contains(caseInsensitiveSearchString) ||
|
plant.name().toLowerCase().contains(caseInsensitiveSearchString) ||
|
||||||
plant.description().toLowerCase().contains(caseInsensitiveSearchString)
|
plant.description().toLowerCase().contains(caseInsensitiveSearchString)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,15 +170,24 @@ public class PlantListModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param zone selected hardiness zone
|
* @param zone selected hardiness zone
|
||||||
* @param from the earliest date to for the filter
|
* @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
|
* @return List of Plants with selected saison
|
||||||
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
|
* @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> getFilteredPlantListBySaisonWithoutGrowthPhase(HardinessZone zone, MonthDay from, MonthDay to) throws HardinessZoneNotSetException, IOException {
|
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)));
|
return getFilteredPlantList(zone, plant -> plant.lifecycle().stream().anyMatch(growthPhase -> growthPhase.startDate().compareTo(from) >= 0 && (growthPhase.startDate().compareTo(to) <= 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string can safely be parsed as a positive Integral value (short/int/long)
|
||||||
|
*
|
||||||
|
* @param subject The string to be tested
|
||||||
|
* @return Whether the string contains only digits
|
||||||
|
*/
|
||||||
|
private boolean isPositiveIntegral(String subject) {
|
||||||
|
return subject != null && subject.matches("[0-9]+");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
package ch.zhaw.gartenverwaltung.taskList;
|
package ch.zhaw.gartenverwaltung.taskList;
|
||||||
|
|
||||||
import ch.zhaw.gartenverwaltung.Config;
|
import ch.zhaw.gartenverwaltung.Config;
|
||||||
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
|
import ch.zhaw.gartenverwaltung.io.*;
|
||||||
import ch.zhaw.gartenverwaltung.io.JsonTaskDatabase;
|
|
||||||
import ch.zhaw.gartenverwaltung.io.PlantDatabase;
|
|
||||||
import ch.zhaw.gartenverwaltung.io.TaskDatabase;
|
|
||||||
import ch.zhaw.gartenverwaltung.types.*;
|
import ch.zhaw.gartenverwaltung.types.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
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.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class TaskListModel {
|
public class TaskListModel {
|
||||||
@@ -24,6 +23,7 @@ public class TaskListModel {
|
|||||||
|
|
||||||
public TaskListModel(){
|
public TaskListModel(){
|
||||||
taskDatabase = new JsonTaskDatabase();
|
taskDatabase = new JsonTaskDatabase();
|
||||||
|
plantDatabase = new JsonPlantDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,6 +77,10 @@ public class TaskListModel {
|
|||||||
taskDatabase.removeTask(task);
|
taskDatabase.removeTask(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Task> filterListByCrop(List<Task> taskList, Long cropId) {
|
||||||
|
return taskList.stream().filter(task -> task.getCropId() == cropId).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to get all Tasks
|
* Method to get all Tasks
|
||||||
* @return List of all Tasks
|
* @return List of all Tasks
|
||||||
@@ -86,6 +90,15 @@ public class TaskListModel {
|
|||||||
return getFilteredTaskList(LocalDate.MIN, LocalDate.MAX);
|
return getFilteredTaskList(LocalDate.MIN, LocalDate.MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to get all Tasks for specific Crop
|
||||||
|
* @return List of all Tasks for with given CropID
|
||||||
|
* @throws IOException If the database cannot be accessed
|
||||||
|
*/
|
||||||
|
public List<Task> getTaskListForCrop(Long cropId) throws IOException {
|
||||||
|
return filterListByCrop(getTaskList(), cropId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to get all Tasks which are today or in future
|
* Method to get all Tasks which are today or in future
|
||||||
* @return List of Tasks
|
* @return List of Tasks
|
||||||
@@ -95,6 +108,15 @@ public class TaskListModel {
|
|||||||
return getFilteredTaskList(LocalDate.now(), LocalDate.MAX);
|
return getFilteredTaskList(LocalDate.now(), LocalDate.MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to get all Tasks which are today or in future for specific Crop
|
||||||
|
* @return List of Tasks with given crop ID
|
||||||
|
* @throws IOException If the database cannot be accessed
|
||||||
|
*/
|
||||||
|
public List<Task> getFutureTasksForCrop(Long cropId) throws IOException {
|
||||||
|
return filterListByCrop(getFutureTasks(), cropId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to get all Tasks which are today or in past
|
* Method to get all Tasks which are today or in past
|
||||||
* @return List of Tasks
|
* @return List of Tasks
|
||||||
@@ -105,17 +127,40 @@ public class TaskListModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to get an Array of 7 Tasklists for the next 7 days. Index 0 is Tasklist for Today.
|
* Method to get all Tasks which are today or in past for specifc crop
|
||||||
* @return Array with length 7 (List<Task>[])
|
* @return List of Tasks with given grop id
|
||||||
* @throws IOException If the database cannot be accessed
|
* @throws IOException If the database cannot be accessed
|
||||||
*/
|
*/
|
||||||
public List<Task>[] getTasksUpcomingWeek() throws IOException {
|
public List<Task> getPastTasksForCrop(Long cropId) throws IOException {
|
||||||
List<Task>[] listArray = new List[7];
|
return filterListByCrop(getPastTasks(), cropId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to get an List of 7 Tasklists for the next 7 days. Index 0 is Tasklist for Today.
|
||||||
|
* @return List with length 7 (List<List<Task>>)
|
||||||
|
* @throws IOException If the database cannot be accessed
|
||||||
|
*/
|
||||||
|
public List<List<Task>> getTasksUpcomingWeek() throws IOException {
|
||||||
|
List<List<Task>> dayTaskList = new ArrayList<>();
|
||||||
for(int i = 0; i < 7; i++) {
|
for(int i = 0; i < 7; i++) {
|
||||||
LocalDate date = LocalDate.now().plusDays(i);
|
LocalDate date = LocalDate.now().plusDays(i);
|
||||||
listArray[i] = taskDatabase.getTaskList(date, date);
|
dayTaskList.add(taskDatabase.getTaskList(date, date));
|
||||||
}
|
}
|
||||||
return listArray;
|
return dayTaskList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to get an List of 7 Tasklists for the next 7 days. (Filtered Index 0 is Tasklist for Today.
|
||||||
|
* @return List with length 7 (List<List<Task>>)
|
||||||
|
* @throws IOException If the database cannot be accessed
|
||||||
|
*/
|
||||||
|
public List<List<Task>> getTasksUpcomingWeekForCrop(Long cropId) throws IOException {
|
||||||
|
List<List<Task>> dayTaskList = new ArrayList<>();
|
||||||
|
for(int i = 0; i < 7; i++) {
|
||||||
|
LocalDate date = LocalDate.now().plusDays(i);
|
||||||
|
dayTaskList.add(filterListByCrop(taskDatabase.getTaskList(date, date), cropId));
|
||||||
|
}
|
||||||
|
return dayTaskList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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 ]",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ch.zhaw.gartenverwaltung.types;
|
|||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -19,20 +20,40 @@ public record Plant(
|
|||||||
List<Pest> pests,
|
List<Pest> pests,
|
||||||
List<GrowthPhase> lifecycle) {
|
List<GrowthPhase> lifecycle) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove all growthPhase which do not belong to the hardiness zone
|
||||||
|
* @param zone hardiness zone
|
||||||
|
*/
|
||||||
public void inZone(HardinessZone zone) {
|
public void inZone(HardinessZone zone) {
|
||||||
lifecycle.removeIf(growthPhase -> !growthPhase.zone().equals(zone));
|
lifecycle.removeIf(growthPhase -> !growthPhase.zone().equals(zone));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get all growthPhases of lifecycle group
|
||||||
|
* @param group lifecycle group
|
||||||
|
* @return list of growthPhases
|
||||||
|
*/
|
||||||
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)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get sow date from given harvest day from lifecycle group
|
||||||
|
* @param harvestDate date of the harvest
|
||||||
|
* @param group lifecycle group
|
||||||
|
* @return LocaleDate of sow date
|
||||||
|
*/
|
||||||
public LocalDate sowDateFromHarvestDate(LocalDate harvestDate, int group) {
|
public LocalDate sowDateFromHarvestDate(LocalDate harvestDate, int group) {
|
||||||
return harvestDate.minusDays(timeToHarvest(group));
|
return harvestDate.minusDays(timeToHarvest(group));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calculate the days between sow and harvest day for lifecycle group
|
||||||
|
* @param group the lifecycle group
|
||||||
|
* @return Integer number of dates between sow and harvest day
|
||||||
|
*/
|
||||||
public int timeToHarvest(int group) {
|
public int timeToHarvest(int group) {
|
||||||
List<GrowthPhase> activeLifecycle = lifecycleForGroup(group);
|
List<GrowthPhase> activeLifecycle = lifecycleForGroup(group);
|
||||||
GrowthPhase sow = activeLifecycle.stream()
|
GrowthPhase sow = activeLifecycle.stream()
|
||||||
@@ -47,4 +68,38 @@ public record Plant(
|
|||||||
int currentYear = LocalDate.now().getYear();
|
int currentYear = LocalDate.now().getYear();
|
||||||
return (int) DAYS.between(harvest.startDate().atYear(currentYear), sow.startDate().atYear(currentYear));
|
return (int) DAYS.between(harvest.startDate().atYear(currentYear), sow.startDate().atYear(currentYear));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* filter out the given growthPhase out of the lifecycle
|
||||||
|
* create list of dates for this growthPhase and return it
|
||||||
|
* @param growthPhase the wanted growthPhase
|
||||||
|
* @return a list of dates of the current year
|
||||||
|
*/
|
||||||
|
public List<LocalDate> getDateListOfGrowthPhase(GrowthPhaseType growthPhase) {
|
||||||
|
List<LocalDate> dates = new LinkedList<>();
|
||||||
|
for (GrowthPhase growth : lifecycle) {
|
||||||
|
if (growth.type().equals(growthPhase)) {
|
||||||
|
dates = addDatesFromMonthDay(growth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* transform monthDay value of the given growthPhase to localDate
|
||||||
|
* return a list of dates from start to end of growth phase
|
||||||
|
* @param growthPhase the current growthPhase
|
||||||
|
* @return a list of dates of the current year
|
||||||
|
*/
|
||||||
|
private List<LocalDate> addDatesFromMonthDay(GrowthPhase growthPhase) {
|
||||||
|
List<LocalDate> dates = new LinkedList<>();
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
LocalDate start = growthPhase.startDate().atYear(today.getYear());
|
||||||
|
LocalDate end = growthPhase.endDate().atYear(today.getYear());
|
||||||
|
while (!start.isAfter(end)) {
|
||||||
|
dates.add(start);
|
||||||
|
start = start.plusDays(1);
|
||||||
|
}
|
||||||
|
return dates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ package ch.zhaw.gartenverwaltung.types;
|
|||||||
import java.time.MonthDay;
|
import java.time.MonthDay;
|
||||||
|
|
||||||
public enum Seasons {
|
public enum Seasons {
|
||||||
AllSEASONS("--01-01", "--12-31"),
|
AllSEASONS("--01-01", "--12-31", "All Seasons"),
|
||||||
SPRING("--03-01", "--05-30"),
|
SPRING("--03-01", "--05-30", "Spring"),
|
||||||
SOMMER("--06-01", "--08-30"),
|
SUMMER("--06-01", "--08-30", "Summer"),
|
||||||
AUTUM("--09-01", "--11-30"),
|
AUTUMN("--09-01", "--11-30", "Autumn"),
|
||||||
WINTER("--12-01", "--02-28");
|
WINTER("--12-01", "--02-28", "Winter");
|
||||||
|
|
||||||
public final String startDate;
|
public final String startDate;
|
||||||
public final String endDate;
|
public final String endDate;
|
||||||
|
public final String name;
|
||||||
|
|
||||||
Seasons(String startDate, String endDate) {
|
Seasons(String startDate, String endDate, String name) {
|
||||||
this.startDate = startDate;
|
this.startDate = startDate;
|
||||||
this.endDate = endDate;
|
this.endDate = endDate;
|
||||||
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MonthDay getStartDate() {
|
public MonthDay getStartDate() {
|
||||||
@@ -23,4 +25,7 @@ public enum Seasons {
|
|||||||
public MonthDay getEndDate() {
|
public MonthDay getEndDate() {
|
||||||
return MonthDay.parse(this.endDate);
|
return MonthDay.parse(this.endDate);
|
||||||
}
|
}
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ public class Task {
|
|||||||
name= "";
|
name= "";
|
||||||
description= "";
|
description= "";
|
||||||
startDate = LocalDate.now();
|
startDate = LocalDate.now();
|
||||||
// this.cropId = cropId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task(String name, String description, LocalDate startDate, long cropId) {
|
public Task(String name, String description, LocalDate startDate, long cropId) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ module ch.zhaw.gartenverwaltung {
|
|||||||
requires com.fasterxml.jackson.databind;
|
requires com.fasterxml.jackson.databind;
|
||||||
requires com.fasterxml.jackson.datatype.jsr310;
|
requires com.fasterxml.jackson.datatype.jsr310;
|
||||||
requires com.fasterxml.jackson.datatype.jdk8;
|
requires com.fasterxml.jackson.datatype.jdk8;
|
||||||
|
requires java.logging;
|
||||||
|
|
||||||
opens ch.zhaw.gartenverwaltung to javafx.fxml;
|
opens ch.zhaw.gartenverwaltung to javafx.fxml;
|
||||||
opens ch.zhaw.gartenverwaltung.types to com.fasterxml.jackson.databind;
|
opens ch.zhaw.gartenverwaltung.types to com.fasterxml.jackson.databind;
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.ScrollPane?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
|
|
||||||
|
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="785.0" prefWidth="899.0"
|
||||||
|
xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.CropDetailController">
|
||||||
|
<children>
|
||||||
|
<ScrollPane fitToWidth="true" prefHeight="759.0" prefWidth="664.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||||
|
<content>
|
||||||
|
<VBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="503.0" prefWidth="897.0">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<Button mnemonicParsing="false" onAction="#goBack" prefHeight="25.0" prefWidth="91.0" text="Go Back">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</Button>
|
||||||
|
<Label fx:id="cropName_label" text="Label">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</Label>
|
||||||
|
<HBox prefHeight="265.0" prefWidth="879.0">
|
||||||
|
<children>
|
||||||
|
<GridPane maxWidth="1.7976931348623157E308" prefHeight="296.0" prefWidth="577.0" HBox.hgrow="ALWAYS">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" maxWidth="284.0" minWidth="10.0" prefWidth="97.33334350585938" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="488.99999237060547" minWidth="10.0" prefWidth="481.9999898274739" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints maxHeight="149.66665903727215" minHeight="10.0" prefHeight="149.66665903727215" valignment="TOP" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="187.9999647140503" minHeight="10.0" prefHeight="51.00000762939453" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="105.66662597656247" minHeight="10.0" prefHeight="54.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints maxHeight="105.66662597656247" minHeight="10.0" prefHeight="46.66666666666666" vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<Label prefHeight="17.0" prefWidth="65.0" text="Description:">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets top="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Label>
|
||||||
|
<Label text="Light-Level:" GridPane.rowIndex="1" />
|
||||||
|
<Label text="Spacing:" GridPane.rowIndex="2" />
|
||||||
|
<Label text="Soil-Type:" GridPane.rowIndex="3" />
|
||||||
|
<Label fx:id="description_label" text="Label" wrapText="true" GridPane.columnIndex="1">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="10.0" top="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="light_label" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="spacing_label" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="2">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="soil_label" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="3">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Label>
|
||||||
|
</children>
|
||||||
|
</GridPane>
|
||||||
|
<ImageView fx:id="imageView" fitHeight="300.0" fitWidth="300.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<Label text="Growth Phases:">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</Label>
|
||||||
|
<VBox fx:id="growthPhases_vbox" prefHeight="135.0" prefWidth="879.0">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</VBox>
|
||||||
|
<Button mnemonicParsing="false" onAction="#editTaskList" prefHeight="25.0" prefWidth="92.0" text="Edit Tasklist">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</Button>
|
||||||
|
<Label text="Pests:" />
|
||||||
|
<VBox fx:id="pests_vbox" prefHeight="200.0" prefWidth="100.0">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</VBox>
|
||||||
|
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" VBox.vgrow="NEVER">
|
||||||
|
<children>
|
||||||
|
<Label text="Area:">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets right="60.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="area_label" text="Label">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets right="10.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Label>
|
||||||
|
<Button fx:id="area_button" mnemonicParsing="false" onAction="#setArea" prefHeight="25.0" prefWidth="116.0" text="Add Area" />
|
||||||
|
</children>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</HBox>
|
||||||
|
<HBox alignment="CENTER_LEFT" layoutX="20.0" layoutY="719.0" prefHeight="100.0" prefWidth="200.0" VBox.vgrow="NEVER">
|
||||||
|
<children>
|
||||||
|
<Label text="Location:">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets right="40.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="location_label" text="Label">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets right="10.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Label>
|
||||||
|
<Button fx:id="location_button" mnemonicParsing="false" onAction="#setLocation" prefHeight="25.0" prefWidth="115.0" text="Add Location" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
</content>
|
||||||
|
</ScrollPane>
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
||||||
@@ -7,9 +7,7 @@
|
|||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?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">
|
||||||
<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>
|
<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">
|
<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>
|
<children>
|
||||||
@@ -21,7 +19,7 @@
|
|||||||
<Insets bottom="10.0" />
|
<Insets bottom="10.0" />
|
||||||
</VBox.margin>
|
</VBox.margin>
|
||||||
</Label>
|
</Label>
|
||||||
<VBox fx:id="myPlants_vbox" prefHeight="200.0" prefWidth="100.0" VBox.vgrow="ALWAYS" />
|
<VBox fx:id="myPlants_vbox" maxWidth="1.7976931348623157E308" 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">
|
<Button fx:id="addPlant_button" mnemonicParsing="false" onAction="#addPlant" prefHeight="45.0" prefWidth="155.0" text="Add new Plant">
|
||||||
<VBox.margin>
|
<VBox.margin>
|
||||||
<Insets top="10.0" />
|
<Insets top="10.0" />
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.geometry.Rectangle2D?>
|
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.ListView?>
|
<?import javafx.scene.control.ListView?>
|
||||||
<?import javafx.scene.control.SplitPane?>
|
<?import javafx.scene.control.SplitPane?>
|
||||||
<?import javafx.scene.control.TextField?>
|
<?import javafx.scene.control.TextField?>
|
||||||
<?import javafx.scene.control.TitledPane?>
|
<?import javafx.scene.control.TitledPane?>
|
||||||
<?import javafx.scene.image.Image?>
|
|
||||||
<?import javafx.scene.image.ImageView?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
@@ -33,15 +31,8 @@
|
|||||||
<TextField fx:id="search_plants" 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">
|
<HBox alignment="CENTER_LEFT" prefHeight="480.0" prefWidth="881.0" VBox.vgrow="ALWAYS">
|
||||||
<children>
|
<children>
|
||||||
<ListView fx:id="list_plants" maxWidth="1.7976931348623157E308" prefHeight="497.0" prefWidth="580.0" HBox.hgrow="ALWAYS" />
|
<ListView fx:id="list_plants" maxWidth="1.7976931348623157E308" prefHeight="497.0" prefWidth="400.0" HBox.hgrow="ALWAYS" />
|
||||||
<ImageView fx:id="img_plant" fitHeight="322.0" fitWidth="861.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER">
|
<ImageView fx:id="img_plant" fitWidth="300" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER" />
|
||||||
<viewport>
|
|
||||||
<Rectangle2D height="300.0" width="300.0" />
|
|
||||||
</viewport>
|
|
||||||
<image>
|
|
||||||
<Image url="@placeholder.png" />
|
|
||||||
</image>
|
|
||||||
</ImageView>
|
|
||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
<Label prefHeight="33.0" prefWidth="919.0" text="Plant Information:">
|
<Label prefHeight="33.0" prefWidth="919.0" text="Plant Information:">
|
||||||
@@ -57,7 +48,7 @@
|
|||||||
<Insets bottom="10.0" top="10.0" />
|
<Insets bottom="10.0" top="10.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</Label>
|
</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" />
|
<Button fx:id="selectSowDay_button" alignment="CENTER" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#selectSowDate" prefHeight="38.0" prefWidth="917.0" text="Select Harvest/Sow Day" />
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
</children>
|
</children>
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.DatePicker?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.RadioButton?>
|
||||||
|
<?import javafx.scene.control.ToggleGroup?>
|
||||||
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
|
|
||||||
|
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="150.0"
|
||||||
|
prefWidth="308.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.SelectSowDayController">
|
||||||
|
<children>
|
||||||
|
<VBox maxWidth="1.7976931348623157E308" prefHeight="408.0" prefWidth="640.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||||
|
<children>
|
||||||
|
<Label fx:id="popup_label" text="Label" />
|
||||||
|
<HBox alignment="CENTER" prefHeight="293.0" prefWidth="631.0">
|
||||||
|
<children>
|
||||||
|
<VBox alignment="CENTER_LEFT" prefHeight="88.0" prefWidth="155.0">
|
||||||
|
<children>
|
||||||
|
<RadioButton fx:id="sow_radio" mnemonicParsing="false" selected="true" text="Sow">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
<toggleGroup>
|
||||||
|
<ToggleGroup fx:id="group" />
|
||||||
|
</toggleGroup>
|
||||||
|
</RadioButton>
|
||||||
|
<RadioButton fx:id="harvest_radio" mnemonicParsing="false" text="Harvest" toggleGroup="$group" />
|
||||||
|
</children>
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets top="10.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</VBox>
|
||||||
|
<DatePicker fx:id="datepicker" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<HBox fillHeight="false" prefHeight="54.0" prefWidth="631.0" VBox.vgrow="NEVER">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="save_button" mnemonicParsing="false" onAction="#save" prefHeight="25.0" prefWidth="53.0" text="Save">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets right="10.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="cancel_button" mnemonicParsing="false" onAction="#cancel" text="Cancel" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"name": "Germinate",
|
"name": "Germinate",
|
||||||
"relativeStartDate": -14,
|
"relativeStartDate": -14,
|
||||||
"relativeEndDate": null,
|
"relativeEndDate": null,
|
||||||
"description": "\"Take an egg carton and fill it with soil. Put the seedling deep enaugh so its half covered with soil. Keep it in 10-15 * Celsius with lots of light.\"",
|
"description": "Take an egg carton and fill it with soil. Put the seedling deep enough so its half covered with soil. Keep it in 10-15 * Celsius with lots of light.",
|
||||||
"interval": null,
|
"interval": null,
|
||||||
"isOptional": false
|
"isOptional": false
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"name": "hilling",
|
"name": "hilling",
|
||||||
"relativeStartDate": 0,
|
"relativeStartDate": 0,
|
||||||
"relativeEndDate": null,
|
"relativeEndDate": null,
|
||||||
"description": "\"When the plants are 20 cm tall, begin hilling the potatoes by gently mounding the soil from the center of your rows around the stems of the plant. Mound up the soil around the plant until just the top few leaves show above the soil. Two weeks later, hill up the soil again when the plants grow another 20 cm.\"",
|
"description": "When the plants are 20 cm tall, begin hilling the potatoes by gently mounding the soil from the center of your rows around the stems of the plant. Mound up the soil around the plant until just the top few leaves show above the soil. Two weeks later, hill up the soil again when the plants grow another 20 cm.",
|
||||||
"interval": 21,
|
"interval": 21,
|
||||||
"isOptional": false
|
"isOptional": false
|
||||||
}
|
}
|
||||||
@@ -94,6 +94,7 @@
|
|||||||
"endDate": "03-10",
|
"endDate": "03-10",
|
||||||
"zone": "ZONE_8A",
|
"zone": "ZONE_8A",
|
||||||
"type": "SOW",
|
"type": "SOW",
|
||||||
|
"group": 0,
|
||||||
"wateringCycle": {
|
"wateringCycle": {
|
||||||
"litersPerSqM": 15,
|
"litersPerSqM": 15,
|
||||||
"interval": 3,
|
"interval": 3,
|
||||||
@@ -115,6 +116,7 @@
|
|||||||
"endDate": "05-10",
|
"endDate": "05-10",
|
||||||
"zone": "ZONE_8A",
|
"zone": "ZONE_8A",
|
||||||
"type": "PLANT",
|
"type": "PLANT",
|
||||||
|
"group": 0,
|
||||||
"wateringCycle": {
|
"wateringCycle": {
|
||||||
"litersPerSqM": 25,
|
"litersPerSqM": 25,
|
||||||
"interval": 3,
|
"interval": 3,
|
||||||
@@ -138,6 +140,7 @@
|
|||||||
"endDate": "05-20",
|
"endDate": "05-20",
|
||||||
"zone": "ZONE_8A",
|
"zone": "ZONE_8A",
|
||||||
"type": "HARVEST",
|
"type": "HARVEST",
|
||||||
|
"group": 0,
|
||||||
"wateringCycle": {
|
"wateringCycle": {
|
||||||
"litersPerSqM": 0,
|
"litersPerSqM": 0,
|
||||||
"interval": null,
|
"interval": null,
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package ch.zhaw.gartenverwaltung.io;
|
||||||
|
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Crop;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
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.format.DateTimeFormatter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class JsonGardenPlanTest {
|
||||||
|
private GardenPlan testDatabase;
|
||||||
|
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
|
||||||
|
void connectToDb() throws URISyntaxException, IOException {
|
||||||
|
assertNotNull(testFile);
|
||||||
|
assertNotNull(dbDataSource);
|
||||||
|
Files.copy(Path.of(testFile.toURI()), Path.of(dbDataSource.toURI()), StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
testDatabase = new JsonGardenPlan(dbDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Check if results are retrieved completely.")
|
||||||
|
void getCropsNotEmpty() {
|
||||||
|
List<Crop> testList;
|
||||||
|
try {
|
||||||
|
testList = testDatabase.getCrops();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
Assertions.assertEquals(3, testList.size());
|
||||||
|
|
||||||
|
List<Long> plantIds = testList.stream().map(Crop::getPlantId).collect(Collectors.toList());
|
||||||
|
List<Long> expected = Arrays.asList(1L, 1L, 0L);
|
||||||
|
Assertions.assertEquals(expected, plantIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Check whether single access works.")
|
||||||
|
void getCropById() throws IOException {
|
||||||
|
Optional<Crop> testCrop = testDatabase.getCropById(1);
|
||||||
|
assertTrue(testCrop.isPresent());
|
||||||
|
Assertions.assertEquals(1, testCrop.get().getPlantId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Check for a nonexisting crop.")
|
||||||
|
void getCropByIdMustFail() throws IOException {
|
||||||
|
Optional<Crop> testCrop = testDatabase.getCropById(99);
|
||||||
|
Assertions.assertFalse(testCrop.isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Add new Crop.")
|
||||||
|
void addNewCrop() {
|
||||||
|
Crop crop = new Crop(3L, LocalDate.parse("2023-02-22", formatter));
|
||||||
|
try {
|
||||||
|
testDatabase.saveCrop(crop);
|
||||||
|
assertTrue(crop.getCropId().isPresent());
|
||||||
|
Optional<Crop> testCrop = testDatabase.getCropById(crop.getCropId().get());
|
||||||
|
|
||||||
|
assertTrue(testCrop.isPresent());
|
||||||
|
Assertions.assertEquals(crop, testCrop.get());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Remove crop")
|
||||||
|
void removeCrop(){
|
||||||
|
try {
|
||||||
|
Optional<Crop> crop = testDatabase.getCropById(2L);
|
||||||
|
Assertions.assertTrue(crop.isPresent());
|
||||||
|
testDatabase.removeCrop(crop.get());
|
||||||
|
crop = testDatabase.getCropById(2L);
|
||||||
|
Assertions.assertFalse(crop.isPresent());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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() {
|
||||||
@@ -26,7 +24,7 @@ public class JsonPlantDatabaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Check if results are retrieved completely")
|
@DisplayName("Check if results are retrieved completely")
|
||||||
void getPlantList() {
|
void getPlantListNotEmpty() {
|
||||||
List<Plant> testList;
|
List<Plant> testList;
|
||||||
try {
|
try {
|
||||||
testList = testDatabase.getPlantList(HardinessZone.ZONE_8A);
|
testList = testDatabase.getPlantList(HardinessZone.ZONE_8A);
|
||||||
@@ -40,12 +38,26 @@ public class JsonPlantDatabaseTest {
|
|||||||
Assertions.assertEquals(expected,names);
|
Assertions.assertEquals(expected,names);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Check if results are retrieved correctly when empty")
|
||||||
|
void getPlantListEmpty() {
|
||||||
|
List<Plant> testList;
|
||||||
|
try {
|
||||||
|
testList = testDatabase.getPlantList(HardinessZone.ZONE_1A);
|
||||||
|
} catch (IOException | HardinessZoneNotSetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
Assertions.assertEquals(0, testList.size());
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Check whether single access works.")
|
@DisplayName("Check whether single access works.")
|
||||||
void getPlantById() {
|
void getPlantByIdAndZone() {
|
||||||
Optional<Plant> testPlant;
|
Optional<Plant> testPlant;
|
||||||
try {
|
try {
|
||||||
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A,1);
|
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A, 1);
|
||||||
} catch (IOException | HardinessZoneNotSetException e) {
|
} catch (IOException | HardinessZoneNotSetException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -53,12 +65,23 @@ public class JsonPlantDatabaseTest {
|
|||||||
Assertions.assertEquals("Early Carrot", testPlant.get().name());
|
Assertions.assertEquals("Early Carrot", testPlant.get().name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Check whether single access respects zone correctly.")
|
||||||
|
void getPlantByIdAndWrongZone() throws HardinessZoneNotSetException, IOException {
|
||||||
|
Optional<Plant> testPlant = testDatabase.getPlantById(HardinessZone.ZONE_1A, 1);
|
||||||
|
Assertions.assertFalse(testPlant.isPresent());
|
||||||
|
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A, 1);
|
||||||
|
Assertions.assertTrue(testPlant.isPresent());
|
||||||
|
|
||||||
|
Assertions.assertEquals("Early Carrot", testPlant.get().name());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Check for a nonexisting plant.")
|
@DisplayName("Check for a nonexisting plant.")
|
||||||
void getPlantByIdMustFail() {
|
void getPlantByIdMustFail() {
|
||||||
Optional<Plant> testPlant;
|
Optional<Plant> testPlant;
|
||||||
try {
|
try {
|
||||||
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A,99);
|
testPlant = testDatabase.getPlantById(HardinessZone.ZONE_8A, 99);
|
||||||
} catch (IOException | HardinessZoneNotSetException e) {
|
} catch (IOException | HardinessZoneNotSetException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package ch.zhaw.gartenverwaltung.taskList;
|
||||||
|
|
||||||
|
import ch.zhaw.gartenverwaltung.io.*;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.HardinessZone;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Plant;
|
||||||
|
import ch.zhaw.gartenverwaltung.types.Task;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
class TaskListModelTest {
|
||||||
|
TaskDatabase taskDatabase;
|
||||||
|
PlantDatabase plantDatabase;
|
||||||
|
List<Task> exampleTaskList;
|
||||||
|
Map<Long, Plant> examplePlantMap;
|
||||||
|
TaskListModel model;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws IOException {
|
||||||
|
createExampleTaskList();
|
||||||
|
taskDatabase = mockTaskDatabase(exampleTaskList);
|
||||||
|
plantDatabase = mockPlantDatabase(examplePlantMap);
|
||||||
|
model = new TaskListModel(taskDatabase, plantDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskDatabase mockTaskDatabase(List<Task> exampleTaskList) throws IOException {
|
||||||
|
TaskDatabase taskDatabase = mock(JsonTaskDatabase.class);
|
||||||
|
when(taskDatabase.getTaskList(LocalDate.MIN, LocalDate.MAX)).thenReturn(exampleTaskList);
|
||||||
|
|
||||||
|
when(taskDatabase.getTaskList(LocalDate.now(), LocalDate.MAX)).thenReturn((exampleTaskList.subList(1, 4)));
|
||||||
|
|
||||||
|
List<Task> pastTasks = new ArrayList<>();
|
||||||
|
pastTasks.add(exampleTaskList.get(0));
|
||||||
|
pastTasks.add(exampleTaskList.get(2));
|
||||||
|
pastTasks.add(exampleTaskList.get(4));
|
||||||
|
when(taskDatabase.getTaskList(LocalDate.MIN, LocalDate.now())).thenReturn(pastTasks);
|
||||||
|
|
||||||
|
|
||||||
|
when(taskDatabase.getTaskList(LocalDate.now(), LocalDate.now())).thenReturn(List.of(exampleTaskList.get(2)));
|
||||||
|
when(taskDatabase.getTaskList(LocalDate.now().plusDays(1L), LocalDate.now().plusDays(1L))).thenReturn(List.of(exampleTaskList.get(1)));
|
||||||
|
when(taskDatabase.getTaskList(LocalDate.now().plusDays(2L), LocalDate.now().plusDays(2L))).thenReturn(List.of());
|
||||||
|
when(taskDatabase.getTaskList(LocalDate.now().plusDays(3L), LocalDate.now().plusDays(3L))).thenReturn(List.of());
|
||||||
|
when(taskDatabase.getTaskList(LocalDate.now().plusDays(4L), LocalDate.now().plusDays(4L))).thenReturn(List.of());
|
||||||
|
when(taskDatabase.getTaskList(LocalDate.now().plusDays(5L), LocalDate.now().plusDays(5L))).thenReturn(List.of());
|
||||||
|
when(taskDatabase.getTaskList(LocalDate.now().plusDays(6L), LocalDate.now().plusDays(6L))).thenReturn(List.of());
|
||||||
|
|
||||||
|
return taskDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlantDatabase mockPlantDatabase(Map<Long, Plant> examplePlantMap) {
|
||||||
|
return new PlantDatabase() {
|
||||||
|
@Override
|
||||||
|
public List<Plant> getPlantList(HardinessZone zone) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Plant> getPlantById(HardinessZone zone, long id) {
|
||||||
|
return Optional.ofNullable(examplePlantMap.get(id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void createExampleTaskList() {
|
||||||
|
exampleTaskList = new ArrayList<>();
|
||||||
|
exampleTaskList.add(new Task("name", "description", LocalDate.now().minusDays(1), 1L));
|
||||||
|
exampleTaskList.add(new Task("name", "description", LocalDate.now().plusDays(1), 2L));
|
||||||
|
exampleTaskList.add(new Task("name", "description", LocalDate.now(), 1L));
|
||||||
|
exampleTaskList.add(new Task("name", "description", LocalDate.of(9019, 5, 5), 1L));
|
||||||
|
exampleTaskList.add(new Task("name", "description", LocalDate.of(2019, 5, 5), 2L));
|
||||||
|
}
|
||||||
|
|
||||||
|
void createExamplePlantMap() {
|
||||||
|
examplePlantMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addTask() throws IOException {
|
||||||
|
Task taskToAdd = new Task("name", "description", LocalDate.now(), 1L);
|
||||||
|
model.addTask(taskToAdd);
|
||||||
|
verify(taskDatabase, times(1)).saveTask(taskToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removeTask() throws IOException {
|
||||||
|
Task taskToRemove = new Task("name", "description", LocalDate.now(), 1L);
|
||||||
|
model.removeTask(taskToRemove);
|
||||||
|
verify(taskDatabase, times(1)).removeTask(taskToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getTaskList() throws IOException {
|
||||||
|
List<Task> listToCheck = model.getTaskList();
|
||||||
|
assertEquals(5, listToCheck.size());
|
||||||
|
assertEquals(exampleTaskList.get(4), listToCheck.get(0));
|
||||||
|
assertEquals(exampleTaskList.get(0), listToCheck.get(1));
|
||||||
|
assertEquals(exampleTaskList.get(2), listToCheck.get(2));
|
||||||
|
assertEquals(exampleTaskList.get(1), listToCheck.get(3));
|
||||||
|
assertEquals(exampleTaskList.get(3), listToCheck.get(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getFutureTasks() throws IOException {
|
||||||
|
List<Task> listToCheck = model.getFutureTasks();
|
||||||
|
assertEquals(3, listToCheck.size());
|
||||||
|
assertEquals(exampleTaskList.get(2), listToCheck.get(0));
|
||||||
|
assertEquals(exampleTaskList.get(1), listToCheck.get(1));
|
||||||
|
assertEquals(exampleTaskList.get(3), listToCheck.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getPastTasks() throws IOException {
|
||||||
|
List<Task> listToCheck = model.getPastTasks();
|
||||||
|
assertEquals(3, listToCheck.size());
|
||||||
|
assertEquals(exampleTaskList.get(4), listToCheck.get(0));
|
||||||
|
assertEquals(exampleTaskList.get(0), listToCheck.get(1));
|
||||||
|
assertEquals(exampleTaskList.get(2), listToCheck.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getTasksUpcomingWeek() throws IOException {
|
||||||
|
List<List<Task>> dayList = model.getTasksUpcomingWeek();
|
||||||
|
assertEquals(7, dayList.size());
|
||||||
|
|
||||||
|
//Check day 0
|
||||||
|
assertEquals(1, dayList.get(0).size());
|
||||||
|
assertEquals(exampleTaskList.get(2), dayList.get(0).get(0));
|
||||||
|
|
||||||
|
//Check day 1
|
||||||
|
assertEquals(1, dayList.get(1).size());
|
||||||
|
assertEquals(exampleTaskList.get(1), dayList.get(1).get(0));
|
||||||
|
|
||||||
|
//Check day 2
|
||||||
|
assertEquals(0, dayList.get(2).size());
|
||||||
|
//Check day 3
|
||||||
|
assertEquals(0, dayList.get(3).size());
|
||||||
|
//Check day 4
|
||||||
|
assertEquals(0, dayList.get(4).size());
|
||||||
|
//Check day 5
|
||||||
|
assertEquals(0, dayList.get(5).size());
|
||||||
|
//Check day 6
|
||||||
|
assertEquals(0, dayList.get(6).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removeTasksForCrop() throws IOException {
|
||||||
|
model.removeTasksForCrop(1L);
|
||||||
|
verify(taskDatabase, times(1)).removeTasksForCrop(1L);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
"startDate" : "2022-05-01",
|
"startDate" : "2022-05-01",
|
||||||
"endDate" : "2022-05-01",
|
"endDate" : "2022-05-01",
|
||||||
"interval" : 0,
|
"interval" : 0,
|
||||||
"cropID" : 0
|
"cropId" : 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id" : 2,
|
"id" : 2,
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"startDate" : "2022-05-01",
|
"startDate" : "2022-05-01",
|
||||||
"endDate" : "2022-09-01",
|
"endDate" : "2022-09-01",
|
||||||
"interval" : 2,
|
"interval" : 2,
|
||||||
"cropID" : 0
|
"cropId" : 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id" : 3,
|
"id" : 3,
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"startDate" : "2022-06-01",
|
"startDate" : "2022-06-01",
|
||||||
"endDate" : "2022-08-01",
|
"endDate" : "2022-08-01",
|
||||||
"interval" : 28,
|
"interval" : 28,
|
||||||
"cropID" : 0
|
"cropId" : 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id" : 4,
|
"id" : 4,
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"startDate" : "2022-07-01",
|
"startDate" : "2022-07-01",
|
||||||
"endDate" : "2022-07-01",
|
"endDate" : "2022-07-01",
|
||||||
"interval" : 0,
|
"interval" : 0,
|
||||||
"cropID" : 0
|
"cropId" : 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id" : 5,
|
"id" : 5,
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"startDate" : "2022-05-01",
|
"startDate" : "2022-05-01",
|
||||||
"endDate" : "2022-09-01",
|
"endDate" : "2022-09-01",
|
||||||
"interval" : 5,
|
"interval" : 5,
|
||||||
"cropID" : 0
|
"cropId" : 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id" : 6,
|
"id" : 6,
|
||||||
@@ -51,6 +51,6 @@
|
|||||||
"startDate" : "2022-09-01",
|
"startDate" : "2022-09-01",
|
||||||
"endDate" : "2022-09-01",
|
"endDate" : "2022-09-01",
|
||||||
"interval" : 0,
|
"interval" : 0,
|
||||||
"cropID" : 0
|
"cropId" : 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user