Projekt-Gerüst: Flutter-App mit Heute-Checkliste, Pflanzenverwaltung, Stellplätzen und Einstellungen
- Flutter/Dart (iOS + Android), Riverpod, go_router, i18n via ARB (deutsch) - Feature-Struktur: today, plants, locations, household (V2-Platzhalter), settings - Intervall-Modell: Aufgaben werden aus lastWatered/lastFertilized + Intervall berechnet - In-Memory-Demo-Daten, Firebase-Anbindung folgt im nächsten Block - Doku: Architektur, Firebase-Einrichtung, Forgejo-Git-Hosting Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8
.claude/settings.local.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(flutter pub *)",
|
||||||
|
"Bash(flutter analyze *)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
45
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
/coverage/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
33
.metadata
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# 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 and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "3b62efc2a3da49882f43c372e0bc53daef7295a6"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
- platform: android
|
||||||
|
create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
|
||||||
|
# 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'
|
||||||
39
README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Planty 🌱
|
||||||
|
|
||||||
|
App zur Verwaltung und Pflege von Pflanzen — für den eigenen Haushalt und Pflanzen-Sitter.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
**V1 in Entwicklung.** Aktueller Stand: App-Gerüst mit Tages-Checkliste, Pflanzenverwaltung, Stellplätzen und Einstellungen (In-Memory-Demo-Daten, noch ohne Firebase-Anbindung).
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
| Version | Inhalt |
|
||||||
|
| --- | --- |
|
||||||
|
| **V1** | Pflanze anlegen (Foto → PlantNet-Erkennung → Claude-Pflegeprofil), Gieß-/Düngeplan, Tages-Checkliste mit Bestätigung, tägliche Push-Erinnerung |
|
||||||
|
| **V2** | Haushalt teilen per Einladung, Sitter-Rolle, gemeinsame Bestätigungen |
|
||||||
|
| **V3** | Krankheits-Diagnose per Foto, Stellplatz-Bewertung, Umtopf-Erinnerungen |
|
||||||
|
|
||||||
|
## Entwicklung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flutter pub get # Abhängigkeiten laden
|
||||||
|
flutter gen-l10n # Übersetzungen generieren (nach Änderungen an lib/l10n/*.arb)
|
||||||
|
flutter run # App auf Gerät/Simulator starten
|
||||||
|
flutter test # Tests ausführen
|
||||||
|
flutter analyze # Statische Analyse
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dokumentation
|
||||||
|
|
||||||
|
- [Architektur & Projektstruktur](docs/architektur.md)
|
||||||
|
- [Firebase-Einrichtung (Schritt für Schritt)](docs/firebase-einrichtung.md)
|
||||||
|
- [Git-Hosting mit Forgejo auf dem Hetzner-VPS](docs/git-forgejo.md)
|
||||||
|
|
||||||
|
## Technik-Überblick
|
||||||
|
|
||||||
|
- **Flutter/Dart** (iOS + Android), State Management mit **Riverpod**, Navigation mit **go_router**
|
||||||
|
- **Firebase** (Auth, Firestore, Storage, Cloud Functions, FCM) — Anbindung folgt im nächsten Block
|
||||||
|
- **KI hybrid:** PlantNet (kostenlose Arterkennung) + Claude (Pflegetexte, Diagnose) über Cloud Functions
|
||||||
|
- **i18n:** Alle Texte liegen in `lib/l10n/app_de.arb`; Englisch ist später nur eine zweite ARB-Datei
|
||||||
|
- Bundle-ID: vorläufig `dev.planty.app` — wird vor dem ersten TestFlight-Upload final gesetzt
|
||||||
28
analysis_options.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
14
android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/to/reference-keystore
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
44
android/app/build.gradle.kts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
id("kotlin-android")
|
||||||
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "dev.planty.planty"
|
||||||
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId = "dev.planty.planty"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
|
minSdk = flutter.minSdkVersion
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutter.versionCode
|
||||||
|
versionName = flutter.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
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.getByName("debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
7
android/app/src/debug/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 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>
|
||||||
45
android/app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application
|
||||||
|
android:label="planty"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
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>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package dev.planty.planty
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity()
|
||||||
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
|
|
@ -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>
|
||||||
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
|
|
@ -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>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal file
|
|
@ -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's 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's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
18
android/app/src/main/res/values/styles.xml
Normal file
|
|
@ -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's 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's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
7
android/app/src/profile/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 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>
|
||||||
24
android/build.gradle.kts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val newBuildDir: Directory =
|
||||||
|
rootProject.layout.buildDirectory
|
||||||
|
.dir("../../build")
|
||||||
|
.get()
|
||||||
|
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||||
|
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(":app")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Delete>("clean") {
|
||||||
|
delete(rootProject.layout.buildDirectory)
|
||||||
|
}
|
||||||
2
android/gradle.properties
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -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-8.14-all.zip
|
||||||
26
android/settings.gradle.kts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
pluginManagement {
|
||||||
|
val flutterSdkPath =
|
||||||
|
run {
|
||||||
|
val properties = java.util.Properties()
|
||||||
|
file("local.properties").inputStream().use { properties.load(it) }
|
||||||
|
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||||
|
flutterSdkPath
|
||||||
|
}
|
||||||
|
|
||||||
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
|
id("com.android.application") version "8.11.1" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include(":app")
|
||||||
60
docs/architektur.md
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Architektur & Projektstruktur
|
||||||
|
|
||||||
|
Dieses Dokument erklärt den Aufbau des Planty-Codes — auch als Blaupause für weitere Flutter-Apps.
|
||||||
|
|
||||||
|
## Ordnerstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
lib/
|
||||||
|
├── main.dart # Einstiegspunkt: lädt SharedPreferences, startet die App
|
||||||
|
├── app.dart # MaterialApp: Theme, Routing, Lokalisierung, Schriftgröße
|
||||||
|
├── core/ # Alles, was NICHT zu einem einzelnen Feature gehört
|
||||||
|
│ ├── router/app_router.dart # Zentrale Routen-Definition (go_router)
|
||||||
|
│ ├── settings/ # App-Einstellungen (Theme, Schriftgröße, Erinnerungszeit)
|
||||||
|
│ ├── theme/app_theme.dart # Farbschema und Bedienbarkeits-Anpassungen
|
||||||
|
│ └── widgets/app_drawer.dart# Das Hamburger-Menü
|
||||||
|
├── features/ # Ein Ordner pro Fachlichkeit
|
||||||
|
│ ├── today/ # Tages-Checkliste (Start-Screen)
|
||||||
|
│ ├── plants/ # Pflanzen: Liste, Profil, Anlegen/Bearbeiten
|
||||||
|
│ ├── locations/ # Stellplätze
|
||||||
|
│ ├── household/ # Haushalt (V2-Platzhalter)
|
||||||
|
│ └── settings/ # Einstellungs-Screen
|
||||||
|
└── l10n/
|
||||||
|
├── app_de.arb # ALLE deutschen Texte (Quelle)
|
||||||
|
└── generated/ # Automatisch generierter Code (nicht anfassen)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schichten innerhalb eines Features
|
||||||
|
|
||||||
|
Jedes Feature folgt demselben Muster (nicht jedes braucht alle Schichten):
|
||||||
|
|
||||||
|
- **`domain/`** — Die fachlichen Modelle (`Plant`, `DueTask`, `PlantLocation`). Reines Dart, keine Flutter- oder Firebase-Abhängigkeiten. Hier steckt die Fachlogik (z. B. „Fälligkeit = letzte Erledigung + Intervall").
|
||||||
|
- **`data/`** — Datenhaltung. Aktuell In-Memory-Notifier mit Demo-Daten; im Firebase-Block werden sie durch Firestore-Anbindungen ersetzt. **Wichtig:** Die Provider-Schnittstelle nach außen bleibt gleich — die Screens merken vom Austausch nichts.
|
||||||
|
- **`application/`** — Abgeleitete Zustände / Anwendungslogik, z. B. `dueTasksProvider`, der aus den Pflanzen die heute fälligen Aufgaben berechnet.
|
||||||
|
- **`presentation/`** — Die Screens und Widgets.
|
||||||
|
|
||||||
|
**Faustregel für Abhängigkeiten:** `presentation → application → data → domain`. Niemals andersherum, und Features greifen nur über Provider aufeinander zu.
|
||||||
|
|
||||||
|
## State Management: Riverpod in 3 Sätzen
|
||||||
|
|
||||||
|
1. Ein **`Notifier`** hält veränderlichen Zustand (z. B. die Pflanzenliste) und bietet Methoden zum Ändern (`addPlant`, `confirmTask`); ein **`Provider`** berechnet abgeleiteten Zustand (z. B. fällige Aufgaben) und aktualisiert sich automatisch, wenn sich seine Quellen ändern.
|
||||||
|
2. Screens sind `ConsumerWidget`s: `ref.watch(provider)` liest den Zustand und baut die UI bei Änderungen neu; `ref.read(provider.notifier).methode()` löst Änderungen aus (in Callbacks).
|
||||||
|
3. `ProviderScope` in `main.dart` ist die Wurzel; dort werden auch echte Abhängigkeiten (SharedPreferences, später Firebase) hineingereicht — Tests können sie durch Mocks ersetzen.
|
||||||
|
|
||||||
|
## Kernentscheidungen im fachlichen Modell
|
||||||
|
|
||||||
|
- **Aufgaben werden nicht gespeichert, sondern berechnet.** Es gibt keine „Task-Tabelle": Aus `lastWatered + wateringIntervalDays` ergibt sich die Fälligkeit. Eine Bestätigung setzt nur `lastWatered` neu — dadurch kann nichts inkonsistent werden, und Überfälliges „wandert" automatisch mit.
|
||||||
|
- **Haushaltszentriert gedacht:** Pflanzen, Stellplätze und Aufgaben gehören dem *Haushalt*, nicht einem Nutzer. In Firestore wird das `households/{id}/plants/{id}` usw. — so ist V2 (Sharing) nur „weiteres Mitglied hinzufügen", keine Datenmigration.
|
||||||
|
- **`null` bei `lastWatered` heißt „noch nie"** → Aufgabe ist sofort fällig. So braucht eine neu angelegte Pflanze keine Sonderbehandlung.
|
||||||
|
|
||||||
|
## Übersetzungen (i18n)
|
||||||
|
|
||||||
|
Alle Texte stehen in `lib/l10n/app_de.arb` als Schlüssel-Wert-Paare. Im Code heißt es nie `Text('Gießen')`, sondern `Text(l10n.taskWaterTitle(name))`. Englisch später hinzufügen = eine Datei `app_en.arb` mit denselben Schlüsseln anlegen und `flutter gen-l10n` ausführen — kein Code-Umbau.
|
||||||
|
|
||||||
|
Nach jeder Änderung an der `.arb`-Datei: `flutter gen-l10n` ausführen (passiert bei `flutter run` auch automatisch).
|
||||||
|
|
||||||
|
## Bedienbarkeit (Zielgruppe: auch ältere Menschen)
|
||||||
|
|
||||||
|
- Schriftgröße in den Einstellungen wirkt **zusätzlich** zur System-Schriftgröße (`app.dart`, `builder`).
|
||||||
|
- „Hoher Kontrast" nutzt Material 3 `contrastLevel` — dasselbe Farbschema, kräftigere Kontraste.
|
||||||
|
- Große Touch-Ziele (`app_theme.dart`), keine Swipe-Gesten, alle Funktionen über sichtbare Buttons und das Hamburger-Menü.
|
||||||
67
docs/firebase-einrichtung.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Firebase-Einrichtung — Schritt für Schritt
|
||||||
|
|
||||||
|
Firebase ist Googles „Backend als Dienst": Es liefert uns Login (**Auth**), Datenbank mit Echtzeit-Sync (**Firestore**), Foto-Speicher (**Storage**), serverseitigen Code (**Cloud Functions**) und Push-Nachrichten (**FCM**) — ohne dass wir einen eigenen Server betreiben.
|
||||||
|
|
||||||
|
> **Stand:** Vorbereitung. Die App läuft aktuell noch mit Demo-Daten ohne Firebase. Dieses Dokument wird mit jedem Firebase-Baustein erweitert und führt dich durch alles, was du selbst (mit deinem Google-Konto) tun musst.
|
||||||
|
|
||||||
|
## Was wofür? (Überblick)
|
||||||
|
|
||||||
|
| Baustein | Aufgabe in Planty |
|
||||||
|
| --- | --- |
|
||||||
|
| **Authentication** | Login mit E-Mail+Passwort, Google und Apple |
|
||||||
|
| **Cloud Firestore** | Datenbank: Haushalte, Pflanzen, Stellplätze, Bestätigungen — synchronisiert in Echtzeit auf alle Geräte des Haushalts |
|
||||||
|
| **Storage** | Speichert die Pflanzen- und Stellplatz-Fotos |
|
||||||
|
| **Cloud Functions** | Unser Server-Code: ruft PlantNet/Claude auf (damit API-Keys nie in der App stecken) und verschickt die täglichen Erinnerungen |
|
||||||
|
| **Cloud Messaging (FCM)** | Stellt die Push-Nachrichten auf dem Sperrbildschirm zu |
|
||||||
|
|
||||||
|
## Schritt 1: Firebase-Projekt anlegen (machst du, ~10 Minuten)
|
||||||
|
|
||||||
|
1. Öffne <https://console.firebase.google.com> und melde dich mit deinem Google-Konto an.
|
||||||
|
2. **„Projekt hinzufügen"** → Name: `planty` → Google Analytics kannst du **deaktivieren** (brauchen wir nicht).
|
||||||
|
3. Nach dem Anlegen bist du in der Projekt-Übersicht. Fertig für heute — Apps registrieren wir gemeinsam per CLI (Schritt 3).
|
||||||
|
|
||||||
|
## Schritt 2: Auf den Blaze-Tarif umstellen (machst du, ~5 Minuten)
|
||||||
|
|
||||||
|
Cloud Functions (unser Server-Code) setzen den **Blaze-Tarif** („Pay as you go") voraus. Keine Sorge vor Kosten:
|
||||||
|
|
||||||
|
- Die großzügigen **Gratis-Kontingente bleiben auch im Blaze-Tarif bestehen** (z. B. 2 Mio. Function-Aufrufe/Monat, 50.000 Firestore-Lesezugriffe/Tag). Bei ~5 Nutzern bleiben wir weit darunter → real **~0 €/Monat**.
|
||||||
|
- In der Console: Zahnrad → **Nutzung und Abrechnung** → **Tarif ändern** → Blaze → Kreditkarte hinterlegen.
|
||||||
|
|
||||||
|
**Kostenschutz einrichten (wichtig, machen wir direkt mit):**
|
||||||
|
|
||||||
|
1. In der Google Cloud Console (<https://console.cloud.google.com>, gleiches Projekt) → **Abrechnung → Budgets und Benachrichtigungen**.
|
||||||
|
2. Budget anlegen: z. B. **5 €/Monat**, E-Mail-Alarm bei 50 %, 90 % und 100 %.
|
||||||
|
3. So bekommst du eine Mail, lange bevor irgendetwas teuer wird.
|
||||||
|
|
||||||
|
## Schritt 3: App mit Firebase verbinden (machen wir gemeinsam per CLI)
|
||||||
|
|
||||||
|
Wenn Schritt 1–2 erledigt sind, läuft das so (führe ich mit dir aus, hier zur Doku):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Firebase-CLI installieren und einloggen (öffnet den Browser)
|
||||||
|
npm install -g firebase-tools
|
||||||
|
firebase login
|
||||||
|
|
||||||
|
# FlutterFire-CLI installieren
|
||||||
|
dart pub global activate flutterfire_cli
|
||||||
|
|
||||||
|
# Verbindet das Flutter-Projekt mit dem Firebase-Projekt:
|
||||||
|
# registriert die iOS- und Android-App und erzeugt lib/firebase_options.dart
|
||||||
|
flutterfire configure
|
||||||
|
```
|
||||||
|
|
||||||
|
`flutterfire configure` erzeugt `lib/firebase_options.dart` — darin stehen die Projekt-Kennungen (keine Geheimnisse, darf ins Git). Die eigentlichen API-Keys (Anthropic, PlantNet) leben **nur** in der Cloud-Functions-Umgebung, niemals in der App.
|
||||||
|
|
||||||
|
## Schritt 4 und folgende (kommen mit den nächsten Blöcken)
|
||||||
|
|
||||||
|
- **Firestore-Datenmodell + Sicherheitsregeln** — wer darf was lesen/schreiben (Haushalts-Mitgliedschaft), Doku folgt beim Firestore-Block.
|
||||||
|
- **Auth einrichten** — E-Mail+Passwort, Google, Apple (Apple-Login braucht den Apple Developer Account).
|
||||||
|
- **Cloud Functions** — PlantNet-/Claude-Anbindung und die geplante Erinnerungs-Function (läuft alle 15 Minuten).
|
||||||
|
- **FCM** — Push-Einrichtung inkl. APNs-Schlüssel für iOS (aus dem Apple Developer Account).
|
||||||
|
|
||||||
|
## Begriffe kurz erklärt
|
||||||
|
|
||||||
|
- **Projekt:** Der Container für alles — eine App = ein Firebase-Projekt.
|
||||||
|
- **Firestore-Dokument/Collection:** Firestore ist keine Tabellen-Datenbank, sondern speichert JSON-artige *Dokumente* in *Collections* (Ordnern), z. B. `households/abc123/plants/xyz789`.
|
||||||
|
- **Security Rules:** Regeln auf dem Server, die festlegen, wer welche Dokumente lesen/schreiben darf — unser Schutz, obwohl die App direkt mit der Datenbank spricht.
|
||||||
|
- **Cloud Function:** Eine Funktion (bei uns TypeScript), die bei Ereignissen oder nach Zeitplan auf Google-Servern läuft.
|
||||||
58
docs/git-forgejo.md
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Git-Hosting mit Forgejo auf dem Hetzner-VPS
|
||||||
|
|
||||||
|
Forgejo ist ein leichtgewichtiger, quelloffener Git-Server mit Web-Oberfläche (Fork von Gitea): Repositories, Issues, Releases — wie ein privates GitHub auf deinem eigenen Server.
|
||||||
|
|
||||||
|
## 1. Forgejo per Docker Compose installieren (auf dem VPS)
|
||||||
|
|
||||||
|
Auf dem VPS z. B. unter `/opt/forgejo/docker-compose.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
forgejo:
|
||||||
|
image: codeberg.org/forgejo/forgejo:12
|
||||||
|
container_name: forgejo
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- USER_UID=1000
|
||||||
|
- USER_GID=1000
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
ports:
|
||||||
|
- "3000:3000" # Web-UI (dahinter Reverse-Proxy mit HTTPS setzen)
|
||||||
|
- "2222:22" # SSH für git push/pull
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/forgejo && docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Dann `http://<vps-ip>:3000` öffnen → Ersteinrichtung durchklicken (SQLite als Datenbank reicht völlig) → Admin-Konto anlegen.
|
||||||
|
|
||||||
|
**Empfohlen:** Wie bei deinen anderen Diensten einen Reverse-Proxy (Caddy/Traefik/nginx) mit HTTPS davorschalten, z. B. `git.deine-domain.de`, und Port 3000 nicht öffentlich lassen. In den Forgejo-Einstellungen die Registrierung deaktivieren (`Site Administration → Configuration`), da nur du den Server nutzt.
|
||||||
|
|
||||||
|
## 2. SSH-Schlüssel hinterlegen (auf dem Mac)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vorhandenen Schlüssel anzeigen (oder mit ssh-keygen -t ed25519 neu erzeugen)
|
||||||
|
cat ~/.ssh/id_ed25519.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
Inhalt in Forgejo unter **Einstellungen → SSH-Schlüssel** einfügen.
|
||||||
|
|
||||||
|
## 3. Repository anlegen und Planty pushen
|
||||||
|
|
||||||
|
In der Forgejo-Web-UI: **Neues Repository** → Name `planty` → **ohne** README/Lizenz initialisieren (das Repo existiert lokal schon). Dann auf dem Mac:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/Documents/Projekte/Planty
|
||||||
|
git remote add origin ssh://git@<vps-host>:2222/<dein-nutzer>/planty.git
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
Ab dann genügt nach jedem Arbeitsstand ein `git push`.
|
||||||
|
|
||||||
|
## 4. Backup nicht vergessen
|
||||||
|
|
||||||
|
Der Ordner `/opt/forgejo/data` enthält alles (Repos + Datenbank). Nimm ihn in dein bestehendes VPS-Backup auf — zusammen mit der lokalen Kopie auf dem Mac hast du dann immer zwei vollständige Stände.
|
||||||
34
ios/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
26
ios/Flutter/AppFrameworkInfo.plist
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>13.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
2
ios/Flutter/Debug.xcconfig
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
2
ios/Flutter/Release.xcconfig
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
43
ios/Podfile
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
# platform :ios, '13.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
619
ios/Runner.xcodeproj/project.pbxproj
Normal file
|
|
@ -0,0 +1,619 @@
|
||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastUpgradeCheck = 1510;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C8080294A63A400263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C807F294A63A400263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C807D294A63A400263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = KSX6GJY9C5;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.planty.planty;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.planty.planty.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.planty.planty.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.planty.planty.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = KSX6GJY9C5;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.planty.planty;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = KSX6GJY9C5;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.planty.planty;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C8088294A63A400263BE5 /* Debug */,
|
||||||
|
331C8089294A63A400263BE5 /* Release */,
|
||||||
|
331C808A294A63A400263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
||||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
101
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
7
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
13
ios/Runner/AppDelegate.swift
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Launch Screen Assets
|
||||||
|
|
||||||
|
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||||
|
|
||||||
|
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||||
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
26
ios/Runner/Base.lproj/Main.storyboard
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
49
ios/Runner/Info.plist
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Planty</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>planty</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
12
ios/RunnerTests/RunnerTests.swift
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
5
l10n.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
arb-dir: lib/l10n
|
||||||
|
template-arb-file: app_de.arb
|
||||||
|
output-localization-file: app_localizations.dart
|
||||||
|
output-dir: lib/l10n/generated
|
||||||
|
nullable-getter: false
|
||||||
44
lib/app.dart
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'core/router/app_router.dart';
|
||||||
|
import 'core/settings/settings_provider.dart';
|
||||||
|
import 'core/theme/app_theme.dart';
|
||||||
|
import 'l10n/generated/app_localizations.dart';
|
||||||
|
|
||||||
|
class PlantyApp extends ConsumerWidget {
|
||||||
|
const PlantyApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final settings = ref.watch(settingsProvider);
|
||||||
|
final router = ref.watch(appRouterProvider);
|
||||||
|
|
||||||
|
return MaterialApp.router(
|
||||||
|
routerConfig: router,
|
||||||
|
onGenerateTitle: (context) => AppLocalizations.of(context).appTitle,
|
||||||
|
theme: buildAppTheme(
|
||||||
|
brightness: Brightness.light,
|
||||||
|
highContrast: settings.highContrast,
|
||||||
|
),
|
||||||
|
darkTheme: buildAppTheme(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
highContrast: settings.highContrast,
|
||||||
|
),
|
||||||
|
themeMode: settings.themeMode,
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
builder: (context, child) {
|
||||||
|
// App-Schriftgröße wird ZUSÄTZLICH zur System-Schriftgröße angewendet,
|
||||||
|
// damit die Systemeinstellung des Nutzers respektiert bleibt.
|
||||||
|
final systemScale = MediaQuery.textScalerOf(context).scale(1.0);
|
||||||
|
return MediaQuery(
|
||||||
|
data: MediaQuery.of(context).copyWith(
|
||||||
|
textScaler: TextScaler.linear(systemScale * settings.textSize.factor),
|
||||||
|
),
|
||||||
|
child: child!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
65
lib/core/router/app_router.dart
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../features/household/presentation/household_screen.dart';
|
||||||
|
import '../../features/locations/presentation/locations_screen.dart';
|
||||||
|
import '../../features/plants/domain/plant.dart';
|
||||||
|
import '../../features/plants/presentation/plant_detail_screen.dart';
|
||||||
|
import '../../features/plants/presentation/plant_form_screen.dart';
|
||||||
|
import '../../features/plants/presentation/plants_screen.dart';
|
||||||
|
import '../../features/settings/presentation/settings_screen.dart';
|
||||||
|
import '../../features/today/presentation/today_screen.dart';
|
||||||
|
|
||||||
|
/// Zentrale Routen-Definition. Die Pfade werden auch vom Drawer genutzt,
|
||||||
|
/// deshalb hier als Konstanten.
|
||||||
|
abstract final class AppRoutes {
|
||||||
|
static const today = '/';
|
||||||
|
static const plants = '/plants';
|
||||||
|
static const plantAdd = '/plants/add';
|
||||||
|
static const plantEdit = '/plants/edit';
|
||||||
|
static const locations = '/locations';
|
||||||
|
static const household = '/household';
|
||||||
|
static const settings = '/settings';
|
||||||
|
}
|
||||||
|
|
||||||
|
final appRouterProvider = Provider<GoRouter>((ref) {
|
||||||
|
return GoRouter(
|
||||||
|
initialLocation: AppRoutes.today,
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.today,
|
||||||
|
builder: (context, state) => const TodayScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.plants,
|
||||||
|
builder: (context, state) => const PlantsScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.plantAdd,
|
||||||
|
builder: (context, state) => const PlantFormScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.plantEdit,
|
||||||
|
builder: (context, state) =>
|
||||||
|
PlantFormScreen(existing: state.extra as Plant?),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/plants/:id',
|
||||||
|
builder: (context, state) =>
|
||||||
|
PlantDetailScreen(plantId: state.pathParameters['id']!),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.locations,
|
||||||
|
builder: (context, state) => const LocationsScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.household,
|
||||||
|
builder: (context, state) => const HouseholdScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.settings,
|
||||||
|
builder: (context, state) => const SettingsScreen(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
101
lib/core/settings/settings_provider.dart
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
/// Wird in main.dart mit der echten Instanz überschrieben.
|
||||||
|
final sharedPreferencesProvider = Provider<SharedPreferences>(
|
||||||
|
(ref) => throw UnimplementedError('sharedPreferencesProvider muss in main.dart überschrieben werden'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Schriftgrößen-Stufen, die der Nutzer in den Einstellungen wählen kann.
|
||||||
|
/// Der Faktor wird zusätzlich zur System-Schriftgröße angewendet.
|
||||||
|
enum AppTextSize {
|
||||||
|
normal(1.0),
|
||||||
|
large(1.15),
|
||||||
|
extraLarge(1.3);
|
||||||
|
|
||||||
|
const AppTextSize(this.factor);
|
||||||
|
final double factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class AppSettings {
|
||||||
|
const AppSettings({
|
||||||
|
required this.themeMode,
|
||||||
|
required this.textSize,
|
||||||
|
required this.highContrast,
|
||||||
|
required this.reminderTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ThemeMode themeMode;
|
||||||
|
final AppTextSize textSize;
|
||||||
|
final bool highContrast;
|
||||||
|
|
||||||
|
/// Uhrzeit der täglichen Sammel-Erinnerung. Wird lokal gespeichert und
|
||||||
|
/// später mit dem Firebase-Backend synchronisiert (V1-Push-Block).
|
||||||
|
final TimeOfDay reminderTime;
|
||||||
|
|
||||||
|
AppSettings copyWith({
|
||||||
|
ThemeMode? themeMode,
|
||||||
|
AppTextSize? textSize,
|
||||||
|
bool? highContrast,
|
||||||
|
TimeOfDay? reminderTime,
|
||||||
|
}) {
|
||||||
|
return AppSettings(
|
||||||
|
themeMode: themeMode ?? this.themeMode,
|
||||||
|
textSize: textSize ?? this.textSize,
|
||||||
|
highContrast: highContrast ?? this.highContrast,
|
||||||
|
reminderTime: reminderTime ?? this.reminderTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsNotifier extends Notifier<AppSettings> {
|
||||||
|
static const _keyThemeMode = 'settings.themeMode';
|
||||||
|
static const _keyTextSize = 'settings.textSize';
|
||||||
|
static const _keyHighContrast = 'settings.highContrast';
|
||||||
|
static const _keyReminderTime = 'settings.reminderTime';
|
||||||
|
|
||||||
|
SharedPreferences get _prefs => ref.read(sharedPreferencesProvider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
AppSettings build() {
|
||||||
|
final reminderRaw = _prefs.getString(_keyReminderTime) ?? '09:00';
|
||||||
|
final parts = reminderRaw.split(':');
|
||||||
|
return AppSettings(
|
||||||
|
themeMode: ThemeMode.values.asNameMap()[_prefs.getString(_keyThemeMode)] ?? ThemeMode.system,
|
||||||
|
textSize: AppTextSize.values.asNameMap()[_prefs.getString(_keyTextSize)] ?? AppTextSize.normal,
|
||||||
|
highContrast: _prefs.getBool(_keyHighContrast) ?? false,
|
||||||
|
reminderTime: TimeOfDay(
|
||||||
|
hour: int.tryParse(parts[0]) ?? 9,
|
||||||
|
minute: int.tryParse(parts.length > 1 ? parts[1] : '0') ?? 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setThemeMode(ThemeMode mode) {
|
||||||
|
state = state.copyWith(themeMode: mode);
|
||||||
|
_prefs.setString(_keyThemeMode, mode.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTextSize(AppTextSize size) {
|
||||||
|
state = state.copyWith(textSize: size);
|
||||||
|
_prefs.setString(_keyTextSize, size.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHighContrast(bool enabled) {
|
||||||
|
state = state.copyWith(highContrast: enabled);
|
||||||
|
_prefs.setBool(_keyHighContrast, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setReminderTime(TimeOfDay time) {
|
||||||
|
state = state.copyWith(reminderTime: time);
|
||||||
|
_prefs.setString(
|
||||||
|
_keyReminderTime,
|
||||||
|
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final settingsProvider =
|
||||||
|
NotifierProvider<SettingsNotifier, AppSettings>(SettingsNotifier.new);
|
||||||
35
lib/core/theme/app_theme.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Planty-Grün als Ausgangsfarbe für das gesamte Farbschema.
|
||||||
|
const _seedColor = Color(0xFF2E7D32);
|
||||||
|
|
||||||
|
ThemeData buildAppTheme({
|
||||||
|
required Brightness brightness,
|
||||||
|
required bool highContrast,
|
||||||
|
}) {
|
||||||
|
final colorScheme = ColorScheme.fromSeed(
|
||||||
|
seedColor: _seedColor,
|
||||||
|
brightness: brightness,
|
||||||
|
// Material 3 kann kontrastverstärkte Varianten desselben Schemas erzeugen.
|
||||||
|
contrastLevel: highContrast ? 1.0 : 0.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return ThemeData(
|
||||||
|
colorScheme: colorScheme,
|
||||||
|
// Großzügige Touch-Ziele: wichtig für die Bedienbarkeit durch ältere Nutzer.
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.padded,
|
||||||
|
visualDensity: VisualDensity.standard,
|
||||||
|
filledButtonTheme: FilledButtonThemeData(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
minimumSize: const Size(64, 52),
|
||||||
|
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
listTileTheme: const ListTileThemeData(
|
||||||
|
minVerticalPadding: 12,
|
||||||
|
),
|
||||||
|
snackBarTheme: const SnackBarThemeData(
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
117
lib/core/widgets/app_drawer.dart
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../features/today/application/due_tasks_provider.dart';
|
||||||
|
import '../../l10n/generated/app_localizations.dart';
|
||||||
|
import '../router/app_router.dart';
|
||||||
|
|
||||||
|
/// Das Hamburger-Menü: von jedem Haupt-Screen aus erreichbar,
|
||||||
|
/// listet alle Funktionen klassisch untereinander auf.
|
||||||
|
class AppDrawer extends ConsumerWidget {
|
||||||
|
const AppDrawer({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final currentPath = GoRouterState.of(context).uri.path;
|
||||||
|
final openTaskCount = ref.watch(dueTasksProvider).length;
|
||||||
|
|
||||||
|
void goTo(String path) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (path != currentPath) {
|
||||||
|
context.go(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Drawer(
|
||||||
|
child: SafeArea(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.eco,
|
||||||
|
size: 36,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
l10n.appTitle,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
_DrawerItem(
|
||||||
|
icon: Icons.checklist,
|
||||||
|
label: l10n.menuToday,
|
||||||
|
selected: currentPath == AppRoutes.today,
|
||||||
|
badgeCount: openTaskCount,
|
||||||
|
onTap: () => goTo(AppRoutes.today),
|
||||||
|
),
|
||||||
|
_DrawerItem(
|
||||||
|
icon: Icons.local_florist,
|
||||||
|
label: l10n.menuPlants,
|
||||||
|
selected: currentPath == AppRoutes.plants,
|
||||||
|
onTap: () => goTo(AppRoutes.plants),
|
||||||
|
),
|
||||||
|
_DrawerItem(
|
||||||
|
icon: Icons.place,
|
||||||
|
label: l10n.menuLocations,
|
||||||
|
selected: currentPath == AppRoutes.locations,
|
||||||
|
onTap: () => goTo(AppRoutes.locations),
|
||||||
|
),
|
||||||
|
_DrawerItem(
|
||||||
|
icon: Icons.group,
|
||||||
|
label: l10n.menuHousehold,
|
||||||
|
selected: currentPath == AppRoutes.household,
|
||||||
|
onTap: () => goTo(AppRoutes.household),
|
||||||
|
),
|
||||||
|
_DrawerItem(
|
||||||
|
icon: Icons.settings,
|
||||||
|
label: l10n.menuSettings,
|
||||||
|
selected: currentPath == AppRoutes.settings,
|
||||||
|
onTap: () => goTo(AppRoutes.settings),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DrawerItem extends StatelessWidget {
|
||||||
|
const _DrawerItem({
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
required this.selected,
|
||||||
|
required this.onTap,
|
||||||
|
this.badgeCount = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
final bool selected;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final int badgeCount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
leading: Icon(icon),
|
||||||
|
title: Text(label, style: const TextStyle(fontSize: 17)),
|
||||||
|
selected: selected,
|
||||||
|
trailing: badgeCount > 0
|
||||||
|
? Badge.count(
|
||||||
|
count: badgeCount,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
onTap: onTap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
lib/features/household/presentation/household_screen.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../core/widgets/app_drawer.dart';
|
||||||
|
import '../../../l10n/generated/app_localizations.dart';
|
||||||
|
|
||||||
|
/// Platzhalter für das Haushalt-Sharing (V2): Einladungen, Mitglieder,
|
||||||
|
/// Sitter-Rolle.
|
||||||
|
class HouseholdScreen extends StatelessWidget {
|
||||||
|
const HouseholdScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(l10n.householdTitle)),
|
||||||
|
drawer: const AppDrawer(),
|
||||||
|
body: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(32),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.group, size: 72, color: theme.colorScheme.primary),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
l10n.householdPlaceholder,
|
||||||
|
style: theme.textTheme.bodyLarge,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Chip(label: Text(l10n.householdComingSoon)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
lib/features/locations/data/locations_provider.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../domain/plant_location.dart';
|
||||||
|
|
||||||
|
/// In-Memory-Stellplätze mit Demo-Daten – wird später durch Firestore ersetzt.
|
||||||
|
class LocationsNotifier extends Notifier<List<PlantLocation>> {
|
||||||
|
@override
|
||||||
|
List<PlantLocation> build() {
|
||||||
|
return const [
|
||||||
|
PlantLocation(id: 'loc-wohnzimmer', name: 'Wohnzimmer'),
|
||||||
|
PlantLocation(id: 'loc-schlafzimmer', name: 'Schlafzimmer'),
|
||||||
|
PlantLocation(id: 'loc-kueche', name: 'Küche'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
void addLocation(String name) {
|
||||||
|
final id = 'loc-${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
state = [...state, PlantLocation(id: id, name: name)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final locationsProvider =
|
||||||
|
NotifierProvider<LocationsNotifier, List<PlantLocation>>(
|
||||||
|
LocationsNotifier.new);
|
||||||
|
|
||||||
|
final locationByIdProvider =
|
||||||
|
Provider.family<PlantLocation?, String?>((ref, id) {
|
||||||
|
if (id == null) return null;
|
||||||
|
final locations = ref.watch(locationsProvider);
|
||||||
|
for (final location in locations) {
|
||||||
|
if (location.id == id) return location;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
13
lib/features/locations/domain/plant_location.dart
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
/// Ein benannter Stellplatz im Haushalt, z. B. "Wohnzimmer-Fensterbank".
|
||||||
|
///
|
||||||
|
/// In V3 kommt hier die Standort-Analyse dazu (Foto, Licht-Einschätzung)
|
||||||
|
/// sowie eine Eignungs-Bewertung pro zugeordneter Pflanze.
|
||||||
|
@immutable
|
||||||
|
class PlantLocation {
|
||||||
|
const PlantLocation({required this.id, required this.name});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
}
|
||||||
87
lib/features/locations/presentation/locations_screen.dart
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../../core/widgets/app_drawer.dart';
|
||||||
|
import '../../../l10n/generated/app_localizations.dart';
|
||||||
|
import '../../plants/data/plants_provider.dart';
|
||||||
|
import '../data/locations_provider.dart';
|
||||||
|
|
||||||
|
class LocationsScreen extends ConsumerWidget {
|
||||||
|
const LocationsScreen({super.key});
|
||||||
|
|
||||||
|
Future<void> _addLocation(BuildContext context, WidgetRef ref) async {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final controller = TextEditingController();
|
||||||
|
final name = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (dialogContext) => AlertDialog(
|
||||||
|
title: Text(l10n.addLocation),
|
||||||
|
content: TextField(
|
||||||
|
controller: controller,
|
||||||
|
autofocus: true,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: l10n.locationNameLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onSubmitted: (value) => Navigator.pop(dialogContext, value),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
|
child: Text(l10n.cancel),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.pop(dialogContext, controller.text),
|
||||||
|
child: Text(l10n.save),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (name != null && name.trim().isNotEmpty) {
|
||||||
|
ref.read(locationsProvider.notifier).addLocation(name.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final locations = ref.watch(locationsProvider);
|
||||||
|
final plants = ref.watch(plantsProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(l10n.locationsTitle)),
|
||||||
|
drawer: const AppDrawer(),
|
||||||
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
onPressed: () => _addLocation(context, ref),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(l10n.addLocation),
|
||||||
|
),
|
||||||
|
body: locations.isEmpty
|
||||||
|
? Center(
|
||||||
|
child: Text(
|
||||||
|
l10n.locationsEmpty,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView.separated(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 8, 0, 96),
|
||||||
|
itemCount: locations.length,
|
||||||
|
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final location = locations[index];
|
||||||
|
final plantCount = plants
|
||||||
|
.where((plant) => plant.locationId == location.id)
|
||||||
|
.length;
|
||||||
|
return ListTile(
|
||||||
|
leading: const Icon(Icons.place),
|
||||||
|
title: Text(
|
||||||
|
location.name,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
subtitle: Text(l10n.locationPlantCount(plantCount)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
102
lib/features/plants/data/plants_provider.dart
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../today/domain/due_task.dart';
|
||||||
|
import '../domain/plant.dart';
|
||||||
|
|
||||||
|
/// In-Memory-Datenhaltung mit Demo-Daten.
|
||||||
|
///
|
||||||
|
/// Wird im Firebase-Block durch ein Firestore-Repository ersetzt –
|
||||||
|
/// die Provider-Schnittstelle nach außen bleibt dabei gleich.
|
||||||
|
class PlantsNotifier extends Notifier<List<Plant>> {
|
||||||
|
@override
|
||||||
|
List<Plant> build() {
|
||||||
|
final today = DateTime.now();
|
||||||
|
DateTime daysAgo(int days) =>
|
||||||
|
DateTime(today.year, today.month, today.day - days);
|
||||||
|
|
||||||
|
return [
|
||||||
|
Plant(
|
||||||
|
id: 'demo-1',
|
||||||
|
nickname: 'Monstera',
|
||||||
|
species: 'Monstera deliciosa (Fensterblatt)',
|
||||||
|
locationId: 'loc-wohnzimmer',
|
||||||
|
description:
|
||||||
|
'Beliebte Zimmerpflanze mit großen, geschlitzten Blättern. Mag helle Standorte ohne direkte Mittagssonne.',
|
||||||
|
careNotes:
|
||||||
|
'Erde zwischen den Wassergaben leicht antrocknen lassen. Staunässe vermeiden.',
|
||||||
|
wateringIntervalDays: 7,
|
||||||
|
fertilizingIntervalDays: 28,
|
||||||
|
lastWatered: daysAgo(8),
|
||||||
|
lastFertilized: daysAgo(14),
|
||||||
|
),
|
||||||
|
Plant(
|
||||||
|
id: 'demo-2',
|
||||||
|
nickname: 'Bogenhanf',
|
||||||
|
species: 'Sansevieria trifasciata',
|
||||||
|
locationId: 'loc-schlafzimmer',
|
||||||
|
description:
|
||||||
|
'Sehr pflegeleichte Pflanze mit aufrechten, festen Blättern. Verzeiht auch mal vergessenes Gießen.',
|
||||||
|
careNotes: 'Nur sparsam gießen, verträgt Trockenheit gut.',
|
||||||
|
wateringIntervalDays: 14,
|
||||||
|
fertilizingIntervalDays: 42,
|
||||||
|
lastWatered: daysAgo(14),
|
||||||
|
lastFertilized: daysAgo(20),
|
||||||
|
),
|
||||||
|
Plant(
|
||||||
|
id: 'demo-3',
|
||||||
|
nickname: 'Orchidee',
|
||||||
|
species: 'Phalaenopsis (Schmetterlingsorchidee)',
|
||||||
|
locationId: 'loc-kueche',
|
||||||
|
description:
|
||||||
|
'Klassische Fensterbank-Orchidee mit langen Blütenrispen. Mag helle, warme Plätze.',
|
||||||
|
careNotes: 'Einmal pro Woche tauchen statt gießen. Kein Wasser im Herz der Pflanze stehen lassen.',
|
||||||
|
wateringIntervalDays: 7,
|
||||||
|
fertilizingIntervalDays: 21,
|
||||||
|
lastWatered: daysAgo(3),
|
||||||
|
lastFertilized: daysAgo(10),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
void addPlant(Plant plant) {
|
||||||
|
state = [...state, plant];
|
||||||
|
}
|
||||||
|
|
||||||
|
void updatePlant(Plant updated) {
|
||||||
|
state = [
|
||||||
|
for (final plant in state)
|
||||||
|
if (plant.id == updated.id) updated else plant,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
void removePlant(String plantId) {
|
||||||
|
state = state.where((plant) => plant.id != plantId).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bestätigt eine Aufgabe: setzt das Erledigungsdatum, wodurch sich die
|
||||||
|
/// nächste Fälligkeit automatisch neu berechnet (Intervall-Modell).
|
||||||
|
void confirmTask(String plantId, CareTaskType type, {DateTime? when}) {
|
||||||
|
final now = when ?? DateTime.now();
|
||||||
|
state = [
|
||||||
|
for (final plant in state)
|
||||||
|
if (plant.id == plantId)
|
||||||
|
switch (type) {
|
||||||
|
CareTaskType.watering => plant.copyWith(lastWatered: now),
|
||||||
|
CareTaskType.fertilizing => plant.copyWith(lastFertilized: now),
|
||||||
|
}
|
||||||
|
else
|
||||||
|
plant,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final plantsProvider =
|
||||||
|
NotifierProvider<PlantsNotifier, List<Plant>>(PlantsNotifier.new);
|
||||||
|
|
||||||
|
final plantByIdProvider = Provider.family<Plant?, String>((ref, id) {
|
||||||
|
final plants = ref.watch(plantsProvider);
|
||||||
|
for (final plant in plants) {
|
||||||
|
if (plant.id == id) return plant;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
71
lib/features/plants/domain/plant.dart
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
/// Eine Pflanze im Haushalt.
|
||||||
|
///
|
||||||
|
/// Das Modell ist bewusst haushaltszentriert gedacht: Sobald das
|
||||||
|
/// Firebase-Backend angebunden ist, leben Pflanzen unter
|
||||||
|
/// `households/{householdId}/plants/{plantId}`.
|
||||||
|
@immutable
|
||||||
|
class Plant {
|
||||||
|
const Plant({
|
||||||
|
required this.id,
|
||||||
|
required this.nickname,
|
||||||
|
required this.species,
|
||||||
|
required this.wateringIntervalDays,
|
||||||
|
required this.fertilizingIntervalDays,
|
||||||
|
this.locationId,
|
||||||
|
this.description = '',
|
||||||
|
this.careNotes = '',
|
||||||
|
this.lastWatered,
|
||||||
|
this.lastFertilized,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// Vom Nutzer vergebener Name, z. B. "Omas Monstera".
|
||||||
|
final String nickname;
|
||||||
|
|
||||||
|
/// Pflanzenart (deutsch und/oder botanisch). Wird später von der
|
||||||
|
/// PlantNet-Erkennung befüllt, kann aber auch frei eingegeben werden.
|
||||||
|
final String species;
|
||||||
|
|
||||||
|
/// Referenz auf einen Stellplatz des Haushalts (optional).
|
||||||
|
final String? locationId;
|
||||||
|
|
||||||
|
/// Beschreibung und Pflegehinweise – werden später von Claude erzeugt.
|
||||||
|
final String description;
|
||||||
|
final String careNotes;
|
||||||
|
|
||||||
|
final int wateringIntervalDays;
|
||||||
|
final int fertilizingIntervalDays;
|
||||||
|
|
||||||
|
/// `null` bedeutet: noch nie erledigt → Aufgabe ist sofort fällig.
|
||||||
|
final DateTime? lastWatered;
|
||||||
|
final DateTime? lastFertilized;
|
||||||
|
|
||||||
|
Plant copyWith({
|
||||||
|
String? nickname,
|
||||||
|
String? species,
|
||||||
|
String? Function()? locationId,
|
||||||
|
String? description,
|
||||||
|
String? careNotes,
|
||||||
|
int? wateringIntervalDays,
|
||||||
|
int? fertilizingIntervalDays,
|
||||||
|
DateTime? lastWatered,
|
||||||
|
DateTime? lastFertilized,
|
||||||
|
}) {
|
||||||
|
return Plant(
|
||||||
|
id: id,
|
||||||
|
nickname: nickname ?? this.nickname,
|
||||||
|
species: species ?? this.species,
|
||||||
|
locationId: locationId != null ? locationId() : this.locationId,
|
||||||
|
description: description ?? this.description,
|
||||||
|
careNotes: careNotes ?? this.careNotes,
|
||||||
|
wateringIntervalDays: wateringIntervalDays ?? this.wateringIntervalDays,
|
||||||
|
fertilizingIntervalDays:
|
||||||
|
fertilizingIntervalDays ?? this.fertilizingIntervalDays,
|
||||||
|
lastWatered: lastWatered ?? this.lastWatered,
|
||||||
|
lastFertilized: lastFertilized ?? this.lastFertilized,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
170
lib/features/plants/presentation/plant_detail_screen.dart
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../../../core/router/app_router.dart';
|
||||||
|
import '../../../l10n/generated/app_localizations.dart';
|
||||||
|
import '../../locations/data/locations_provider.dart';
|
||||||
|
import '../data/plants_provider.dart';
|
||||||
|
|
||||||
|
class PlantDetailScreen extends ConsumerWidget {
|
||||||
|
const PlantDetailScreen({super.key, required this.plantId});
|
||||||
|
|
||||||
|
final String plantId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final plant = ref.watch(plantByIdProvider(plantId));
|
||||||
|
|
||||||
|
if (plant == null) {
|
||||||
|
// Pflanze wurde gelöscht, während der Screen offen war.
|
||||||
|
return Scaffold(appBar: AppBar(), body: const SizedBox.shrink());
|
||||||
|
}
|
||||||
|
|
||||||
|
final location = ref.watch(locationByIdProvider(plant.locationId));
|
||||||
|
final locale = Localizations.localeOf(context).toString();
|
||||||
|
final dateFormat = DateFormat('d. MMMM y', locale);
|
||||||
|
String formatDate(DateTime? date) =>
|
||||||
|
date != null ? dateFormat.format(date) : l10n.never;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(plant.nickname),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
tooltip: l10n.plantDetailTitle,
|
||||||
|
onPressed: () => context.push(AppRoutes.plantEdit, extra: plant),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete_outline),
|
||||||
|
tooltip: l10n.delete,
|
||||||
|
onPressed: () async {
|
||||||
|
final confirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (dialogContext) => AlertDialog(
|
||||||
|
title: Text(l10n.deletePlantQuestion(plant.nickname)),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(dialogContext, false),
|
||||||
|
child: Text(l10n.cancel),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.pop(dialogContext, true),
|
||||||
|
child: Text(l10n.delete),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (confirmed == true && context.mounted) {
|
||||||
|
ref.read(plantsProvider.notifier).removePlant(plant.id);
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
_InfoTile(
|
||||||
|
icon: Icons.local_florist,
|
||||||
|
label: l10n.speciesLabel,
|
||||||
|
value: plant.species,
|
||||||
|
),
|
||||||
|
_InfoTile(
|
||||||
|
icon: Icons.place,
|
||||||
|
label: l10n.locationLabel,
|
||||||
|
value: location?.name ?? l10n.locationNone,
|
||||||
|
),
|
||||||
|
const Divider(height: 32),
|
||||||
|
_InfoTile(
|
||||||
|
icon: Icons.water_drop,
|
||||||
|
label: l10n.wateringEvery(plant.wateringIntervalDays),
|
||||||
|
value: l10n.lastWatered(formatDate(plant.lastWatered)),
|
||||||
|
),
|
||||||
|
_InfoTile(
|
||||||
|
icon: Icons.compost,
|
||||||
|
label: l10n.fertilizingEvery(plant.fertilizingIntervalDays),
|
||||||
|
value: l10n.lastFertilized(formatDate(plant.lastFertilized)),
|
||||||
|
),
|
||||||
|
if (plant.description.isNotEmpty) ...[
|
||||||
|
const Divider(height: 32),
|
||||||
|
Text(
|
||||||
|
plant.description,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (plant.careNotes.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.tips_and_updates,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
plant.careNotes,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InfoTile extends StatelessWidget {
|
||||||
|
const _InfoTile({
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: theme.colorScheme.primary),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(label, style: theme.textTheme.titleMedium),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
187
lib/features/plants/presentation/plant_form_screen.dart
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../../l10n/generated/app_localizations.dart';
|
||||||
|
import '../../locations/data/locations_provider.dart';
|
||||||
|
import '../data/plants_provider.dart';
|
||||||
|
import '../domain/plant.dart';
|
||||||
|
|
||||||
|
/// Manuelles Anlegen und Bearbeiten einer Pflanze.
|
||||||
|
///
|
||||||
|
/// Im KI-Block von V1 wird dieser Screen um den Foto-Flow ergänzt:
|
||||||
|
/// Foto → PlantNet-Erkennung → Claude befüllt Art, Beschreibung und
|
||||||
|
/// Intervall-Vorschläge vor – der Nutzer bestätigt hier nur noch.
|
||||||
|
class PlantFormScreen extends ConsumerStatefulWidget {
|
||||||
|
const PlantFormScreen({super.key, this.existing});
|
||||||
|
|
||||||
|
final Plant? existing;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<PlantFormScreen> createState() => _PlantFormScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlantFormScreenState extends ConsumerState<PlantFormScreen> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late final TextEditingController _nicknameController;
|
||||||
|
late final TextEditingController _speciesController;
|
||||||
|
late final TextEditingController _wateringController;
|
||||||
|
late final TextEditingController _fertilizingController;
|
||||||
|
String? _locationId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final existing = widget.existing;
|
||||||
|
_nicknameController = TextEditingController(text: existing?.nickname ?? '');
|
||||||
|
_speciesController = TextEditingController(text: existing?.species ?? '');
|
||||||
|
_wateringController = TextEditingController(
|
||||||
|
text: existing?.wateringIntervalDays.toString() ?? '7');
|
||||||
|
_fertilizingController = TextEditingController(
|
||||||
|
text: existing?.fertilizingIntervalDays.toString() ?? '28');
|
||||||
|
_locationId = existing?.locationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nicknameController.dispose();
|
||||||
|
_speciesController.dispose();
|
||||||
|
_wateringController.dispose();
|
||||||
|
_fertilizingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _save() {
|
||||||
|
if (!_formKey.currentState!.validate()) return;
|
||||||
|
|
||||||
|
final notifier = ref.read(plantsProvider.notifier);
|
||||||
|
final existing = widget.existing;
|
||||||
|
if (existing != null) {
|
||||||
|
notifier.updatePlant(existing.copyWith(
|
||||||
|
nickname: _nicknameController.text.trim(),
|
||||||
|
species: _speciesController.text.trim(),
|
||||||
|
locationId: () => _locationId,
|
||||||
|
wateringIntervalDays: int.parse(_wateringController.text.trim()),
|
||||||
|
fertilizingIntervalDays: int.parse(_fertilizingController.text.trim()),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
notifier.addPlant(Plant(
|
||||||
|
id: 'plant-${DateTime.now().millisecondsSinceEpoch}',
|
||||||
|
nickname: _nicknameController.text.trim(),
|
||||||
|
species: _speciesController.text.trim(),
|
||||||
|
locationId: _locationId,
|
||||||
|
wateringIntervalDays: int.parse(_wateringController.text.trim()),
|
||||||
|
fertilizingIntervalDays: int.parse(_fertilizingController.text.trim()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateRequired(String? value) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
if (value == null || value.trim().isEmpty) return l10n.requiredField;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateInterval(String? value) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final parsed = int.tryParse(value?.trim() ?? '');
|
||||||
|
if (parsed == null || parsed <= 0) return l10n.invalidNumber;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final locations = ref.watch(locationsProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
widget.existing != null ? l10n.plantDetailTitle : l10n.addPlant),
|
||||||
|
),
|
||||||
|
body: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: _nicknameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: l10n.nicknameLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
validator: _validateRequired,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _speciesController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: l10n.speciesLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
validator: _validateRequired,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
DropdownButtonFormField<String?>(
|
||||||
|
initialValue: _locationId,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: l10n.locationLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem<String?>(
|
||||||
|
value: null,
|
||||||
|
child: Text(l10n.locationNone),
|
||||||
|
),
|
||||||
|
for (final location in locations)
|
||||||
|
DropdownMenuItem<String?>(
|
||||||
|
value: location.id,
|
||||||
|
child: Text(location.name),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) => setState(() => _locationId = value),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _wateringController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: l10n.wateringIntervalLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
prefixIcon: const Icon(Icons.water_drop),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
validator: _validateInterval,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _fertilizingController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: l10n.fertilizingIntervalLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
prefixIcon: const Icon(Icons.compost),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
validator: _validateInterval,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: _save,
|
||||||
|
icon: const Icon(Icons.check),
|
||||||
|
label: Text(l10n.save),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
child: Text(l10n.cancel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
92
lib/features/plants/presentation/plants_screen.dart
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../../core/router/app_router.dart';
|
||||||
|
import '../../../core/widgets/app_drawer.dart';
|
||||||
|
import '../../../l10n/generated/app_localizations.dart';
|
||||||
|
import '../../locations/data/locations_provider.dart';
|
||||||
|
import '../data/plants_provider.dart';
|
||||||
|
|
||||||
|
class PlantsScreen extends ConsumerWidget {
|
||||||
|
const PlantsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final plants = ref.watch(plantsProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(l10n.plantsTitle)),
|
||||||
|
drawer: const AppDrawer(),
|
||||||
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
onPressed: () => context.push(AppRoutes.plantAdd),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(l10n.addPlant),
|
||||||
|
),
|
||||||
|
body: plants.isEmpty
|
||||||
|
? _EmptyView(l10n: l10n)
|
||||||
|
: ListView.separated(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 8, 0, 96),
|
||||||
|
itemCount: plants.length,
|
||||||
|
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final plant = plants[index];
|
||||||
|
final location =
|
||||||
|
ref.watch(locationByIdProvider(plant.locationId));
|
||||||
|
return ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
radius: 24,
|
||||||
|
child: const Icon(Icons.local_florist),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
plant.nickname,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
location != null
|
||||||
|
? '${plant.species}\n${location.name}'
|
||||||
|
: plant.species,
|
||||||
|
),
|
||||||
|
isThreeLine: location != null,
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
onTap: () => context.push('/plants/${plant.id}'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EmptyView extends StatelessWidget {
|
||||||
|
const _EmptyView({required this.l10n});
|
||||||
|
|
||||||
|
final AppLocalizations l10n;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(32),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.local_florist, size: 72),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
l10n.plantsEmpty,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
l10n.plantsEmptyHint,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
113
lib/features/settings/presentation/settings_screen.dart
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../../core/settings/settings_provider.dart';
|
||||||
|
import '../../../core/widgets/app_drawer.dart';
|
||||||
|
import '../../../l10n/generated/app_localizations.dart';
|
||||||
|
|
||||||
|
class SettingsScreen extends ConsumerWidget {
|
||||||
|
const SettingsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final settings = ref.watch(settingsProvider);
|
||||||
|
final notifier = ref.read(settingsProvider.notifier);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(l10n.settingsTitle)),
|
||||||
|
drawer: const AppDrawer(),
|
||||||
|
body: ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
_SectionTitle(l10n.settingsAppearance),
|
||||||
|
SegmentedButton<ThemeMode>(
|
||||||
|
segments: [
|
||||||
|
ButtonSegment(
|
||||||
|
value: ThemeMode.system,
|
||||||
|
label: Text(l10n.themeSystem),
|
||||||
|
icon: const Icon(Icons.brightness_auto),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: ThemeMode.light,
|
||||||
|
label: Text(l10n.themeLight),
|
||||||
|
icon: const Icon(Icons.light_mode),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: ThemeMode.dark,
|
||||||
|
label: Text(l10n.themeDark),
|
||||||
|
icon: const Icon(Icons.dark_mode),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: {settings.themeMode},
|
||||||
|
onSelectionChanged: (selection) =>
|
||||||
|
notifier.setThemeMode(selection.first),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_SectionTitle(l10n.settingsTextSize),
|
||||||
|
SegmentedButton<AppTextSize>(
|
||||||
|
segments: [
|
||||||
|
ButtonSegment(
|
||||||
|
value: AppTextSize.normal,
|
||||||
|
label: Text(l10n.textSizeNormal),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: AppTextSize.large,
|
||||||
|
label: Text(l10n.textSizeLarge),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: AppTextSize.extraLarge,
|
||||||
|
label: Text(l10n.textSizeXLarge),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: {settings.textSize},
|
||||||
|
onSelectionChanged: (selection) =>
|
||||||
|
notifier.setTextSize(selection.first),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(l10n.settingsHighContrast),
|
||||||
|
value: settings.highContrast,
|
||||||
|
onChanged: notifier.setHighContrast,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
const Divider(height: 32),
|
||||||
|
_SectionTitle(l10n.settingsReminderTime),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
leading: const Icon(Icons.notifications_active),
|
||||||
|
title: Text(
|
||||||
|
settings.reminderTime.format(context),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
subtitle: Text(l10n.settingsReminderTimeHint),
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
onTap: () async {
|
||||||
|
final picked = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: settings.reminderTime,
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
notifier.setReminderTime(picked);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SectionTitle extends StatelessWidget {
|
||||||
|
const _SectionTitle(this.title);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
|
child: Text(title, style: Theme.of(context).textTheme.titleMedium),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
lib/features/today/application/due_tasks_provider.dart
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../plants/data/plants_provider.dart';
|
||||||
|
import '../../plants/domain/plant.dart';
|
||||||
|
import '../domain/due_task.dart';
|
||||||
|
|
||||||
|
DateTime _dateOnly(DateTime dt) => DateTime(dt.year, dt.month, dt.day);
|
||||||
|
|
||||||
|
DateTime? _nextDue(DateTime? lastDone, int intervalDays, DateTime today) {
|
||||||
|
// Noch nie erledigt → sofort fällig.
|
||||||
|
if (lastDone == null) return today;
|
||||||
|
return _dateOnly(lastDone).add(Duration(days: intervalDays));
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<DueTask> _tasksForPlant(Plant plant, DateTime today) sync* {
|
||||||
|
final watering = _nextDue(plant.lastWatered, plant.wateringIntervalDays, today);
|
||||||
|
if (watering != null && !watering.isAfter(today)) {
|
||||||
|
yield DueTask(plant: plant, type: CareTaskType.watering, dueDate: watering);
|
||||||
|
}
|
||||||
|
final fertilizing =
|
||||||
|
_nextDue(plant.lastFertilized, plant.fertilizingIntervalDays, today);
|
||||||
|
if (fertilizing != null && !fertilizing.isAfter(today)) {
|
||||||
|
yield DueTask(
|
||||||
|
plant: plant, type: CareTaskType.fertilizing, dueDate: fertilizing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alle heute fälligen und überfälligen Aufgaben, Überfälliges zuerst.
|
||||||
|
final dueTasksProvider = Provider<List<DueTask>>((ref) {
|
||||||
|
final plants = ref.watch(plantsProvider);
|
||||||
|
final today = _dateOnly(DateTime.now());
|
||||||
|
|
||||||
|
final tasks = [
|
||||||
|
for (final plant in plants) ..._tasksForPlant(plant, today),
|
||||||
|
];
|
||||||
|
tasks.sort((a, b) {
|
||||||
|
final byDate = a.dueDate.compareTo(b.dueDate);
|
||||||
|
if (byDate != 0) return byDate;
|
||||||
|
return a.plant.nickname.compareTo(b.plant.nickname);
|
||||||
|
});
|
||||||
|
return tasks;
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Das nächste zukünftige Fälligkeitsdatum – für den "Alles versorgt"-Screen
|
||||||
|
/// ("Nächste Aufgabe: Freitag").
|
||||||
|
final nextDueDateProvider = Provider<DateTime?>((ref) {
|
||||||
|
final plants = ref.watch(plantsProvider);
|
||||||
|
final today = _dateOnly(DateTime.now());
|
||||||
|
|
||||||
|
DateTime? next;
|
||||||
|
for (final plant in plants) {
|
||||||
|
for (final candidate in [
|
||||||
|
_nextDue(plant.lastWatered, plant.wateringIntervalDays, today),
|
||||||
|
_nextDue(plant.lastFertilized, plant.fertilizingIntervalDays, today),
|
||||||
|
]) {
|
||||||
|
if (candidate == null || !candidate.isAfter(today)) continue;
|
||||||
|
if (next == null || candidate.isBefore(next)) next = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
25
lib/features/today/domain/due_task.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import '../../plants/domain/plant.dart';
|
||||||
|
|
||||||
|
enum CareTaskType { watering, fertilizing }
|
||||||
|
|
||||||
|
/// Eine heute fällige (oder überfällige) Pflegeaufgabe.
|
||||||
|
///
|
||||||
|
/// Aufgaben werden nicht gespeichert, sondern aus den Pflanzen berechnet:
|
||||||
|
/// Fälligkeit = letzte Erledigung + Intervall (Intervall-Modell).
|
||||||
|
@immutable
|
||||||
|
class DueTask {
|
||||||
|
const DueTask({
|
||||||
|
required this.plant,
|
||||||
|
required this.type,
|
||||||
|
required this.dueDate,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Plant plant;
|
||||||
|
final CareTaskType type;
|
||||||
|
final DateTime dueDate;
|
||||||
|
|
||||||
|
/// 0 = heute fällig, 1 = seit gestern überfällig, usw.
|
||||||
|
int overdueDays(DateTime today) => today.difference(dueDate).inDays;
|
||||||
|
}
|
||||||
176
lib/features/today/presentation/today_screen.dart
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../../../core/router/app_router.dart';
|
||||||
|
import '../../../core/widgets/app_drawer.dart';
|
||||||
|
import '../../../l10n/generated/app_localizations.dart';
|
||||||
|
import '../../plants/data/plants_provider.dart';
|
||||||
|
import '../application/due_tasks_provider.dart';
|
||||||
|
import '../domain/due_task.dart';
|
||||||
|
|
||||||
|
/// Start-Screen der App: die Tages-Checkliste.
|
||||||
|
/// Push-Erinnerungen führen später direkt hierher.
|
||||||
|
class TodayScreen extends ConsumerWidget {
|
||||||
|
const TodayScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final tasks = ref.watch(dueTasksProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(l10n.todayTitle)),
|
||||||
|
drawer: const AppDrawer(),
|
||||||
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
onPressed: () => context.push(AppRoutes.plantAdd),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(l10n.addPlant),
|
||||||
|
),
|
||||||
|
body: tasks.isEmpty ? const _AllDoneView() : _TaskListView(tasks: tasks),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TaskListView extends ConsumerWidget {
|
||||||
|
const _TaskListView({required this.tasks});
|
||||||
|
|
||||||
|
final List<DueTask> tasks;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 96),
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Text(
|
||||||
|
l10n.todayOpenTasks(tasks.length),
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
for (final task in tasks) _TaskCard(task: task),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TaskCard extends ConsumerWidget {
|
||||||
|
const _TaskCard({required this.task});
|
||||||
|
|
||||||
|
final DueTask task;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final today = DateTime.now();
|
||||||
|
final overdueDays =
|
||||||
|
task.overdueDays(DateTime(today.year, today.month, today.day));
|
||||||
|
|
||||||
|
final title = switch (task.type) {
|
||||||
|
CareTaskType.watering => l10n.taskWaterTitle(task.plant.nickname),
|
||||||
|
CareTaskType.fertilizing => l10n.taskFertilizeTitle(task.plant.nickname),
|
||||||
|
};
|
||||||
|
final icon = switch (task.type) {
|
||||||
|
CareTaskType.watering => Icons.water_drop,
|
||||||
|
CareTaskType.fertilizing => Icons.compost,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 32, color: theme.colorScheme.primary),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(title, style: theme.textTheme.titleMedium),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
overdueDays > 0
|
||||||
|
? l10n.taskOverdue(overdueDays)
|
||||||
|
: l10n.taskDueToday,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: overdueDays > 0
|
||||||
|
? theme.colorScheme.error
|
||||||
|
: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
ref
|
||||||
|
.read(plantsProvider.notifier)
|
||||||
|
.confirmTask(task.plant.id, task.type);
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
..hideCurrentSnackBar()
|
||||||
|
..showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content:
|
||||||
|
Text(l10n.taskDoneConfirmation(task.plant.nickname)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.check),
|
||||||
|
label: Text(l10n.taskDone),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AllDoneView extends ConsumerWidget {
|
||||||
|
const _AllDoneView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final nextDue = ref.watch(nextDueDateProvider);
|
||||||
|
final locale = Localizations.localeOf(context).toString();
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(32),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
size: 72,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
l10n.todayAllDone,
|
||||||
|
style: theme.textTheme.headlineSmall,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
nextDue != null
|
||||||
|
? l10n.todayNextTask(
|
||||||
|
DateFormat('EEEE, d. MMMM', locale).format(nextDue))
|
||||||
|
: l10n.todayEmptyHint,
|
||||||
|
style: theme.textTheme.bodyLarge,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
123
lib/l10n/app_de.arb
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
{
|
||||||
|
"@@locale": "de",
|
||||||
|
"appTitle": "Planty",
|
||||||
|
"menuToday": "Heute",
|
||||||
|
"menuPlants": "Meine Pflanzen",
|
||||||
|
"menuLocations": "Stellplätze",
|
||||||
|
"menuHousehold": "Haushalt",
|
||||||
|
"menuSettings": "Einstellungen",
|
||||||
|
"todayTitle": "Heute",
|
||||||
|
"todayAllDone": "Alles versorgt!",
|
||||||
|
"todayEmptyHint": "Heute stehen keine Aufgaben an.",
|
||||||
|
"todayNextTask": "Nächste Aufgabe: {date}",
|
||||||
|
"@todayNextTask": {
|
||||||
|
"placeholders": {
|
||||||
|
"date": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"todayOpenTasks": "{count, plural, one{1 offene Aufgabe} other{{count} offene Aufgaben}}",
|
||||||
|
"@todayOpenTasks": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": { "type": "int" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"taskWaterTitle": "{name} gießen",
|
||||||
|
"@taskWaterTitle": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"taskFertilizeTitle": "{name} düngen",
|
||||||
|
"@taskFertilizeTitle": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"taskDueToday": "Heute fällig",
|
||||||
|
"taskOverdue": "{days, plural, one{Seit gestern überfällig} other{Seit {days} Tagen überfällig}}",
|
||||||
|
"@taskOverdue": {
|
||||||
|
"placeholders": {
|
||||||
|
"days": { "type": "int" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"taskDone": "Erledigt",
|
||||||
|
"taskDoneConfirmation": "{name} als erledigt markiert.",
|
||||||
|
"@taskDoneConfirmation": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"addPlant": "Pflanze hinzufügen",
|
||||||
|
"plantsTitle": "Meine Pflanzen",
|
||||||
|
"plantsEmpty": "Noch keine Pflanzen angelegt.",
|
||||||
|
"plantsEmptyHint": "Lege deine erste Pflanze über den Button unten an.",
|
||||||
|
"plantDetailTitle": "Pflanzenprofil",
|
||||||
|
"nicknameLabel": "Spitzname",
|
||||||
|
"speciesLabel": "Pflanzenart",
|
||||||
|
"locationLabel": "Stellplatz",
|
||||||
|
"locationNone": "Kein Stellplatz",
|
||||||
|
"wateringEvery": "Gießen: alle {days} Tage",
|
||||||
|
"@wateringEvery": {
|
||||||
|
"placeholders": {
|
||||||
|
"days": { "type": "int" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fertilizingEvery": "Düngen: alle {days} Tage",
|
||||||
|
"@fertilizingEvery": {
|
||||||
|
"placeholders": {
|
||||||
|
"days": { "type": "int" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wateringIntervalLabel": "Gieß-Intervall (Tage)",
|
||||||
|
"fertilizingIntervalLabel": "Dünge-Intervall (Tage)",
|
||||||
|
"lastWatered": "Zuletzt gegossen: {date}",
|
||||||
|
"@lastWatered": {
|
||||||
|
"placeholders": {
|
||||||
|
"date": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastFertilized": "Zuletzt gedüngt: {date}",
|
||||||
|
"@lastFertilized": {
|
||||||
|
"placeholders": {
|
||||||
|
"date": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"never": "noch nie",
|
||||||
|
"save": "Speichern",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"deletePlantQuestion": "{name} wirklich löschen?",
|
||||||
|
"@deletePlantQuestion": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requiredField": "Bitte ausfüllen",
|
||||||
|
"invalidNumber": "Bitte eine Zahl größer 0 eingeben",
|
||||||
|
"locationsTitle": "Stellplätze",
|
||||||
|
"locationsEmpty": "Noch keine Stellplätze angelegt.",
|
||||||
|
"addLocation": "Stellplatz hinzufügen",
|
||||||
|
"locationNameLabel": "Name des Stellplatzes",
|
||||||
|
"locationPlantCount": "{count, plural, =0{Keine Pflanzen} one{1 Pflanze} other{{count} Pflanzen}}",
|
||||||
|
"@locationPlantCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": { "type": "int" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"householdTitle": "Haushalt",
|
||||||
|
"householdPlaceholder": "Hier kannst du bald deinen Haushalt teilen, damit Familie oder Pflanzen-Sitter die Pflanzen mitversorgen können.",
|
||||||
|
"householdComingSoon": "Kommt in Version 2",
|
||||||
|
"settingsTitle": "Einstellungen",
|
||||||
|
"settingsAppearance": "Erscheinungsbild",
|
||||||
|
"themeSystem": "System",
|
||||||
|
"themeLight": "Hell",
|
||||||
|
"themeDark": "Dunkel",
|
||||||
|
"settingsTextSize": "Schriftgröße",
|
||||||
|
"textSizeNormal": "Normal",
|
||||||
|
"textSizeLarge": "Groß",
|
||||||
|
"textSizeXLarge": "Sehr groß",
|
||||||
|
"settingsHighContrast": "Hoher Kontrast",
|
||||||
|
"settingsReminderTime": "Erinnerungszeit",
|
||||||
|
"settingsReminderTimeHint": "Zu dieser Uhrzeit erinnert dich Planty an offene Aufgaben.",
|
||||||
|
"demoDataBanner": "Demo-Daten – werden durch deine echten Pflanzen ersetzt"
|
||||||
|
}
|
||||||
488
lib/l10n/generated/app_localizations.dart
Normal file
|
|
@ -0,0 +1,488 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
|
||||||
|
import 'app_localizations_de.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
|
/// Callers can lookup localized strings with an instance of AppLocalizations
|
||||||
|
/// returned by `AppLocalizations.of(context)`.
|
||||||
|
///
|
||||||
|
/// Applications need to include `AppLocalizations.delegate()` in their app's
|
||||||
|
/// `localizationDelegates` list, and the locales they support in the app's
|
||||||
|
/// `supportedLocales` list. For example:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// import 'generated/app_localizations.dart';
|
||||||
|
///
|
||||||
|
/// return MaterialApp(
|
||||||
|
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
/// supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
/// home: MyApplicationHome(),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Update pubspec.yaml
|
||||||
|
///
|
||||||
|
/// Please make sure to update your pubspec.yaml to include the following
|
||||||
|
/// packages:
|
||||||
|
///
|
||||||
|
/// ```yaml
|
||||||
|
/// dependencies:
|
||||||
|
/// # Internationalization support.
|
||||||
|
/// flutter_localizations:
|
||||||
|
/// sdk: flutter
|
||||||
|
/// intl: any # Use the pinned version from flutter_localizations
|
||||||
|
///
|
||||||
|
/// # Rest of dependencies
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## iOS Applications
|
||||||
|
///
|
||||||
|
/// iOS applications define key application metadata, including supported
|
||||||
|
/// locales, in an Info.plist file that is built into the application bundle.
|
||||||
|
/// To configure the locales supported by your app, you’ll need to edit this
|
||||||
|
/// file.
|
||||||
|
///
|
||||||
|
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
||||||
|
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
||||||
|
/// project’s Runner folder.
|
||||||
|
///
|
||||||
|
/// Next, select the Information Property List item, select Add Item from the
|
||||||
|
/// Editor menu, then select Localizations from the pop-up menu.
|
||||||
|
///
|
||||||
|
/// Select and expand the newly-created Localizations item then, for each
|
||||||
|
/// locale your application supports, add a new item and select the locale
|
||||||
|
/// you wish to add from the pop-up menu in the Value field. This list should
|
||||||
|
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
|
||||||
|
/// property.
|
||||||
|
abstract class AppLocalizations {
|
||||||
|
AppLocalizations(String locale)
|
||||||
|
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
||||||
|
|
||||||
|
final String localeName;
|
||||||
|
|
||||||
|
static AppLocalizations of(BuildContext context) {
|
||||||
|
return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const LocalizationsDelegate<AppLocalizations> delegate =
|
||||||
|
_AppLocalizationsDelegate();
|
||||||
|
|
||||||
|
/// A list of this localizations delegate along with the default localizations
|
||||||
|
/// delegates.
|
||||||
|
///
|
||||||
|
/// Returns a list of localizations delegates containing this delegate along with
|
||||||
|
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
|
||||||
|
/// and GlobalWidgetsLocalizations.delegate.
|
||||||
|
///
|
||||||
|
/// Additional delegates can be added by appending to this list in
|
||||||
|
/// MaterialApp. This list does not have to be used at all if a custom list
|
||||||
|
/// of delegates is preferred or required.
|
||||||
|
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
||||||
|
<LocalizationsDelegate<dynamic>>[
|
||||||
|
delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// A list of this localizations delegate's supported locales.
|
||||||
|
static const List<Locale> supportedLocales = <Locale>[Locale('de')];
|
||||||
|
|
||||||
|
/// No description provided for @appTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Planty'**
|
||||||
|
String get appTitle;
|
||||||
|
|
||||||
|
/// No description provided for @menuToday.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Heute'**
|
||||||
|
String get menuToday;
|
||||||
|
|
||||||
|
/// No description provided for @menuPlants.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Meine Pflanzen'**
|
||||||
|
String get menuPlants;
|
||||||
|
|
||||||
|
/// No description provided for @menuLocations.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Stellplätze'**
|
||||||
|
String get menuLocations;
|
||||||
|
|
||||||
|
/// No description provided for @menuHousehold.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Haushalt'**
|
||||||
|
String get menuHousehold;
|
||||||
|
|
||||||
|
/// No description provided for @menuSettings.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Einstellungen'**
|
||||||
|
String get menuSettings;
|
||||||
|
|
||||||
|
/// No description provided for @todayTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Heute'**
|
||||||
|
String get todayTitle;
|
||||||
|
|
||||||
|
/// No description provided for @todayAllDone.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Alles versorgt!'**
|
||||||
|
String get todayAllDone;
|
||||||
|
|
||||||
|
/// No description provided for @todayEmptyHint.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Heute stehen keine Aufgaben an.'**
|
||||||
|
String get todayEmptyHint;
|
||||||
|
|
||||||
|
/// No description provided for @todayNextTask.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Nächste Aufgabe: {date}'**
|
||||||
|
String todayNextTask(String date);
|
||||||
|
|
||||||
|
/// No description provided for @todayOpenTasks.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'{count, plural, one{1 offene Aufgabe} other{{count} offene Aufgaben}}'**
|
||||||
|
String todayOpenTasks(int count);
|
||||||
|
|
||||||
|
/// No description provided for @taskWaterTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'{name} gießen'**
|
||||||
|
String taskWaterTitle(String name);
|
||||||
|
|
||||||
|
/// No description provided for @taskFertilizeTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'{name} düngen'**
|
||||||
|
String taskFertilizeTitle(String name);
|
||||||
|
|
||||||
|
/// No description provided for @taskDueToday.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Heute fällig'**
|
||||||
|
String get taskDueToday;
|
||||||
|
|
||||||
|
/// No description provided for @taskOverdue.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'{days, plural, one{Seit gestern überfällig} other{Seit {days} Tagen überfällig}}'**
|
||||||
|
String taskOverdue(int days);
|
||||||
|
|
||||||
|
/// No description provided for @taskDone.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Erledigt'**
|
||||||
|
String get taskDone;
|
||||||
|
|
||||||
|
/// No description provided for @taskDoneConfirmation.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'{name} als erledigt markiert.'**
|
||||||
|
String taskDoneConfirmation(String name);
|
||||||
|
|
||||||
|
/// No description provided for @addPlant.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Pflanze hinzufügen'**
|
||||||
|
String get addPlant;
|
||||||
|
|
||||||
|
/// No description provided for @plantsTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Meine Pflanzen'**
|
||||||
|
String get plantsTitle;
|
||||||
|
|
||||||
|
/// No description provided for @plantsEmpty.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Noch keine Pflanzen angelegt.'**
|
||||||
|
String get plantsEmpty;
|
||||||
|
|
||||||
|
/// No description provided for @plantsEmptyHint.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Lege deine erste Pflanze über den Button unten an.'**
|
||||||
|
String get plantsEmptyHint;
|
||||||
|
|
||||||
|
/// No description provided for @plantDetailTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Pflanzenprofil'**
|
||||||
|
String get plantDetailTitle;
|
||||||
|
|
||||||
|
/// No description provided for @nicknameLabel.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Spitzname'**
|
||||||
|
String get nicknameLabel;
|
||||||
|
|
||||||
|
/// No description provided for @speciesLabel.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Pflanzenart'**
|
||||||
|
String get speciesLabel;
|
||||||
|
|
||||||
|
/// No description provided for @locationLabel.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Stellplatz'**
|
||||||
|
String get locationLabel;
|
||||||
|
|
||||||
|
/// No description provided for @locationNone.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Kein Stellplatz'**
|
||||||
|
String get locationNone;
|
||||||
|
|
||||||
|
/// No description provided for @wateringEvery.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Gießen: alle {days} Tage'**
|
||||||
|
String wateringEvery(int days);
|
||||||
|
|
||||||
|
/// No description provided for @fertilizingEvery.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Düngen: alle {days} Tage'**
|
||||||
|
String fertilizingEvery(int days);
|
||||||
|
|
||||||
|
/// No description provided for @wateringIntervalLabel.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Gieß-Intervall (Tage)'**
|
||||||
|
String get wateringIntervalLabel;
|
||||||
|
|
||||||
|
/// No description provided for @fertilizingIntervalLabel.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Dünge-Intervall (Tage)'**
|
||||||
|
String get fertilizingIntervalLabel;
|
||||||
|
|
||||||
|
/// No description provided for @lastWatered.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Zuletzt gegossen: {date}'**
|
||||||
|
String lastWatered(String date);
|
||||||
|
|
||||||
|
/// No description provided for @lastFertilized.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Zuletzt gedüngt: {date}'**
|
||||||
|
String lastFertilized(String date);
|
||||||
|
|
||||||
|
/// No description provided for @never.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'noch nie'**
|
||||||
|
String get never;
|
||||||
|
|
||||||
|
/// No description provided for @save.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Speichern'**
|
||||||
|
String get save;
|
||||||
|
|
||||||
|
/// No description provided for @cancel.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Abbrechen'**
|
||||||
|
String get cancel;
|
||||||
|
|
||||||
|
/// No description provided for @delete.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Löschen'**
|
||||||
|
String get delete;
|
||||||
|
|
||||||
|
/// No description provided for @deletePlantQuestion.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'{name} wirklich löschen?'**
|
||||||
|
String deletePlantQuestion(String name);
|
||||||
|
|
||||||
|
/// No description provided for @requiredField.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Bitte ausfüllen'**
|
||||||
|
String get requiredField;
|
||||||
|
|
||||||
|
/// No description provided for @invalidNumber.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Bitte eine Zahl größer 0 eingeben'**
|
||||||
|
String get invalidNumber;
|
||||||
|
|
||||||
|
/// No description provided for @locationsTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Stellplätze'**
|
||||||
|
String get locationsTitle;
|
||||||
|
|
||||||
|
/// No description provided for @locationsEmpty.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Noch keine Stellplätze angelegt.'**
|
||||||
|
String get locationsEmpty;
|
||||||
|
|
||||||
|
/// No description provided for @addLocation.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Stellplatz hinzufügen'**
|
||||||
|
String get addLocation;
|
||||||
|
|
||||||
|
/// No description provided for @locationNameLabel.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Name des Stellplatzes'**
|
||||||
|
String get locationNameLabel;
|
||||||
|
|
||||||
|
/// No description provided for @locationPlantCount.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'{count, plural, =0{Keine Pflanzen} one{1 Pflanze} other{{count} Pflanzen}}'**
|
||||||
|
String locationPlantCount(int count);
|
||||||
|
|
||||||
|
/// No description provided for @householdTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Haushalt'**
|
||||||
|
String get householdTitle;
|
||||||
|
|
||||||
|
/// No description provided for @householdPlaceholder.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Hier kannst du bald deinen Haushalt teilen, damit Familie oder Pflanzen-Sitter die Pflanzen mitversorgen können.'**
|
||||||
|
String get householdPlaceholder;
|
||||||
|
|
||||||
|
/// No description provided for @householdComingSoon.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Kommt in Version 2'**
|
||||||
|
String get householdComingSoon;
|
||||||
|
|
||||||
|
/// No description provided for @settingsTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Einstellungen'**
|
||||||
|
String get settingsTitle;
|
||||||
|
|
||||||
|
/// No description provided for @settingsAppearance.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Erscheinungsbild'**
|
||||||
|
String get settingsAppearance;
|
||||||
|
|
||||||
|
/// No description provided for @themeSystem.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'System'**
|
||||||
|
String get themeSystem;
|
||||||
|
|
||||||
|
/// No description provided for @themeLight.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Hell'**
|
||||||
|
String get themeLight;
|
||||||
|
|
||||||
|
/// No description provided for @themeDark.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Dunkel'**
|
||||||
|
String get themeDark;
|
||||||
|
|
||||||
|
/// No description provided for @settingsTextSize.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Schriftgröße'**
|
||||||
|
String get settingsTextSize;
|
||||||
|
|
||||||
|
/// No description provided for @textSizeNormal.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Normal'**
|
||||||
|
String get textSizeNormal;
|
||||||
|
|
||||||
|
/// No description provided for @textSizeLarge.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Groß'**
|
||||||
|
String get textSizeLarge;
|
||||||
|
|
||||||
|
/// No description provided for @textSizeXLarge.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Sehr groß'**
|
||||||
|
String get textSizeXLarge;
|
||||||
|
|
||||||
|
/// No description provided for @settingsHighContrast.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Hoher Kontrast'**
|
||||||
|
String get settingsHighContrast;
|
||||||
|
|
||||||
|
/// No description provided for @settingsReminderTime.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Erinnerungszeit'**
|
||||||
|
String get settingsReminderTime;
|
||||||
|
|
||||||
|
/// No description provided for @settingsReminderTimeHint.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Zu dieser Uhrzeit erinnert dich Planty an offene Aufgaben.'**
|
||||||
|
String get settingsReminderTimeHint;
|
||||||
|
|
||||||
|
/// No description provided for @demoDataBanner.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Demo-Daten – werden durch deine echten Pflanzen ersetzt'**
|
||||||
|
String get demoDataBanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppLocalizationsDelegate
|
||||||
|
extends LocalizationsDelegate<AppLocalizations> {
|
||||||
|
const _AppLocalizationsDelegate();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<AppLocalizations> load(Locale locale) {
|
||||||
|
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isSupported(Locale locale) =>
|
||||||
|
<String>['de'].contains(locale.languageCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||||
|
// Lookup logic when only language code is specified.
|
||||||
|
switch (locale.languageCode) {
|
||||||
|
case 'de':
|
||||||
|
return AppLocalizationsDe();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw FlutterError(
|
||||||
|
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
|
||||||
|
'an issue with the localizations generation tool. Please file an issue '
|
||||||
|
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||||||
|
'that was used.',
|
||||||
|
);
|
||||||
|
}
|
||||||
237
lib/l10n/generated/app_localizations_de.dart
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
// ignore: unused_import
|
||||||
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
import 'app_localizations.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
|
/// The translations for German (`de`).
|
||||||
|
class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
AppLocalizationsDe([String locale = 'de']) : super(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get appTitle => 'Planty';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuToday => 'Heute';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuPlants => 'Meine Pflanzen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuLocations => 'Stellplätze';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHousehold => 'Haushalt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Einstellungen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get todayTitle => 'Heute';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get todayAllDone => 'Alles versorgt!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get todayEmptyHint => 'Heute stehen keine Aufgaben an.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String todayNextTask(String date) {
|
||||||
|
return 'Nächste Aufgabe: $date';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String todayOpenTasks(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count offene Aufgaben',
|
||||||
|
one: '1 offene Aufgabe',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String taskWaterTitle(String name) {
|
||||||
|
return '$name gießen';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String taskFertilizeTitle(String name) {
|
||||||
|
return '$name düngen';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get taskDueToday => 'Heute fällig';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String taskOverdue(int days) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
days,
|
||||||
|
locale: localeName,
|
||||||
|
other: 'Seit $days Tagen überfällig',
|
||||||
|
one: 'Seit gestern überfällig',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get taskDone => 'Erledigt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String taskDoneConfirmation(String name) {
|
||||||
|
return '$name als erledigt markiert.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addPlant => 'Pflanze hinzufügen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get plantsTitle => 'Meine Pflanzen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get plantsEmpty => 'Noch keine Pflanzen angelegt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get plantsEmptyHint =>
|
||||||
|
'Lege deine erste Pflanze über den Button unten an.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get plantDetailTitle => 'Pflanzenprofil';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nicknameLabel => 'Spitzname';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get speciesLabel => 'Pflanzenart';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get locationLabel => 'Stellplatz';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get locationNone => 'Kein Stellplatz';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String wateringEvery(int days) {
|
||||||
|
return 'Gießen: alle $days Tage';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String fertilizingEvery(int days) {
|
||||||
|
return 'Düngen: alle $days Tage';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wateringIntervalLabel => 'Gieß-Intervall (Tage)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get fertilizingIntervalLabel => 'Dünge-Intervall (Tage)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String lastWatered(String date) {
|
||||||
|
return 'Zuletzt gegossen: $date';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String lastFertilized(String date) {
|
||||||
|
return 'Zuletzt gedüngt: $date';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get never => 'noch nie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get save => 'Speichern';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancel => 'Abbrechen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get delete => 'Löschen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String deletePlantQuestion(String name) {
|
||||||
|
return '$name wirklich löschen?';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get requiredField => 'Bitte ausfüllen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get invalidNumber => 'Bitte eine Zahl größer 0 eingeben';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get locationsTitle => 'Stellplätze';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get locationsEmpty => 'Noch keine Stellplätze angelegt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addLocation => 'Stellplatz hinzufügen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get locationNameLabel => 'Name des Stellplatzes';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String locationPlantCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count Pflanzen',
|
||||||
|
one: '1 Pflanze',
|
||||||
|
zero: 'Keine Pflanzen',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get householdTitle => 'Haushalt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get householdPlaceholder =>
|
||||||
|
'Hier kannst du bald deinen Haushalt teilen, damit Familie oder Pflanzen-Sitter die Pflanzen mitversorgen können.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get householdComingSoon => 'Kommt in Version 2';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsTitle => 'Einstellungen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsAppearance => 'Erscheinungsbild';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get themeSystem => 'System';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get themeLight => 'Hell';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get themeDark => 'Dunkel';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsTextSize => 'Schriftgröße';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get textSizeNormal => 'Normal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get textSizeLarge => 'Groß';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get textSizeXLarge => 'Sehr groß';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsHighContrast => 'Hoher Kontrast';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsReminderTime => 'Erinnerungszeit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsReminderTimeHint =>
|
||||||
|
'Zu dieser Uhrzeit erinnert dich Planty an offene Aufgaben.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get demoDataBanner =>
|
||||||
|
'Demo-Daten – werden durch deine echten Pflanzen ersetzt';
|
||||||
|
}
|
||||||
17
lib/main.dart
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import 'app.dart';
|
||||||
|
import 'core/settings/settings_provider.dart';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
runApp(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [sharedPreferencesProvider.overrideWithValue(prefs)],
|
||||||
|
child: const PlantyApp(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
671
pubspec.lock
Normal file
|
|
@ -0,0 +1,671 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "91.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.4.1"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.1"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
cli_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_config
|
||||||
|
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.15.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
|
cupertino_icons:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cupertino_icons
|
||||||
|
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.9"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.3"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_riverpod:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_riverpod
|
||||||
|
sha256: "9255e1e3ad6e38906a1b4f8287678f95f378744c5b46b1985588543f3f19046e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.2"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
go_router:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: go_router
|
||||||
|
sha256: "5922b2861e2235a3504896f0d6fa07d84141b480cf52eecd2f42cd25585a9e8a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "17.3.0"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.20.2"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.2"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.0.2"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.10"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.17"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.11.1"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.17.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.2"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.6"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.2"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
riverpod:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: riverpod
|
||||||
|
sha256: "17100416c51db7810c71a7bb2c34d1f881faa0074fd452afb0c4db6f8f126c76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.2"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.5"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.23"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_foundation
|
||||||
|
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.6"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.3"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.13"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.2"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
state_notifier:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: state_notifier
|
||||||
|
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.26.3"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.7"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.12"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.6.0"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.2.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.10.7 <4.0.0"
|
||||||
|
flutter: ">=3.38.0"
|
||||||
96
pubspec.yaml
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
name: planty
|
||||||
|
description: "Planty – App zur Verwaltung und Pflege von Pflanzen."
|
||||||
|
# The following line prevents the package from being accidentally published to
|
||||||
|
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||||
|
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
|
|
||||||
|
# The following defines the version and build number for your application.
|
||||||
|
# A version number is three numbers separated by dots, like 1.2.43
|
||||||
|
# followed by an optional build number separated by a +.
|
||||||
|
# Both the version and the builder number may be overridden in flutter
|
||||||
|
# build by specifying --build-name and --build-number, respectively.
|
||||||
|
# In Android, build-name is used as versionName while build-number used as versionCode.
|
||||||
|
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
||||||
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
||||||
|
# Read more about iOS versioning at
|
||||||
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
|
version: 1.0.0+1
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.10.7
|
||||||
|
|
||||||
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
|
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||||
|
# dependencies can be manually updated by changing the version numbers below to
|
||||||
|
# the latest version available on pub.dev. To see which dependencies have newer
|
||||||
|
# versions available, run `flutter pub outdated`.
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
# The following adds the Cupertino Icons font to your application.
|
||||||
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
cupertino_icons: ^1.0.8
|
||||||
|
flutter_riverpod: ^3.3.2
|
||||||
|
go_router: ^17.3.0
|
||||||
|
shared_preferences: ^2.5.5
|
||||||
|
intl: any
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
|
# encourage good coding practices. The lint set provided by the package is
|
||||||
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
|
# package. See that file for information about deactivating specific lint
|
||||||
|
# rules and activating additional ones.
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
|
|
||||||
|
# For information on the generic Dart part of this file, see the
|
||||||
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
# The following section is specific to Flutter packages.
|
||||||
|
flutter:
|
||||||
|
generate: true
|
||||||
|
|
||||||
|
# The following line ensures that the Material Icons font is
|
||||||
|
# included with your application, so that you can use the icons in
|
||||||
|
# the material Icons class.
|
||||||
|
uses-material-design: true
|
||||||
|
|
||||||
|
# To add assets to your application, add an assets section, like this:
|
||||||
|
# assets:
|
||||||
|
# - images/a_dot_burr.jpeg
|
||||||
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
# https://flutter.dev/to/resolution-aware-images
|
||||||
|
|
||||||
|
# For details regarding adding assets from package dependencies, see
|
||||||
|
# https://flutter.dev/to/asset-from-package
|
||||||
|
|
||||||
|
# To add custom fonts to your application, add a fonts section here,
|
||||||
|
# in this "flutter" section. Each entry in this list should have a
|
||||||
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
|
# list giving the asset and other descriptors for the font. For
|
||||||
|
# example:
|
||||||
|
# fonts:
|
||||||
|
# - family: Schyler
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/Schyler-Regular.ttf
|
||||||
|
# - asset: fonts/Schyler-Italic.ttf
|
||||||
|
# style: italic
|
||||||
|
# - family: Trajan Pro
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/TrajanPro.ttf
|
||||||
|
# - asset: fonts/TrajanPro_Bold.ttf
|
||||||
|
# weight: 700
|
||||||
|
#
|
||||||
|
# For details regarding fonts from package dependencies,
|
||||||
|
# see https://flutter.dev/to/font-from-package
|
||||||
40
test/widget_test.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:planty/app.dart';
|
||||||
|
import 'package:planty/core/settings/settings_provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
Future<Widget> buildTestApp() async {
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return ProviderScope(
|
||||||
|
overrides: [sharedPreferencesProvider.overrideWithValue(prefs)],
|
||||||
|
child: const PlantyApp(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('App startet mit der Heute-Checkliste', (tester) async {
|
||||||
|
await tester.pumpWidget(await buildTestApp());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Demo-Daten enthalten überfällige Aufgaben → Checkliste sichtbar.
|
||||||
|
expect(find.text('Heute'), findsOneWidget);
|
||||||
|
expect(find.textContaining('offene Aufgabe'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Aufgabe bestätigen entfernt sie aus der Liste', (tester) async {
|
||||||
|
await tester.pumpWidget(await buildTestApp());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final doneButtons = find.text('Erledigt');
|
||||||
|
final countBefore = tester.widgetList(doneButtons).length;
|
||||||
|
expect(countBefore, greaterThan(0));
|
||||||
|
|
||||||
|
await tester.tap(doneButtons.first);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(tester.widgetList(find.text('Erledigt')).length, countBefore - 1);
|
||||||
|
});
|
||||||
|
}
|
||||||