Compare commits

...

15 Commits

Author SHA1 Message Date
Thomas Hassenstein bc0cba7286 Dateien nach "/" hochladen
pubspec.yaml aufräumen von kommentaren
2024-01-10 19:22:59 +01:00
Thomas Hassenstein 388e2deb61 test/unit_test.dart gelöscht
Deleted unused Tests
2024-01-10 19:21:40 +01:00
Thomas Hassenstein 06e62dc7e5 lib/textscan.dart gelöscht
Auskommentierte Klasse für OCR löschen
2024-01-10 18:26:17 +01:00
Thomas Hassenstein d409005e4c Dateien nach "test" hochladen
Update Test Cases
2024-01-10 18:24:39 +01:00
Thomas Hassenstein 83b02eca1a test/navigation_test.dart gelöscht
Löschen von Navigation_Tests (Aufräumen auch von auskommentierten Code)
2024-01-10 18:24:07 +01:00
Thomas Hassenstein e3d9c8666c Dateien nach "test" hochladen
Test Anpassung Kommentare
2024-01-10 10:29:35 +01:00
Thomas Hassenstein 684d7fc15c Dateien nach "test" hochladen
Tests überarbeiten und auskommentieren dort wo Firebase Mocking gebraucht wird (nicht hinbekommen wie das funktioniert)
2024-01-10 10:25:53 +01:00
Thomas Hassenstein b89ff0770e Dateien nach "assets/images" hochladen
Image Crop
2024-01-10 09:03:57 +01:00
Thomas Hassenstein 918b51258d README.md aktualisiert
Bild 1Position
2024-01-10 09:01:49 +01:00
Thomas Hassenstein 5d7eb7ec3b Dateien nach "assets/images" hochladen
Image Position
2024-01-10 09:00:42 +01:00
Thomas Hassenstein 6fc8322202 Dateien nach "assets/images" hochladen
Image Crop
2024-01-10 08:59:47 +01:00
Thomas Hassenstein daec63e58b README.md aktualisiert
Relative Bildpfade angepasst
2024-01-10 08:57:57 +01:00
Thomas Hassenstein f7f1d8855a README.md aktualisiert
Readme.aktualisiert um Ablauf zu zeigen
2024-01-10 08:54:48 +01:00
Thomas Hassenstein 81105d0067 Dateien nach "assets/images" hochladen
Zusätzliche Bilder hinzugefügt
2024-01-10 08:48:46 +01:00
Thomas Hassenstein d1a83c8a9d Menü erstellt
Menü Navigation bereitgestellt
Rezept Funktionalität eingebaut
Test Anfänge bereitgestellt
Readme.md befüllt
2024-01-10 07:20:15 +01:00
28 changed files with 1031 additions and 221 deletions

View File

@ -1,15 +1,27 @@
# kochkomplize
Helfer zur Digitalisierung von alten Kochrezepten.
**Bild/Skizze:**
Ein Bild wie die Applikation aussehen soll findet man unter assets/images mit dem Namen skizze.png
**Bild/Ablauf:**
![Startseite](assets/images/Kochkomplize_Startseite.PNG)
![Rezept Übersicht](assets/images/Kochkomplize_Rezept_Overview.PNG)
![Rezept anlegen](assets/images/Kochkomplize_Rezept_anlegen.PNG)
![Rezept löschen](assets/images/Kochkomplize_Löschen.PNG)
**Funktionalität:**
1. Die App soll grundsätzlich im Browser als auch auf Mobilen Endgeräten laufen. (entweder iOS oder Android)
2. Es soll möglich sein Rezepte abzuspeichern und neue anzulegen.
3. Man soll Bilder für Rezepte hochladen können.
4. Es soll möglich sein alte handgeschriebene Texte mit Texterkennung auf Bildern zu importieren und zu formatieren.
5. Rezepte sollen beliebig sortiert und ergänzt werden können.
6. Falls der Umfang der App nicht umfangreich genug sein sollte dann noch die Möglichkeit von anderen Nutzern Rezepte anzuschauen.
4. Es soll möglich sein alte handgeschriebene Texte Bildern zu importieren.
5. Rezepte sollen gelöscht werden können.
6. Bonus: Texterkennung wenn die Packages es auch für Web zulassen.
**Eingesetzte Technologien:**
Flutter
Dart
Firebase

View File

@ -1,28 +1,5 @@
# 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

View File

@ -45,7 +45,7 @@ android {
applicationId "com.example.kochkomplize"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@ -0,0 +1,30 @@
{
"project_info": {
"project_number": "460915828512",
"firebase_url": "https://kochkomplize-default-rtdb.europe-west1.firebasedatabase.app",
"project_id": "kochkomplize",
"storage_bucket": "kochkomplize.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:460915828512:android:73914cecf1d06ff0771945",
"android_client_info": {
"package_name": "com.example.kochkomplize"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBzHwDZscVmyTuyJx9darx_8KkhpiCcNNQ"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "2"
}

View File

@ -30,4 +30,5 @@
android:name="flutterEmbedding"
android:value="2" />
</application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 KiB

View File

@ -0,0 +1,30 @@
<?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>API_KEY</key>
<string>AIzaSyDn1MhJq7gjUtyFJaewgC425jwJmAVCrDk</string>
<key>GCM_SENDER_ID</key>
<string>460915828512</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.example.kochkomplize</string>
<key>PROJECT_ID</key>
<string>kochkomplize</string>
<key>STORAGE_BUCKET</key>
<string>kochkomplize.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:460915828512:ios:ec4610516aa6bb0d771945</string>
</dict>
</plist>

View File

@ -45,5 +45,9 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSPhotoLibraryUsageDescription</key>
<string>Wir benötigen Zugriff auf Ihre Fotos, um Bilder auszuwählen.</string>
<key>NSCameraUsageDescription</key>
<string>Wir benötigen Zugriff auf Ihre Kamera, um Fotos aufzunehmen.</string>
</dict>
</plist>

View File

@ -0,0 +1,7 @@
{
"file_generated_by": "FlutterFire CLI",
"purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory",
"GOOGLE_APP_ID": "1:460915828512:ios:ec4610516aa6bb0d771945",
"FIREBASE_PROJECT_ID": "kochkomplize",
"GCM_SENDER_ID": "460915828512"
}

View File

@ -0,0 +1,68 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for macos - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyAACZYqWGQre2FqeXxC5qid1E0K2yuXD5Q',
appId: '1:460915828512:web:4db5d6cfa1e11481771945',
messagingSenderId: '460915828512',
projectId: 'kochkomplize',
authDomain: 'kochkomplize.firebaseapp.com',
databaseURL: 'https://kochkomplize-default-rtdb.europe-west1.firebasedatabase.app',
storageBucket: 'kochkomplize.appspot.com',
measurementId: 'G-0W73YW74KY',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyBzHwDZscVmyTuyJx9darx_8KkhpiCcNNQ',
appId: '1:460915828512:android:73914cecf1d06ff0771945',
messagingSenderId: '460915828512',
projectId: 'kochkomplize',
databaseURL: 'https://kochkomplize-default-rtdb.europe-west1.firebasedatabase.app',
storageBucket: 'kochkomplize.appspot.com',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyDn1MhJq7gjUtyFJaewgC425jwJmAVCrDk',
appId: '1:460915828512:ios:ec4610516aa6bb0d771945',
messagingSenderId: '460915828512',
projectId: 'kochkomplize',
databaseURL: 'https://kochkomplize-default-rtdb.europe-west1.firebasedatabase.app',
storageBucket: 'kochkomplize.appspot.com',
iosBundleId: 'com.example.kochkomplize',
);
}

View File

@ -1,12 +0,0 @@
import 'package:flutter/material.dart';
import 'menu_content.dart';
//todo implement logik for image import
class ImportContent implements MenuContent {
@override
Widget build(BuildContext context) {
// Hier wird der Inhalt für Menüpunkt 2 erstellt
return const Text('Inhalt für Menüpunkt 2 - Import');
}
}

View File

@ -1,9 +1,18 @@
import 'package:flutter/material.dart';
import 'startpage.dart';
import 'recipeformpage.dart';
import 'menu.dart';
import 'menu_content.dart';
import 'recipes.dart';
import 'recipesoverview.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
void main() {
runApp(const MyApp());
}
@ -35,18 +44,32 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> {
MenuContent? _selectedMenuContent;
@override
void initState() {
super.initState();
_selectedMenuContent = Startpage(); // Initialisiere mit Startseite
}
void _onMenuItemSelected(String menuItem) {
setState(() {
switch (menuItem) {
case 'Meine Rezepte':
_selectedMenuContent = RecipesContent();
_selectedMenuContent = RecipesOverview();
break;
case 'Startseite':
_selectedMenuContent = Startpage();
break;
// case 'Texterkennung Bild':
// _selectedMenuContent = TextScan();
// break;
}
});
}
void _importImage() {
//todo
void _addRecipe() {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const Recipeformpage()),
);
}
@override
@ -58,11 +81,14 @@ class _MyHomePageState extends State<MyHomePage> {
),
drawer: Menu(onMenuItemSelected: _onMenuItemSelected),
body: _selectedMenuContent?.build(context) ?? Container(),
floatingActionButton: FloatingActionButton(
onPressed: _importImage,
tooltip: 'Import',
floatingActionButton: _selectedMenuContent is RecipesOverview
? FloatingActionButton(
onPressed: _addRecipe,
tooltip: 'Rezept hinzufügen',
child: const Icon(Icons.add),
),
)
: null, // Kein Button für andere Seiten
);
}
}

View File

@ -20,11 +20,11 @@ class Menu extends StatelessWidget {
child: Text('Hauptmenü', style: TextStyle(color: Colors.black, fontSize: 20.0)),
),
),
_buildListTile(context, 'Startseite'),
_buildListTile(context, 'Meine Rezepte'),
_buildListTile(context, 'Rezept importieren'),
_buildListTile(context, 'Texterkennung Bild'),
_buildListTile(context, '(Beta) Rezepte finden'),
// Füge hier weitere ListTiles für zusätzliche Menüpunkte hinzu
//_buildListTile(context, 'Rezept importieren'),
//_buildListTile(context, 'Texterkennung Bild'),
//_buildListTile(context, '(Beta) Rezepte finden'),
],
),
);

View File

@ -1,26 +0,0 @@
import 'package:flutter/material.dart';
class RecipeDetailPage extends StatelessWidget {
final Map<String, dynamic> recipe;
const RecipeDetailPage({Key? key, required this.recipe}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(recipe["title"]),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.network(recipe["image"]),
Text(recipe["title"]),
// Weitere Details oder Inhalte für die Detailseite
],
),
),
);
}
}

View File

@ -0,0 +1,110 @@
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_storage/firebase_storage.dart';
class RecipeDetailPage extends StatelessWidget {
final String recipeId;
const RecipeDetailPage({Key? key, required this.recipeId, required Map recipe}) : super(key: key);
Future<void> _showDeleteDialog(BuildContext context) async {
bool confirmDelete = await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Rezept löschen'),
content: const Text('Sind Sie sicher, dass Sie dieses Rezept löschen möchten?'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Löschen'),
),
],
);
},
) ?? false; // Dialog-Abbruch auch als "Nicht löschen" werten
if (confirmDelete) {
await _deleteRecipe(context);
}
}
Future<void> _deleteRecipe(BuildContext context) async {
DatabaseReference recipeRef = FirebaseDatabase.instance.ref('rezepte/$recipeId');
DataSnapshot snapshot = await recipeRef.get();
if (snapshot.exists) {
Map<dynamic, dynamic> recipe = snapshot.value as Map<dynamic, dynamic>;
// Lösche die Bilder aus dem Storage, wenn URLs vorhanden sind
for (final key in ['bild1', 'bild2', 'bild3']) {
final String? imageUrl = recipe[key];
if (imageUrl != null && imageUrl.isNotEmpty) {
try {
await FirebaseStorage.instance.refFromURL(imageUrl).delete();
} catch (e) {
debugPrint("Fehler beim Löschen von $imageUrl: $e");
}
}
}
// Lösche den Rezepteintrag aus der Datenbank
await recipeRef.remove();
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Rezept konnte nicht gefunden werden.')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Rezept Details'),
),
body: FutureBuilder<DataSnapshot>(
future: FirebaseDatabase.instance.ref('rezepte/$recipeId').get(),
builder: (BuildContext context, AsyncSnapshot<DataSnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData || snapshot.data?.value == null) {
return const Center(child: Text('Rezept nicht gefunden'));
}
var recipeData = snapshot.data!.value as Map<dynamic, dynamic>;
return SingleChildScrollView(
child: Column(
children: [
if (recipeData['bild1'] != null)
Image.network(recipeData['bild1']),
Text(recipeData['titel'] ?? 'Kein Titel', style: Theme.of(context).textTheme.titleLarge),
Text(recipeData['beschreibung1'] ?? 'Keine Beschreibung'),
Text(recipeData['beschreibung2'] ?? 'Keine Beschreibung'),
Text(recipeData['beschreibung3'] ?? 'Keine Beschreibung'),
if (recipeData['bild2'] != null && recipeData['bild2'].isNotEmpty)
Image.network(recipeData['bild2']),
if (recipeData['bild3'] != null && recipeData['bild3'].isNotEmpty)
Image.network(recipeData['bild3']),
],
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showDeleteDialog(context),
tooltip: 'Löschen',
child: const Icon(Icons.delete),
),
);
}
}

View File

@ -0,0 +1,175 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:path/path.dart';
class Recipeformpage extends StatefulWidget {
const Recipeformpage({super.key});
@override
RecipeformpageState createState() => RecipeformpageState();
}
class RecipeformpageState extends State<Recipeformpage> {
final _formKey = GlobalKey<FormState>();
String title = '';
String description1 = '';
String description2 = '';
String description3 = '';
String image1 = '';
String image2 = '';
String image3 = '';
final ImagePicker _picker = ImagePicker();
Future<void> _pickAndUploadImage(String fieldName) async {
final XFile? pickedFile = await _picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
// Lese die Datei als Byte-Array ein
Uint8List fileBytes = await pickedFile.readAsBytes();
String fileName = basename(pickedFile.path);
Reference storageRef = FirebaseStorage.instance.ref().child('rezeptbilder/$fileName');
// Starte den Upload-Prozess
UploadTask uploadTask = storageRef.putData(fileBytes);
// Warte auf das Ende des Uploads
await uploadTask.whenComplete(() {});
// Erhalte die Download-URL
String downloadUrl = await storageRef.getDownloadURL();
setState(() {
// Aktualisiere den entsprechenden Bild-URL-Zustand
switch (fieldName) {
case 'image1':
image1 = downloadUrl;
break;
case 'image2':
image2 = downloadUrl;
break;
case 'image3':
image3 = downloadUrl;
break;
default:
break;
}
});
}
}
void _saveForm(BuildContext context) async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
DatabaseReference databaseRef = FirebaseDatabase.instance.ref().child('rezepte');
String? rezeptId = databaseRef.push().key; // Eindeutiger Schlüssel für das neue Rezept
databaseRef.child(rezeptId!).set({
'titel': title,
'beschreibung1': description1,
'beschreibung2': description2,
'beschreibung3': description3,
'bild1': image1,
'bild2': image2,
'bild3': image3,
});
Navigator.of(context).pop(); // Schließt das Formular nach dem Speichern
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Neues Rezept"),
),
body: Stack(
children: <Widget>[
Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: <Widget>[
TextFormField(
decoration: const InputDecoration(labelText: 'Titel'),
onSaved: (value) => title = value ?? '',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Bitte einen Titel eingeben';
}
return null;
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Zutaten: '),
onSaved: (value) => description1 = value ?? '',
),
TextFormField(
decoration: const InputDecoration(labelText: 'Anleitung: '),
onSaved: (value) => description2 = value ?? '',
),
TextFormField(
decoration: const InputDecoration(labelText: 'Anmerkungen: '),
onSaved: (value) => description3 = value ?? '',
),
_imageField('Anzeigebild', 'image1'),
_imageField('Handgeschriebenes Rezept', 'image2'),
_imageField('Textscan', 'image3'),
],
),
),
Positioned(
right: 16,
bottom: 16,
child: FloatingActionButton(
onPressed: () => _saveForm(context),
tooltip: 'Rezept speichern',
child: const Icon(Icons.save),
),
),
],
),
);
}
Widget _imageField(String label, String fieldName) {
String? imageUrl;
switch (fieldName) {
case 'image1':
imageUrl = image1;
break;
case 'image2':
imageUrl = image2;
break;
case 'image3':
imageUrl = image3;
break;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (imageUrl != null && imageUrl.isNotEmpty)
Image.network(imageUrl, width: 100, height: 100),
Row(
children: [
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: label),
),
),
IconButton(
icon: const Icon(Icons.photo_camera),
onPressed: () => _pickAndUploadImage(fieldName),
),
],
),
],
);
}
}

View File

@ -1,43 +0,0 @@
import 'package:flutter/material.dart';
import 'menu_content.dart';
import 'recipe.dart';
class RecipesContent implements MenuContent {
@override
Widget build(BuildContext context) {
// Datenquelle exemplarisch definiert
List<Map<String, dynamic>> recipes = [
{"title": "Rezept 1", "image": "assets/images/burger.png"},
{"title": "Rezept 2", "image": "assets/images/spaghetti.png"},
// weitere Rezepte
];
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: recipes.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RecipeDetailPage(recipe: recipes[index]),
),
);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.network(recipes[index]["image"], width: 100, height: 100),
Text(recipes[index]["title"]),
],
),
),
);
},
);
}
}

View File

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'menu_content.dart';
import 'recipedetailpage.dart';
class RecipesOverview implements MenuContent {
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseDatabase.instance.ref('rezepte').onValue,
builder: (context, AsyncSnapshot<DatabaseEvent> snapshot) {
if (snapshot.hasData && !snapshot.hasError && snapshot.data!.snapshot.value != null) {
Map<dynamic, dynamic> recipes = snapshot.data!.snapshot.value as Map<dynamic, dynamic>;
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: recipes.length,
itemBuilder: (BuildContext context, int index) {
String key = recipes.keys.elementAt(index);
var value = recipes[key];
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RecipeDetailPage(recipeId: key, recipe: const {},),
),
);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.network(value["bild1"], width: 100, height: 100),
Text(value["titel"]),
],
),
),
);
},
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
);
}
}

14
lib/startpage.dart 100644
View File

@ -0,0 +1,14 @@
import 'package:flutter/material.dart';
import 'menu_content.dart';
class Startpage implements MenuContent {
@override
Widget build(BuildContext context) {
return Center(
child: AspectRatio(
aspectRatio: 1 / 1, // Für ein quadratisches Logo
child: Image.asset('assets/images/Logo.png', fit: BoxFit.contain),
),
);
}
}

View File

@ -1,16 +0,0 @@
// textscan.dart
import 'package:flutter/material.dart';
import 'menu_content.dart';
//todo Logik implementieren
//links
//https://davidserrano.io/text-recognition-in-flutter-create-ocr-scanner-app
//https://www.google.com/search?q=textscan+flutter&oq=textscan+flutter&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIICAEQABgWGB7SAQgzMDg0ajBqN6gCALACAA&sourceid=chrome&ie=UTF-8
class TextScanContent implements MenuContent {
@override
Widget build(BuildContext context) {
// Hier wird der Inhalt für Menüpunkt 3 erstellt
return const Text('Inhalt für Menüpunkt 3 - Text Scan');
}
}

View File

@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: f5628cd9c92ed11083f425fd1f8f1bc60ecdda458c81d73b143aeda036c35fe7
url: "https://pub.dev"
source: hosted
version: "1.3.16"
async:
dependency: transitive
description:
@ -41,6 +49,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
url: "https://pub.dev"
source: hosted
version: "0.3.3+8"
cupertino_icons:
dependency: "direct main"
description:
@ -57,6 +73,118 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
file_selector_linux:
dependency: transitive
description:
name: file_selector_linux
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
url: "https://pub.dev"
source: hosted
version: "0.9.2+1"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6
url: "https://pub.dev"
source: hosted
version: "0.9.3+3"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
url: "https://pub.dev"
source: hosted
version: "2.6.2"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
url: "https://pub.dev"
source: hosted
version: "0.9.3+1"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "96607c0e829a581c2a483c658f04e8b159964c3bae2730f73297070bc85d40bb"
url: "https://pub.dev"
source: hosted
version: "2.24.2"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63
url: "https://pub.dev"
source: hosted
version: "5.0.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: d585bdf3c656c3f7821ba1bd44da5f13365d22fcecaf5eb75c4295246aaa83c0
url: "https://pub.dev"
source: hosted
version: "2.10.0"
firebase_database:
dependency: "direct main"
description:
name: firebase_database
sha256: "8568ad41f9312ab1f162f70c1e3e7cb7420b8bc8d07e4d543e575bb0cb41f8a5"
url: "https://pub.dev"
source: hosted
version: "10.4.0"
firebase_database_platform_interface:
dependency: transitive
description:
name: firebase_database_platform_interface
sha256: "4366ade2390f8799a317bb13af29c2a1fdfc84f4d04372094756b86a6cbfd305"
url: "https://pub.dev"
source: hosted
version: "0.2.5+16"
firebase_database_web:
dependency: transitive
description:
name: firebase_database_web
sha256: "4920a83b917493b37fd408cbb01c289ef8a422d9ed48982f908a9850290262f9"
url: "https://pub.dev"
source: hosted
version: "0.2.3+16"
firebase_storage:
dependency: "direct main"
description:
name: firebase_storage
sha256: "75e6cb6bed65138b5bbd86bfd7cf9bc9a175fb0c31aacc400e9203df117ffbe6"
url: "https://pub.dev"
source: hosted
version: "11.6.0"
firebase_storage_platform_interface:
dependency: transitive
description:
name: firebase_storage_platform_interface
sha256: "545a3a8edf337850403bb0fa03c8074a53deb87c0107d19755c77a82ce07919e"
url: "https://pub.dev"
source: hosted
version: "5.1.3"
firebase_storage_web:
dependency: transitive
description:
name: firebase_storage_web
sha256: ee6870ff79aa304b8996ba18a4aefe1e8b3fc31fd385eab6574180267aa8d393
url: "https://pub.dev"
source: hosted
version: "3.6.17"
flutter:
dependency: "direct main"
description: flutter
@ -70,11 +198,128 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da
url: "https://pub.dev"
source: hosted
version: "2.0.17"
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"
google_mlkit_commons:
dependency: transitive
description:
name: google_mlkit_commons
sha256: "046586b381cdd139f7f6a05ad6998f7e339d061bd70158249907358394b5f496"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
google_mlkit_text_recognition:
dependency: "direct main"
description:
name: google_mlkit_text_recognition
sha256: d484de2a10961a6f0ff8b54cc92b71bfbb0e65509be0903edca0e1f9256ca4c2
url: "https://pub.dev"
source: hosted
version: "0.11.0"
http:
dependency: transitive
description:
name: http
sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139
url: "https://pub.dev"
source: hosted
version: "1.1.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
image_picker:
dependency: "direct main"
description:
name: image_picker
sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd"
url: "https://pub.dev"
source: hosted
version: "1.0.7"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: "1a27bf4cc0330389cebe465bab08fe6dec97e44015b4899637344bb7297759ec"
url: "https://pub.dev"
source: hosted
version: "0.8.9+2"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3
url: "https://pub.dev"
source: hosted
version: "3.0.2"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: eac0a62104fa12feed213596df0321f57ce5a572562f72a68c4ff81e9e4caacf
url: "https://pub.dev"
source: hosted
version: "0.8.9"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
url: "https://pub.dev"
source: hosted
version: "0.2.1+1"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
url: "https://pub.dev"
source: hosted
version: "0.2.1+1"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b
url: "https://pub.dev"
source: hosted
version: "2.9.3"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
url: "https://pub.dev"
source: hosted
version: "0.2.1+1"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
lints:
dependency: transitive
description:
@ -107,14 +352,86 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.0"
path:
mime:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
url: "https://pub.dev"
source: hosted
version: "1.0.4"
path:
dependency: "direct main"
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
platform:
dependency: transitive
description:
name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
sky_engine:
dependency: transitive
description: flutter
@ -168,6 +485,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
vector_math:
dependency: transitive
description:
@ -184,5 +509,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.0"
win32:
dependency: transitive
description:
name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
url: "https://pub.dev"
source: hosted
version: "5.2.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.4"
sdks:
dart: ">=3.2.0 <4.0.0"
flutter: ">=3.10.0"

View File

@ -1,7 +1,5 @@
name: kochkomplize
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
description: "Kochhilfe mit Rezepten und Texterkennung"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
@ -13,6 +11,12 @@ dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
firebase_core: ^2.24.2
firebase_database: ^10.4.0
firebase_storage: ^11.6.0
path_provider: ^2.1.2
image_picker: ^1.0.7
path: ^1.8.0
dev_dependencies:
flutter_test:
@ -23,5 +27,6 @@ flutter:
uses-material-design: true
assets:
- assets/images/
- assets/database/Kochkomplize.db

View File

@ -1,50 +1,108 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:kochkomplize/main.dart';
import 'package:kochkomplize/menu.dart';
Widget buildMenu() {
return MaterialApp(
home: Scaffold(
body: Menu(onMenuItemSelected: (menuItem) {}),
),
);
}
import 'package:kochkomplize/recipeformpage.dart';
void main() {
testWidgets('Check menu text', (WidgetTester tester) async {
await tester.pumpWidget(buildMenu());
testWidgets('Menu renders correctly', (WidgetTester tester) async {
void onMenuItemSelectedMock(String item) {}
// Verwenden Sie einen GlobalKey, um auf den Scaffold zuzugreifen
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
await tester.pumpWidget(MaterialApp(home: Scaffold(key: scaffoldKey, drawer: Menu(onMenuItemSelected: onMenuItemSelectedMock))));
// Öffnen Sie den Drawer programmatisch
scaffoldKey.currentState!.openDrawer();
await tester.pumpAndSettle(); // Warten, bis die Animation abgeschlossen ist
// Überprüfen Sie nun, ob die Elemente im Drawer vorhanden sind
expect(find.byType(Drawer), findsOneWidget);
expect(find.text('Hauptmenü'), findsOneWidget);
expect(find.text('Startseite'), findsOneWidget);
expect(find.text('Meine Rezepte'), findsOneWidget);
});
testWidgets('Check number of list items', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
expect(find.byType(GestureDetector), findsNWidgets(3));
testWidgets('Tapping each menu item calls onMenuItemSelected with correct item', (WidgetTester tester) async {
String selectedMenuItem = '';
void onMenuItemSelectedMock(String item) {
selectedMenuItem = item;
}
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
await tester.pumpWidget(MaterialApp(home: Scaffold(key: scaffoldKey, drawer: Menu(onMenuItemSelected: onMenuItemSelectedMock))));
scaffoldKey.currentState!.openDrawer();
await tester.pumpAndSettle(); // Warten, bis die Animation abgeschlossen ist
// Tippen Sie auf das Menüelement 'Startseite' und überprüfen Sie die Funktionalität
await tester.tap(find.text('Startseite'));
await tester.pumpAndSettle();
expect(selectedMenuItem, 'Startseite');
// Wiederholen Sie den Vorgang für 'Meine Rezepte'
scaffoldKey.currentState!.openDrawer();
await tester.pumpAndSettle();
await tester.tap(find.text('Meine Rezepte'));
await tester.pumpAndSettle();
expect(selectedMenuItem, 'Meine Rezepte');
// Hier können Sie weitere Tests für andere Menüpunkte hinzufügen
});
// testWidgets('Check display of recipe titles', (WidgetTester tester) async {
// await tester.pumpWidget(const MyApp());
// expect(find.text('Rezept 1'), findsOneWidget);
// expect(find.text('Rezept 2'), findsOneWidget);
// });
//
// testWidgets('Check display of recipe images', (WidgetTester tester) async {
// await tester.pumpWidget(const MyApp());
// expect(find.byType(Image), findsNWidgets(2));
// });
//
// testWidgets('Check navigation to recipe detail page', (WidgetTester tester) async {
// await tester.pumpWidget(const MyApp());
// await tester.tap(find.text('Rezept 1'));
// await tester.pumpAndSettle(); // Warte darauf, dass die Navigation abgeschlossen ist
// expect(find.text('Details für Rezept 1'), findsOneWidget);
// });
testWidgets('Drawer closes after an item is selected', (WidgetTester tester) async {
void onMenuItemSelectedMock(String item) {}
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
await tester.pumpWidget(MaterialApp(home: Scaffold(key: scaffoldKey, drawer: Menu(onMenuItemSelected: onMenuItemSelectedMock))));
scaffoldKey.currentState!.openDrawer();
await tester.pumpAndSettle();
await tester.tap(find.text('Startseite'));
await tester.pumpAndSettle();
// Überprüfen Sie, ob der Drawer geschlossen ist
expect(scaffoldKey.currentState!.isDrawerOpen, isFalse);
});
testWidgets('Drawer header renders correctly', (WidgetTester tester) async {
// Erstellen Sie eine Mock-Funktion, die einen String als Parameter erwartet
void onMenuItemSelectedMock(String item) {}
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
await tester.pumpWidget(MaterialApp(
home: Scaffold(key: scaffoldKey, drawer: Menu(onMenuItemSelected: onMenuItemSelectedMock))
));
scaffoldKey.currentState!.openDrawer();
await tester.pumpAndSettle();
// Überprüfen Sie, ob der DrawerHeader vorhanden ist
expect(find.byType(DrawerHeader), findsOneWidget);
expect(find.text('Hauptmenü'), findsOneWidget);
});
testWidgets('Recipeformpage has form fields', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: Recipeformpage()));
// Prüfen Sie, ob die Formularfelder vorhanden sind
expect(find.byType(TextFormField), findsWidgets);
// Prüfen Sie den Speichern-Button
expect(find.byType(FloatingActionButton), findsOneWidget);
});
testWidgets('MyHomePage renders correctly', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: MyHomePage(title: 'Kochkomplize')));
// Überprüfen Sie, ob der Titel korrekt angezeigt wird
expect(find.text('Kochkomplize'), findsOneWidget);
});
}