add recipe upload handling on frontend side and in LocalEndpoint
continuous-integration/drone/push Build is passing Details

pull/50/head
maxime.BATISTA@etu.uca.fr 2 years ago
parent c04fec0927
commit ebeed7a28c

@ -42,30 +42,30 @@ namespace Endpoint
public ImmutableList<RecipeInfo> GetRecommendedRecipes()
{
return new List<RecipeInfo> {
new RecipeInfo("Chicken Salad", 500, 20, new Uri("https://healthyfitnessmeals.com/wp-content/uploads/2021/04/Southwest-chicken-salad-7-500x500.jpg"), 4, new Guid()),
new RecipeInfo("Chocolate Cake", 2500, 10, new Uri("https://bakewithshivesh.com/wp-content/uploads/2022/08/IMG_0248-scaled.jpg"), 3, new Guid()),
new RecipeInfo("Salmon", 20, 10, new Uri("https://www.wholesomeyum.com/wp-content/uploads/2021/06/wholesomeyum-Pan-Seared-Salmon-Recipe-13.jpg"), 4, new Guid()),
new RecipeInfo("Fish", 50, 30, new Uri("https://www.ciaanet.org/wp-content/uploads/2022/07/Atlantic-and-Pacific-whole-salmon-1024x683.jpg"), 4.5F, new Guid()),
new RecipeInfo("Space Cake", 800, 5, new Uri("https://static.youmiam.com/images/recipe/1500x1000/space-cake-22706?placeholder=web_recipe&sig=f14a7a86da837c6b8cc678cde424d6d5902f99ec&v3"), 5, new Guid())
new RecipeInfo("Chicken Salad", 500, 20, new Uri("https://healthyfitnessmeals.com/wp-content/uploads/2021/04/Southwest-chicken-salad-7-500x500.jpg"), 4, Guid.NewGuid()),
new RecipeInfo("Chocolate Cake", 2500, 10, new Uri("https://bakewithshivesh.com/wp-content/uploads/2022/08/IMG_0248-scaled.jpg"), 3, Guid.NewGuid()),
new RecipeInfo("Salmon", 20, 10, new Uri("https://www.wholesomeyum.com/wp-content/uploads/2021/06/wholesomeyum-Pan-Seared-Salmon-Recipe-13.jpg"), 4, Guid.NewGuid()),
new RecipeInfo("Fish", 50, 30, new Uri("https://www.ciaanet.org/wp-content/uploads/2022/07/Atlantic-and-Pacific-whole-salmon-1024x683.jpg"), 4.5F, Guid.NewGuid()),
new RecipeInfo("Space Cake", 800, 5, new Uri("https://static.youmiam.com/images/recipe/1500x1000/space-cake-22706?placeholder=web_recipe&sig=f14a7a86da837c6b8cc678cde424d6d5902f99ec&v3"), 5, Guid.NewGuid())
}.ToImmutableList();
}
public ImmutableList<RecipeInfo> GetFavorites()
{
return new List<RecipeInfo> {
new RecipeInfo("Chicken Salad", 500, 20, new Uri("https://healthyfitnessmeals.com/wp-content/uploads/2021/04/Southwest-chicken-salad-7-500x500.jpg"), 4, new Guid()),
new RecipeInfo("Space Cake", 800, 5, new Uri("https://static.youmiam.com/images/recipe/1500x1000/space-cake-22706?placeholder=web_recipe&sig=f14a7a86da837c6b8cc678cde424d6d5902f99ec&v3"), 5, new Guid()),
new RecipeInfo("Fish And Ships", 450, 15, new Uri("https://upload.wikimedia.org/wikipedia/commons/f/ff/Fish_and_chips_blackpool.jpg"), 4, new Guid()),
new RecipeInfo("Caesar Salad", 150, 20, new Uri("https://www.galbani.fr/wp-content/uploads/2020/04/AdobeStock_157570276-2.jpeg"), 1, new Guid())
new RecipeInfo("Chicken Salad", 500, 20, new Uri("https://healthyfitnessmeals.com/wp-content/uploads/2021/04/Southwest-chicken-salad-7-500x500.jpg"), 4, Guid.NewGuid()),
new RecipeInfo("Space Cake", 800, 5, new Uri("https://static.youmiam.com/images/recipe/1500x1000/space-cake-22706?placeholder=web_recipe&sig=f14a7a86da837c6b8cc678cde424d6d5902f99ec&v3"), 5, Guid.NewGuid()),
new RecipeInfo("Fish And Ships", 450, 15, new Uri("https://upload.wikimedia.org/wikipedia/commons/f/ff/Fish_and_chips_blackpool.jpg"), 4, Guid.NewGuid()),
new RecipeInfo("Caesar Salad", 150, 20, new Uri("https://www.galbani.fr/wp-content/uploads/2020/04/AdobeStock_157570276-2.jpeg"), 1, Guid.NewGuid())
}.ToImmutableList();
}
public ImmutableList<(RecipeInfo, uint)> GetWeeklyList()
{
return new List<(RecipeInfo, uint)> {
(new RecipeInfo("Space Cake", 800, 5, new Uri("https://static.youmiam.com/images/recipe/1500x1000/space-cake-22706?placeholder=web_recipe&sig=f14a7a86da837c6b8cc678cde424d6d5902f99ec&v3"), 5, new Guid()), 4),
(new RecipeInfo("Chicken Curry", 500, 45, new Uri("https://cdn.chefclub.tools/uploads/recipes/cover-thumbnail/f287b191-dc8e-4c85-bbb6-e26387c354d3.jpg"), 3, new Guid()), 4),
(new RecipeInfo("Spaghetti Bolognese", 1000, 30, new Uri("https://media.istockphoto.com/id/1144823591/fr/photo/spaghetti-dans-un-plat-sur-un-fond-blanc.jpg?s=612x612&w=0&k=20&c=qFzd8iE185mpsX7hWqYaieOWlzJVCkzFdYsxmwUT3-Q="), 1, new Guid()), 1),
(new RecipeInfo("Space Cake", 800, 5, new Uri("https://static.youmiam.com/images/recipe/1500x1000/space-cake-22706?placeholder=web_recipe&sig=f14a7a86da837c6b8cc678cde424d6d5902f99ec&v3"), 5, Guid.NewGuid()), 4),
(new RecipeInfo("Chicken Curry", 500, 45, new Uri("https://cdn.chefclub.tools/uploads/recipes/cover-thumbnail/f287b191-dc8e-4c85-bbb6-e26387c354d3.jpg"), 3, Guid.NewGuid()), 4),
(new RecipeInfo("Spaghetti Bolognese", 1000, 30, new Uri("https://media.istockphoto.com/id/1144823591/fr/photo/spaghetti-dans-un-plat-sur-un-fond-blanc.jpg?s=612x612&w=0&k=20&c=qFzd8iE185mpsX7hWqYaieOWlzJVCkzFdYsxmwUT3-Q="), 1, Guid.NewGuid()), 1),
}.ToImmutableList();
}

@ -12,11 +12,11 @@ namespace LocalEndpoint
public List<RecipeInfo> PopularRecipes()
{
return new List<RecipeInfo> {
new RecipeInfo("Chicken Curry", 500, 45, new Uri("https://cdn.chefclub.tools/uploads/recipes/cover-thumbnail/f287b191-dc8e-4c85-bbb6-e26387c354d3.jpg"), 3, new Guid()),
new RecipeInfo("Spaghetti Bolognese", 1000, 30, new Uri("https://media.istockphoto.com/id/1144823591/fr/photo/spaghetti-dans-un-plat-sur-un-fond-blanc.jpg?s=612x612&w=0&k=20&c=qFzd8iE185mpsX7hWqYaieOWlzJVCkzFdYsxmwUT3-Q="), 1, new Guid()),
new RecipeInfo("Beef Stroganoff", 100, 10, new Uri("https://www.cookwithnabeela.com/wp-content/uploads/2023/02/BeefStroganoff.webp"), 5, new Guid()),
new RecipeInfo("Fish And Ships", 450, 15, new Uri("https://upload.wikimedia.org/wikipedia/commons/f/ff/Fish_and_chips_blackpool.jpg"), 4, new Guid()),
new RecipeInfo("Caesar Salad", 150, 20, new Uri("https://www.galbani.fr/wp-content/uploads/2020/04/AdobeStock_157570276-2.jpeg"), 1, new Guid())
new RecipeInfo("Chicken Curry", 500, 45, new Uri("https://cdn.chefclub.tools/uploads/recipes/cover-thumbnail/f287b191-dc8e-4c85-bbb6-e26387c354d3.jpg"), 3, Guid.NewGuid()),
new RecipeInfo("Spaghetti Bolognese", 1000, 30, new Uri("https://media.istockphoto.com/id/1144823591/fr/photo/spaghetti-dans-un-plat-sur-un-fond-blanc.jpg?s=612x612&w=0&k=20&c=qFzd8iE185mpsX7hWqYaieOWlzJVCkzFdYsxmwUT3-Q="), 1, Guid.NewGuid()),
new RecipeInfo("Beef Stroganoff", 100, 10, new Uri("https://www.cookwithnabeela.com/wp-content/uploads/2023/02/BeefStroganoff.webp"), 5, Guid.NewGuid()),
new RecipeInfo("Fish And Ships", 450, 15, new Uri("https://upload.wikimedia.org/wikipedia/commons/f/ff/Fish_and_chips_blackpool.jpg"), 4, Guid.NewGuid()),
new RecipeInfo("Caesar Salad", 150, 20, new Uri("https://www.galbani.fr/wp-content/uploads/2020/04/AdobeStock_157570276-2.jpeg"), 1, Guid.NewGuid())
};
}
@ -36,7 +36,7 @@ namespace LocalEndpoint
if (recipes == null) {
recipes = new AccountRecipes(account);
recipes.UploadRecipe(new Recipe(new RecipeInfo("Cupcake", 500, 12, new Uri("https://www.mycake.fr/wp-content/uploads/2015/12/rs_cupcake_4x3.jpg"), 4.2F, new Guid()), account.User, new List<Ingredient> { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList()));
recipes.UploadRecipe(new Recipe(new RecipeInfo("Cupcake", 500, 12, new Uri("https://www.mycake.fr/wp-content/uploads/2015/12/rs_cupcake_4x3.jpg"), 4.2F, Guid.NewGuid()), account.User, new List<Ingredient> { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList()));
accountsRecipes.Add(account, recipes);
}
return recipes;

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Models
{
public class RecipeBuilder
{
private readonly string name;
private readonly User owner;
private uint callPerPers;
private uint cookTimeMins;
private Uri? image;
private List<Ingredient> ingredients = new List<Ingredient>();
private List<PreparationStep> steps = new List<PreparationStep>();
public RecipeBuilder(string name, User owner)
{
this.name = name;
this.owner = owner;
}
public RecipeBuilder SetCallPerPers(uint callPerPers)
{
this.callPerPers = callPerPers;
return this;
}
public RecipeBuilder SetCookTimeMins(uint cookTimeMins)
{
this.cookTimeMins = cookTimeMins;
return this;
}
public RecipeBuilder SetImage(Uri image)
{
this.image = image;
return this;
}
public RecipeBuilder AddIngredient(Ingredient ingredient)
{
this.ingredients.Add(ingredient);
return this;
}
public RecipeBuilder AddStep(PreparationStep step)
{
this.steps.Add(step);
return this;
}
public Recipe Build()
{
RecipeInfo info = new RecipeInfo(name, callPerPers, cookTimeMins, image, 0, Guid.NewGuid());
return new Recipe(info, owner, ingredients.ToImmutableList(), steps.ToImmutableList());
}
}
}

@ -4,7 +4,7 @@
string Name,
uint CalPerPers,
uint CookTimeMins,
Uri Image,
Uri? Image,
float AverageNote,
Guid Id);
}

@ -21,7 +21,8 @@
<Entry
Style="{StaticResource UserInput}"
Placeholder="Ingredient Name"
HeightRequest="40"/>
HeightRequest="40"
x:Name="NameEntry"/>
</Border>
<Border
Grid.Column="1"
@ -32,7 +33,9 @@
<Entry
Style="{StaticResource UserInput}"
Placeholder="Quantity"
HeightRequest="40"/>
HeightRequest="40"
Keyboard="Numeric"
x:Name="QuantityEntry"/>
</Border>
<Border
Grid.Column="2"
@ -44,7 +47,8 @@
Title="Unit"
TextColor="{StaticResource TextColorPrimary}"
TitleColor="{StaticResource TextColorSecondary}"
FontFamily="PoppinsMedium">
FontFamily="PoppinsMedium"
x:Name="UnitPicker">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>G</x:String>

@ -1,3 +1,5 @@
using Models;
namespace ShoopNCook.Views;
public partial class IngredientEntry : ContentView
@ -6,4 +8,17 @@ public partial class IngredientEntry : ContentView
{
InitializeComponent();
}
public Ingredient MakeValue()
{
float quantity;
if (!float.TryParse(QuantityEntry.Text, out quantity))
{
quantity = 0;
// TODO handle quantity text malformation by raising exception
}
return new Ingredient(NameEntry.Text, quantity);
}
}

@ -13,6 +13,7 @@ public partial class OwnedRecipeView : ContentView
{
InitializeComponent();
if (info.Image != null)
RecipeImage.Source = ImageSource.FromUri(info.Image);
Note = info.AverageNote;
Title = info.Name;
@ -38,15 +39,15 @@ public partial class OwnedRecipeView : ContentView
private void SetNote(float note)
{
int i = 1;
foreach (Image img in Stars.Children)
note = (uint)note; //truncate integer as we currently do not handle semi stars
foreach (Image img in Stars.Children.Reverse())
{
if (i < note)
if (note > 0)
{
img.Opacity = 0;
i++;
img.Opacity = 1;
note--;
}
else img.Opacity = 1;
else img.Opacity = 0;
}
}

@ -10,10 +10,13 @@ public partial class RecipeView : ContentView
public RecipeView(RecipeInfo info, Action onClickCallback)
{
InitializeComponent();
if (info.Image != null)
RecipeImage.Source = ImageSource.FromUri(info.Image);
Note = info.AverageNote;
Title = info.Name;
Subtitle = info.CookTimeMins + " min";
RecipeImage.Source = ImageSource.FromUri(info.Image);
callback = onClickCallback;
@ -38,15 +41,15 @@ public partial class RecipeView : ContentView
private void SetNote(float note)
{
int i = 1;
foreach (Image img in Stars.Children)
note = (uint)note; //truncate integer as we currently do not handle semi stars
foreach (Image img in Stars.Children.Reverse())
{
if (i < note)
if (note > 0)
{
img.Opacity = 0;
i++;
img.Opacity = 1;
note--;
}
else img.Opacity = 1;
else img.Opacity = 0;
}
}
private void OnRecipeTapped(object sender, EventArgs e)

@ -22,7 +22,8 @@
MaxLength="10000"
Style="{StaticResource UserInput}"
AutoSize="TextChanges"
FontSize="15"/>
FontSize="15"
x:Name="StepEditor"/>
</Border>
</VerticalStackLayout>

@ -1,3 +1,5 @@
using Models;
namespace ShoopNCook.Views;
public partial class StepEntry : ContentView
@ -12,6 +14,11 @@ public partial class StepEntry : ContentView
Ordinal = ordinal;
}
public PreparationStep MakeStep()
{
return new PreparationStep("Step " + Ordinal, StepEditor.Text);
}
public uint Ordinal {
get => uint.Parse(OrdinalLabel.Text);
set => OrdinalLabel.Text = value.ToString();

@ -12,11 +12,12 @@ public partial class StoredRecipeView : ContentView
{
InitializeComponent();
clickCallback = onClickCallback;
if (info.Image != null)
RecipeImage.Source = ImageSource.FromUri(info.Image);
Note = info.AverageNote;
Title = info.Name;
RecipeImage.Source = ImageSource.FromUri(info.Image);
clickCallback = onClickCallback;
Counter.Count = personCount;
}
@ -32,15 +33,15 @@ public partial class StoredRecipeView : ContentView
private void SetNote(float note)
{
int i = 1;
foreach (Image img in Stars.Children)
note = (uint)note; //truncate integer as we currently do not handle semi stars
foreach (Image img in Stars.Children.Reverse())
{
if (i < note)
if (note > 0)
{
img.Opacity = 0;
i++;
img.Opacity = 1;
note--;
}
else img.Opacity = 1;
else img.Opacity = 0;
}
}

@ -56,6 +56,7 @@
VerticalOptions="End"
TranslationY="20"
TranslationX="-20">
<ImageButton
Source="edit.svg"
WidthRequest="30"/>
@ -66,7 +67,8 @@
<!--Ingredients-->
<Entry
Style="{StaticResource UserInput}"
Placeholder="Specify your recipe name"/>
Placeholder="Specify your recipe name"
x:Name="RecipeNameEntry"/>
<Label
Style="{StaticResource h2}"
Text="Ingredient list (for 1 person)"/>
@ -115,6 +117,21 @@
Text="minutes"/>
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label
Style="{StaticResource h3}"
VerticalTextAlignment="Center"
Text="Estimated cal/persons: "/>
<Entry
Style="{StaticResource UserInput}"
Keyboard="Numeric"
x:Name="EnergyInput"/>
<Label
Style="{StaticResource h3}"
VerticalTextAlignment="Center"
Text="cal/pers"/>
</HorizontalStackLayout>
<!--Preparation entry steps list-->
<Label
Style="{StaticResource h2}"
@ -154,7 +171,8 @@
Grid.Row="3"
Style="{StaticResource UserButton}"
BackgroundColor="{StaticResource ActionButton}"
Text="Upload recipe"/>
Text="Upload recipe"
Clicked="OnUploadRecipeClicked"/>
</Grid>
</ContentPage>

@ -1,12 +1,21 @@
using Models;
using ShoopNCook.Views;
namespace ShoopNCook.Pages;
public partial class CreateRecipePage : ContentPage
{
public CreateRecipePage()
private User owner;
private Action<Recipe> onRecipeCreated;
private IUserNotifier notifier;
public CreateRecipePage(User owner, IUserNotifier notifier, Action<Recipe> onRecipeCreated)
{
InitializeComponent();
this.owner = owner;
this.onRecipeCreated = onRecipeCreated;
this.notifier = notifier;
}
private void OnAddIngredientTapped(object sender, TappedEventArgs e)
@ -18,8 +27,48 @@ public partial class CreateRecipePage : ContentPage
{
StepList.Children.Add(new StepEntry((uint) StepList.Children.Count() + 1));
}
private async void OnBackButtonClicked(object sender, EventArgs e)
private void OnBackButtonClicked(object sender, EventArgs e)
{
Navigation.PopAsync();
}
private void OnUploadRecipeClicked(object sender, EventArgs e)
{
await Navigation.PopAsync();
uint callPerPers;
uint cookTimeMins;
bool hadErrors = false;
if (!uint.TryParse(EnergyInput.Text, out callPerPers))
{
hadErrors = true;
//TODO change EnergyInput background to red.
}
if (!uint.TryParse(CookTimeInput.Text, out cookTimeMins))
{
hadErrors = true;
//TODO change CookTimeInput background to red.
}
if (hadErrors)
{
notifier.Error("You need to fix input errors before upload.");
return;
}
RecipeBuilder builder = new RecipeBuilder(RecipeNameEntry.Text, owner)
.SetCallPerPers(callPerPers)
.SetCookTimeMins(cookTimeMins)
//TODO .SetImage(RecipeImage)
;
foreach (IngredientEntry entry in IngredientList.Children)
builder.AddIngredient(entry.MakeValue());
foreach (StepEntry entry in StepList.Children)
builder.AddStep(entry.MakeStep());
onRecipeCreated(builder.Build());
}
}

@ -52,7 +52,7 @@
Style="{StaticResource UserButton}"
BackgroundColor="{StaticResource Selected}"
Text="Add a new recipe"
Clicked="AddRecipeButtonClicked"/>
Clicked="OnAddRecipeButtonClicked"/>
</Border>
</Grid>
</ContentPage>

@ -1,7 +1,6 @@
using Endpoint;
using LocalEndpoint;
using Models;
using ShoopNCook.Controllers;
using ShoopNCook.Views;
namespace ShoopNCook.Pages;
@ -10,6 +9,7 @@ public partial class MyRecipesPage : ContentPage
{
private IUserNotifier notifier;
private IRecipesService service;
private IAccountRecipes recipes;
public MyRecipesPage(
@ -20,9 +20,13 @@ public partial class MyRecipesPage : ContentPage
InitializeComponent();
this.notifier = notifier;
this.service = service;
this.recipes = service.GetRecipesOf(account);
recipes.GetAccountRecipes().ForEach(info =>
recipes.GetAccountRecipes().ForEach(AddRecipeView);
}
private void AddRecipeView(RecipeInfo info)
{
RecipesLayout.Children.Add(new OwnedRecipeView(info, () =>
{
@ -32,7 +36,6 @@ public partial class MyRecipesPage : ContentPage
},
() => RemoveRecipe(info)
));
});
}
private void RemoveRecipe(RecipeInfo info)
@ -53,13 +56,23 @@ public partial class MyRecipesPage : ContentPage
notifier.Success("Recipe successfully removed");
}
private async void OnBackButtonClicked(object sender, EventArgs e)
private void OnBackButtonClicked(object sender, EventArgs e)
{
await Navigation.PopAsync();
Navigation.PopAsync();
}
private async void AddRecipeButtonClicked(object sender, EventArgs e)
private void OnAddRecipeButtonClicked(object sender, EventArgs e)
{
await Shell.Current.Navigation.PushAsync(new CreateRecipePage());
var page = new CreateRecipePage(recipes.Account.User, notifier, recipe =>
{
if (!recipes.UploadRecipe(recipe))
{
notifier.Error("Could not upload recipe.");
return;
}
notifier.Success("Recipe Successfuly uploaded !");
AddRecipeView(recipe.Info);
Shell.Current.Navigation.PopAsync(); //go back to current recipe page.
});
Shell.Current.Navigation.PushAsync(page); //display RecipePage editor
}
}
Loading…
Cancel
Save