-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d3af56d
commit 96780bf
Showing
17 changed files
with
989 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"java.compile.nullAnalysis.mode": "disabled", | ||
"java.configuration.updateBuildConfiguration": "interactive" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import 'package:dart_nostr/dart_nostr.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
import 'package:let_him_cook/src/features/recipes/recipe.dart'; | ||
|
||
class RecipeEditState { | ||
final String title; | ||
final List<String> tags; | ||
final String summary; | ||
final String prepTime; | ||
final String cookTime; | ||
final String servings; | ||
final List<MapEntry<String, String>> ingredients; | ||
final List<String> directions; | ||
final List<String> images; | ||
final Map<String, String> relatedRecipes; | ||
final bool isPublishing; | ||
|
||
RecipeEditState({ | ||
this.title = '', | ||
this.tags = const [], | ||
this.summary = '', | ||
this.prepTime = '', | ||
this.cookTime = '', | ||
this.servings = '', | ||
this.ingredients = const [], | ||
this.directions = const [], | ||
this.images = const [], | ||
this.relatedRecipes = const {}, | ||
this.isPublishing = false, | ||
}); | ||
|
||
RecipeEditState copyWith({ | ||
String? title, | ||
List<String>? tags, | ||
String? summary, | ||
String? prepTime, | ||
String? cookTime, | ||
String? servings, | ||
List<MapEntry<String, String>>? ingredients, | ||
List<String>? directions, | ||
List<String>? images, | ||
Map<String, String>? relatedRecipes, | ||
bool? isPublishing, | ||
}) { | ||
return RecipeEditState( | ||
title: title ?? this.title, | ||
tags: tags ?? this.tags, | ||
summary: summary ?? this.summary, | ||
prepTime: prepTime ?? this.prepTime, | ||
cookTime: cookTime ?? this.cookTime, | ||
servings: servings ?? this.servings, | ||
ingredients: ingredients ?? this.ingredients, | ||
directions: directions ?? this.directions, | ||
images: images ?? this.images, | ||
relatedRecipes: relatedRecipes ?? this.relatedRecipes, | ||
isPublishing: isPublishing ?? this.isPublishing, | ||
); | ||
} | ||
} | ||
|
||
class RecipeEditNotifier extends StateNotifier<RecipeEditState> { | ||
RecipeEditNotifier(NostrEvent? recipe) : super(RecipeEditState()) { | ||
if (recipe != null) { | ||
state = state.copyWith( | ||
title: recipe.title, | ||
tags: recipe.categories, | ||
summary: recipe.summary, | ||
prepTime: recipe.prepTime, | ||
cookTime: recipe.cookTime, | ||
servings: recipe.servings, | ||
ingredients: | ||
recipe.ingredients.map((e) => MapEntry(e, e)).toList(), | ||
directions: recipe.directions, | ||
images: [recipe.image], | ||
); | ||
} | ||
} | ||
|
||
void updateTitle(String val) => state = state.copyWith(title: val); | ||
void updateSummary(String val) => state = state.copyWith(summary: val); | ||
void updatePrepTime(String val) => state = state.copyWith(prepTime: val); | ||
void updateCookTime(String val) => state = state.copyWith(cookTime: val); | ||
void updateServings(String val) => state = state.copyWith(servings: val); | ||
void updateIngredients(List<MapEntry<String, String>> val) => | ||
state = state.copyWith(ingredients: val); | ||
void updateDirections(String val) => state = state.copyWith(directions: [val]); | ||
void addImage(String url) => | ||
state = state.copyWith(images: [...state.images, url]); | ||
void updateRelatedRecipes(Map<String, String> newMap) { | ||
state = state.copyWith(relatedRecipes: newMap); | ||
} | ||
|
||
Future<void> publishRecipe() async { | ||
state = state.copyWith(isPublishing: true); | ||
//await NostrService.publishRecipe(state); | ||
state = state.copyWith(isPublishing: false); | ||
} | ||
|
||
removeTag(int index) {} | ||
|
||
removeImage(int index) {} | ||
|
||
addTag(String newTag) {} | ||
} | ||
|
||
final recipeEditProvider = | ||
StateNotifierProvider.family<RecipeEditNotifier, RecipeEditState, NostrEvent?>( | ||
(ref, recipe) => RecipeEditNotifier(recipe), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import 'package:dart_nostr/dart_nostr.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
import 'package:let_him_cook/src/features/recipes/recipe_edit_provider.dart'; | ||
import 'package:let_him_cook/src/features/recipes/widgets/images_combo_box.dart'; | ||
import 'package:let_him_cook/src/features/recipes/widgets/markdown_editor.dart'; | ||
import 'package:let_him_cook/src/features/recipes/widgets/recipe_combo_box.dart'; | ||
import 'package:let_him_cook/src/features/recipes/widgets/tags_combo_box.dart'; | ||
import 'package:let_him_cook/src/features/recipes/widgets/tuple_combo_box.dart'; | ||
|
||
class RecipeEditScreen extends ConsumerWidget { | ||
final NostrEvent? recipe; // If null, create new recipe | ||
|
||
const RecipeEditScreen({super.key, this.recipe}); | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
final editState = ref.watch(recipeEditProvider(recipe)); | ||
|
||
return Scaffold( | ||
appBar: AppBar( | ||
title: Text(recipe == null ? 'Create Recipe' : 'Edit Recipe'), | ||
actions: [ | ||
TextButton( | ||
onPressed: editState.isPublishing | ||
? null | ||
: () { | ||
ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.publishRecipe(); | ||
}, | ||
child: Text( | ||
recipe == null ? 'Publish' : 'Update', | ||
style: TextStyle( | ||
color: editState.isPublishing ? Colors.grey : Colors.white), | ||
), | ||
), | ||
], | ||
), | ||
body: SingleChildScrollView( | ||
padding: const EdgeInsets.all(16.0), | ||
child: Column( | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
_buildTextField( | ||
label: 'Title*', | ||
hint: 'Unique recipe title', | ||
value: editState.title, | ||
onChanged: (val) => ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.updateTitle(val), | ||
), | ||
const SizedBox(height: 16), | ||
TagsComboBox( | ||
selectedTags: editState.tags, | ||
onTagSelected: (newTag) => | ||
ref.read(recipeEditProvider(recipe).notifier).addTag(newTag), | ||
onTagRemoved: (index) => ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.removeTag(index), | ||
), | ||
const SizedBox(height: 16), | ||
_buildTextField( | ||
label: 'Brief Summary', | ||
hint: 'A short description of the dish', | ||
value: editState.summary, | ||
maxLines: 3, | ||
onChanged: (val) => ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.updateSummary(val), | ||
), | ||
const SizedBox(height: 16), | ||
RecipeComboBox( | ||
selectedRecipes: editState.relatedRecipes, | ||
onChanged: (updatedMap) { | ||
ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.updateRelatedRecipes(updatedMap); | ||
}, | ||
placeholder: 'Select a recipe...', | ||
), | ||
const SizedBox(height: 16), | ||
_buildTextField( | ||
label: 'Prep Time', | ||
hint: 'e.g., 20 min', | ||
value: editState.prepTime, | ||
onChanged: (val) => ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.updatePrepTime(val), | ||
), | ||
_buildTextField( | ||
label: 'Cook Time', | ||
hint: 'e.g., 1 hour', | ||
value: editState.cookTime, | ||
onChanged: (val) => ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.updateCookTime(val), | ||
), | ||
_buildTextField( | ||
label: 'Servings', | ||
hint: 'e.g., 4 persons', | ||
value: editState.servings, | ||
onChanged: (val) => ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.updateServings(val), | ||
), | ||
const SizedBox(height: 16), | ||
TupleComboBox( | ||
selectedItems: editState.ingredients, | ||
amountPlaceholder: 'Quantity (e.g., 1 cup)', | ||
itemPlaceholder: 'Ingredient (e.g., flour)', | ||
onChanged: (ingredients) => ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.updateIngredients(ingredients), | ||
), | ||
const SizedBox(height: 16), | ||
MarkdownEditor( | ||
content: editState.directions.toString(), | ||
onChanged: (val) => ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.updateDirections(val), | ||
), | ||
const SizedBox(height: 16), | ||
ImagesComboBox( | ||
images: editState.images, | ||
onImageAdded: (url) => | ||
ref.read(recipeEditProvider(recipe).notifier).addImage(url), | ||
onImageRemoved: (index) => ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.removeImage(index), | ||
), | ||
const SizedBox(height: 24), | ||
ElevatedButton( | ||
onPressed: editState.isPublishing | ||
? null | ||
: () { | ||
ref | ||
.read(recipeEditProvider(recipe).notifier) | ||
.publishRecipe(); | ||
}, | ||
child: editState.isPublishing | ||
? const CircularProgressIndicator() | ||
: Text(recipe == null ? 'Publish' : 'Update'), | ||
), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
|
||
Widget _buildTextField({ | ||
required String label, | ||
required String hint, | ||
required String value, | ||
int maxLines = 1, | ||
required Function(String) onChanged, | ||
}) { | ||
return Column( | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)), | ||
const SizedBox(height: 4), | ||
TextField( | ||
decoration: InputDecoration( | ||
hintText: hint, | ||
border: const OutlineInputBorder(), | ||
), | ||
maxLines: maxLines, | ||
onChanged: onChanged, | ||
), | ||
], | ||
); | ||
} | ||
} |
Oops, something went wrong.