fix analysis issues in ai_recipe_generation

pull/2242/head
Eric Windmill 2 years ago
parent 403d3afd41
commit 5dd9fba073

@ -54,7 +54,7 @@ class AnimatedAppBar extends StatelessWidget {
expandedHeight: expandedHeight,
collapsedHeight: collapsedHeight,
backgroundColor: Theme.of(context).primaryColor,
shape: AppBarShapeBorder(50),
shape: const AppBarShapeBorder(50),
title: Column(
children: [
Row(
@ -68,7 +68,7 @@ class AnimatedAppBar extends StatelessWidget {
semanticsLabel: 'Chef cat icon',
),
),
SizedBox(
const SizedBox(
width: MarketplaceTheme.spacing1,
),
if (scrollController.positions.isNotEmpty &&
@ -83,15 +83,15 @@ class AnimatedAppBar extends StatelessWidget {
headerText,
style: MarketplaceTheme.heading3,
),
Spacer(),
const Spacer(),
if (scrollController.positions.isNotEmpty &&
scrollController.offset > 200)
IconButton(
onPressed: () => showDialog(
onPressed: () => showDialog<Null>(
context: context,
builder: (context) => AppInfoDialog(),
builder: (context) => const AppInfoDialog(),
),
icon: Icon(
icon: const Icon(
Symbols.info,
color: Colors.black12,
),
@ -102,7 +102,7 @@ class AnimatedAppBar extends StatelessWidget {
),
flexibleSpace: FlexibleSpaceBar(
background: Padding(
padding: EdgeInsets.all(MarketplaceTheme.spacing4),
padding: const EdgeInsets.all(MarketplaceTheme.spacing4),
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Row(
@ -118,12 +118,12 @@ class AnimatedAppBar extends StatelessWidget {
),
IconButton(
onPressed: () {
showDialog(
showDialog<Null>(
context: context,
builder: (context) => AppInfoDialog(),
builder: (context) => const AppInfoDialog(),
);
},
icon: Icon(
icon: const Icon(
Symbols.info,
color: Colors.black12,
),
@ -134,7 +134,7 @@ class AnimatedAppBar extends StatelessWidget {
),
),
bottom: PreferredSize(
preferredSize: Size(double.infinity, 0),
preferredSize: const Size(double.infinity, 0),
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
@ -144,7 +144,7 @@ class AnimatedAppBar extends StatelessWidget {
: MarketplaceTheme.spacing1,
),
child: AnimatedDefaultTextStyle(
duration: Duration(milliseconds: 0),
duration: const Duration(milliseconds: 0),
style: textStyle,
child: Text(
headerText,

@ -6,10 +6,10 @@ class PromptData {
PromptData({
required this.images,
required this.textInput,
basicIngredients,
cuisines,
dietaryRestrictions,
additionalTextInputs,
Set<BasicIngredientsFilter>? basicIngredients,
Set<CuisineFilter>? cuisines,
Set<DietaryRestrictionsFilter>? dietaryRestrictions,
List<String>? additionalTextInputs,
}) : additionalTextInputs = additionalTextInputs ?? [],
selectedBasicIngredients = basicIngredients ?? {},
selectedCuisines = cuisines ?? {},
@ -52,16 +52,15 @@ class PromptData {
List<String>? additionalTextInputs,
Set<BasicIngredientsFilter>? basicIngredients,
Set<CuisineFilter>? cuisineSelections,
Set<CuisineFilter>? dietaryRestrictions,
Set<DietaryRestrictionsFilter>? dietaryRestrictions,
}) {
return PromptData(
images: images ?? this.images,
textInput: textInput ?? this.textInput,
additionalTextInputs: additionalTextInputs ?? this.additionalTextInputs,
basicIngredients: basicIngredients ?? this.selectedBasicIngredients,
cuisines: cuisineSelections ?? this.selectedCuisines,
dietaryRestrictions:
dietaryRestrictions ?? this.selectedDietaryRestrictions,
basicIngredients: basicIngredients ?? selectedBasicIngredients,
cuisines: cuisineSelections ?? selectedCuisines,
dietaryRestrictions: dietaryRestrictions ?? selectedDietaryRestrictions,
);
}
}

@ -31,17 +31,17 @@ class PromptScreen extends StatelessWidget {
builder: (context, constraints) {
return SingleChildScrollView(
physics: canScroll
? BouncingScrollPhysics()
: NeverScrollableScrollPhysics(),
? const BouncingScrollPhysics()
: const NeverScrollableScrollPhysics(),
child: Container(
padding: constraints.isMobile
? EdgeInsets.only(
? const EdgeInsets.only(
left: MarketplaceTheme.spacing7,
right: MarketplaceTheme.spacing7,
bottom: MarketplaceTheme.spacing7,
top: MarketplaceTheme.spacing7,
)
: EdgeInsets.only(
: const EdgeInsets.only(
left: MarketplaceTheme.spacing7,
right: MarketplaceTheme.spacing7,
bottom: MarketplaceTheme.spacing1,
@ -51,7 +51,7 @@ class PromptScreen extends StatelessWidget {
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: MarketplaceTheme.borderColor),
borderRadius: BorderRadius.only(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(50),
bottomRight:
@ -199,7 +199,7 @@ class PromptScreen extends StatelessWidget {
padding: const EdgeInsets.all(elementPadding),
child: _TextField(
controller: viewModel.promptTextController,
onChanged: (String value) {
onChanged: (value) {
viewModel.notify();
},
),
@ -212,7 +212,7 @@ class PromptScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!constraints.isMobile) Spacer(flex: 1),
if (!constraints.isMobile) const Spacer(flex: 1),
if (!constraints.isMobile)
Expanded(
flex: 3,
@ -226,13 +226,13 @@ class PromptScreen extends StatelessWidget {
MarketplaceTheme.secondary.withOpacity(.1),
),
),
Spacer(flex: 1),
const Spacer(flex: 1),
Expanded(
flex: constraints.isMobile ? 10 : 3,
child: MarketplaceButton(
onPressed: () {
final promptData = viewModel.buildPrompt();
showDialog(
showDialog<Null>(
context: context,
builder: (context) {
return FullPromptDialog(
@ -245,12 +245,12 @@ class PromptScreen extends StatelessWidget {
icon: Symbols.info_rounded,
),
),
Spacer(flex: 1),
const Spacer(flex: 1),
Expanded(
flex: constraints.isMobile ? 10 : 3,
child: MarketplaceButton(
onPressed: () async {
viewModel.submitPrompt().then((_) async {
await viewModel.submitPrompt().then((_) async {
if (viewModel.recipe != null) {
bool? shouldSave = await showDialog<bool>(
context: context,
@ -278,7 +278,7 @@ class PromptScreen extends StatelessWidget {
icon: Symbols.send,
),
),
Spacer(flex: 1),
const Spacer(flex: 1),
],
),
),
@ -294,7 +294,7 @@ class PromptScreen extends StatelessWidget {
hoverColor: MarketplaceTheme.secondary.withOpacity(.1),
),
),
SizedBox(height: 200.0),
const SizedBox(height: 200.0),
],
),
),
@ -307,7 +307,6 @@ class PromptScreen extends StatelessWidget {
class _FilterChipSection extends StatelessWidget {
const _FilterChipSection({
super.key,
required this.child,
required this.label,
});
@ -351,38 +350,34 @@ class _FilterChipSection extends StatelessWidget {
class _TextField extends StatelessWidget {
const _TextField({
super.key,
required this.controller,
this.onChanged,
});
final TextEditingController controller;
final Function(String)? onChanged;
final Null Function(String)? onChanged;
@override
Widget build(BuildContext context) {
return TextField(
scrollPadding: EdgeInsets.only(bottom: 150),
scrollPadding: const EdgeInsets.only(bottom: 150),
maxLines: null,
onChanged: onChanged,
minLines: 3,
controller: controller,
onTapOutside: (event) {
// FocusManager.instance.primaryFocus?.unfocus();
},
style: MaterialStateTextStyle.resolveWith(
style: WidgetStateTextStyle.resolveWith(
(states) => MarketplaceTheme.dossierParagraph),
decoration: InputDecoration(
fillColor: Theme.of(context).splashColor,
hintText: "Add additional context...",
hintStyle: MaterialStateTextStyle.resolveWith(
hintStyle: WidgetStateTextStyle.resolveWith(
(states) => MarketplaceTheme.dossierParagraph,
),
enabledBorder: OutlineInputBorder(
enabledBorder: const OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(width: 1, color: Colors.black12),
),
focusedBorder: OutlineInputBorder(
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(width: 1, color: Colors.black45),
),

@ -11,7 +11,7 @@ class AppInfoDialog extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon ?? Symbols.label_important_outline),
SizedBox(
const SizedBox(
width: 10,
),
Expanded(
@ -43,35 +43,35 @@ class AppInfoDialog extends StatelessWidget {
"Use the form on this screen to ask Cat Chef to make a recipe for you.",
style: MarketplaceTheme.heading3,
),
SizedBox(
const SizedBox(
height: MarketplaceTheme.spacing4,
),
bulletRow(
"Add images of ingredients you have, like a picture of the inside of your fridge or pantry.",
icon: Symbols.looks_one,
),
SizedBox(
const SizedBox(
height: MarketplaceTheme.spacing7,
),
bulletRow(
"Choose what kind of food you're in the mood for, and what staple ingredients you have that might not be pictured.",
icon: Symbols.looks_two,
),
SizedBox(
const SizedBox(
height: MarketplaceTheme.spacing7,
),
bulletRow(
"In the text box at the bottom, add any additional context that you'd like. \nFor example, you could say \"I'm in a hurry! Make sure the recipe doesn't take longer than 30 minutes to make.\"",
icon: Symbols.looks_3,
),
SizedBox(
const SizedBox(
height: MarketplaceTheme.spacing7,
),
bulletRow(
"Submit the prompt, and Chef Noodle will give you a recipe!",
icon: Symbols.looks_4,
),
SizedBox(
const SizedBox(
height: MarketplaceTheme.spacing4,
),
Text(
@ -80,7 +80,7 @@ class AppInfoDialog extends StatelessWidget {
),
const SizedBox(height: MarketplaceTheme.spacing4),
TextButton.icon(
icon: Icon(
icon: const Icon(
Symbols.close,
color: Colors.black87,
),
@ -92,9 +92,9 @@ class AppInfoDialog extends StatelessWidget {
Navigator.pop(context);
},
style: ButtonStyle(
shape: MaterialStateProperty.resolveWith(
shape: WidgetStateProperty.resolveWith(
(states) {
return RoundedRectangleBorder(
return const RoundedRectangleBorder(
side: BorderSide(color: Colors.black26),
borderRadius: BorderRadius.all(
Radius.circular(MarketplaceTheme.defaultBorderRadius),
@ -102,7 +102,7 @@ class AppInfoDialog extends StatelessWidget {
);
},
),
textStyle: MaterialStateTextStyle.resolveWith(
textStyle: WidgetStateTextStyle.resolveWith(
(states) {
return MarketplaceTheme.dossierParagraph
.copyWith(color: Colors.black45);

@ -15,7 +15,7 @@ class FullPromptDialog extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon ?? Symbols.label_important_outline),
SizedBox(
const SizedBox(
width: 10,
),
Expanded(
@ -47,7 +47,7 @@ class FullPromptDialog extends StatelessWidget {
if (promptData.images.isNotEmpty)
Container(
height: 100,
decoration: BoxDecoration(
decoration: const BoxDecoration(
border: Border.symmetric(
horizontal: BorderSide(
color: MarketplaceTheme.borderColor,
@ -73,7 +73,7 @@ class FullPromptDialog extends StatelessWidget {
...promptData.additionalTextInputs.map((i) => bulletRow(i)),
const SizedBox(height: MarketplaceTheme.spacing4),
TextButton.icon(
icon: Icon(
icon: const Icon(
Symbols.close,
color: Colors.black87,
),
@ -85,9 +85,9 @@ class FullPromptDialog extends StatelessWidget {
Navigator.pop(context);
},
style: ButtonStyle(
shape: MaterialStateProperty.resolveWith(
shape: WidgetStateProperty.resolveWith(
(states) {
return RoundedRectangleBorder(
return const RoundedRectangleBorder(
side: BorderSide(color: Colors.black26),
borderRadius: BorderRadius.all(
Radius.circular(MarketplaceTheme.defaultBorderRadius),
@ -95,7 +95,7 @@ class FullPromptDialog extends StatelessWidget {
);
},
),
textStyle: MaterialStateTextStyle.resolveWith(
textStyle: WidgetStateTextStyle.resolveWith(
(states) {
return MarketplaceTheme.dossierParagraph
.copyWith(color: Colors.black45);

@ -13,7 +13,7 @@ import '../../../widgets/prompt_image_widget.dart';
import '../prompt_view_model.dart';
class AddImageToPromptWidget extends StatefulWidget {
AddImageToPromptWidget({
const AddImageToPromptWidget({
super.key,
this.width = 100,
this.height = 100,
@ -50,13 +50,13 @@ class _AddImageToPromptWidgetState extends State<AddImageToPromptWidget> {
transitionBuilder: (context, animation, secondaryAnimation, child) {
return AnimatedOpacity(
opacity: animation.value,
duration: Duration(milliseconds: 100),
duration: const Duration(milliseconds: 100),
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) {
return Dialog.fullscreen(
insetAnimationDuration: Duration(seconds: 1),
insetAnimationDuration: const Duration(seconds: 1),
child: FutureBuilder(
future: _initializeControllerFuture,
builder: (context, snapshot) {
@ -169,7 +169,7 @@ class _CameraViewState extends State<CameraView> {
@override
Widget build(BuildContext context) {
CameraController _controller = widget.controller;
CameraController controller = widget.controller;
return Stack(
children: [
Center(
@ -179,11 +179,11 @@ class _CameraViewState extends State<CameraView> {
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
height: _controller.value.previewSize!.width,
width: _controller.value.previewSize!.height,
height: controller.value.previewSize!.width,
width: controller.value.previewSize!.height,
child: Center(
child: CameraPreview(
_controller,
controller,
// child: ElevatedButton(
// child: Text('Button'),
// onPressed: () {},
@ -215,7 +215,7 @@ class _CameraViewState extends State<CameraView> {
color: flashOn ? Colors.yellowAccent : Colors.white,
),
onPressed: () {
_controller.setFlashMode(
controller.setFlashMode(
flashOn ? FlashMode.off : FlashMode.always);
setState(() {
flashOn = !flashOn;
@ -227,7 +227,7 @@ class _CameraViewState extends State<CameraView> {
padding:
const EdgeInsets.only(right: MarketplaceTheme.spacing4),
child: IconButton(
icon: Icon(
icon: const Icon(
Symbols.cancel,
color: Colors.white,
size: 40,
@ -252,7 +252,7 @@ class _CameraViewState extends State<CameraView> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(
icon: const Icon(
Symbols.camera,
color: Colors.white,
size: 70,
@ -260,11 +260,11 @@ class _CameraViewState extends State<CameraView> {
onPressed: () async {
try {
await widget.initializeControllerFuture;
final image = await _controller.takePicture();
final image = await controller.takePicture();
if (!context.mounted) return;
Navigator.of(context).pop(image);
} catch (e) {
print(e);
rethrow;
}
},
),
@ -274,6 +274,5 @@ class _CameraViewState extends State<CameraView> {
)
],
);
;
}
}

@ -1,51 +0,0 @@
import 'package:flutter/material.dart';
class PromptTextInput extends StatelessWidget {
PromptTextInput({
super.key,
required this.onChanged,
required this.onSendPressed,
textEditingController,
}) : _controller = textEditingController ?? TextEditingController();
final ValueChanged<String> onChanged;
final VoidCallback onSendPressed;
final TextEditingController _controller;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.transparent),
borderRadius: BorderRadius.circular(40),
color: Colors.white,
),
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width - 110,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: TextField(
maxLines: null,
controller: _controller,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Additional context...",
),
onChanged: (value) {
onChanged(value);
},
),
),
),
],
),
);
}
}

@ -29,20 +29,20 @@ class _SavedRecipesScreenState extends State<SavedRecipesScreen>
builder: (context, constraints) {
return Padding(
padding: constraints.isMobile
? EdgeInsets.only(
? const EdgeInsets.only(
left: MarketplaceTheme.spacing7,
right: MarketplaceTheme.spacing7,
bottom: MarketplaceTheme.spacing7,
top: MarketplaceTheme.spacing7,
)
: EdgeInsets.only(
: const EdgeInsets.only(
left: MarketplaceTheme.spacing7,
right: MarketplaceTheme.spacing7,
bottom: MarketplaceTheme.spacing1,
top: MarketplaceTheme.spacing7,
),
child: ClipRRect(
borderRadius: BorderRadius.only(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(MarketplaceTheme.defaultBorderRadius),
topRight: Radius.circular(50),
bottomRight:
@ -52,7 +52,7 @@ class _SavedRecipesScreenState extends State<SavedRecipesScreen>
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MarketplaceTheme.borderColor),
borderRadius: BorderRadius.only(
borderRadius: const BorderRadius.only(
topLeft:
Radius.circular(MarketplaceTheme.defaultBorderRadius),
topRight: Radius.circular(50),
@ -66,8 +66,8 @@ class _SavedRecipesScreenState extends State<SavedRecipesScreen>
child: constraints.isMobile
? ListView.builder(
physics: widget.canScroll
? PageScrollPhysics()
: NeverScrollableScrollPhysics(),
? const PageScrollPhysics()
: const NeverScrollableScrollPhysics(),
itemCount: viewModel.recipes.length,
itemBuilder: (context, idx) {
final recipe = viewModel.recipes[idx];
@ -76,7 +76,7 @@ class _SavedRecipesScreenState extends State<SavedRecipesScreen>
child: Align(
heightFactor: .5,
child: Padding(
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
horizontal: MarketplaceTheme.spacing7,
vertical: MarketplaceTheme.spacing7,
),
@ -97,15 +97,15 @@ class _SavedRecipesScreenState extends State<SavedRecipesScreen>
)
: GridView.count(
physics: widget.canScroll
? PageScrollPhysics()
: NeverScrollableScrollPhysics(),
? const PageScrollPhysics()
: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
childAspectRatio: 1.5,
children: [
...List.generate(viewModel.recipes.length, (idx) {
final recipe = viewModel.recipes[idx];
return Padding(
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
horizontal: MarketplaceTheme.spacing7,
vertical: MarketplaceTheme.spacing7,
),
@ -128,7 +128,7 @@ class _SavedRecipesScreenState extends State<SavedRecipesScreen>
}
class _ListTile extends StatefulWidget {
_ListTile({
const _ListTile({
super.key,
required this.recipe,
this.idx = 0,
@ -158,7 +158,7 @@ class _ListTileState extends State<_ListTile> {
return GestureDetector(
child: HighlightBorderOnHoverWidget(
borderRadius: BorderRadius.only(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(MarketplaceTheme.defaultBorderRadius),
topRight: Radius.circular(50),
bottomRight: Radius.circular(MarketplaceTheme.defaultBorderRadius),
@ -166,7 +166,7 @@ class _ListTileState extends State<_ListTile> {
),
color: color,
child: Container(
decoration: BoxDecoration(
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
offset: Offset(0, -2),
@ -185,7 +185,7 @@ class _ListTileState extends State<_ListTile> {
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(MarketplaceTheme.defaultBorderRadius),
topRight: Radius.circular(50),
bottomRight:
@ -195,7 +195,7 @@ class _ListTileState extends State<_ListTile> {
),
color: color.withOpacity(.3),
),
padding: EdgeInsets.all(MarketplaceTheme.spacing7),
padding: const EdgeInsets.all(MarketplaceTheme.spacing7),
child: Stack(
children: [
Text(
@ -225,15 +225,15 @@ class _ListTileState extends State<_ListTile> {
),
),
onTap: () async {
await showDialog(
await showDialog<Null>(
context: context,
builder: (context) {
return RecipeDialogScreen(
recipe: widget.recipe,
subheading: Row(
children: [
Text('My rating:'),
SizedBox(width: 10),
const Text('My rating:'),
const SizedBox(width: 10),
StartRating(
initialRating: widget.recipe.rating,
starColor: MarketplaceTheme.tertiary,

@ -24,11 +24,11 @@ class RecipeDisplayWidget extends StatelessWidget {
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
const Icon(
Symbols.stat_0_rounded,
size: 12,
),
SizedBox(
const SizedBox(
width: 5,
),
Expanded(
@ -52,7 +52,7 @@ class RecipeDisplayWidget extends StatelessWidget {
if (instructions.first.startsWith(RegExp('[0-9]'))) {
for (var instruction in instructions) {
widgets.add(Text(instruction));
widgets.add(SizedBox(height: MarketplaceTheme.spacing6));
widgets.add(const SizedBox(height: MarketplaceTheme.spacing6));
}
} else {
for (var i = 0; i < instructions.length; i++) {
@ -60,7 +60,7 @@ class RecipeDisplayWidget extends StatelessWidget {
'${i + 1}. ${instructions[i]}',
softWrap: true,
));
widgets.add(SizedBox(height: MarketplaceTheme.spacing6));
widgets.add(const SizedBox(height: MarketplaceTheme.spacing6));
}
}
@ -70,12 +70,12 @@ class RecipeDisplayWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
physics: ClampingScrollPhysics(),
physics: const ClampingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.all(MarketplaceTheme.defaultBorderRadius),
padding: const EdgeInsets.all(MarketplaceTheme.defaultBorderRadius),
color: MarketplaceTheme.primary.withOpacity(.5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -104,24 +104,24 @@ class RecipeDisplayWidget extends StatelessWidget {
),
TextButton(
style: ButtonStyle(
backgroundColor:
MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.hovered)) {
backgroundColor: WidgetStateColor.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
return MarketplaceTheme.scrim.withOpacity(.6);
}
return Colors.white;
}),
shape: MaterialStateProperty.resolveWith(
shape: WidgetStateProperty.resolveWith(
(states) {
return RoundedRectangleBorder(
side: BorderSide(color: MarketplaceTheme.primary),
side: const BorderSide(
color: MarketplaceTheme.primary),
borderRadius: BorderRadius.circular(
MarketplaceTheme.defaultBorderRadius,
),
);
},
),
textStyle: MaterialStateTextStyle.resolveWith(
textStyle: WidgetStateTextStyle.resolveWith(
(states) {
return MarketplaceTheme.dossierParagraph.copyWith(
color: Colors.black45,
@ -130,7 +130,7 @@ class RecipeDisplayWidget extends StatelessWidget {
),
),
onPressed: () async {
await showDialog(
await showDialog<dynamic>(
context: context,
builder: (context) {
return AlertDialog(
@ -144,7 +144,7 @@ class RecipeDisplayWidget extends StatelessWidget {
);
},
child: Transform.translate(
offset: Offset(0, 5),
offset: const Offset(0, 5),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: MarketplaceTheme.spacing6),
@ -159,7 +159,7 @@ class RecipeDisplayWidget extends StatelessWidget {
),
),
Transform.translate(
offset: Offset(1, -6),
offset: const Offset(1, -6),
child: Transform.rotate(
angle: -pi / 20.0,
child: Text(
@ -175,12 +175,12 @@ class RecipeDisplayWidget extends StatelessWidget {
)
],
),
Divider(
const Divider(
height: 40,
color: Colors.black26,
),
Table(
columnWidths: {
columnWidths: const {
0: FlexColumnWidth(2),
1: FlexColumnWidth(3),
},
@ -212,17 +212,17 @@ class RecipeDisplayWidget extends StatelessWidget {
fontWeight: FontWeight.bold,
),
),
Text(''),
const Text(''),
]),
...recipe.nutritionInformation.entries.map((entry) {
return TableRow(children: [
Row(
children: [
Icon(
const Icon(
Symbols.stat_0_rounded,
size: 12,
),
SizedBox(
const SizedBox(
width: 5,
),
Expanded(
@ -234,7 +234,8 @@ class RecipeDisplayWidget extends StatelessWidget {
),
],
),
Text(entry.value, style: MarketplaceTheme.label)
Text(entry.value as String,
style: MarketplaceTheme.label)
]);
}),
],
@ -258,7 +259,7 @@ class RecipeDisplayWidget extends StatelessWidget {
Text('Ingredients:', style: MarketplaceTheme.subheading1),
),
..._buildIngredients(recipe.ingredients),
SizedBox(height: MarketplaceTheme.spacing4),
const SizedBox(height: MarketplaceTheme.spacing4),
Padding(
padding: const EdgeInsets.symmetric(
vertical: MarketplaceTheme.spacing7),

@ -28,11 +28,11 @@ void main() async {
camera = cameras.first;
}
runApp(MainApp());
runApp(const MainApp());
}
class MainApp extends StatefulWidget {
MainApp({super.key});
const MainApp({super.key});
@override
State<MainApp> createState() => _MainAppState();
@ -105,7 +105,7 @@ class _MainAppState extends State<MainApp> {
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: MarketplaceTheme.theme,
scrollBehavior: ScrollBehavior().copyWith(
scrollBehavior: const ScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
@ -113,7 +113,7 @@ class _MainAppState extends State<MainApp> {
PointerDeviceKind.unknown,
},
),
home: AdaptiveRouter(),
home: const AdaptiveRouter(),
),
),
),

@ -50,7 +50,8 @@ class _AdaptiveRouterState extends State<AdaptiveRouter>
innerScrollAllowed = scrollController.offset >= 230;
if (scrollController.offset >= 230) {
scrollController.animateTo(230,
duration: Duration(milliseconds: 100), curve: Curves.decelerate);
duration: const Duration(milliseconds: 100),
curve: Curves.decelerate);
}
// Don't change the text opacity if scrolling down from original position (overscroll)
@ -87,11 +88,11 @@ class _AdaptiveRouterState extends State<AdaptiveRouter>
}
List<NavigationRailDestination> destinations = [
NavigationRailDestination(
const NavigationRailDestination(
icon: Icon(Symbols.home),
label: Text('Create a recipe'),
),
NavigationRailDestination(
const NavigationRailDestination(
icon: Icon(Symbols.bookmarks),
label: Text('Saved Recipes'),
)
@ -143,21 +144,21 @@ class _AdaptiveRouterState extends State<AdaptiveRouter>
child: Container(
height: bottomTabBarHeight,
decoration: ShapeDecoration(
shadows: [
shadows: const [
BoxShadow(
offset: Offset(1, -1),
color: Colors.black45,
blurRadius: 5,
)
],
shape: BottomBarShapeBorder(50),
shape: const BottomBarShapeBorder(50),
color: Theme.of(context).primaryColor,
),
child: TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.black26,
controller: tabController,
onTap: (int idx) {
onTap: (idx) {
setState(() {});
},
dividerColor: Colors.transparent,
@ -174,7 +175,7 @@ class _AdaptiveRouterState extends State<AdaptiveRouter>
height: 160,
width: 160,
child: IconLoadingAnimator(
icons: [
icons: const [
Symbols.icecream,
Symbols.local_pizza,
Symbols.restaurant_menu,
@ -204,7 +205,7 @@ class _AdaptiveRouterState extends State<AdaptiveRouter>
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
MarketplaceTheme.defaultBorderRadius),
boxShadow: [
boxShadow: const [
BoxShadow(
offset: Offset(-1, 1),
color: Colors.black45,
@ -218,7 +219,7 @@ class _AdaptiveRouterState extends State<AdaptiveRouter>
),
),
child: Padding(
padding: EdgeInsets.all(MarketplaceTheme.spacing6),
padding: const EdgeInsets.all(MarketplaceTheme.spacing6),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [

@ -6,15 +6,18 @@ const recipePath = '/recipes';
final firestore = FirebaseFirestore.instance;
class FirestoreService {
static saveRecipe(Recipe recipe) {
firestore.collection(recipePath).doc(recipe.id).set(recipe.toFirestore());
static Future<Null> saveRecipe(Recipe recipe) async {
await firestore
.collection(recipePath)
.doc(recipe.id)
.set(recipe.toFirestore());
}
static deleteRecipe(Recipe recipe) async {
static Future<Null> deleteRecipe(Recipe recipe) async {
await firestore.doc("$recipePath/${recipe.id}").delete();
}
static updateRecipe(Recipe recipe) async {
static Future<Null> updateRecipe(Recipe recipe) async {
await firestore
.doc("$recipePath/${recipe.id}")
.update(recipe.toFirestore());

@ -2,8 +2,6 @@ import 'package:google_generative_ai/google_generative_ai.dart';
import '../features/prompt/prompt_model.dart';
/// DONT USE THIS CLASS IN THE TALK.
/// It wasn't written to accommodate copy+pasting Gemini code.
class GeminiService {
static Future<GenerateContentResponse> generateContent(
GenerativeModel model, PromptData prompt) async {
@ -19,7 +17,7 @@ class GeminiService {
final mainText = TextPart(prompt.textInput);
final additionalTextParts =
prompt.additionalTextInputs.map((t) => TextPart(t));
final imagesParts = [];
final imagesParts = <DataPart>[];
for (var f in prompt.images) {
final bytes = await (f.readAsBytes());

@ -1,71 +0,0 @@
import 'package:google_generative_ai/google_generative_ai.dart';
import '../features/prompt/prompt_model.dart';
/// This code is for visual purposes only. I never actually use it when the app is running.
const formatting = '''
Return the recipe as valid JSON using the following structure:
{
"id": \$uniqueId,
"title": \$recipeTitle,
"ingredients": \$ingredients,
"description": \$description,
"instructions": \$instructions,
"cuisine": \$cuisineType,
"allergens": \$allergens,
"servings": \$servings,
"nutritionInformation": {
"calories": "\$calories",
"fat": "\$fat",
"carbohydrates": "\$carbohydrates",
"protein": "\$protein",
},
}
uniqueId should be unique and of type String.
title, description, cuisine, allergens, and servings should be of String type.
ingredients and instructions should be of type List<String>.
nutritionInformation should be of type Map<String, String>.
''';
class GenerativeAIService {
static Future<GenerateContentResponse> generateContent(
GenerativeModel model,
PromptData prompt,
) async {
final imagesParts = [];
for (var f in prompt.images) {
final bytes = await (f.readAsBytes());
imagesParts.add(DataPart('image/jpeg', bytes));
}
return await model.generateContent(
[
Content.multi([
TextPart('''
You are a Cat who's a chef that travels around the world a lot, and your travels inspire recipes.
Recommend a recipe for me based on the provided image.
The recipe should only contain real, edible ingredients.
If the image or images attached don't contain any food items, respond to say that you cannot recommend a recipe with inedible ingredients.
Adhere to food safety and handling best practices like ensuring that poultry is fully cooked.
I'm in the mood for the following types of cuisine: ${prompt.cuisines},
I have the following dietary restrictions: ${prompt.selectedDietaryRestrictions}
Optionally also include the following ingredients: ${prompt.ingredients}
After providing the recipe, explain creatively why the recipe is good based on only the ingredients used in the recipe. Tell a short story of a travel experience that inspired the recipe.
List out any ingredients that are potential allergens.
Provide a summary of how many people the recipe will serve and the the nutritional information per serving.
'''),
for (var text in prompt.additionalTextInputs) TextPart(text),
TextPart(formatting),
...imagesParts,
])
],
);
}
}

@ -4,15 +4,15 @@ import 'package:google_fonts/google_fonts.dart';
abstract class MarketplaceTheme {
static ThemeData theme = ThemeData(
fontFamily: GoogleFonts.lexend().fontFamily,
textTheme: GoogleFonts.lexendTextTheme()
.copyWith()
.apply(bodyColor: Color(0xff000000), displayColor: Color(0xff000000)),
colorScheme: ColorScheme.light(
textTheme: GoogleFonts.lexendTextTheme().copyWith().apply(
bodyColor: const Color(0xff000000),
displayColor: const Color(0xff000000)),
colorScheme: const ColorScheme.light(
primary: Color(0xffA2E3F6),
secondary: Color(0xff4FAD85),
tertiary: Color(0xffDE7A60),
scrim: Color(0xffFFABC7),
background: Color(0xffFDF7F0),
surface: Color(0xffFDF7F0),
onSecondary: Color(0xff000000),
shadow: Color(0xffAEAEAE),
onPrimary: Color(0xffFFFFFF),
@ -20,14 +20,14 @@ abstract class MarketplaceTheme {
useMaterial3: true,
canvasColor: Colors.transparent,
navigationBarTheme: NavigationBarThemeData(
indicatorColor: Color(0xffA2E3F6),
indicatorColor: const Color(0xffA2E3F6),
indicatorShape: CircleBorder(
side: BorderSide.lerp(
BorderSide(
const BorderSide(
color: Color(0xff000000),
width: 2,
),
BorderSide(
const BorderSide(
color: Color(0xff000000),
width: 2,
),

@ -1,27 +0,0 @@
import 'dart:convert';
/// Matches a complete title: 0-10 whitespaces, followed by 1-6 #s, followed by the title text
final markdownHeadingMatcher =
RegExp(r'^ {0,10}(#{1,6})(?:[ \x09\x0b\x0c].*?)?(?:\s(#*)\s*)?$');
/// Matches the #s only
final headingSyntaxMatcher = RegExp(r'(#{1,6})');
/// This is VERY naive. For demo purposes only.
(bool, String) getTitleFromMarkdownRecipe(String markdown) {
final LineSplitter splitter = LineSplitter();
final mdCopy = markdown;
final mdAsLines = splitter.convert(mdCopy);
final matchingLine = mdAsLines.firstWhere((line) {
RegExpMatch? match = markdownHeadingMatcher.firstMatch(line);
return match != null;
});
RegExpMatch? match = markdownHeadingMatcher.firstMatch(matchingLine);
if (match != null) {
final split = matchingLine.split(headingSyntaxMatcher);
if (split.length == 1) return (true, split.first);
return (true, split.last);
}
return (false, markdown);
}

@ -28,7 +28,7 @@ const _tapRadius = 15.0,
///
/// It will both visualize them and add them to [recordedTaps].
class TapRecorder extends SingleChildRenderObjectWidget {
const TapRecorder({Key? key, required Widget child}) : super(child: child);
const TapRecorder({super.key, required Widget child}) : super(child: child);
@override
RenderObject createRenderObject(BuildContext context) {

@ -1,11 +1,10 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../theme.dart';
class AddImage extends StatefulWidget {
AddImage({
const AddImage({
super.key,
required this.onTap,
this.height = 100,
@ -39,12 +38,12 @@ class _AddImageState extends State<AddImage> {
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (PointerEnterEvent event) {
onEnter: (event) {
setState(() {
hovered = true;
});
},
onExit: (PointerExitEvent event) {
onExit: (event) {
setState(() {
hovered = false;
});
@ -71,7 +70,7 @@ class _AddImageState extends State<AddImage> {
decoration: BoxDecoration(
color: buttonColor,
),
child: Center(
child: const Center(
child: Icon(
Symbols.add_photo_alternate_rounded,
size: 32,

@ -4,14 +4,14 @@ import 'package:gemini_io_talk/util/extensions.dart';
import 'package:gemini_io_talk/util/filter_chip_enums.dart';
class FilterChipSelectionInput<T extends Enum> extends StatefulWidget {
FilterChipSelectionInput({
const FilterChipSelectionInput({
super.key,
required this.onChipSelected,
required this.selectedValues,
required this.allValues,
});
final Function(Set) onChipSelected;
final Null Function(Set) onChipSelected;
final Set<T> selectedValues;
final List<T> allValues;
@ -34,9 +34,9 @@ class _CategorySelectionInputState<T extends Enum>
runSpacing: constraints.isMobile ? 5.0 : -5.0,
children: List<Widget>.generate(
widget.allValues.length,
(int idx) {
(idx) {
final chipData = widget.allValues[idx];
String label(chipData) {
String label(dynamic chipData) {
if (chipData is CuisineFilter) {
return cuisineReadable(chipData);
} else if (chipData is DietaryRestrictionsFilter) {
@ -49,11 +49,11 @@ class _CategorySelectionInputState<T extends Enum>
}
return FilterChip(
color: MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.hovered)) {
color: WidgetStateColor.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
return MarketplaceTheme.secondary.withOpacity(.5);
}
if (states.contains(MaterialState.selected)) {
if (states.contains(WidgetState.selected)) {
return MarketplaceTheme.secondary.withOpacity(.3);
}
return Theme.of(context).splashColor;
@ -61,13 +61,13 @@ class _CategorySelectionInputState<T extends Enum>
surfaceTintColor: Colors.transparent,
shadowColor: Colors.transparent,
backgroundColor: Colors.transparent,
padding: EdgeInsets.all(4),
padding: const EdgeInsets.all(4),
label: Text(
label(chipData),
style: MarketplaceTheme.dossierParagraph,
),
selected: widget.selectedValues.contains(chipData),
onSelected: (bool selected) {
onSelected: (selected) {
setState(
() {
if (selected) {

@ -1,4 +1,3 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../theme.dart';
@ -27,12 +26,12 @@ class _HighlightBorderOnHoverWidgetState
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (PointerEnterEvent event) {
onEnter: (event) {
setState(() {
hovered = true;
});
},
onExit: (PointerExitEvent event) {
onExit: (event) {
setState(() {
hovered = false;
});

@ -79,8 +79,8 @@ class _IconLoadingAnimatorState extends State<IconLoadingAnimator> {
),
),
child: AnimatedSwitcher(
duration: widget.animationDuration ?? Duration(milliseconds: 200),
transitionBuilder: (Widget child, Animation<double> animation) {
duration: widget.animationDuration ?? const Duration(milliseconds: 200),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,

@ -52,20 +52,20 @@ class _MarketplaceButtonState extends State<MarketplaceButton> {
),
onPressed: widget.onPressed,
style: ButtonStyle(
backgroundColor: MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.hovered)) {
backgroundColor: WidgetStateColor.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
return widget.hoverColor ??
MarketplaceTheme.secondary.withOpacity(.3);
}
return widget.buttonBackgroundColor ??
Theme.of(context).splashColor.withOpacity(.3);
}),
shape: MaterialStateProperty.resolveWith(
shape: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(MaterialState.hovered)) {
if (states.contains(WidgetState.hovered)) {
// TODO: how can I animate between states?
}
return RoundedRectangleBorder(
return const RoundedRectangleBorder(
side: BorderSide(color: Colors.black26),
borderRadius: BorderRadius.all(
Radius.circular(MarketplaceTheme.defaultBorderRadius),
@ -73,7 +73,7 @@ class _MarketplaceButtonState extends State<MarketplaceButton> {
);
},
),
textStyle: MaterialStateTextStyle.resolveWith(
textStyle: WidgetStateTextStyle.resolveWith(
(states) {
return MarketplaceTheme.dossierParagraph.copyWith(
color: Colors.black45,

@ -8,7 +8,7 @@ import 'cross_image_widget.dart';
typedef OnTapRemoveImageCallback = void Function(XFile);
class PromptImage extends StatelessWidget {
PromptImage({
const PromptImage({
super.key,
required this.file,
this.onTapIcon,
@ -27,7 +27,7 @@ class PromptImage extends StatelessWidget {
children: [
Positioned(
child: ClipRRect(
borderRadius: BorderRadius.all(
borderRadius: const BorderRadius.all(
Radius.circular(
MarketplaceTheme.defaultBorderRadius,
),
@ -46,7 +46,7 @@ class PromptImage extends StatelessWidget {
child: GestureDetector(
onTap: onTapIcon,
child: Container(
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),

@ -31,6 +31,8 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1
analysis_defaults:
path: ../analysis_defaults
flutter:
uses-material-design: true

@ -1,34 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:gemini_io_talk/util/markdown.dart';
const testMarkdown = '''
## Potato Leek Soup
**Ingredients:**
- 3 leeks
- 3 potatoes
- 1 onion
- 2 cloves garlic
- 4 cups vegetable broth
- 1/2 cup milk
- 1/4 cup butter
- 1/4 cup flour
- 1 teaspoon salt
- 1/2 teaspoon pepper
**Instructions:**
1. In a large pot or Dutch oven, heat the butter over medium heat. Add the leeks, onion, and garlic and cook until softened, about 5 minutes.
2. Add the potatoes, vegetable broth, salt, and pepper to the pot. Bring to a boil, then reduce heat and simmer for 15 minutes, or until the potatoes are tender.
3. In a small bowl, whisk together the milk and flour until smooth. Add to the pot and cook, stirring constantly, until the soup has thickened, about 5 minutes.
4. Serve immediately.
''';
main() {
group('parse recipe from markdown', () {
test('parses title', () {
final (gotTitle, title) = getTitleFromMarkdownRecipe(testMarkdown);
print(title);
expect(gotTitle, true);
});
});
}
Loading…
Cancel
Save