Compare commits

..

3 Commits

Author SHA1 Message Date
Thomas Hassenstein ff4c06fa2d README.md aktualisiert
Aufräumen
2023-11-21 22:02:20 +01:00
Thomas Hassenstein e458c5da3f README.md aktualisiert
Spacing
2023-11-21 22:01:58 +01:00
Thomas Hassenstein 21cec1e6c5 README.md aktualisiert
Beschreibung aktualisiert
2023-11-21 22:01:24 +01:00
28 changed files with 224 additions and 1031 deletions

View File

@ -1,27 +1,18 @@
# kochkomplize # kochkomplize
Helfer zur Digitalisierung von alten Kochrezepten.
**Bild/Ablauf:** **Bild/Skizze:**
![Startseite](assets/images/Kochkomplize_Startseite.PNG) Ein Bild wie die Applikation aussehen soll findet man unter assets/images mit dem Namen skizze.png
![Rezept Übersicht](assets/images/Kochkomplize_Rezept_Overview.PNG)
![Rezept anlegen](assets/images/Kochkomplize_Rezept_anlegen.PNG) **Allgemeine Beschreibung:**
![Rezept löschen](assets/images/Kochkomplize_Löschen.PNG) Kochkomplize solle eine Koch- und Backapp sein die auch bei der Digitalisierung von alten Handgeschrieben Rezepten mit Hilfe von Texterkennung helfen soll.
**Funktionalität:** **Funktionalität:**
1. Die App soll grundsätzlich im Browser als auch auf Mobilen Endgeräten laufen. (entweder iOS oder Android) 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. 2. Es soll möglich sein Rezepte abzuspeichern und neue anzulegen.
3. Man soll Bilder für Rezepte hochladen können. 3. Man soll Bilder für Rezepte hochladen können.
4. Es soll möglich sein alte handgeschriebene Texte Bildern zu importieren. 4. Es soll möglich sein alte handgeschriebene Rezepte mit Texterkennung auf Bildern zu importieren und zu formatieren.
5. Rezepte sollen gelöscht werden können. 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.
6. Bonus: Texterkennung wenn die Packages es auch für Web zulassen.
**Eingesetzte Technologien:**
Flutter
Dart
Firebase

View File

@ -1,5 +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 include: package:flutter_lints/flutter.yaml
linter: 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: 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" applicationId "com.example.kochkomplize"
// You can update the following values to match your application needs. // 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. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 21 minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName

View File

@ -1,30 +0,0 @@
{
"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,5 +30,4 @@
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
</application> </application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest> </manifest>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 681 KiB

View File

@ -1,30 +0,0 @@
<?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,9 +45,5 @@
<true/> <true/>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <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> </dict>
</plist> </plist>

View File

@ -1,7 +0,0 @@
{
"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

@ -1,68 +0,0 @@
// 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',
);
}

12
lib/import.dart 100644
View File

@ -0,0 +1,12 @@
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,18 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'startpage.dart';
import 'recipeformpage.dart';
import 'menu.dart'; import 'menu.dart';
import 'menu_content.dart'; import 'menu_content.dart';
import 'recipesoverview.dart'; import 'recipes.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()); runApp(const MyApp());
} }
@ -44,32 +35,18 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
MenuContent? _selectedMenuContent; MenuContent? _selectedMenuContent;
@override
void initState() {
super.initState();
_selectedMenuContent = Startpage(); // Initialisiere mit Startseite
}
void _onMenuItemSelected(String menuItem) { void _onMenuItemSelected(String menuItem) {
setState(() { setState(() {
switch (menuItem) { switch (menuItem) {
case 'Meine Rezepte': case 'Meine Rezepte':
_selectedMenuContent = RecipesOverview(); _selectedMenuContent = RecipesContent();
break; break;
case 'Startseite':
_selectedMenuContent = Startpage();
break;
// case 'Texterkennung Bild':
// _selectedMenuContent = TextScan();
// break;
} }
}); });
} }
void _addRecipe() { void _importImage() {
Navigator.of(context).push( //todo
MaterialPageRoute(builder: (context) => const Recipeformpage()),
);
} }
@override @override
@ -81,14 +58,11 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
drawer: Menu(onMenuItemSelected: _onMenuItemSelected), drawer: Menu(onMenuItemSelected: _onMenuItemSelected),
body: _selectedMenuContent?.build(context) ?? Container(), body: _selectedMenuContent?.build(context) ?? Container(),
floatingActionButton: _selectedMenuContent is RecipesOverview floatingActionButton: FloatingActionButton(
? FloatingActionButton( onPressed: _importImage,
onPressed: _addRecipe, tooltip: 'Import',
tooltip: 'Rezept hinzufügen',
child: const Icon(Icons.add), 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)), child: Text('Hauptmenü', style: TextStyle(color: Colors.black, fontSize: 20.0)),
), ),
), ),
_buildListTile(context, 'Startseite'),
_buildListTile(context, 'Meine Rezepte'), _buildListTile(context, 'Meine Rezepte'),
//_buildListTile(context, 'Rezept importieren'), _buildListTile(context, 'Rezept importieren'),
//_buildListTile(context, 'Texterkennung Bild'), _buildListTile(context, 'Texterkennung Bild'),
//_buildListTile(context, '(Beta) Rezepte finden'), _buildListTile(context, '(Beta) Rezepte finden'),
// Füge hier weitere ListTiles für zusätzliche Menüpunkte hinzu
], ],
), ),
); );

26
lib/recipe.dart 100644
View File

@ -0,0 +1,26 @@
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

@ -1,110 +0,0 @@
// 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

@ -1,175 +0,0 @@
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),
),
],
),
],
);
}
}

43
lib/recipes.dart 100644
View File

@ -0,0 +1,43 @@
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

@ -1,48 +0,0 @@
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());
}
},
);
}
}

View File

@ -1,14 +0,0 @@
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),
),
);
}
}

16
lib/textscan.dart 100644
View File

@ -0,0 +1,16 @@
// 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,14 +1,6 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: f5628cd9c92ed11083f425fd1f8f1bc60ecdda458c81d73b143aeda036c35fe7
url: "https://pub.dev"
source: hosted
version: "1.3.16"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -49,14 +41,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" 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: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -73,118 +57,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" 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: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -198,128 +70,11 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" 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: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: lints:
dependency: transitive dependency: transitive
description: description:
@ -352,86 +107,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
mime:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
url: "https://pub.dev"
source: hosted
version: "1.0.4"
path: path:
dependency: "direct main" dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -485,14 +168,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" 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: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -509,22 +184,5 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.0" 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: sdks:
dart: ">=3.2.0 <4.0.0" dart: ">=3.2.0 <4.0.0"
flutter: ">=3.10.0"

View File

@ -1,5 +1,7 @@
name: kochkomplize name: kochkomplize
description: "Kochhilfe mit Rezepten und Texterkennung" 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.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1 version: 1.0.0+1
@ -11,12 +13,6 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.2 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: dev_dependencies:
flutter_test: flutter_test:
@ -27,6 +23,5 @@ flutter:
uses-material-design: true uses-material-design: true
assets: assets:
- assets/images/ - assets/images/
- assets/database/Kochkomplize.db

View File

@ -1,108 +1,50 @@
// 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/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:kochkomplize/main.dart'; import 'package:kochkomplize/main.dart';
import 'package:kochkomplize/menu.dart'; import 'package:kochkomplize/menu.dart';
import 'package:kochkomplize/recipeformpage.dart';
Widget buildMenu() {
return MaterialApp(
home: Scaffold(
body: Menu(onMenuItemSelected: (menuItem) {}),
),
);
}
void main() { void main() {
testWidgets('Check menu text', (WidgetTester tester) async {
testWidgets('Menu renders correctly', (WidgetTester tester) async { await tester.pumpWidget(buildMenu());
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); expect(find.text('Meine Rezepte'), findsOneWidget);
}); });
testWidgets('Tapping each menu item calls onMenuItemSelected with correct item', (WidgetTester tester) async { testWidgets('Check number of list items', (WidgetTester tester) async {
String selectedMenuItem = ''; await tester.pumpWidget(const MyApp());
expect(find.byType(GestureDetector), findsNWidgets(3));
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('Drawer closes after an item is selected', (WidgetTester tester) async { // testWidgets('Check display of recipe titles', (WidgetTester tester) async {
void onMenuItemSelectedMock(String item) {} // await tester.pumpWidget(const MyApp());
// expect(find.text('Rezept 1'), findsOneWidget);
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey(); // expect(find.text('Rezept 2'), findsOneWidget);
// });
await tester.pumpWidget(MaterialApp(home: Scaffold(key: scaffoldKey, drawer: Menu(onMenuItemSelected: onMenuItemSelectedMock)))); //
// testWidgets('Check display of recipe images', (WidgetTester tester) async {
scaffoldKey.currentState!.openDrawer(); // await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle(); // expect(find.byType(Image), findsNWidgets(2));
// });
await tester.tap(find.text('Startseite')); //
await tester.pumpAndSettle(); // testWidgets('Check navigation to recipe detail page', (WidgetTester tester) async {
// await tester.pumpWidget(const MyApp());
// Überprüfen Sie, ob der Drawer geschlossen ist // await tester.tap(find.text('Rezept 1'));
expect(scaffoldKey.currentState!.isDrawerOpen, isFalse); // await tester.pumpAndSettle(); // Warte darauf, dass die Navigation abgeschlossen ist
}); // expect(find.text('Details für Rezept 1'), findsOneWidget);
// });
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);
});
} }