Base Verion
|
@ -0,0 +1,138 @@
|
||||||
|
# Do not remove or rename entries in this file, only add new ones
|
||||||
|
# See https://github.com/flutter/flutter/issues/128635 for more context.
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.lock
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Visual Studio Code related
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.settings/
|
||||||
|
.vscode/*
|
||||||
|
|
||||||
|
# Flutter repo-specific
|
||||||
|
/bin/cache/
|
||||||
|
/bin/internal/bootstrap.bat
|
||||||
|
/bin/internal/bootstrap.sh
|
||||||
|
/bin/mingit/
|
||||||
|
/dev/benchmarks/mega_gallery/
|
||||||
|
/dev/bots/.recipe_deps
|
||||||
|
/dev/bots/android_tools/
|
||||||
|
/dev/devicelab/ABresults*.json
|
||||||
|
/dev/docs/doc/
|
||||||
|
/dev/docs/api_docs.zip
|
||||||
|
/dev/docs/flutter.docs.zip
|
||||||
|
/dev/docs/lib/
|
||||||
|
/dev/docs/pubspec.yaml
|
||||||
|
/dev/integration_tests/**/xcuserdata
|
||||||
|
/dev/integration_tests/**/Pods
|
||||||
|
/packages/flutter/coverage/
|
||||||
|
version
|
||||||
|
analysis_benchmark.json
|
||||||
|
|
||||||
|
# packages file containing multi-root paths
|
||||||
|
.packages.generated
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
**/generated_plugin_registrant.dart
|
||||||
|
.packages
|
||||||
|
.pub-preload-cache/
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
build/
|
||||||
|
flutter_*.png
|
||||||
|
linked_*.ds
|
||||||
|
unlinked.ds
|
||||||
|
unlinked_spec.ds
|
||||||
|
|
||||||
|
# Android related
|
||||||
|
**/android/**/gradle-wrapper.jar
|
||||||
|
.gradle/
|
||||||
|
**/android/captures/
|
||||||
|
**/android/gradlew
|
||||||
|
**/android/gradlew.bat
|
||||||
|
**/android/local.properties
|
||||||
|
**/android/**/GeneratedPluginRegistrant.java
|
||||||
|
**/android/key.properties
|
||||||
|
*.jks
|
||||||
|
|
||||||
|
# iOS/XCode related
|
||||||
|
**/ios/**/*.mode1v3
|
||||||
|
**/ios/**/*.mode2v3
|
||||||
|
**/ios/**/*.moved-aside
|
||||||
|
**/ios/**/*.pbxuser
|
||||||
|
**/ios/**/*.perspectivev3
|
||||||
|
**/ios/**/*sync/
|
||||||
|
**/ios/**/.sconsign.dblite
|
||||||
|
**/ios/**/.tags*
|
||||||
|
**/ios/**/.vagrant/
|
||||||
|
**/ios/**/DerivedData/
|
||||||
|
**/ios/**/Icon?
|
||||||
|
**/ios/**/Pods/
|
||||||
|
**/ios/**/.symlinks/
|
||||||
|
**/ios/**/profile
|
||||||
|
**/ios/**/xcuserdata
|
||||||
|
**/ios/.generated/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
**/ios/Flutter/App.framework
|
||||||
|
**/ios/Flutter/Flutter.framework
|
||||||
|
**/ios/Flutter/Flutter.podspec
|
||||||
|
**/ios/Flutter/Generated.xcconfig
|
||||||
|
**/ios/Flutter/ephemeral
|
||||||
|
**/ios/Flutter/app.flx
|
||||||
|
**/ios/Flutter/app.zip
|
||||||
|
**/ios/Flutter/flutter_assets/
|
||||||
|
**/ios/Flutter/flutter_export_environment.sh
|
||||||
|
**/ios/ServiceDefinitions.json
|
||||||
|
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
**/Flutter/ephemeral/
|
||||||
|
**/Pods/
|
||||||
|
**/macos/Flutter/GeneratedPluginRegistrant.swift
|
||||||
|
**/macos/Flutter/ephemeral
|
||||||
|
**/xcuserdata/
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
**/windows/flutter/generated_plugin_registrant.cc
|
||||||
|
**/windows/flutter/generated_plugin_registrant.h
|
||||||
|
**/windows/flutter/generated_plugins.cmake
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
**/linux/flutter/generated_plugin_registrant.cc
|
||||||
|
**/linux/flutter/generated_plugin_registrant.h
|
||||||
|
**/linux/flutter/generated_plugins.cmake
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Symbols
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!**/ios/**/default.mode1v3
|
||||||
|
!**/ios/**/default.mode2v3
|
||||||
|
!**/ios/**/default.pbxuser
|
||||||
|
!**/ios/**/default.perspectivev3
|
||||||
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
!/dev/ci/**/Gemfile.lock
|
||||||
|
!.vscode/settings.json
|
|
@ -0,0 +1,35 @@
|
||||||
|
stages:
|
||||||
|
- test
|
||||||
|
- analyze
|
||||||
|
|
||||||
|
code_quality:
|
||||||
|
stage: test
|
||||||
|
image: "cirrusci/flutter:1.22.5"
|
||||||
|
before_script:
|
||||||
|
- cd "$CI_PROJECT_DIR/garden_planner"
|
||||||
|
- pub global activate dart_code_metrics
|
||||||
|
- export PATH="$PATH:$HOME/.pub-cache/bin"
|
||||||
|
script:
|
||||||
|
- cd "$CI_PROJECT_DIR/garden_planner"
|
||||||
|
- metrics lib -r codeclimate > gl-code-quality-report.json
|
||||||
|
artifacts:
|
||||||
|
reports:
|
||||||
|
codequality: garden_planner/gl-code-quality-report.json
|
||||||
|
|
||||||
|
analyze:sonar:
|
||||||
|
stage: analyze
|
||||||
|
image:
|
||||||
|
name: sonarsource/sonar-scanner-cli:4.5
|
||||||
|
entrypoint: [""]
|
||||||
|
variables:
|
||||||
|
# Defines the location of the analysis task cache
|
||||||
|
SONAR_USER_HOME: "${CI_PROJECT_DIR}/garden_planner/.sonar"
|
||||||
|
# Shallow cloning needs to be disabled.
|
||||||
|
# See https://docs.sonarqube.org/latest/analysis/gitlab-cicd/.
|
||||||
|
GIT_DEPTH: 0
|
||||||
|
cache:
|
||||||
|
key: "${CI_JOB_NAME}"
|
||||||
|
paths:
|
||||||
|
- garden_planner/.sonar/cache
|
||||||
|
script:
|
||||||
|
- sonar-scanner
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Beet Pflanz-App
|
||||||
|
|
||||||
|
[![pipeline status](https://gitlab.vierling.cloud/Joerg/cpd_project/badges/main/pipeline.svg)](https://gitlab.vierling.cloud/Joerg/cpd_project/-/commits/main)
|
||||||
|
|
||||||
|
[![pipeline status](https://gitlab.vierling.cloud/Joerg/cpd_project/badges/main/pipeline.svg)](https://gitlab.vierling.cloud/Joerg/cpd_project/-/commits/main)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Die Beet Pflanz-App ist eine Flutter-Anwendung, die im Rahmen des Studiums an der HS-Mannheim für CPD entwickelt wurde.
|
||||||
|
Die App unterstützt den Benutzer sein Beet zu Planen und die optimalen Setzpositionen zu bestimmen.
|
||||||
|
Zudem können die wichtigen Informationen der Pflanzen über das Jahr angezeigt werden.
|
||||||
|
|
||||||
|
Das Beet kann gespeichert werden und wird beim starten geladen
|
||||||
|
|
||||||
|
## Funktionen
|
||||||
|
|
||||||
|
- Darstellung der Beetreihen und Pflanzenplatzierungen
|
||||||
|
- Drag-and-Drop zum Hinzufügen von Pflanzen
|
||||||
|
- Entfernung von gepflanzeten Pflanzen.
|
||||||
|
- Option zur Anzeige von Platzanforderungen für jede Reihe
|
||||||
|
- Anzeige von wichtigen Informationen der Pflanzen über das ganze Jahr
|
||||||
|
- Speichern des aktuellen Beets
|
||||||
|
- Laden des gespeicherten Beets
|
||||||
|
|
||||||
|
### Zukünftige Funktionen
|
||||||
|
- Anzeigen der Wassermengen der Einzelnen Pflanzen bzw. Rhein angezeigt werden.
|
||||||
|
- Verwalten von mehreren Beeten
|
||||||
|
|
||||||
|
#### Installation
|
||||||
|
|
||||||
|
1. Flutter muss installiert sein
|
||||||
|
2. Die App befindet sich im Unterordener "garden_planner"
|
||||||
|
3. Öffnen Sie die Datei lib/constants.dart und stellen Sie sicher, dass die richtige API-URL eingetragen ist.
|
||||||
|
Wenn Sie die App lokal ausführen möchten, passen Sie die API-URL auf http://localhost:3000 an.
|
||||||
|
4. Lokale Backend-Dienste können über Docker-Compose gestartet werden damit http://localhost:3000 verfügbar ist
|
||||||
|
Befehl "docker-compose up --build"
|
|
@ -0,0 +1,11 @@
|
||||||
|
FROM node:latest
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
|
@ -0,0 +1,113 @@
|
||||||
|
-- CREATE TABLE
|
||||||
|
CREATE TABLE IF NOT EXISTS plants (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255),
|
||||||
|
description VARCHAR(255),
|
||||||
|
water_requirement REAL,
|
||||||
|
horizontal_space REAL,
|
||||||
|
vertical_space REAL,
|
||||||
|
image_path VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS plant_times (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
plant_id INTEGER,
|
||||||
|
color VARCHAR(255),
|
||||||
|
description VARCHAR(255),
|
||||||
|
from_date DATE,
|
||||||
|
until_date DATE,
|
||||||
|
action_needed boolean,
|
||||||
|
FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS beets (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
plant_id INTEGER,
|
||||||
|
position INTEGER,
|
||||||
|
beet_row INTEGER,
|
||||||
|
FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO plants (name, description, horizontal_space, vertical_space, water_requirement, image_path)
|
||||||
|
VALUES
|
||||||
|
('Tomate', '', 0.6, 0.8, 0.8, 'lib/assets/plants/tomatoes-gc17bf34c6_640.jpg'),
|
||||||
|
('Kopfsalat', 'Maikönig', 0.25, 0.25, 0.5, 'lib/assets/plants/salad-seedling-g46a52dd37_640.jpg'),
|
||||||
|
('Radieschen', '', 0.15, 0.2, 0.4, 'lib/assets/plants/root-g27af04562_640.jpg'),
|
||||||
|
('Himbeere', 'Nugana', 0.4, 0.6, 0.9, 'lib/assets/plants/raspberries-ge56ab3ffc_640.jpg'),
|
||||||
|
('Himbeere', 'Glen Ample', 0.4, 0.6, 0.9, 'lib/assets/plants/raspberries-gce73a006c_640.jpg'),
|
||||||
|
('Jostabeere', '', 2.5, 2.5, 1.2, 'lib/assets/plants/jostaberry-gdf8566383_640.jpg'),
|
||||||
|
('Johannisbeere', '', 0.4, 0.6, 0.8, 'lib/assets/plants/currant-geaf055095_640.jpg'),
|
||||||
|
('Brombeere', 'Navaho', 1, 1, 1.1, 'lib/assets/plants/blackberries-gae933f2d8_640.jpg'),
|
||||||
|
('Karotte', '', 0.15, 0.2, 0.7, 'lib/assets/plants/carrot.jpg'),
|
||||||
|
('Gurke', '', 0.4, 0.6, 0.6, 'lib/assets/plants/cucumber.jpg'),
|
||||||
|
('Paprika', '', 0.3, 0.3, 0.5, 'lib/assets/plants/pepper.jpg'),
|
||||||
|
('Erdbeere', '', 0.3, 0.3, 0.8, 'lib/assets/plants/strawberry.jpg'),
|
||||||
|
('Basilikum', '', 0.2, 0.2, 0.3, 'lib/assets/plants/basil.jpg');
|
||||||
|
|
||||||
|
INSERT INTO plant_times (plant_id, from_date, until_date, description, action_needed, color)
|
||||||
|
VALUES
|
||||||
|
-- Tomate
|
||||||
|
(1, '2023-04-01', '2023-05-15', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(1, '2023-05-15', '2023-06-15', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(1, '2023-06-15', '2023-07-31', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Kopfsalat
|
||||||
|
(2, '2023-04-01', '2023-06-01', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(2, '2023-06-01', '2023-07-15', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(2, '2023-07-15', '2023-08-31', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Radieschen
|
||||||
|
(3, '2023-03-15', '2023-05-01', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(3, '2023-05-01', '2023-06-15', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(3, '2023-06-15', '2023-07-31', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Himbeere (Nugana)
|
||||||
|
(4, '2023-04-15', '2023-05-31', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(4, '2023-05-31', '2023-07-15', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(4, '2023-07-15', '2023-08-31', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Himbeere (Glen Ample)
|
||||||
|
(5, '2023-04-15', '2023-05-31', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(5, '2023-05-31', '2023-07-15', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(5, '2023-07-15', '2023-08-31', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Jostabeere
|
||||||
|
(6, '2023-03-01', '2023-05-01', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(6, '2023-05-01', '2023-07-01', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(6, '2023-07-01', '2023-09-15', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Johannisbeere
|
||||||
|
(7, '2023-03-15', '2023-05-01', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(7, '2023-05-01', '2023-07-01', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(7, '2023-07-01', '2023-08-31', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Brombeere (Navaho)
|
||||||
|
(8, '2023-04-15', '2023-06-01', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(8, '2023-06-01', '2023-07-31', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(8, '2023-07-31', '2023-09-30', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Karotte
|
||||||
|
(9, '2023-04-01', '2023-05-15', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(9, '2023-05-15', '2023-07-01', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(9, '2023-07-01', '2023-08-31', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Gurke
|
||||||
|
(10, '2023-04-15', '2023-06-01', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(10, '2023-06-01', '2023-08-01', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(10, '2023-08-01', '2023-09-30', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Paprika
|
||||||
|
(11, '2023-03-15', '2023-05-15', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(11, '2023-05-15', '2023-07-01', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(11, '2023-07-01', '2023-09-30', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Erdbeere
|
||||||
|
(12, '2023-03-01', '2023-05-01', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(12, '2023-05-01', '2023-07-01', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(12, '2023-07-01', '2023-08-31', 'Erntezeit', FALSE, '4294198070'),
|
||||||
|
|
||||||
|
-- Basilikum
|
||||||
|
(13, '2023-03-15', '2023-04-30', 'Aussaat', TRUE, '4294961979'),
|
||||||
|
(13, '2023-04-30', '2023-06-15', 'Wachstumsphase', FALSE, '438858537'),
|
||||||
|
(13, '2023-06-15', '2023-08-15', 'Erntezeit', FALSE, '4294198070');
|
|
@ -0,0 +1,811 @@
|
||||||
|
{
|
||||||
|
"name": "api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.20.2",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"pg": "^8.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/accepts": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-types": "~2.1.34",
|
||||||
|
"negotiator": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/array-flatten": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||||
|
},
|
||||||
|
"node_modules/body-parser": {
|
||||||
|
"version": "1.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||||
|
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"content-type": "~1.0.5",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "1.2.0",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"qs": "6.11.0",
|
||||||
|
"raw-body": "2.5.2",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/buffer-writer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bytes": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-bind": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.1",
|
||||||
|
"get-intrinsic": "^1.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/content-disposition": {
|
||||||
|
"version": "0.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
|
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "5.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/content-type": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-signature": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||||
|
},
|
||||||
|
"node_modules/cors": {
|
||||||
|
"version": "2.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||||
|
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||||
|
"dependencies": {
|
||||||
|
"object-assign": "^4",
|
||||||
|
"vary": "^1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/depd": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/destroy": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ee-first": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||||
|
},
|
||||||
|
"node_modules/encodeurl": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/escape-html": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||||
|
},
|
||||||
|
"node_modules/etag": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express": {
|
||||||
|
"version": "4.18.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
||||||
|
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"accepts": "~1.3.8",
|
||||||
|
"array-flatten": "1.1.1",
|
||||||
|
"body-parser": "1.20.1",
|
||||||
|
"content-disposition": "0.5.4",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"cookie": "0.5.0",
|
||||||
|
"cookie-signature": "1.0.6",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"finalhandler": "1.2.0",
|
||||||
|
"fresh": "0.5.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"merge-descriptors": "1.0.1",
|
||||||
|
"methods": "~1.1.2",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"path-to-regexp": "0.1.7",
|
||||||
|
"proxy-addr": "~2.0.7",
|
||||||
|
"qs": "6.11.0",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"send": "0.18.0",
|
||||||
|
"serve-static": "1.15.0",
|
||||||
|
"setprototypeof": "1.2.0",
|
||||||
|
"statuses": "2.0.1",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"utils-merge": "1.0.1",
|
||||||
|
"vary": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express/node_modules/body-parser": {
|
||||||
|
"version": "1.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||||
|
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "1.2.0",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"qs": "6.11.0",
|
||||||
|
"raw-body": "2.5.1",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express/node_modules/raw-body": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/finalhandler": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"statuses": "2.0.1",
|
||||||
|
"unpipe": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/forwarded": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fresh": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||||
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.1",
|
||||||
|
"has": "^1.0.3",
|
||||||
|
"has-proto": "^1.0.1",
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-errors": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"inherits": "2.0.4",
|
||||||
|
"setprototypeof": "1.2.0",
|
||||||
|
"statuses": "2.0.1",
|
||||||
|
"toidentifier": "1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
|
},
|
||||||
|
"node_modules/ipaddr.js": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/media-typer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/merge-descriptors": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
|
||||||
|
},
|
||||||
|
"node_modules/methods": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"bin": {
|
||||||
|
"mime": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/object-inspect": {
|
||||||
|
"version": "1.12.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||||
|
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/on-finished": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||||
|
"dependencies": {
|
||||||
|
"ee-first": "1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/packet-reader": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
||||||
|
},
|
||||||
|
"node_modules/parseurl": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-to-regexp": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
|
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||||
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.0.tgz",
|
||||||
|
"integrity": "sha512-meLUVPn2TWgJyLmy7el3fQQVwft4gU5NGyvV0XbD41iU9Jbg8lCH4zexhIkihDzVHJStlt6r088G6/fWeNjhXA==",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-writer": "2.0.0",
|
||||||
|
"packet-reader": "1.0.0",
|
||||||
|
"pg-connection-string": "^2.6.0",
|
||||||
|
"pg-pool": "^3.6.0",
|
||||||
|
"pg-protocol": "^1.6.0",
|
||||||
|
"pg-types": "^2.1.0",
|
||||||
|
"pgpass": "1.x"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"pg-cloudflare": "^1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-cloudflare": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-connection-string": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg=="
|
||||||
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/proxy-addr": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||||
|
"dependencies": {
|
||||||
|
"forwarded": "0.2.0",
|
||||||
|
"ipaddr.js": "1.9.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/qs": {
|
||||||
|
"version": "6.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||||
|
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/range-parser": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/raw-body": {
|
||||||
|
"version": "2.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||||
|
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
|
},
|
||||||
|
"node_modules/send": {
|
||||||
|
"version": "0.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||||
|
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "1.2.0",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"fresh": "0.5.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"mime": "1.6.0",
|
||||||
|
"ms": "2.1.3",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"statuses": "2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/send/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
|
},
|
||||||
|
"node_modules/serve-static": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||||
|
"dependencies": {
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"send": "0.18.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/setprototypeof": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||||
|
},
|
||||||
|
"node_modules/side-channel": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.0",
|
||||||
|
"get-intrinsic": "^1.0.2",
|
||||||
|
"object-inspect": "^1.9.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/split2": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/statuses": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/toidentifier": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/type-is": {
|
||||||
|
"version": "1.6.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||||
|
"dependencies": {
|
||||||
|
"media-typer": "0.3.0",
|
||||||
|
"mime-types": "~2.1.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/unpipe": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/utils-merge": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vary": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.20.2",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"pg": "^8.11.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const cors = require('cors');
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = 3000;
|
||||||
|
|
||||||
|
app.use(cors()); // Enable CORS
|
||||||
|
app.use(bodyParser.json()); // Parse JSON request bodies
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
host: 'gardenplanner-db',
|
||||||
|
port: 5432,
|
||||||
|
database: 'gardenPlaner',
|
||||||
|
user: 'garden',
|
||||||
|
password: 'garden',
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/plants/:id', (req, res) => {
|
||||||
|
const plantId = req.params.id;
|
||||||
|
|
||||||
|
pool.query('SELECT * FROM plants WHERE id = $1', [plantId])
|
||||||
|
.then((result) => {
|
||||||
|
const plant = result.rows[0];
|
||||||
|
if (!plant) {
|
||||||
|
res.status(404).json({ error: 'Pflanze nicht gefunden' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.query('SELECT * FROM plant_times WHERE plant_id = $1', [plantId])
|
||||||
|
.then((result) => {
|
||||||
|
const plantTimes = result.rows;
|
||||||
|
|
||||||
|
const plantWithTimes = {
|
||||||
|
...plant,
|
||||||
|
times: plantTimes
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(plantWithTimes);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Fehler beim landen der Zeiten', error);
|
||||||
|
res.status(500).json({ error: 'Fehler beim landen der Zeiten' });
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Fehler beim landen der Zeiten:', error);
|
||||||
|
res.status(500).json({ error: 'Fehler beim landen der Zeiten' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/plants', (req, res) => {
|
||||||
|
Promise.all([
|
||||||
|
pool.query('SELECT * FROM plants'),
|
||||||
|
pool.query('SELECT * FROM plant_times')
|
||||||
|
])
|
||||||
|
.then(([plantsResult, plantTimesResult]) => {
|
||||||
|
const rawPlants = plantsResult.rows;
|
||||||
|
const plantTimes = plantTimesResult.rows;
|
||||||
|
|
||||||
|
const plants = rawPlants.map((plant) => ({
|
||||||
|
...plant,
|
||||||
|
times: plantTimes.filter((time) => time.plant_id === plant.id)
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.json(plants);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to fetch plants and plant times:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to fetch plants and plant times' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/beet', (req, res) => {
|
||||||
|
|
||||||
|
pool.query('SELECT * FROM beets')
|
||||||
|
.then(( beetResult) => {
|
||||||
|
const beet = beetResult.rows;
|
||||||
|
|
||||||
|
res.json(beet);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to fetch plants and plant times:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to fetch plants and plant times' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/beet', (req, res) => {
|
||||||
|
const beetEntries = req.body;
|
||||||
|
|
||||||
|
console.log(beetEntries);
|
||||||
|
|
||||||
|
if (!beetEntries || !Array.isArray(beetEntries)) {
|
||||||
|
res.status(400).json({ error: 'falscher body' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearQuery = 'DELETE FROM beets';
|
||||||
|
const insertQuery = 'INSERT INTO beets (plant_id, position, beet_row) VALUES ($1, $2, $3)';
|
||||||
|
const values = beetEntries.map(({ plantId, position, beet_row }) => [plantId, position, beet_row]);
|
||||||
|
|
||||||
|
pool
|
||||||
|
.connect()
|
||||||
|
.then((client) => {
|
||||||
|
return client
|
||||||
|
.query(clearQuery) // Clear the beets table
|
||||||
|
.then(() =>
|
||||||
|
Promise.all(
|
||||||
|
values.map((params) => client.query(insertQuery, params))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.finally(() => client.release());
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
res.json({ message: 'Beet gespeichert' });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Fehler beim speichern:', error);
|
||||||
|
res.status(500).json({ error: 'Fehler beim speichern' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`Server is running on port ${PORT}`);
|
||||||
|
});
|
|
@ -0,0 +1,25 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
gardenplanner-db:
|
||||||
|
image: postgres
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: garden
|
||||||
|
POSTGRES_PASSWORD: garden
|
||||||
|
POSTGRES_DB: gardenPlaner
|
||||||
|
volumes:
|
||||||
|
- ./api/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||||
|
gardenplanner-api:
|
||||||
|
build: ./api
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- gardenplanner-db
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
- platform: android
|
||||||
|
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
- platform: linux
|
||||||
|
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
- platform: web
|
||||||
|
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
- platform: windows
|
||||||
|
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
|
@ -0,0 +1,4 @@
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
|
@ -0,0 +1,13 @@
|
||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
|
@ -0,0 +1,71 @@
|
||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||||
|
if (flutterRoot == null) {
|
||||||
|
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId "com.example.garden_planner"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
|
minSdkVersion flutter.minSdkVersion
|
||||||
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.garden_planner">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.garden_planner">
|
||||||
|
<application
|
||||||
|
android:label="garden_planner"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2"/>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.example.cpd_project
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity() {
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.example.garden_planner
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity() {
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground"/>
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white"/>
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
After Width: | Height: | Size: 544 B |
After Width: | Height: | Size: 442 B |
After Width: | Height: | Size: 721 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS'plant_drop.dart Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter'plant_drop.dart Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS'plant_drop.dart Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter'plant_drop.dart Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.garden_planner">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,31 @@
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.7.10'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:7.2.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
|
@ -0,0 +1,11 @@
|
||||||
|
include ':app'
|
||||||
|
|
||||||
|
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||||
|
def properties = new Properties()
|
||||||
|
|
||||||
|
assert localPropertiesFile.exists()
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||||
|
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
|
@ -0,0 +1,13 @@
|
||||||
|
class BeetEntry {
|
||||||
|
final int plantId;
|
||||||
|
final int position;
|
||||||
|
final int beetRow;
|
||||||
|
|
||||||
|
BeetEntry(this.plantId, this.position, this.beetRow);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'plantId': plantId,
|
||||||
|
'position': position,
|
||||||
|
'beet_row': beetRow,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:garden_planner/api/api_entities/beet_entry.dart';
|
||||||
|
import 'package:garden_planner/entities/beet.dart';
|
||||||
|
import 'package:garden_planner/entities/beet_entry_return.dart';
|
||||||
|
import 'package:garden_planner/entities/plant.dart';
|
||||||
|
import 'package:garden_planner/entities/plant_time.dart';
|
||||||
|
|
||||||
|
import 'http_connection.dart';
|
||||||
|
|
||||||
|
class GardenApiService {
|
||||||
|
final HttpConnector httpConnector;
|
||||||
|
|
||||||
|
GardenApiService(this.httpConnector);
|
||||||
|
|
||||||
|
Future<List<Plant>> getAllAvailablePlants() async {
|
||||||
|
final response = await httpConnector.getAllPlants();
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final List<dynamic> data = json.decode(response.body);
|
||||||
|
|
||||||
|
return data.map<Plant>((plantData) => convertPlant(plantData)).toList();
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to fetch plants');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> saveBeet(Beet beet) async {
|
||||||
|
final beetEntries = _generateBeetEntries(beet);
|
||||||
|
|
||||||
|
final body =
|
||||||
|
jsonEncode(beetEntries.map((entry) => entry.toJson()).toList());
|
||||||
|
final beetSaved = (await httpConnector.saveBeet(body)).statusCode == 200;
|
||||||
|
|
||||||
|
return beetSaved
|
||||||
|
? 'Beet wurde erfolgreich gespeichert'
|
||||||
|
: 'Fehler beim Speichern des Beets.';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<BeetApiEntryReturn>> getBeet() async {
|
||||||
|
final response = await httpConnector.getBeet();
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final List<dynamic> sortedBeetEntriesFormDB =
|
||||||
|
_sortBeetFromDB(response.body);
|
||||||
|
|
||||||
|
final List<BeetApiEntryReturn> beetReturn = [];
|
||||||
|
|
||||||
|
for (final entry in sortedBeetEntriesFormDB) {
|
||||||
|
final plant = await getPlant(entry['plant_id'] as int);
|
||||||
|
beetReturn.add(BeetApiEntryReturn(plant, entry['beet_row'] as int));
|
||||||
|
}
|
||||||
|
|
||||||
|
return beetReturn;
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to getBeet from api');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dynamic> _sortBeetFromDB(String response) {
|
||||||
|
final List<dynamic> entries = json.decode(response);
|
||||||
|
|
||||||
|
entries.sort((a, b) {
|
||||||
|
final int rowA = a['beet_row'] as int;
|
||||||
|
final int rowB = b['beet_row'] as int;
|
||||||
|
final int positionA = a['position'] as int;
|
||||||
|
final int positionB = b['position'] as int;
|
||||||
|
|
||||||
|
if (rowA == rowB) {
|
||||||
|
return positionA.compareTo(positionB);
|
||||||
|
} else {
|
||||||
|
return rowA.compareTo(rowB);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Plant> getPlant(int id) async {
|
||||||
|
final response = await httpConnector.getPlant(id);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final dynamic data = json.decode(response.body);
|
||||||
|
|
||||||
|
return convertPlant(data);
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to fetch plant with id: $id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plant convertPlant(dynamic plantData) {
|
||||||
|
final List<dynamic> timesData = plantData['times'];
|
||||||
|
|
||||||
|
final plantTimes = timesData
|
||||||
|
.map((time) => PlantTime(
|
||||||
|
color: Color(int.parse(time['color'])),
|
||||||
|
description: time['description'] as String,
|
||||||
|
from: DateTime.parse(time['from_date']),
|
||||||
|
until: DateTime.parse(time['until_date']),
|
||||||
|
action: time['action_needed'] as bool,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final plant = Plant(
|
||||||
|
id: plantData['id'] as int,
|
||||||
|
name: plantData['name'] as String,
|
||||||
|
waterRequirement: plantData['water_requirement'].toDouble(),
|
||||||
|
horizontalSpace: plantData['horizontal_space'].toDouble(),
|
||||||
|
verticalSpace: plantData['vertical_space'].toDouble(),
|
||||||
|
supType: plantData['description'] as String,
|
||||||
|
imagePath: plantData['image_path'].toString(),
|
||||||
|
times: plantTimes,
|
||||||
|
);
|
||||||
|
|
||||||
|
return plant;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BeetEntry> _generateBeetEntries(Beet beet) {
|
||||||
|
final entries = <BeetEntry>[];
|
||||||
|
|
||||||
|
for (int row = 0; row < beet.beetRows.length; row++) {
|
||||||
|
for (int position = 0;
|
||||||
|
position < beet.beetRows[row].plants.length;
|
||||||
|
position++) {
|
||||||
|
final plantId = beet.beetRows[row].plants[position].id;
|
||||||
|
final entry = BeetEntry(plantId, position, row);
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import '../constance.dart';
|
||||||
|
|
||||||
|
//Not testet becase only wrap around http
|
||||||
|
class HttpConnector {
|
||||||
|
static final String _apiLocation = Constance.apiLocation;
|
||||||
|
|
||||||
|
Future<http.Response> getAllPlants() async {
|
||||||
|
final url = Uri.parse('$_apiLocation/plants');
|
||||||
|
final response = await http.get(url);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<http.Response> getPlant(int id) async {
|
||||||
|
final url = Uri.parse('$_apiLocation/plants/$id');
|
||||||
|
final response = await http.get(url);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<http.Response> getBeet() async {
|
||||||
|
final url = Uri.parse('$_apiLocation/beet');
|
||||||
|
final response = await http.get(url);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<http.Response> saveBeet(String body) async {
|
||||||
|
final url = Uri.parse('$_apiLocation/beet');
|
||||||
|
final headers = {'Content-Type': 'application/json'};
|
||||||
|
|
||||||
|
final response = await http.post(
|
||||||
|
url,
|
||||||
|
headers: headers,
|
||||||
|
body: utf8.encode(body),
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 479 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 401 B |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 502 B |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 107 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 108 KiB |
After Width: | Height: | Size: 510 B |
|
@ -0,0 +1,50 @@
|
||||||
|
import 'plant.dart';
|
||||||
|
|
||||||
|
class Beet {
|
||||||
|
List<BeetRow> beetrows= [BeetRow()];
|
||||||
|
|
||||||
|
void Add(BeetRow beetrow){
|
||||||
|
beetrows.add(beetrow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BeetRow {
|
||||||
|
List<Plant> plants= [];
|
||||||
|
|
||||||
|
double get verticalSpace {
|
||||||
|
return getMaxVerticalSpace(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
double get horizontalSpace {
|
||||||
|
return plants.map((plant) => plant.horizontalSpace)
|
||||||
|
.reduce((value, element) => value+=element);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Add(Plant plant){
|
||||||
|
plants.add(plant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMaxVerticalSpace(BeetRow beetrow) {
|
||||||
|
double maxVerticalSpace = 0;
|
||||||
|
|
||||||
|
for (var plant in beetrow.plants) {
|
||||||
|
if (plant.verticalSpace > maxVerticalSpace) {
|
||||||
|
maxVerticalSpace = plant.verticalSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxVerticalSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMaxHorizontalSpace(Beet beet) {
|
||||||
|
double maxHorizontalSpace = 0;
|
||||||
|
|
||||||
|
for (var beetrow in beet.beetrows) {
|
||||||
|
if (beetrow.horizontalSpace > maxHorizontalSpace) {
|
||||||
|
maxHorizontalSpace = beetrow.horizontalSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxHorizontalSpace;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Constance {
|
||||||
|
static String get apptitle => 'Garden Beet Planner';
|
||||||
|
|
||||||
|
static String get apiLocation => 'https://cpd.vierling.cloud';
|
||||||
|
|
||||||
|
static String get defaultDescription => 'nchts zu tun';
|
||||||
|
|
||||||
|
static int get maxNumberOfRows => 3;
|
||||||
|
|
||||||
|
static Color get defaultColor => Colors.brown.withOpacity(0.5);
|
||||||
|
|
||||||
|
static bool get sidebarAtStart => true;
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'beet.dart';
|
||||||
|
import 'plant.dart';
|
||||||
|
|
||||||
|
class Content extends StatefulWidget {
|
||||||
|
final bool showSpaceRequirement;
|
||||||
|
|
||||||
|
const Content({
|
||||||
|
Key? key,
|
||||||
|
required this.showSpaceRequirement,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ContentState createState() => _ContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContentState extends State<Content> {
|
||||||
|
Beet beet = Beet();
|
||||||
|
|
||||||
|
List<Widget> getRows(Beet beet) {
|
||||||
|
|
||||||
|
List<Widget> displayedRows = [];
|
||||||
|
List<Widget> verticalSpaceContainers = [];
|
||||||
|
|
||||||
|
verticalSpaceContainers=getVerticalSpaceContainers(beet);
|
||||||
|
|
||||||
|
for(int i =0; i<beet.beetrows.length;i++) {
|
||||||
|
var beetRow = beet.beetrows[i];
|
||||||
|
Widget? verticalSpace = null;
|
||||||
|
|
||||||
|
|
||||||
|
if (widget.showSpaceRequirement && beetRow.plants.isNotEmpty) {
|
||||||
|
displayedRows.add(getHorizontalSpaceRow(beetRow));
|
||||||
|
verticalSpace=verticalSpaceContainers[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
displayedRows.add(getPlantRow(beetRow, verticalSpace));
|
||||||
|
}
|
||||||
|
|
||||||
|
return displayedRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> getHorizontalSpaceValue(BeetRow beetRow) {
|
||||||
|
double preUsedSpace = 0;
|
||||||
|
List<double> spaceElements = [];
|
||||||
|
|
||||||
|
for (var plant in beetRow.plants) {
|
||||||
|
spaceElements.add(preUsedSpace + (plant.horizontalSpace / 2));
|
||||||
|
preUsedSpace += plant.horizontalSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return spaceElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> getVerticalSpaceValue(Beet beet) {
|
||||||
|
double preUsedSpace = 0;
|
||||||
|
List<double> spaceElements = [];
|
||||||
|
|
||||||
|
for (var rows in beet.beetrows) {
|
||||||
|
spaceElements.add(preUsedSpace + (rows.verticalSpace / 2));
|
||||||
|
preUsedSpace += rows.verticalSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return spaceElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getHorizontalSpaceRow(BeetRow beetRow) {
|
||||||
|
var requiredSpaceValues = getHorizontalSpaceValue(beetRow);
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
margin: EdgeInsets.all(4),
|
||||||
|
height: 100,
|
||||||
|
width: 150,
|
||||||
|
color: Colors.green[200],
|
||||||
|
child: Column(
|
||||||
|
children: const [
|
||||||
|
Text("-")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
for (var item in requiredSpaceValues)
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
margin: EdgeInsets.all(4),
|
||||||
|
height: 100,
|
||||||
|
width: 150,
|
||||||
|
color: Colors.green[200],
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(item.toString()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Container> getVerticalSpaceContainers(Beet beet) {
|
||||||
|
var requiredSpaceValues = getVerticalSpaceValue(beet);
|
||||||
|
List<Container> containers= [];
|
||||||
|
|
||||||
|
for (var item in requiredSpaceValues) {
|
||||||
|
containers.add(
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
margin: EdgeInsets.all(4),
|
||||||
|
height: 100,
|
||||||
|
width: 150,
|
||||||
|
color: Colors.green[200],
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(item.toString()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return containers;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getPlantRow(BeetRow beetRow, Widget? verticalSpaceContainers) {
|
||||||
|
|
||||||
|
int plantNumber=0;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if(verticalSpaceContainers!=null)
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
margin: EdgeInsets.all(4),
|
||||||
|
height: 100,
|
||||||
|
width: 150,
|
||||||
|
color: Colors.green[200],
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
verticalSpaceContainers
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
for (var plant in beetRow.plants)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
margin: const EdgeInsets.all(4),
|
||||||
|
height: 100,
|
||||||
|
width: 150,
|
||||||
|
color: Colors.green[200],
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(plant.name),
|
||||||
|
Text('Wasserbedarf: ${plant.waterRequirement}'),
|
||||||
|
Text('Platz: ${plant.horizontalSpace}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DragTarget<Plant>(
|
||||||
|
onAccept: (droppedItem) {
|
||||||
|
setState(() {
|
||||||
|
beetRow.Add(droppedItem);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
builder: (context, candidateData, rejectedData) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
margin: const EdgeInsets.all(4),
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
color: Colors.grey[200],
|
||||||
|
child: const Center(
|
||||||
|
child: Text('Drop Plant here'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: Scrollbar(
|
||||||
|
thumbVisibility: true,
|
||||||
|
controller: scrollController,
|
||||||
|
child: ListView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
controller: scrollController,
|
||||||
|
children: <Widget>[
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
...getRows(beet),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
beet.Add(BeetRow());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text('Neue Reihe'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ContentControl extends StatelessWidget {
|
||||||
|
final bool showSpaceRequirements;
|
||||||
|
final ValueChanged<bool> onValueChanged;
|
||||||
|
|
||||||
|
ContentControl({super.key,
|
||||||
|
required this.showSpaceRequirements,
|
||||||
|
required this.onValueChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
Text('a'),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Text('Platzbedarf ausblenden'),
|
||||||
|
Checkbox(
|
||||||
|
value: showSpaceRequirements,
|
||||||
|
onChanged: (value) => onValueChanged(value!),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
Text('c'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:garden_planner/entities/beet_row.dart';
|
||||||
|
|
||||||
|
class Beet {
|
||||||
|
int _internalRow = 0;
|
||||||
|
List<BeetRow> beetRows = [];
|
||||||
|
|
||||||
|
Beet() {
|
||||||
|
addNewRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNewRow() {
|
||||||
|
beetRows.add(BeetRow(_internalRow));
|
||||||
|
_internalRow += 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import 'package:garden_planner/entities/plant.dart';
|
||||||
|
|
||||||
|
class BeetApiEntryReturn {
|
||||||
|
final Plant plant;
|
||||||
|
final int beetRow;
|
||||||
|
|
||||||
|
BeetApiEntryReturn(this.plant, this.beetRow);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import 'package:garden_planner/entities/plant_in_row.dart';
|
||||||
|
|
||||||
|
class BeetRow {
|
||||||
|
int id;
|
||||||
|
List<PlantInRow> plants = [];
|
||||||
|
|
||||||
|
BeetRow(this.id);
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:garden_planner/entities/plant_time.dart';
|
||||||
|
|
||||||
|
class Plant {
|
||||||
|
final String name;
|
||||||
|
final double waterRequirement;
|
||||||
|
final double horizontalSpace;
|
||||||
|
final double verticalSpace;
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
String? image;
|
||||||
|
String? supType;
|
||||||
|
List<PlantTime> times = [];
|
||||||
|
|
||||||
|
Plant({
|
||||||
|
String? imagePath,
|
||||||
|
List<PlantTime>? times,
|
||||||
|
required this.name,
|
||||||
|
required this.waterRequirement,
|
||||||
|
required this.horizontalSpace,
|
||||||
|
required this.verticalSpace,
|
||||||
|
required this.id,
|
||||||
|
required this.supType,
|
||||||
|
}) {
|
||||||
|
image = imagePath;
|
||||||
|
if (times != null) {
|
||||||
|
this.times.addAll(times);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:garden_planner/entities/plant.dart';
|
||||||
|
|
||||||
|
class PlantInRow extends Plant {
|
||||||
|
final int position;
|
||||||
|
|
||||||
|
PlantInRow({required this.position, required Plant plant})
|
||||||
|
: super(
|
||||||
|
supType: plant.supType,
|
||||||
|
horizontalSpace: plant.horizontalSpace,
|
||||||
|
id: plant.id,
|
||||||
|
name: plant.name,
|
||||||
|
verticalSpace: plant.verticalSpace,
|
||||||
|
waterRequirement: plant.waterRequirement) {
|
||||||
|
image = plant.image;
|
||||||
|
times = plant.times;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PlantTime {
|
||||||
|
final DateTime from;
|
||||||
|
final DateTime until;
|
||||||
|
final String description;
|
||||||
|
final Color color;
|
||||||
|
final bool action;
|
||||||
|
|
||||||
|
PlantTime(
|
||||||
|
{required this.color,
|
||||||
|
required this.description,
|
||||||
|
required this.from,
|
||||||
|
required this.until,
|
||||||
|
required this.action});
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
import 'package:daydart/daydart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Footer extends StatelessWidget {
|
||||||
|
|
||||||
|
final Function(DateTime) onNewDaySelected;
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
Footer({
|
||||||
|
Key? key,
|
||||||
|
required this.onNewDaySelected,
|
||||||
|
required this.date
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
get onChanged => {};
|
||||||
|
|
||||||
|
double GetDayoftheYear(DateTime date){
|
||||||
|
|
||||||
|
return DayDart(date).dayOfYear().toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime GetDatedtime(int dayOfTheYear){
|
||||||
|
var currentYear = DayDart().year();
|
||||||
|
|
||||||
|
DayDart date = DayDart('$currentYear-01-01');
|
||||||
|
date.add(dayOfTheYear-1,DayUnits.D);
|
||||||
|
|
||||||
|
return date.toDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
|
||||||
|
children: [
|
||||||
|
Container(),
|
||||||
|
Container(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(GetDatedtime(GetDayoftheYear(date).toInt()).toString()),
|
||||||
|
Slider(
|
||||||
|
value: GetDayoftheYear(date),
|
||||||
|
min: 1,
|
||||||
|
max: 365,
|
||||||
|
onChanged: (value) {
|
||||||
|
onNewDaySelected(GetDatedtime(value.toInt()));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
Container()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Header extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
final String title;
|
||||||
|
final Function() onSidebarToggle;
|
||||||
|
|
||||||
|
const Header({Key? key, required this.title, required this.onSidebarToggle})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => Size.fromHeight(60);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppBar(
|
||||||
|
title: Text(title),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.menu),
|
||||||
|
onPressed: () {
|
||||||
|
onSidebarToggle(); // Einblenden der Sidebar
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
import 'package:garden_planner/api/garden_api.service.dart';
|
||||||
|
import 'package:garden_planner/entities/beet.dart';
|
||||||
|
import 'package:garden_planner/entities/beet_entry_return.dart';
|
||||||
|
import 'package:garden_planner/entities/beet_row.dart';
|
||||||
|
import 'package:garden_planner/entities/plant.dart';
|
||||||
|
import 'package:garden_planner/logic/beet_row.service.dart';
|
||||||
|
import 'package:garden_planner/logic/plant.service.dart';
|
||||||
|
|
||||||
|
class BeetService {
|
||||||
|
void addPlantToRowById(final Beet beet, final BeetRowService rowService,
|
||||||
|
final int rowId, final Plant plant) {
|
||||||
|
final BeetRow row = rowService.getRowById(beet, rowId);
|
||||||
|
rowService.addPlant(row, plant);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNewRow(final Beet beet) {
|
||||||
|
beet.beetRows.add(BeetRow(beet.beetRows.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMaxHorizontalSpaceOfRows(
|
||||||
|
final BeetRowService beetRowService, final Beet beet) {
|
||||||
|
double maxHorizontalSpace = 0;
|
||||||
|
|
||||||
|
for (final BeetRow beetRow in beet.beetRows) {
|
||||||
|
final double horizontalSpace =
|
||||||
|
beetRowService.getTotalHorizontalSpace(beetRow);
|
||||||
|
|
||||||
|
if (horizontalSpace > maxHorizontalSpace) {
|
||||||
|
maxHorizontalSpace = horizontalSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxHorizontalSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isActionNeeded(final BeetRowService beetRowService,
|
||||||
|
final PlantService plantService, final Beet beet, final DateTime date) {
|
||||||
|
final bool isActionNeeded = beet.beetRows.any((final BeetRow row) =>
|
||||||
|
beetRowService.isActionNeeded(plantService, row, date));
|
||||||
|
|
||||||
|
return isActionNeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> getVerticalPlantingSpace(
|
||||||
|
final BeetRowService beetRowService, final Beet beet) {
|
||||||
|
double preUsedSpace = 0;
|
||||||
|
final List<double> spaceElements = [];
|
||||||
|
|
||||||
|
for (final BeetRow row in beet.beetRows) {
|
||||||
|
final double maxVerticalSpace =
|
||||||
|
beetRowService.getMaxVerticalSpaceOfPlantInTheRow(row);
|
||||||
|
var position = preUsedSpace + (maxVerticalSpace / 2);
|
||||||
|
|
||||||
|
spaceElements.add(position);
|
||||||
|
preUsedSpace += maxVerticalSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return spaceElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> saveBeet(
|
||||||
|
final GardenApiService apiService, final Beet beet) async {
|
||||||
|
return apiService.saveBeet(beet);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadBeet(final GardenApiService apiService,
|
||||||
|
final BeetRowService rowService, final Beet beet) async {
|
||||||
|
final List<BeetApiEntryReturn> beetEntries = await apiService.getBeet();
|
||||||
|
|
||||||
|
for (final BeetApiEntryReturn beetEntry in beetEntries) {
|
||||||
|
_addLoadedPlant(beet, rowService, beetEntry.plant, beetEntry.beetRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addLoadedPlant(final Beet beet, final BeetRowService rowService,
|
||||||
|
final Plant plant, final int row) {
|
||||||
|
while (beet.beetRows.length <= row) {
|
||||||
|
addNewRow(beet);
|
||||||
|
}
|
||||||
|
|
||||||
|
rowService.addPlant(beet.beetRows[row], plant);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
import 'package:garden_planner/entities/beet.dart';
|
||||||
|
import 'package:garden_planner/entities/beet_row.dart';
|
||||||
|
import 'package:garden_planner/entities/plant.dart';
|
||||||
|
import 'package:garden_planner/entities/plant_in_row.dart';
|
||||||
|
import 'package:garden_planner/logic/plant.service.dart';
|
||||||
|
|
||||||
|
class BeetRowService {
|
||||||
|
double getTotalHorizontalSpace(final BeetRow beetRow) {
|
||||||
|
return beetRow.plants
|
||||||
|
.map((plant) => plant.horizontalSpace)
|
||||||
|
.reduce((value, element) => value + element);
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMaxVerticalSpaceOfPlantInTheRow(final BeetRow beetRow) {
|
||||||
|
double maxVerticalSpace = 0;
|
||||||
|
|
||||||
|
for (var plant in beetRow.plants) {
|
||||||
|
if (plant.verticalSpace > maxVerticalSpace) {
|
||||||
|
maxVerticalSpace = plant.verticalSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxVerticalSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isActionNeeded(final PlantService plantService, final BeetRow beetRow,
|
||||||
|
final DateTime date) {
|
||||||
|
bool isActionNeeded =
|
||||||
|
beetRow.plants.any((plant) => plantService.isActionNeeded(plant, date));
|
||||||
|
|
||||||
|
return isActionNeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> getHorizontalPlantingPosition(final BeetRow beetRow) {
|
||||||
|
double preUsedSpace = 0;
|
||||||
|
List<double> spaceElements = [];
|
||||||
|
|
||||||
|
for (var plant in beetRow.plants) {
|
||||||
|
double position = preUsedSpace + (plant.horizontalSpace / 2);
|
||||||
|
|
||||||
|
spaceElements.add(position);
|
||||||
|
preUsedSpace += plant.horizontalSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return spaceElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeetRow getRowById(final Beet beet, final int rowId) {
|
||||||
|
return beet.beetRows.firstWhere((element) => element.id == rowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removePlantFromRowById(final BeetRow beetRow, final int position) {
|
||||||
|
beetRow.plants.removeWhere((plant) => plant.position == position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addPlant(final BeetRow row, final Plant plant) {
|
||||||
|
row.plants.add(_generatePlantInRow(row, plant));
|
||||||
|
}
|
||||||
|
|
||||||
|
PlantInRow _generatePlantInRow(final BeetRow row, final Plant plant) {
|
||||||
|
var numberOfCurrentPlant = row.plants.length;
|
||||||
|
return PlantInRow(position: numberOfCurrentPlant, plant: plant);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:daydart/daydart.dart';
|
||||||
|
|
||||||
|
class DateHelper {
|
||||||
|
//ignores th year an moves the Dates every time to the current year
|
||||||
|
static bool isDateBetween(
|
||||||
|
final DateTime date, final DateTime from, final DateTime until) {
|
||||||
|
final currentDate = transformToCurrentYear(date);
|
||||||
|
final currentFrom = transformToCurrentYear(from);
|
||||||
|
final currentUntil = transformToCurrentYear(until);
|
||||||
|
|
||||||
|
var isBetween = (currentDate.isAtSameMomentAs(currentFrom) ||
|
||||||
|
currentDate.isAfter(currentFrom)) &&
|
||||||
|
(currentDate.isAtSameMomentAs(currentUntil) ||
|
||||||
|
currentDate.isBefore(currentUntil));
|
||||||
|
|
||||||
|
return isBetween;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime getDateTimeByDayOfYear(final int dayOfTheYear) {
|
||||||
|
final currentYear = DayDart().year();
|
||||||
|
final date = DayDart('$currentYear-01-01');
|
||||||
|
date.add(dayOfTheYear - 1, DayUnits.D);
|
||||||
|
|
||||||
|
return date.toDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getDayOfYear(final DateTime date) {
|
||||||
|
return DayDart(date).dayOfYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime transformToCurrentYear(final DateTime date) {
|
||||||
|
final currentYear = DateTime.now().year;
|
||||||
|
final newDate = DateTime(currentYear, date.month, date.day);
|
||||||
|
|
||||||
|
return newDate;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:garden_planner/constance.dart';
|
||||||
|
|
||||||
|
import '../entities/plant.dart';
|
||||||
|
import '../entities/plant_time.dart';
|
||||||
|
import 'date.helper.dart';
|
||||||
|
|
||||||
|
class PlantService {
|
||||||
|
String getTimeDescription(final Plant plant, final DateTime date) {
|
||||||
|
var plantTimes = _getPlantTimes(plant, date);
|
||||||
|
if (plantTimes.isNotEmpty) {
|
||||||
|
return plantTimes.first.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Constance.defaultDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getColor(final Plant plant, final DateTime date) {
|
||||||
|
Color color = Constance.defaultColor;
|
||||||
|
|
||||||
|
if (plant.times.isNotEmpty) {
|
||||||
|
for (var time in plant.times) {
|
||||||
|
if (DateHelper.isDateBetween(date, time.from, time.until)) {
|
||||||
|
color = time.color;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isActionNeeded(final Plant plant, final DateTime date) {
|
||||||
|
return _getPlantTimes(plant, date).any((time) => time.action);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PlantTime> _getPlantTimes(final Plant plant, final DateTime date) {
|
||||||
|
return plant.times
|
||||||
|
.where((time) => DateHelper.isDateBetween(date, time.from, time.until))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
<<<<<<< refs/remotes/origin/main
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:garden_planner/constance.dart';
|
||||||
|
|
||||||
|
import 'api/garden_api.service.dart';
|
||||||
|
import 'api/http_connection.dart';
|
||||||
|
import 'logic/beet.service.dart';
|
||||||
|
import 'logic/beet_row.service.dart';
|
||||||
|
import 'logic/plant.service.dart';
|
||||||
|
import 'repositories/beet.repositories.dart';
|
||||||
|
import 'widgets/content.dart';
|
||||||
|
import 'widgets/header.dart';
|
||||||
|
import 'widgets/sidebar.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
BeetRepository beetRepository = BeetRepository(
|
||||||
|
BeetRowService(),
|
||||||
|
PlantService(),
|
||||||
|
GardenApiService(HttpConnector()),
|
||||||
|
BeetService(),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
runApp(GardenPlanner(beetRepository: beetRepository));
|
||||||
|
}
|
||||||
|
|
||||||
|
class GardenPlanner extends StatefulWidget {
|
||||||
|
final BeetRepository beetRepository;
|
||||||
|
|
||||||
|
const GardenPlanner({super.key, required this.beetRepository});
|
||||||
|
|
||||||
|
@override
|
||||||
|
GardenPlannerState createState() => GardenPlannerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GardenPlannerState extends State<GardenPlanner> {
|
||||||
|
bool _isSidebarOpen = Constance.sidebarAtStart;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadBeet();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadBeet() async {
|
||||||
|
await widget.beetRepository.loadBeet();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveBeet() async {
|
||||||
|
await widget.beetRepository.saveBeet();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void toogleSidebar() {
|
||||||
|
setState(() {
|
||||||
|
_isSidebarOpen = !_isSidebarOpen;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Garden Planner',
|
||||||
|
theme: ThemeData(
|
||||||
|
primarySwatch: Colors.green,
|
||||||
|
),
|
||||||
|
home: Scaffold(
|
||||||
|
appBar: Header(
|
||||||
|
onSidebarToggle: toogleSidebar,
|
||||||
|
onSave: saveBeet,
|
||||||
|
),
|
||||||
|
body: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
if (_isSidebarOpen)
|
||||||
|
Container(
|
||||||
|
width: 250,
|
||||||
|
margin: const EdgeInsets.only(top: 10, bottom: 10),
|
||||||
|
decoration: _getDecorator(),
|
||||||
|
padding: const EdgeInsets.only(right: 5, left: 5),
|
||||||
|
child: Sidebar(beetRepository: widget.beetRepository),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Content(beetRepository: widget.beetRepository),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxDecoration _getDecorator() {
|
||||||
|
return const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [
|
||||||
|
Colors.green,
|
||||||
|
Colors.green,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(40),
|
||||||
|
bottomRight: Radius.circular(0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
=======
|
||||||
|
import 'package:daydart/daydart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'contentcontrol.dart';
|
||||||
|
import 'footer.dart';
|
||||||
|
import 'header.dart';
|
||||||
|
import 'content.dart';
|
||||||
|
import 'sidebar.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(MyApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Drag and Drop Beispiel',
|
||||||
|
theme: ThemeData(
|
||||||
|
primarySwatch: Colors.blue,
|
||||||
|
),
|
||||||
|
home: DragAndDropScreen(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DragAndDropScreen extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_DragAndDropScreenState createState() => _DragAndDropScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DragAndDropScreenState extends State<DragAndDropScreen> {
|
||||||
|
bool showSpaceRequirements = false;
|
||||||
|
bool isSidebarOpen= true;
|
||||||
|
DateTime selectedDate=DateTime.now();
|
||||||
|
|
||||||
|
void toggleSidebar() {
|
||||||
|
setState(() {
|
||||||
|
isSidebarOpen = !isSidebarOpen;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void newDaySelected(DateTime date) {
|
||||||
|
setState(() {
|
||||||
|
this.selectedDate=date;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: Header(title: 'Drag and Drop Beispiel',
|
||||||
|
onSidebarToggle: toggleSidebar,),
|
||||||
|
body: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
if (isSidebarOpen) Sidebar(),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ContentControl(
|
||||||
|
showSpaceRequirements: showSpaceRequirements,
|
||||||
|
onValueChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
showSpaceRequirements = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
Content(
|
||||||
|
showSpaceRequirement: showSpaceRequirements,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Footer(onNewDaySelected:newDaySelected,date: selectedDate),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>>>>>>> working
|
|
@ -0,0 +1,13 @@
|
||||||
|
class Plant {
|
||||||
|
final String name;
|
||||||
|
final double waterRequirement;
|
||||||
|
final double horizontalSpace;
|
||||||
|
final double verticalSpace;
|
||||||
|
|
||||||
|
Plant({
|
||||||
|
required this.name,
|
||||||
|
required this.waterRequirement,
|
||||||
|
required this.horizontalSpace,
|
||||||
|
required this.verticalSpace,
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
import 'package:garden_planner/api/garden_api.service.dart';
|
||||||
|
import 'package:garden_planner/constance.dart';
|
||||||
|
import 'package:garden_planner/entities/beet.dart';
|
||||||
|
import 'package:garden_planner/entities/beet_row.dart';
|
||||||
|
import 'package:garden_planner/entities/plant.dart';
|
||||||
|
import 'package:garden_planner/logic/beet.service.dart';
|
||||||
|
import 'package:garden_planner/logic/beet_row.service.dart';
|
||||||
|
import 'package:garden_planner/logic/plant.service.dart';
|
||||||
|
|
||||||
|
class BeetRepository {
|
||||||
|
late Beet beet;
|
||||||
|
final BeetService beetService;
|
||||||
|
final BeetRowService beetRowService;
|
||||||
|
final PlantService plantService;
|
||||||
|
final GardenApiService apiService;
|
||||||
|
|
||||||
|
late String _messages;
|
||||||
|
|
||||||
|
BeetRepository(this.beetRowService, this.plantService, this.apiService,
|
||||||
|
this.beetService) {
|
||||||
|
beet = Beet();
|
||||||
|
_messages = "Keine Meldungen";
|
||||||
|
}
|
||||||
|
|
||||||
|
String get messages => _messages;
|
||||||
|
|
||||||
|
bool get newRowAllowed => beet.beetRows.length < Constance.maxNumberOfRows;
|
||||||
|
|
||||||
|
void addPlantToRowById(int rowId, Plant plant) {
|
||||||
|
beetService.addPlantToRowById(beet, beetRowService, rowId, plant);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNewRowToBeet() {
|
||||||
|
beetService.addNewRow(beet);
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMaxHorizontalSpace() {
|
||||||
|
return beetService.getMaxHorizontalSpaceOfRows(beetRowService, beet);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isActionNeeded(DateTime date) {
|
||||||
|
return beetService.isActionNeeded(beetRowService, plantService, beet, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> getVerticalSpaceValue() {
|
||||||
|
return beetService.getVerticalPlantingSpace(beetRowService, beet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removePlantFromRowById(int rowId, int plantInRowId) {
|
||||||
|
var row = beetRowService.getRowById(beet, rowId);
|
||||||
|
beetRowService.removePlantFromRowById(row, plantInRowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveBeet() async {
|
||||||
|
_messages = await beetService.saveBeet(apiService, beet);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Iterable<Plant>> getAllPlants() async {
|
||||||
|
return apiService.getAllAvailablePlants();
|
||||||
|
}
|
||||||
|
|
||||||
|
getBackgroundColorOfPlant(Plant plant, DateTime date) {
|
||||||
|
return plantService.getColor(plant, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTimeDescription(Plant plant, DateTime date) {
|
||||||
|
return plantService.getTimeDescription(plant, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadBeet() async {
|
||||||
|
await beetService.loadBeet(apiService, beetRowService, beet);
|
||||||
|
_messages = "Beet geladen";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> getHorizontalPlantingPositionForRow(BeetRow beetRow) {
|
||||||
|
return beetRowService.getHorizontalPlantingPosition(beetRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
BeetRow getRow(int index) {
|
||||||
|
return beet.beetRows[index];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'plant.dart';
|
||||||
|
|
||||||
|
class Sidebar extends StatelessWidget {
|
||||||
|
List<Plant> sidebarItems = [
|
||||||
|
Plant(
|
||||||
|
name: 'Pflanze 1',
|
||||||
|
waterRequirement: 3,
|
||||||
|
horizontalSpace: 2,
|
||||||
|
verticalSpace: 2,
|
||||||
|
),
|
||||||
|
Plant(
|
||||||
|
name: 'Pflanze 2',
|
||||||
|
waterRequirement: 5,
|
||||||
|
horizontalSpace: 1,
|
||||||
|
verticalSpace: 3,
|
||||||
|
),
|
||||||
|
Plant(
|
||||||
|
name: 'Pflanze 3',
|
||||||
|
waterRequirement: 2,
|
||||||
|
horizontalSpace: 3,
|
||||||
|
verticalSpace: 1,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
Sidebar({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
for (var plant in sidebarItems)
|
||||||
|
Draggable<Plant>(
|
||||||
|
data: plant,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
margin: EdgeInsets.all(4),
|
||||||
|
color: Colors.white,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(plant.name),
|
||||||
|
Text('Wasserbedarf: ${plant.waterRequirement}'),
|
||||||
|
Text('Platzbedarf: ${plant.horizontalSpace} x ${plant
|
||||||
|
.verticalSpace}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
feedback: Container(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
margin: EdgeInsets.all(4),
|
||||||
|
color: Colors.blue[200],
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(plant.name),
|
||||||
|
Text('Wasserbedarf: ${plant.waterRequirement}'),
|
||||||
|
Text('Platzbedarf: ${plant.horizontalSpace} x ${plant
|
||||||
|
.verticalSpace}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
childWhenDragging: Container(),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../entities/plant.dart';
|
||||||
|
import '../entities/plant_in_row.dart';
|
||||||
|
import '../repositories/beet.repositories.dart';
|
||||||
|
import 'content_widgets/control_bar.dart';
|
||||||
|
import 'content_widgets/dashboard.dart';
|
||||||
|
import 'content_widgets/footer.dart';
|
||||||
|
import 'content_widgets/new_beet_row.dart';
|
||||||
|
|
||||||
|
class Content extends StatefulWidget {
|
||||||
|
final BeetRepository beetRepository;
|
||||||
|
|
||||||
|
const Content({required this.beetRepository, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ContentState createState() => ContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContentState extends State<Content> {
|
||||||
|
bool showSpaceRequirements = false;
|
||||||
|
bool showImages = false;
|
||||||
|
bool isSidebarOpen = true;
|
||||||
|
DateTime selectedDate = DateTime.now();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleSidebar() {
|
||||||
|
setState(() {
|
||||||
|
isSidebarOpen = !isSidebarOpen;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveBeet() {
|
||||||
|
setState(() {
|
||||||
|
widget.beetRepository.saveBeet();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void plantDroppedOnRow(int rowId, Plant plant) {
|
||||||
|
setState(() {
|
||||||
|
widget.beetRepository.addPlantToRowById(rowId, plant);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void newRow() {
|
||||||
|
setState(() {
|
||||||
|
widget.beetRepository.addNewRowToBeet();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void newDaySelected(DateTime date) {
|
||||||
|
setState(() {
|
||||||
|
selectedDate = date;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void plantRemovedFromRow(int rowId, PlantInRow plantInRow) {
|
||||||
|
setState(() {
|
||||||
|
widget.beetRepository.removePlantFromRowById(rowId, plantInRow.position);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Control(
|
||||||
|
showSpaceRequirements: showSpaceRequirements,
|
||||||
|
onShowSpaceChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
showSpaceRequirements = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showImages: showImages,
|
||||||
|
onImagesChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
showImages = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
actionIsNeeded: widget.beetRepository.isActionNeeded(selectedDate),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(children: [
|
||||||
|
Dashboard(
|
||||||
|
onPlantDroppedToRow: plantDroppedOnRow,
|
||||||
|
beetRepository: widget.beetRepository,
|
||||||
|
showSpaceRequirement: showSpaceRequirements,
|
||||||
|
showImages: showImages,
|
||||||
|
currentDate: selectedDate,
|
||||||
|
onPlantRemoveFromRow: plantRemovedFromRow),
|
||||||
|
if (widget.beetRepository.newRowAllowed) NewBeetRow(onNewRow: newRow)
|
||||||
|
])),
|
||||||
|
Footer(
|
||||||
|
onNewDaySelected: newDaySelected,
|
||||||
|
beetRepository: widget.beetRepository,
|
||||||
|
date: selectedDate,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Control extends StatelessWidget {
|
||||||
|
final bool showSpaceRequirements;
|
||||||
|
final Function(bool) onShowSpaceChanged;
|
||||||
|
|
||||||
|
final bool showImages;
|
||||||
|
final Function(bool) onImagesChanged;
|
||||||
|
final bool actionIsNeeded;
|
||||||
|
|
||||||
|
const Control({
|
||||||
|
Key? key,
|
||||||
|
required this.showSpaceRequirements,
|
||||||
|
required this.onShowSpaceChanged,
|
||||||
|
required this.showImages,
|
||||||
|
required this.onImagesChanged,
|
||||||
|
required this.actionIsNeeded,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(10),
|
||||||
|
padding: const EdgeInsets.only(left: 1, right: 1, top: 10, bottom: 10),
|
||||||
|
decoration: _getDecoration(),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final smallSpace = constraints.maxWidth < 300;
|
||||||
|
|
||||||
|
if (smallSpace) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: _getControlElements(smallSpace),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: _getControlElements(smallSpace),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _getControlElements(bool reducedSpace) {
|
||||||
|
return [
|
||||||
|
if (actionIsNeeded)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.warning),
|
||||||
|
if (!reducedSpace) const Text("Aktion nötig"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!reducedSpace && !actionIsNeeded)
|
||||||
|
const Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
Text("Nichts zu tun"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
const Text('Bilder / Text'),
|
||||||
|
Checkbox(
|
||||||
|
value: showImages,
|
||||||
|
onChanged: (value) => onImagesChanged(value ?? false),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
const Text('Pflanzposition'),
|
||||||
|
Checkbox(
|
||||||
|
value: showSpaceRequirements,
|
||||||
|
onChanged: (value) => onShowSpaceChanged(value ?? false),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxDecoration _getDecoration() {
|
||||||
|
return const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.topRight,
|
||||||
|
colors: [
|
||||||
|
Colors.green,
|
||||||
|
Colors.yellow,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(40)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../entities/plant.dart';
|
||||||
|
import '../../entities/plant_in_row.dart';
|
||||||
|
import '../../repositories/beet.repositories.dart';
|
||||||
|
import 'dashboard_widgets/plant_row.dart';
|
||||||
|
import 'dashboard_widgets/space/plant_row_horizontal_space.dart';
|
||||||
|
|
||||||
|
class Dashboard extends StatelessWidget {
|
||||||
|
final bool showSpaceRequirement;
|
||||||
|
final DateTime currentDate;
|
||||||
|
final Function(int, Plant) onPlantDroppedToRow;
|
||||||
|
final Function(int, PlantInRow) onPlantRemoveFromRow;
|
||||||
|
final bool showImages;
|
||||||
|
final BeetRepository beetRepository;
|
||||||
|
|
||||||
|
const Dashboard({
|
||||||
|
Key? key,
|
||||||
|
required this.onPlantDroppedToRow,
|
||||||
|
required this.beetRepository,
|
||||||
|
required this.showSpaceRequirement,
|
||||||
|
required this.showImages,
|
||||||
|
required this.currentDate,
|
||||||
|
required this.onPlantRemoveFromRow,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var verticalSpace = beetRepository.getVerticalSpaceValue();
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
margin: const EdgeInsets.only(left: 10),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
for (int rowIndex = 0;
|
||||||
|
rowIndex < beetRepository.beet.beetRows.length;
|
||||||
|
rowIndex++)
|
||||||
|
SizedBox(
|
||||||
|
height: showSpaceRequirement &&
|
||||||
|
beetRepository.getRow(rowIndex).plants.isNotEmpty
|
||||||
|
? 170
|
||||||
|
: 120,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (showSpaceRequirement &&
|
||||||
|
beetRepository.getRow(rowIndex).plants.isNotEmpty)
|
||||||
|
PlantRowHorizontalSpace(
|
||||||
|
plantingPositions:
|
||||||
|
beetRepository.getHorizontalPlantingPositionForRow(
|
||||||
|
beetRepository.getRow(rowIndex)),
|
||||||
|
),
|
||||||
|
PlantRow(
|
||||||
|
showSpaceRequirement: showSpaceRequirement,
|
||||||
|
row: beetRepository.getRow(rowIndex),
|
||||||
|
verticalSpace: verticalSpace[rowIndex],
|
||||||
|
beetRepository: beetRepository,
|
||||||
|
showImages: showImages,
|
||||||
|
date: currentDate,
|
||||||
|
onPlantRemove: (PlantInRow plantInRow) {
|
||||||
|
onPlantRemoveFromRow(
|
||||||
|
beetRepository.getRow(rowIndex).id,
|
||||||
|
plantInRow,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onPlantDropped: (Plant plant) {
|
||||||
|
onPlantDroppedToRow(
|
||||||
|
beetRepository.getRow(rowIndex).id,
|
||||||
|
plant,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../../entities/plant.dart';
|
||||||
|
|
||||||
|
class PlantDrop extends StatelessWidget {
|
||||||
|
final Function(Plant) onPlantDropped;
|
||||||
|
final bool showSpaceRequirement;
|
||||||
|
|
||||||
|
const PlantDrop({
|
||||||
|
Key? key,
|
||||||
|
required this.onPlantDropped,
|
||||||
|
required this.showSpaceRequirement,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DragTarget<Plant>(
|
||||||
|
onAccept: (droppedItem) {
|
||||||
|
onPlantDropped(droppedItem);
|
||||||
|
},
|
||||||
|
builder: (context, candidateData, rejectedData) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(0),
|
||||||
|
height: showSpaceRequirement ? 100 : 100,
|
||||||
|
width: 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.brown,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'lib/assets/layout/planting-64-white.png',
|
||||||
|
),
|
||||||
|
const Expanded(
|
||||||
|
child: Text('Pflanzen',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../../entities/plant_in_row.dart';
|
||||||
|
import '../../../../repositories/beet.repositories.dart';
|
||||||
|
|
||||||
|
class PlantElement extends StatelessWidget {
|
||||||
|
final bool showImages;
|
||||||
|
final Function(PlantInRow) onRemovePlant;
|
||||||
|
final PlantInRow plant;
|
||||||
|
final BeetRepository beetRepository;
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
const PlantElement({
|
||||||
|
Key? key,
|
||||||
|
required this.showImages,
|
||||||
|
required this.onRemovePlant,
|
||||||
|
required this.plant,
|
||||||
|
required this.beetRepository,
|
||||||
|
required this.date,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(5),
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
height: 100,
|
||||||
|
width: 160,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: beetRepository.getBackgroundColorOfPlant(plant, date),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: showImages ? _buildImageContent() : _buildTextContent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTextContent() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
plant.name,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
Text(beetRepository.getTimeDescription(plant, date)),
|
||||||
|
Expanded(child: _buildDropElement()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildImageContent() {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: Image.asset(plant.image ?? '')),
|
||||||
|
_buildDropElement(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDropElement() {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
onPressed: () {
|
||||||
|
onRemovePlant(plant);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../../entities/beet_row.dart';
|
||||||
|
import '../../../../entities/plant.dart';
|
||||||
|
import '../../../../entities/plant_in_row.dart';
|
||||||
|
import '../../../../repositories/beet.repositories.dart';
|
||||||
|
import 'plant_drop.dart';
|
||||||
|
import 'plant_element.dart';
|
||||||
|
import 'space/plant_row_space.dart';
|
||||||
|
|
||||||
|
class PlantRow extends StatelessWidget {
|
||||||
|
final Function(Plant) onPlantDropped;
|
||||||
|
final Function(PlantInRow) onPlantRemove;
|
||||||
|
final bool showSpaceRequirement;
|
||||||
|
final double verticalSpace;
|
||||||
|
final BeetRow row;
|
||||||
|
final bool showImages;
|
||||||
|
final BeetRepository beetRepository;
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
const PlantRow(
|
||||||
|
{Key? key,
|
||||||
|
required this.row,
|
||||||
|
required this.verticalSpace,
|
||||||
|
required this.onPlantDropped,
|
||||||
|
required this.showSpaceRequirement,
|
||||||
|
required this.beetRepository,
|
||||||
|
required this.onPlantRemove,
|
||||||
|
required this.showImages,
|
||||||
|
required this.date})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(children: [
|
||||||
|
if (showSpaceRequirement && row.plants.isNotEmpty)
|
||||||
|
PlantRowSpace(verticalSpace: verticalSpace),
|
||||||
|
for (PlantInRow plant in row.plants)
|
||||||
|
PlantElement(
|
||||||
|
showImages: showImages,
|
||||||
|
onRemovePlant: onPlantRemove,
|
||||||
|
plant: plant,
|
||||||
|
beetRepository: beetRepository,
|
||||||
|
date: date),
|
||||||
|
PlantDrop(
|
||||||
|
showSpaceRequirement: showSpaceRequirement,
|
||||||
|
onPlantDropped: onPlantDropped)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PlantRowHorizontalSpace extends StatelessWidget {
|
||||||
|
final List<double> plantingPositions;
|
||||||
|
|
||||||
|
const PlantRowHorizontalSpace({
|
||||||
|
Key? key,
|
||||||
|
required this.plantingPositions,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
_buildCross(),
|
||||||
|
...plantingPositions
|
||||||
|
.map((position) => _buildPlantingPosition(position)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCross() {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(5),
|
||||||
|
height: 50,
|
||||||
|
width: 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.brown,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: const Center(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPlantingPosition(double position) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(5),
|
||||||
|
height: 50,
|
||||||
|
width: 160,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.brown,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'lib/assets/layout/width-64.png',
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${position.toStringAsFixed(2)} m',
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PlantRowSpace extends StatelessWidget {
|
||||||
|
final double verticalSpace;
|
||||||
|
|
||||||
|
const PlantRowSpace({
|
||||||
|
Key? key,
|
||||||
|
required this.verticalSpace,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(5),
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.brown,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'lib/assets/layout/height-64.png',
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${verticalSpace.toStringAsFixed(2)} m',
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PlantRowHorizontalWater extends StatelessWidget {
|
||||||
|
final List<double> waterConsumption;
|
||||||
|
|
||||||
|
const PlantRowHorizontalWater({
|
||||||
|
Key? key,
|
||||||
|
required this.waterConsumption,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(children: [
|
||||||
|
for (var item in waterConsumption)
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.all(5),
|
||||||
|
height: 50,
|
||||||
|
width: 160,
|
||||||
|
color: Colors.brown,
|
||||||
|
child: Flex(
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'lib/assets/layout/horizontal-water-pipe-64.png',
|
||||||
|
),
|
||||||
|
Text("$item m", style: const TextStyle(color: Colors.white)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PlantRowWater extends StatelessWidget {
|
||||||
|
final double waterRequirment;
|
||||||
|
|
||||||
|
const PlantRowWater({Key? key, required this.waterRequirment})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(5),
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
color: Colors.blue,
|
||||||
|
child: Flex(
|
||||||
|
direction: Axis.vertical,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset('lib/assets/layout/vertical-water-pipe-64.png'),
|
||||||
|
Text("$waterRequirment l",
|
||||||
|
style: const TextStyle(color: Colors.white)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../../logic/date.helper.dart';
|
||||||
|
import '../../repositories/beet.repositories.dart';
|
||||||
|
|
||||||
|
class Footer extends StatelessWidget {
|
||||||
|
final Function(DateTime) onNewDaySelected;
|
||||||
|
final BeetRepository beetRepository;
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
const Footer({
|
||||||
|
required this.beetRepository,
|
||||||
|
required this.date,
|
||||||
|
required this.onNewDaySelected,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(children: [
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.calendar_today),
|
||||||
|
onPressed: () {
|
||||||
|
_showDatePicker(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(DateFormat('dd.MM.yyyy').format(
|
||||||
|
DateHelper.transformToCurrentYear(date),
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
Slider(
|
||||||
|
value: DateHelper.getDayOfYear(date).toDouble(),
|
||||||
|
min: 1,
|
||||||
|
max: 365,
|
||||||
|
activeColor: Colors.green,
|
||||||
|
inactiveColor: Colors.lightGreen,
|
||||||
|
onChanged: (value) {
|
||||||
|
onNewDaySelected(
|
||||||
|
DateHelper.getDateTimeByDayOfYear(value.toInt()),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showDatePicker(BuildContext context) async {
|
||||||
|
final selectedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: date,
|
||||||
|
firstDate: DateTime(DateTime.now().year),
|
||||||
|
lastDate: DateTime(DateTime.now().year + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedDate != null) {
|
||||||
|
onNewDaySelected(selectedDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class NewBeetRow extends StatelessWidget {
|
||||||
|
final VoidCallback onNewRow;
|
||||||
|
|
||||||
|
const NewBeetRow({Key? key, required this.onNewRow}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(20),
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: onNewRow,
|
||||||
|
child: const Text('Neue Reihe'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:garden_planner/constance.dart';
|
||||||
|
|
||||||
|
class Header extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
final Function() onSidebarToggle;
|
||||||
|
final Function() onSave;
|
||||||
|
|
||||||
|
const Header({Key? key, required this.onSidebarToggle, required this.onSave})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(60);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.menu_open),
|
||||||
|
onPressed: () {
|
||||||
|
onSidebarToggle();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(Constance.apptitle),
|
||||||
|
actions: [
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.all(5),
|
||||||
|
child:
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Bestätigen'),
|
||||||
|
content: const Text('Möchten Sie speichern?'),
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.redAccent,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Abbrechen'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text('Ja'),
|
||||||
|
onPressed: () {
|
||||||
|
onSave();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
label: const Text('Speichern'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.black,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../entities/plant.dart';
|
||||||
|
import '../repositories/beet.repositories.dart';
|
||||||
|
|
||||||
|
class Sidebar extends StatelessWidget {
|
||||||
|
final BeetRepository beetRepository;
|
||||||
|
|
||||||
|
const Sidebar({required this.beetRepository, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(top: 20),
|
||||||
|
padding: const EdgeInsets.all(15),
|
||||||
|
child: const Text(
|
||||||
|
"Verfügbare Pflanzen",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(15),
|
||||||
|
child: const Text(
|
||||||
|
"Platziere das Bild auf dem Beet",
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
Expanded(
|
||||||
|
child: FutureBuilder<Iterable<Plant>>(
|
||||||
|
future: beetRepository.getAllPlants(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return const Text("Fehler beim Laden");
|
||||||
|
} else if (snapshot.hasData) {
|
||||||
|
final plants = snapshot.data!;
|
||||||
|
|
||||||
|
return Scrollbar(
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
itemCount: plants.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final plant = plants.elementAt(index);
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(5),
|
||||||
|
margin: const EdgeInsets.only(
|
||||||
|
bottom: 10,
|
||||||
|
top: 10,
|
||||||
|
right: 10,
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Draggable<Plant>(
|
||||||
|
data: plant,
|
||||||
|
feedback: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
margin: const EdgeInsets.all(4),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
plant.image.toString(),
|
||||||
|
width: 100,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Image.asset(
|
||||||
|
plant.image.toString(),
|
||||||
|
width: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
right: 8,
|
||||||
|
left: 8,
|
||||||
|
top: 8,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${plant.name} \n ${plant.supType ?? ''}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Text(
|
||||||
|
'${plant.horizontalSpace} x ${plant.verticalSpace} m',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const Text('Keine Pflanzen gefunden');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
flutter/ephemeral
|
|
@ -0,0 +1,138 @@
|
||||||
|
# Project-level configuration.
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
|
# The name of the executable created for the application. Change this to change
|
||||||
|
# the on-disk name of your application.
|
||||||
|
set(BINARY_NAME "garden_planner")
|
||||||
|
# The unique GTK application identifier for this application. See:
|
||||||
|
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||||
|
set(APPLICATION_ID "com.example.garden_planner")
|
||||||
|
|
||||||
|
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||||
|
# versions of CMake.
|
||||||
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
|
||||||
|
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||||
|
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||||
|
|
||||||
|
# Root filesystem for cross-building.
|
||||||
|
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||||
|
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Define build configuration options.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
|
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||||
|
STRING "Flutter build mode" FORCE)
|
||||||
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||||
|
"Debug" "Profile" "Release")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Compilation settings that should be applied to most targets.
|
||||||
|
#
|
||||||
|
# Be cautious about adding new options here, as plugins use this function by
|
||||||
|
# default. In most cases, you should add new options to specific targets instead
|
||||||
|
# of modifying this function.
|
||||||
|
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||||
|
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||||
|
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||||
|
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||||
|
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Flutter library and tool build rules.
|
||||||
|
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||||
|
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||||
|
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
|
||||||
|
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||||
|
|
||||||
|
# Define the application target. To change its name, change BINARY_NAME above,
|
||||||
|
# not the value here, or `flutter run` will no longer work.
|
||||||
|
#
|
||||||
|
# Any new source files that you add to the application should be added here.
|
||||||
|
add_executable(${BINARY_NAME}
|
||||||
|
"main.cc"
|
||||||
|
"my_application.cc"
|
||||||
|
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply the standard set of build settings. This can be removed for applications
|
||||||
|
# that need different build settings.
|
||||||
|
apply_standard_settings(${BINARY_NAME})
|
||||||
|
|
||||||
|
# Add dependency libraries. Add any application-specific dependencies here.
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||||
|
|
||||||
|
# Run the Flutter tool portions of the build. This must not be removed.
|
||||||
|
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||||
|
|
||||||
|
# Only the install-generated bundle's copy of the executable will launch
|
||||||
|
# correctly, since the resources must in the right relative locations. To avoid
|
||||||
|
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||||
|
# the default top-level location.
|
||||||
|
set_target_properties(${BINARY_NAME}
|
||||||
|
PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generated plugin build rules, which manage building the plugins and adding
|
||||||
|
# them to the application.
|
||||||
|
include(flutter/generated_plugins.cmake)
|
||||||
|
|
||||||
|
|
||||||
|
# === Installation ===
|
||||||
|
# By default, "installing" just makes a relocatable bundle in the build
|
||||||
|
# directory.
|
||||||
|
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||||
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
|
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Start with a clean build bundle directory every time.
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
|
||||||
|
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||||
|
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||||
|
|
||||||
|
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||||
|
install(FILES "${bundled_library}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endforeach(bundled_library)
|
||||||
|
|
||||||
|
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||||
|
# from a previous install.
|
||||||
|
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||||
|
|
||||||
|
# Install the AOT library on non-Debug builds only.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||||
|
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endif()
|
|
@ -0,0 +1,88 @@
|
||||||
|
# This file controls Flutter-level build steps. It should not be edited.
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||||
|
|
||||||
|
# Configuration provided via flutter tool.
|
||||||
|
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||||
|
|
||||||
|
# TODO: Move the rest of this into files in ephemeral. See
|
||||||
|
# https://github.com/flutter/flutter/issues/57146.
|
||||||
|
|
||||||
|
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||||
|
# which isn't available in 3.10.
|
||||||
|
function(list_prepend LIST_NAME PREFIX)
|
||||||
|
set(NEW_LIST "")
|
||||||
|
foreach(element ${${LIST_NAME}})
|
||||||
|
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||||
|
endforeach(element)
|
||||||
|
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# === Flutter Library ===
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||||
|
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||||
|
|
||||||
|
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||||
|
|
||||||
|
# Published to parent scope for install step.
|
||||||
|
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||||
|
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||||
|
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||||
|
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||||
|
"fl_basic_message_channel.h"
|
||||||
|
"fl_binary_codec.h"
|
||||||
|
"fl_binary_messenger.h"
|
||||||
|
"fl_dart_project.h"
|
||||||
|
"fl_engine.h"
|
||||||
|
"fl_json_message_codec.h"
|
||||||
|
"fl_json_method_codec.h"
|
||||||
|
"fl_message_codec.h"
|
||||||
|
"fl_method_call.h"
|
||||||
|
"fl_method_channel.h"
|
||||||
|
"fl_method_codec.h"
|
||||||
|
"fl_method_response.h"
|
||||||
|
"fl_plugin_registrar.h"
|
||||||
|
"fl_plugin_registry.h"
|
||||||
|
"fl_standard_message_codec.h"
|
||||||
|
"fl_standard_method_codec.h"
|
||||||
|
"fl_string_codec.h"
|
||||||
|
"fl_value.h"
|
||||||
|
"fl_view.h"
|
||||||
|
"flutter_linux.h"
|
||||||
|
)
|
||||||
|
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||||
|
add_library(flutter INTERFACE)
|
||||||
|
target_include_directories(flutter INTERFACE
|
||||||
|
"${EPHEMERAL_DIR}"
|
||||||
|
)
|
||||||
|
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||||
|
target_link_libraries(flutter INTERFACE
|
||||||
|
PkgConfig::GTK
|
||||||
|
PkgConfig::GLIB
|
||||||
|
PkgConfig::GIO
|
||||||
|
)
|
||||||
|
add_dependencies(flutter flutter_assemble)
|
||||||
|
|
||||||
|
# === Flutter tool backend ===
|
||||||
|
# _phony_ is a non-existent file to force this command to run every time,
|
||||||
|
# since currently there's no way to get a full input/output list from the
|
||||||
|
# flutter tool.
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E env
|
||||||
|
${FLUTTER_TOOL_ENVIRONMENT}
|
||||||
|
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||||
|
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(flutter_assemble DEPENDS
|
||||||
|
"${FLUTTER_LIBRARY}"
|
||||||
|
${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
g_autoptr(MyApplication) app = my_application_new();
|
||||||
|
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
#include <gdk/gdkx.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
struct _MyApplication {
|
||||||
|
GtkApplication parent_instance;
|
||||||
|
char** dart_entrypoint_arguments;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||||
|
|
||||||
|
// Implements GApplication::activate.
|
||||||
|
static void my_application_activate(GApplication* application) {
|
||||||
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
|
GtkWindow* window =
|
||||||
|
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||||
|
|
||||||
|
// Use a header bar when running in GNOME as this is the common style used
|
||||||
|
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||||
|
// desktop).
|
||||||
|
// If running on X and not using GNOME then just use a traditional title bar
|
||||||
|
// in case the window manager does more exotic layout, e.g. tiling.
|
||||||
|
// If running on Wayland assume the header bar will work (may need changing
|
||||||
|
// if future cases occur).
|
||||||
|
gboolean use_header_bar = TRUE;
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
GdkScreen* screen = gtk_window_get_screen(window);
|
||||||
|
if (GDK_IS_X11_SCREEN(screen)) {
|
||||||
|
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||||
|
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||||
|
use_header_bar = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (use_header_bar) {
|
||||||
|
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||||
|
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||||
|
gtk_header_bar_set_title(header_bar, "garden_planner");
|
||||||
|
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||||
|
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||||
|
} else {
|
||||||
|
gtk_window_set_title(window, "garden_planner");
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
|
gtk_widget_show(GTK_WIDGET(window));
|
||||||
|
|
||||||
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
|
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||||
|
|
||||||
|
FlView* view = fl_view_new(project);
|
||||||
|
gtk_widget_show(GTK_WIDGET(view));
|
||||||
|
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||||
|
|
||||||
|
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||||
|
|
||||||
|
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GApplication::local_command_line.
|
||||||
|
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
|
||||||
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
|
// Strip out the first argument as it is the binary name.
|
||||||
|
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||||
|
|
||||||
|
g_autoptr(GError) error = nullptr;
|
||||||
|
if (!g_application_register(application, nullptr, &error)) {
|
||||||
|
g_warning("Failed to register: %s", error->message);
|
||||||
|
*exit_status = 1;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_application_activate(application);
|
||||||
|
*exit_status = 0;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GObject::dispose.
|
||||||
|
static void my_application_dispose(GObject* object) {
|
||||||
|
MyApplication* self = MY_APPLICATION(object);
|
||||||
|
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||||
|
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_application_class_init(MyApplicationClass* klass) {
|
||||||
|
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||||
|
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||||
|
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_application_init(MyApplication* self) {}
|
||||||
|
|
||||||
|
MyApplication* my_application_new() {
|
||||||
|
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||||
|
"application-id", APPLICATION_ID,
|
||||||
|
"flags", G_APPLICATION_NON_UNIQUE,
|
||||||
|
nullptr));
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||||
|
#define FLUTTER_MY_APPLICATION_H_
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
|
||||||
|
GtkApplication)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* my_application_new:
|
||||||
|
*
|
||||||
|
* Creates a new Flutter-based application.
|
||||||
|
*
|
||||||
|
* Returns: a new #MyApplication.
|
||||||
|
*/
|
||||||
|
MyApplication* my_application_new();
|
||||||
|
|
||||||
|
#endif // FLUTTER_MY_APPLICATION_H_
|
|
@ -0,0 +1,30 @@
|
||||||
|
name: garden_planner
|
||||||
|
description: App zum Planen des Beets mit der passenden Position und Länge der jeweiligen Leitung für die dazugehörige Wasserleitung
|
||||||
|
|
||||||
|
version: 1.0.0+1
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.19.6 <3.0.0'
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
cupertino_icons: ^1.0.2
|
||||||
|
daydart: ^0.0.5
|
||||||
|
postgres: ^2.1.0
|
||||||
|
http: ^0.13.3
|
||||||
|
intl: ^0.17.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
mockito: ^5.0.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
flutter_lints: ^2.0.1
|
||||||
|
flutter:
|
||||||
|
uses-material-design: true
|
||||||
|
assets:
|
||||||
|
- lib/assets/layout/
|
||||||
|
- lib/assets/plants/
|
|
@ -0,0 +1,22 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:garden_planner/entities/beet_entry_return.dart';
|
||||||
|
import 'package:garden_planner/entities/plant.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('beetEntryReturn entity Test', () {
|
||||||
|
// Arrange
|
||||||
|
Plant plant = Plant(
|
||||||
|
name: "name",
|
||||||
|
waterRequirement: 1,
|
||||||
|
horizontalSpace: 2,
|
||||||
|
verticalSpace: 3,
|
||||||
|
id: 4,
|
||||||
|
supType: "description");
|
||||||
|
const int beetRow = 5;
|
||||||
|
// Act
|
||||||
|
final beetEntryReturn = BeetApiEntryReturn(plant, beetRow);
|
||||||
|
// Assert
|
||||||
|
expect(beetEntryReturn.plant, equals(plant));
|
||||||
|
expect(beetEntryReturn.beetRow, equals(beetRow));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:garden_planner/api/api_entities/beet_entry.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('BeetEntry entity Test', () {
|
||||||
|
// Arrange
|
||||||
|
const int plantId = 1;
|
||||||
|
const int position = 2;
|
||||||
|
const int beetrow = 3;
|
||||||
|
// Act
|
||||||
|
final beetEntry = BeetEntry(plantId, position, beetrow);
|
||||||
|
// Assert
|
||||||
|
expect(beetEntry.plantId, equals(plantId));
|
||||||
|
expect(beetEntry.position, equals(position));
|
||||||
|
expect(beetEntry.beetRow, equals(beetrow));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('BeetEntry entity toJson() should work', () {
|
||||||
|
// Arrange
|
||||||
|
const int plantId = 1;
|
||||||
|
const int position = 2;
|
||||||
|
const int beetrow = 3;
|
||||||
|
// Act
|
||||||
|
final beetEntry = BeetEntry(plantId, position, beetrow);
|
||||||
|
// Act
|
||||||
|
final jsonMap = beetEntry.toJson();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(jsonMap, isA<Map<String, dynamic>>());
|
||||||
|
expect(jsonMap['plantId'], equals(1));
|
||||||
|
expect(jsonMap['position'], equals(2));
|
||||||
|
expect(jsonMap['beet_row'], equals(3));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:garden_planner/api/garden_api.service.dart';
|
||||||
|
import 'package:garden_planner/entities/beet_entry_return.dart';
|
||||||
|
|
||||||
|
import '../helpers/plant_generator.dart';
|
||||||
|
import '../mock/mock_http_client.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late GardenApiService gardenApiService;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
gardenApiService = GardenApiService(MockHttpClient());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test getAllPlants', () async {
|
||||||
|
// Arrange
|
||||||
|
final expectedPlants = [
|
||||||
|
PlantGenerator.getPlant(),
|
||||||
|
PlantGenerator.getPlant2()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final result = (await gardenApiService.getAllAvailablePlants()).toList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
for (int i = 0; i < result.length; i++) {
|
||||||
|
expect(result[i].name, equals(expectedPlants[i].name),
|
||||||
|
reason: "Name is not equal");
|
||||||
|
expect(result[i].supType, equals(expectedPlants[i].supType),
|
||||||
|
reason: "Description is not equal");
|
||||||
|
expect(result[i].verticalSpace, equals(expectedPlants[i].verticalSpace),
|
||||||
|
reason: "Vertical space is not equal");
|
||||||
|
expect(
|
||||||
|
result[i].horizontalSpace, equals(expectedPlants[i].horizontalSpace),
|
||||||
|
reason: "Horizontal space is not equal");
|
||||||
|
expect(result[i].times.length, equals(expectedPlants[i].times.length),
|
||||||
|
reason: "Times length is not equal");
|
||||||
|
expect(result[i].image, equals(expectedPlants[i].image),
|
||||||
|
reason: "Image path is not equal");
|
||||||
|
expect(result[i].waterRequirement,
|
||||||
|
equals(expectedPlants[i].waterRequirement),
|
||||||
|
reason: "Water requirement is not equal");
|
||||||
|
|
||||||
|
for (int j = 0; j < result[i].times.length; j++) {
|
||||||
|
expect(result[i].times[j].description,
|
||||||
|
equals(expectedPlants[i].times[j].description),
|
||||||
|
reason: "Time description is not equal Plant:$i Time:$j");
|
||||||
|
expect(result[i].times[j].action,
|
||||||
|
equals(expectedPlants[i].times[j].action),
|
||||||
|
reason: "Time action is not equal Plant:$i Time:$j");
|
||||||
|
expect(
|
||||||
|
result[i].times[j].until, equals(expectedPlants[i].times[j].until),
|
||||||
|
reason: "Time until is not equal Plant:$i Time:$j");
|
||||||
|
expect(result[i].times[j].from, equals(expectedPlants[i].times[j].from),
|
||||||
|
reason: "Time from is not equal Plant:$i Time:$j");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test loadBeet', () async {
|
||||||
|
// Arrange
|
||||||
|
final expectedBeetEntries = [
|
||||||
|
BeetApiEntryReturn(PlantGenerator.getPlant2(), 0),
|
||||||
|
BeetApiEntryReturn(PlantGenerator.getPlant(), 0),
|
||||||
|
BeetApiEntryReturn(PlantGenerator.getPlant2(), 1),
|
||||||
|
BeetApiEntryReturn(PlantGenerator.getPlant(), 1)
|
||||||
|
];
|
||||||
|
// Act
|
||||||
|
var beetEntries = await gardenApiService.getBeet();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(
|
||||||
|
beetEntries.length,
|
||||||
|
equals(expectedBeetEntries.length),
|
||||||
|
reason: 'Number of beet entries is not equal',
|
||||||
|
);
|
||||||
|
|
||||||
|
for (int i = 0; i < beetEntries.length; i++) {
|
||||||
|
expect(
|
||||||
|
beetEntries[i].plant.id,
|
||||||
|
equals(expectedBeetEntries[i].plant.id),
|
||||||
|
reason: 'plant $i is not equal',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
beetEntries[i].plant.times.length,
|
||||||
|
equals(expectedBeetEntries[i].plant.times.length),
|
||||||
|
reason: 'plant.times.length at index $i is not equal',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
beetEntries[i].beetRow,
|
||||||
|
equals(expectedBeetEntries[i].beetRow),
|
||||||
|
reason: 'beetrow at index $i is not equal',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test getPlant', () async {
|
||||||
|
// Arrange
|
||||||
|
final expectedPlant = PlantGenerator.getPlant();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final plant = await gardenApiService.getPlant(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(plant.id, equals(expectedPlant.id), reason: 'Plant ID is not equal');
|
||||||
|
expect(plant.name, equals(expectedPlant.name),
|
||||||
|
reason: 'Plant Name is not equal');
|
||||||
|
expect(plant.supType, equals(expectedPlant.supType),
|
||||||
|
reason: 'Plant SubType is not equal');
|
||||||
|
expect(plant.waterRequirement, equals(expectedPlant.waterRequirement),
|
||||||
|
reason: 'Plant Waterrewuirement is not equal');
|
||||||
|
expect(plant.horizontalSpace, equals(expectedPlant.horizontalSpace),
|
||||||
|
reason: 'Plant horizontalSpace is not equal');
|
||||||
|
expect(plant.verticalSpace, equals(expectedPlant.verticalSpace),
|
||||||
|
reason: 'Plant verticalSpace is not equal');
|
||||||
|
expect(plant.image, equals(expectedPlant.image),
|
||||||
|
reason: 'Plant image length is not equal');
|
||||||
|
|
||||||
|
expect(plant.times.length, equals(expectedPlant.times.length),
|
||||||
|
reason: 'Planttimes length is not equal');
|
||||||
|
for (int j = 0; j < plant.times.length; j++) {
|
||||||
|
final time = plant.times[j];
|
||||||
|
final expectedTime = expectedPlant.times[j];
|
||||||
|
|
||||||
|
expect(time.description, equals(expectedTime.description),
|
||||||
|
reason: 'Planttimes description is not equal');
|
||||||
|
expect(time.from, equals(expectedTime.from),
|
||||||
|
reason: 'Planttimes from is not equal');
|
||||||
|
expect(time.until, equals(expectedTime.until),
|
||||||
|
reason: 'Planttimes until is not equal');
|
||||||
|
expect(time.action, equals(expectedTime.action),
|
||||||
|
reason: 'Planttimes action description is not equal');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:garden_planner/entities/beet_row.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('BeetRow entity Test', () {
|
||||||
|
// Arrange
|
||||||
|
const rowId = 1;
|
||||||
|
// Act
|
||||||
|
final beetRow = BeetRow(rowId);
|
||||||
|
// Assert
|
||||||
|
expect(beetRow.id, equals(rowId), reason: 'id is not equals');
|
||||||
|
expect(beetRow.plants, isEmpty, reason: 'plant is not equals');
|
||||||
|
});
|
||||||
|
}
|