-
Notifications
You must be signed in to change notification settings - Fork 130
feat(friendly-meals-flutter): add ingredients OCR #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,9 @@ | ||
import 'dart:typed_data'; | ||
|
||
import 'package:firebase_ai/firebase_ai.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_markdown/flutter_markdown.dart'; | ||
import 'package:image_picker/image_picker.dart'; | ||
|
||
class RecipeGeneratorScreen extends StatefulWidget { | ||
const RecipeGeneratorScreen({super.key}); | ||
|
@@ -15,6 +18,54 @@ class _RecipeGeneratorScreenState extends State<RecipeGeneratorScreen> { | |
bool _showRecipeCard = false; | ||
String _generatedRecipe = ""; | ||
bool _isLoading = false; | ||
bool _isExtractingIngredients = false; | ||
|
||
void _pickImageAndExtractIngredients() async { | ||
setState(() { | ||
_isExtractingIngredients = true; | ||
}); | ||
|
||
try { | ||
final imagePicker = ImagePicker(); | ||
final pickedFile = await imagePicker.pickImage( | ||
source: ImageSource.gallery, | ||
); | ||
|
||
if (pickedFile != null) { | ||
final imageBytes = await pickedFile.readAsBytes(); | ||
final model = FirebaseAI.googleAI().generativeModel( | ||
model: 'gemini-2.5-flash-lite', | ||
); | ||
|
||
final prompt = Content.multi([ | ||
TextPart(""" | ||
Please analyze this image and list all visible food ingredients. | ||
Format the response as a comma-separated list of ingredients. | ||
Be specific with measurements where possible, | ||
but focus on identifying the ingredients accurately. | ||
"""), | ||
InlineDataPart('image/jpeg', imageBytes), | ||
]); | ||
|
||
final response = await model.generateContent([prompt]); | ||
|
||
if (response.text != null) { | ||
_ingredientsController.text = response.text!; | ||
} | ||
} | ||
} catch (e) { | ||
ScaffoldMessenger.of(context).showSnackBar( | ||
SnackBar( | ||
content: Text('Error processing image: $e'), | ||
backgroundColor: Colors.red, | ||
), | ||
); | ||
Comment on lines
+57
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using To prevent potential crashes, you should always check if the widget is still
|
||
} finally { | ||
setState(() { | ||
_isExtractingIngredients = false; | ||
}); | ||
} | ||
} | ||
|
||
void _handleGenerateRecipe() async { | ||
if (_ingredientsController.text.isEmpty) { | ||
|
@@ -74,9 +125,18 @@ class _RecipeGeneratorScreenState extends State<RecipeGeneratorScreen> { | |
const SizedBox(height: 16.0), | ||
TextField( | ||
controller: _ingredientsController, | ||
decoration: const InputDecoration( | ||
decoration: InputDecoration( | ||
labelText: 'Enter ingredients (comma separated)', | ||
border: OutlineInputBorder(), | ||
border: const OutlineInputBorder(), | ||
suffixIcon: _isExtractingIngredients | ||
? const Padding( | ||
padding: EdgeInsets.all(8.0), | ||
child: CircularProgressIndicator(), | ||
) | ||
: IconButton( | ||
icon: const Icon(Icons.camera_alt), | ||
onPressed: _pickImageAndExtractIngredients, | ||
), | ||
), | ||
), | ||
const SizedBox(height: 16.0), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better maintainability and robustness, it's recommended to extract hardcoded values into constants and dynamically determine the image MIME type.
'gemini-2.5-flash-lite'
and the prompt text are hardcoded. Extracting them intoconst
variables improves readability and makes them easier to update.'image/jpeg'
. The user might select a different image format (e.g., PNG), which could cause processing to fail. Using themime
package to determine the MIME type from the file path makes this more robust.To apply this suggestion, you'll need to add
import 'package:mime/mime.dart';
at the top of your file. Themime
package is already available as a transitive dependency.