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