diff --git a/README.md b/README.md index 3ef8cac..da528ed 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,11 @@ Ein Bild wie die Applikation aussehen soll findet man unter assets/images mit de 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. diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..35cb8f2 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -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 diff --git a/android/app/build.gradle b/android/app/build.gradle index a274f71..c77286f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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 diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..10dcaeb --- /dev/null +++ b/android/app/google-services.json @@ -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" +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 402b011..913e1d9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -30,4 +30,5 @@ android:name="flutterEmbedding" android:value="2" /> + diff --git a/assets/database/Kochkomplize.db b/assets/database/Kochkomplize.db new file mode 100644 index 0000000..9e75ba6 Binary files /dev/null and b/assets/database/Kochkomplize.db differ diff --git a/assets/images/Logo.png b/assets/images/Logo.png new file mode 100644 index 0000000..f20637e Binary files /dev/null and b/assets/images/Logo.png differ diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..0f83924 --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyDn1MhJq7gjUtyFJaewgC425jwJmAVCrDk + GCM_SENDER_ID + 460915828512 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.kochkomplize + PROJECT_ID + kochkomplize + STORAGE_BUCKET + kochkomplize.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:460915828512:ios:ec4610516aa6bb0d771945 + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 1b420ec..00f5eb5 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,5 +45,9 @@ UIApplicationSupportsIndirectInputEvents + NSPhotoLibraryUsageDescription + Wir benötigen Zugriff auf Ihre Fotos, um Bilder auszuwählen. + NSCameraUsageDescription + Wir benötigen Zugriff auf Ihre Kamera, um Fotos aufzunehmen. diff --git a/ios/firebase_app_id_file.json b/ios/firebase_app_id_file.json new file mode 100644 index 0000000..4d1969f --- /dev/null +++ b/ios/firebase_app_id_file.json @@ -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" +} \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..bbbf5c1 --- /dev/null +++ b/lib/firebase_options.dart @@ -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', + ); +} diff --git a/lib/import.dart b/lib/import.dart deleted file mode 100644 index c23bbd9..0000000 --- a/lib/import.dart +++ /dev/null @@ -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'); - } -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index da7f8e9..27c6bb1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 { 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 { ), 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 ); } -} \ No newline at end of file +} + diff --git a/lib/menu.dart b/lib/menu.dart index f48eafd..8d83554 100644 --- a/lib/menu.dart +++ b/lib/menu.dart @@ -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'), ], ), ); diff --git a/lib/recipe.dart b/lib/recipe.dart deleted file mode 100644 index 63abd46..0000000 --- a/lib/recipe.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; - -class RecipeDetailPage extends StatelessWidget { - final Map 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 - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/recipedetailpage.dart b/lib/recipedetailpage.dart new file mode 100644 index 0000000..4d729e6 --- /dev/null +++ b/lib/recipedetailpage.dart @@ -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 _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: [ + 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 _deleteRecipe(BuildContext context) async { + DatabaseReference recipeRef = FirebaseDatabase.instance.ref('rezepte/$recipeId'); + DataSnapshot snapshot = await recipeRef.get(); + if (snapshot.exists) { + Map recipe = snapshot.value as Map; + + // 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( + future: FirebaseDatabase.instance.ref('rezepte/$recipeId').get(), + builder: (BuildContext context, AsyncSnapshot 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; + + 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), + ), + ); + } +} \ No newline at end of file diff --git a/lib/recipeformpage.dart b/lib/recipeformpage.dart new file mode 100644 index 0000000..6966e5f --- /dev/null +++ b/lib/recipeformpage.dart @@ -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 { + final _formKey = GlobalKey(); + String title = ''; + String description1 = ''; + String description2 = ''; + String description3 = ''; + String image1 = ''; + String image2 = ''; + String image3 = ''; + + final ImagePicker _picker = ImagePicker(); + + Future _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: [ + Form( + key: _formKey, + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + 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), + ), + ], + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/recipes.dart b/lib/recipes.dart deleted file mode 100644 index a418251..0000000 --- a/lib/recipes.dart +++ /dev/null @@ -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> 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"]), - ], - ), - ), - ); - }, - ); - } -} - diff --git a/lib/recipesoverview.dart b/lib/recipesoverview.dart new file mode 100644 index 0000000..d67b809 --- /dev/null +++ b/lib/recipesoverview.dart @@ -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 snapshot) { + if (snapshot.hasData && !snapshot.hasError && snapshot.data!.snapshot.value != null) { + Map recipes = snapshot.data!.snapshot.value as Map; + 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()); + } + }, + ); + } +} \ No newline at end of file diff --git a/lib/startpage.dart b/lib/startpage.dart new file mode 100644 index 0000000..5fb4611 --- /dev/null +++ b/lib/startpage.dart @@ -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), + ), + ); + } +} diff --git a/lib/textscan.dart b/lib/textscan.dart index fd1a95c..ba62a84 100644 --- a/lib/textscan.dart +++ b/lib/textscan.dart @@ -1,16 +1,11 @@ -// textscan.dart -import 'package:flutter/material.dart'; -import 'menu_content.dart'; +// import 'dart:io'; +// import 'package:flutter/material.dart'; +// import 'package:image_picker/image_picker.dart'; +// import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart'; +// import 'menu_content.dart'; +//import 'package:ocr_scan_text/ocr_scan_text.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'); - } -} \ No newline at end of file +//todo https://pub.dev/packages/ocr_scan_text/install +//todo https://pub.dev/packages/google_mlkit_text_recognition/install +//todo https://pub.dev/packages/flutter_tesseract_ocr \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 99bb583..5b92675 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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" diff --git a/pubspec.yaml b/pubspec.yaml index 10eb0c7..b719acd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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,18 @@ dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 + #sqflite: ^2.3.0 + #sqlite3_flutter_libs: ^0.5.18 + #sqlite3: ^2.3.0 + 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 + #cloud_firestore: ^4.14.0 + #ocr_scan_text: 1.3.1 + google_mlkit_text_recognition: ^0.11.0 + path: ^1.8.0 dev_dependencies: flutter_test: @@ -23,5 +33,6 @@ flutter: uses-material-design: true assets: - assets/images/ + - assets/database/Kochkomplize.db diff --git a/test/navigation_test.dart b/test/navigation_test.dart new file mode 100644 index 0000000..cfe795e --- /dev/null +++ b/test/navigation_test.dart @@ -0,0 +1,25 @@ +// 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) {}), +// ), +// ); +// } +// +// void main() { +// testWidgets('Check menu text', (WidgetTester tester) async { +// await tester.pumpWidget(buildMenu()); +// 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(2)); +// }); +// +// } diff --git a/test/unit_test.dart b/test/unit_test.dart new file mode 100644 index 0000000..d9306c8 --- /dev/null +++ b/test/unit_test.dart @@ -0,0 +1,22 @@ +// import 'package:flutter_test/flutter_test.dart'; +// import 'package:firebase_core/firebase_core.dart'; +// import 'package:kochkomplize/main.dart'; +// import 'package:kochkomplize/recipesoverview.dart'; +// import 'package:kochkomplize/startpage.dart'; +// +// void main() { +// test('_MyHomePageState should update selected menu content', () { +// final state = _MyHomePageState(); +// +// state._onMenuItemSelected('Meine Rezepte'); +// expect(state._selectedMenuContent, isInstanceOf()); +// +// state._onMenuItemSelected('Startseite'); +// expect(state._selectedMenuContent, isInstanceOf()); +// }); +// +// test('Firebase initializes correctly', () async { +// await main(); // Ihr main-Methodenaufruf +// expect(Firebase.apps.length, isNonZero); +// }); +// } \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart index 2b27c29..4fcf084 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,50 +1,21 @@ -// 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/recipesoverview.dart'; void main() { - testWidgets('Check menu text', (WidgetTester tester) async { - await tester.pumpWidget(buildMenu()); - expect(find.text('Meine Rezepte'), findsOneWidget); - }); - - testWidgets('Check number of list items', (WidgetTester tester) async { + testWidgets('Menu navigation test', (WidgetTester tester) async { await tester.pumpWidget(const MyApp()); - expect(find.byType(GestureDetector), findsNWidgets(3)); - }); - // 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); - // }); -} + // Öffne das Menü + await tester.tap(find.byIcon(Icons.menu)); + await tester.pumpAndSettle(); + + // Wähle einen Menüpunkt + await tester.tap(find.text('Meine Rezepte')); + await tester.pumpAndSettle(); + + // Überprüfe, ob das richtige Widget angezeigt wird + expect(find.byType(RecipesOverview), findsOneWidget); + }); +} \ No newline at end of file