Merge pull request 'Integrate Endpoint/API/models for main functionallities' (#50) from models/integration into master
continuous-integration/drone/push Build is passing Details

Reviewed-on: ShopNCook/ShopNCook#50
pull/51/head
Maxime BATISTA 2 years ago
commit e2df22c858

@ -8,18 +8,18 @@ public partial class App : Application, ConnectionObserver, IApp
private IEndpoint Endpoint = new LocalEndpoint(); private IEndpoint Endpoint = new LocalEndpoint();
public UserNotifier Notifier => new ConsoleUserNotifier(); public IUserNotifier Notifier => new ConsoleUserNotifier();
public App() public App()
{ {
InitializeComponent(); InitializeComponent();
ForceLogin(); //start in login state ForceLogin(); //start in login shell
} }
public void OnAccountConnected(Account account) public void OnAccountConnected(Account account)
{ {
Shell shell = new MainAppShell(account, this); Shell shell = new MainAppShell(account, Endpoint, this);
shell.GoToAsync("//Main"); shell.GoToAsync("//Home");
MainPage = shell; MainPage = shell;
} }

@ -1,17 +1,17 @@
namespace ShoopNCook; namespace ShoopNCook;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Models; using Models;
using Endpoint; using Endpoint;
using ShoopNCook.Controllers; using ShoopNCook.Controllers;
using ShoopNCook.Pages; using ShoopNCook.Pages;
public partial class ConnectAppShell : Shell public partial class ConnectAppShell : Shell
{ {
public ConnectAppShell(ConnectionObserver observer, IAccountManager accounts, UserNotifier notifier) public ConnectAppShell(ConnectionObserver observer, IAccountManager accounts, IUserNotifier notifier)
{ {
ConnectionController controller = new ConnectionController(observer, accounts, notifier); ConnectionController controller = new ConnectionController(observer, accounts, notifier);
InitializeComponent(); InitializeComponent();
LoginPage.ContentTemplate = new DataTemplate(() => new LoginPage(controller)); LoginPage.ContentTemplate = new DataTemplate(() => new LoginPage(controller));
RegisterPage.ContentTemplate = new DataTemplate(() => new RegisterPage(controller)); RegisterPage.ContentTemplate = new DataTemplate(() => new RegisterPage(controller));
} }
} }

@ -4,8 +4,15 @@
/// A notice reporter implementation that prints in console the applications's user notices. /// A notice reporter implementation that prints in console the applications's user notices.
/// </summary> /// </summary>
public class ConsoleUserNotifier : public class ConsoleUserNotifier :
UserNotifier IUserNotifier
{ {
public void Success(string message)
{
Console.WriteLine("<User Notice> Success: " + message);
}
public void Error(string message) public void Error(string message)
{ {
Console.WriteLine("<User Notice> Error: " + message); Console.WriteLine("<User Notice> Error: " + message);

@ -7,8 +7,8 @@ namespace ShoopNCook.Controllers
{ {
private readonly ConnectionObserver observer; private readonly ConnectionObserver observer;
private readonly IAccountManager accounts; private readonly IAccountManager accounts;
private readonly UserNotifier notifier; private readonly IUserNotifier notifier;
public ConnectionController(ConnectionObserver observer, IAccountManager accounts, UserNotifier notifier) { public ConnectionController(ConnectionObserver observer, IAccountManager accounts, IUserNotifier notifier) {
this.observer = observer; this.observer = observer;
this.accounts = accounts; this.accounts = accounts;
this.notifier = notifier; this.notifier = notifier;

@ -1,8 +1,6 @@
using System; using Endpoint;
using System.Collections.Generic; using Models;
using System.Linq; using ShoopNCook.Pages;
using System.Text;
using System.Threading.Tasks;
namespace ShoopNCook.Controllers namespace ShoopNCook.Controllers
{ {
@ -10,9 +8,14 @@ namespace ShoopNCook.Controllers
{ {
private readonly IApp app; private readonly IApp app;
private readonly IEndpoint endpoint;
private readonly Account account;
public MorePageController(IApp app) { public MorePageController(Account account, IEndpoint endpoint, IApp app)
{
this.app = app; this.app = app;
this.endpoint = endpoint;
this.account = account;
} }
public void Logout() public void Logout()
@ -20,5 +23,15 @@ namespace ShoopNCook.Controllers
app.Notifier.Notice("You have been loged out."); app.Notifier.Notice("You have been loged out.");
app.ForceLogin(); app.ForceLogin();
} }
public void GoToMyRecipesPage()
{
Shell.Current.Navigation.PushAsync(new MyRecipesPage(account, endpoint.RecipesService, app.Notifier));
}
public void GoToProfilePage()
{
Shell.Current.Navigation.PushAsync(new ProfilePage(account));
}
} }
} }

@ -0,0 +1,23 @@
using Models;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LocalEndpoint
{
public interface IAccountOwnedRecipes
{
public Account Account { get; }
public bool UploadRecipe(Recipe recipe);
public bool RemoveRecipe(RecipeInfo info);
public ImmutableList<RecipeInfo> GetAccountRecipes();
}
}

@ -0,0 +1,25 @@
using Models;
using System.Collections.Immutable;
namespace Endpoint
{
public interface IAccountRecipesPreferences
{
public Account Account { get; }
public void AddToFavorites(RecipeInfo info);
public void RemoveFromFavorites(RecipeInfo info);
public void SetReviewScore(RecipeInfo info, uint score);
public bool AddToWeeklyList(RecipeInfo info, uint persAmount);
public AccountRecipeRate GetRate(RecipeInfo info);
public ImmutableList<RecipeInfo> GetFavorites();
public ImmutableList<RecipeInfo> GetRecommendedRecipes();
public ImmutableList<(RecipeInfo, uint)> GetWeeklyList();
}
}

@ -6,6 +6,8 @@ namespace Endpoint
{ {
public IAccountManager AccountManager { get; } public IAccountManager AccountManager { get; }
public IRecipesService RecipesService { get; }
} }
} }

@ -0,0 +1,23 @@
using LocalEndpoint;
using Models;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Endpoint
{
public interface IRecipesService
{
public ImmutableList<RecipeInfo> PopularRecipes();
public Recipe GetRecipe(RecipeInfo info);
public IAccountOwnedRecipes GetRecipesOf(Account account);
public IAccountRecipesPreferences GetPreferencesOf(Account account);
}
}

@ -9,7 +9,7 @@ namespace ShoopNCook
{ {
public interface IApp public interface IApp
{ {
public UserNotifier Notifier { get; } public IUserNotifier Notifier { get; }
public void ForceLogin(); public void ForceLogin();
} }

@ -1,18 +1,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ShoopNCook namespace ShoopNCook
{ {
public interface UserNotifier public interface IUserNotifier
{
{ public void Success(string message);
public void Notice(string message);
public void Notice(string message);
public void Error(string message);
public void Error(string message);
public void Warn(string message);
} public void Warn(string message);
} }
}

@ -0,0 +1,11 @@
using Endpoint;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LocalEndpoint
{
internal record AccountData(IAccountOwnedRecipes Recipes, IAccountRecipesPreferences Preferences);
}

@ -1,37 +0,0 @@
using Models;
using Endpoint;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LocalEndpoint
{
internal class AccountManager : IAccountManager
{
private static readonly Uri DEFAULT_ACCOUNT_IMAGE = new Uri("https://www.pngkey.com/png/full/115-1150152_default-profile-picture-avatar-png-green.png");
private Account userAccount = new Account(new User(DEFAULT_ACCOUNT_IMAGE, "Stub Account"), "test@example.com");
private string userPassword = "123456";
public Account? Login(string email, string password)
{
if (userAccount.Email == email && userPassword == password)
{
return userAccount;
}
return null;
}
public Account? Register(string email, string username, string password)
{
if (email == null || username == null || password == null)
return null;
userAccount = new Account(new User(DEFAULT_ACCOUNT_IMAGE, username), email);
userPassword = password;
return userAccount;
}
}
}

@ -0,0 +1,52 @@
using LocalEndpoint;
using Models;
using System.Collections.Immutable;
namespace Endpoint
{
internal class AccountOwnedRecipes : IAccountOwnedRecipes
{
public Account Account { get; init; }
private readonly Dictionary<Guid, Recipe> ownedRecipes = new Dictionary<Guid, Recipe>();
private readonly RecipesDatabase db;
public AccountOwnedRecipes(Account account, RecipesDatabase db)
{
Account = account;
this.db = db;
//Retrieve all owned recipes from database.
db.ListAll().ForEach(recipe =>
{
if (recipe.Owner == account.User) ownedRecipes[recipe.Info.Id] = recipe;
});
}
public bool UploadRecipe(Recipe recipe)
{
Guid id = recipe.Info.Id;
if (ownedRecipes.ContainsKey(id))
{
return false;
}
db.Insert(recipe);
ownedRecipes.Add(id, recipe);
return true;
}
public bool RemoveRecipe(RecipeInfo info)
{
db.Remove(info.Id);
return ownedRecipes.Remove(info.Id);
}
public ImmutableList<RecipeInfo> GetAccountRecipes()
{
return ownedRecipes.Values.ToImmutableList().ConvertAll(r => r.Info);
}
}
}

@ -0,0 +1,102 @@
using Endpoint;
using Models;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LocalEndpoint
{
internal class AccountRecipesPreferences : IAccountRecipesPreferences
{
//Binds a recipe's id to it's account review ratings.
private readonly Dictionary<Guid, AccountRecipeRate> ratings = new Dictionary<Guid, AccountRecipeRate>();
//Binds a recipe's id to its amount of person stored in the account's weekly list
private readonly Dictionary<Guid, uint> weekly = new Dictionary<Guid, uint>();
private readonly RecipesDatabase db;
public AccountRecipesPreferences(Account account, RecipesDatabase db)
{
Account = account;
this.db = db;
}
public Account Account { get; init; }
public ImmutableList<RecipeInfo> GetRecommendedRecipes()
{
return db.ListAll().ConvertAll(recipe => recipe.Info);
}
public ImmutableList<RecipeInfo> GetFavorites()
{
List<RecipeInfo> favorites = new List<RecipeInfo>();
foreach (Recipe recipe in db.ListAll())
{
if (ratings.TryGetValue(recipe.Info.Id, out AccountRecipeRate? rate))
{
if (rate.IsFavorite)
favorites.Add(recipe.Info);
}
}
return favorites.ToImmutableList();
}
public ImmutableList<(RecipeInfo, uint)> GetWeeklyList()
{
List<(RecipeInfo, uint)> weekly = new List<(RecipeInfo, uint)>();
foreach (Recipe recipe in db.ListAll())
{
if (this.weekly.TryGetValue(recipe.Info.Id, out uint personAmmount))
{
weekly.Add((recipe.Info, personAmmount));
}
}
return weekly.ToImmutableList();
}
public AccountRecipeRate GetRate(RecipeInfo info)
{
AccountRecipeRate rate = null;
if (!ratings.TryGetValue(info.Id, out rate))
{
rate = new AccountRecipeRate();
ratings.Add(info.Id, rate);
}
return rate;
}
public void AddToFavorites(RecipeInfo info)
{
AccountRecipeRate rate = GetRate(info);
ratings[info.Id] = new AccountRecipeRate(true, rate.Rate);
}
public bool AddToWeeklyList(RecipeInfo info, uint persAmount)
{
if (weekly.ContainsKey(info.Id))
return false;
weekly[info.Id] = persAmount;
return true;
}
public void RemoveFromFavorites(RecipeInfo info)
{
AccountRecipeRate rate = GetRate(info);
ratings[info.Id] = new AccountRecipeRate(false, rate.Rate);
}
public void SetReviewScore(RecipeInfo info, uint score)
{
AccountRecipeRate rate = GetRate(info);
ratings[info.Id] = new AccountRecipeRate(rate.IsFavorite, score);
}
}
}

@ -0,0 +1,33 @@
using Models;
using Endpoint;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LocalEndpoint
{
internal class ConnectionManager : IAccountManager
{
private Account userAccount = Constants.MAIN_USER_ACCOUNT;
private string userPassword = Constants.MAIN_USER_PASSWORD;
public Account? Login(string email, string password)
{
if (Constants.MAIN_USER_ACCOUNT.Email == email && Constants.MAIN_USER_PASSWORD == password)
return Constants.MAIN_USER_ACCOUNT;
return null;
}
public Account? Register(string email, string username, string password)
{
if (email == null || username == null || password == null)
return null;
userAccount = new Account(new User(Constants.DEFAULT_ACCOUNT_IMAGE, username), email);
userPassword = password;
return userAccount;
}
}
}

@ -0,0 +1,20 @@
using Models;
namespace LocalEndpoint
{
internal class Constants
{
public static readonly Uri DEFAULT_ACCOUNT_IMAGE = new Uri("https://www.pngkey.com/png/full/115-1150152_default-profile-picture-avatar-png-green.png");
// User account for tests
public static readonly Account MAIN_USER_ACCOUNT = new Account(new User(DEFAULT_ACCOUNT_IMAGE, "Example Account"), "test@example.com");
public static readonly string MAIN_USER_PASSWORD = "123456";
// other user samples
public static readonly User USER1 = new User(new Uri("https://i.ibb.co/L6t6bGR/DALL-E-2023-05-10-20-27-31-cook-looking-at-the-camera-with-a-chef-s-hat-laughing-in-an-exaggerated-w.png"), "The Funny Chief");
public static readonly User USER2 = new User(DEFAULT_ACCOUNT_IMAGE, "Yanis");
public static readonly User USER3 = new User(DEFAULT_ACCOUNT_IMAGE, "Leo");
}
}

@ -1,10 +1,37 @@
using Endpoint; using Endpoint;
using Models;
using System.Collections.Immutable;
namespace LocalEndpoint namespace LocalEndpoint
{ {
/// <summary>
/// The local endpoint is an implementation of the Endpoint API definition.
///
/// </summary>
public class LocalEndpoint : IEndpoint public class LocalEndpoint : IEndpoint
{ {
public IAccountManager AccountManager => new AccountManager(); private readonly IAccountManager accountManager = new ConnectionManager();
private readonly IRecipesService recipesService;
public LocalEndpoint()
{
RecipesDatabase db = new RecipesDatabase();
//miam
db.Insert(new Recipe(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()), Constants.USER1, new List<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList()));
db.Insert(new Recipe(new RecipeInfo("Chocolate Cake", 2500, 10, new Uri("https://bakewithshivesh.com/wp-content/uploads/2022/08/IMG_0248-scaled.jpg"), 3, Guid.NewGuid()), Constants.USER2, new List<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList()));
db.Insert(new Recipe(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()), Constants.MAIN_USER_ACCOUNT.User, new List<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList()));
db.Insert(new Recipe(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()), Constants.MAIN_USER_ACCOUNT.User, new List<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList()));
db.Insert(new Recipe(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()), Constants.USER3, new List<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList()));
recipesService = new RecipesService(db);
}
public IAccountManager AccountManager => accountManager;
public IRecipesService RecipesService => recipesService;
} }
} }

@ -0,0 +1,43 @@
using Models;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace LocalEndpoint
{
//Simple class to simulate a recipe database
internal class RecipesDatabase
{
private Dictionary<Guid, Recipe> recipes = new Dictionary<Guid, Recipe>();
public Recipe? Lookup(Guid id)
{
Recipe? recipe;
recipes.TryGetValue(id, out recipe);
return recipe;
}
public Recipe Get(Guid id)
{
return recipes[id];
}
public void Insert(Recipe recipe)
{
recipes[recipe.Info.Id] = recipe;
}
public void Remove(Guid id)
{
recipes.Remove(id);
}
public ImmutableList<Recipe> ListAll()
{
return recipes.Values.ToImmutableList();
}
}
}

@ -0,0 +1,55 @@
using Endpoint;
using Models;
using System.Collections.Immutable;
namespace LocalEndpoint
{
internal class RecipesService : IRecipesService
{
private readonly RecipesDatabase db;
private readonly Dictionary<Account, AccountData> accountsData = new Dictionary<Account, AccountData>();
public RecipesService(RecipesDatabase db)
{
this.db = db;
}
public ImmutableList<RecipeInfo> PopularRecipes()
{
return db.ListAll().Take(4).ToImmutableList().ConvertAll(v => v.Info);
}
public Recipe GetRecipe(RecipeInfo info)
{
return db.Get(info.Id);
}
public IAccountOwnedRecipes GetRecipesOf(Account account)
{
return GetOrInitData(account).Recipes;
}
public IAccountRecipesPreferences GetPreferencesOf(Account account)
{
return GetOrInitData(account).Preferences;
}
private AccountData GetOrInitData(Account account)
{
AccountData? data;
accountsData.TryGetValue(account, out data);
if (data == null)
{
AccountOwnedRecipes recipes = new AccountOwnedRecipes(account, db);
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()));
AccountRecipesPreferences preferences = new AccountRecipesPreferences(account, db);
data = new AccountData(recipes, preferences);
accountsData.Add(account, data);
}
return data;
}
}
}

@ -1,17 +1,17 @@
namespace ShoopNCook; namespace ShoopNCook;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Models; using Models;
using ShoopNCook.Controllers; using ShoopNCook.Controllers;
using ShoopNCook.Pages; using ShoopNCook.Pages;
using Endpoint;
public partial class MainAppShell : Shell public partial class MainAppShell : Shell
{ {
public MainAppShell(Account account, IApp app) public MainAppShell(Account account, IEndpoint endpoint, IApp app)
{ {
InitializeComponent(); InitializeComponent();
HomeTab.ContentTemplate = new DataTemplate(() => new HomePage(account, app)); HomeTab.ContentTemplate = new DataTemplate(() => new HomePage(account, app.Notifier, endpoint));
FavoritesTab.ContentTemplate = new DataTemplate(() => new FavoritesPage(account, app)); FavoritesTab.ContentTemplate = new DataTemplate(() => new FavoritesPage(account, app.Notifier, endpoint.RecipesService));
MyListTab.ContentTemplate = new DataTemplate(() => new MyListPage(account, app)); MyListTab.ContentTemplate = new DataTemplate(() => new MyListPage(account, app.Notifier, endpoint.RecipesService));
MoreTab.ContentTemplate = new DataTemplate(() => new MorePage(account, new MorePageController(app))); MoreTab.ContentTemplate = new DataTemplate(() => new MorePage(account, new MorePageController(account, endpoint, app)));
} }
} }

@ -1,15 +1,4 @@
namespace Models namespace Models
{ {
public class Account public record Account(User User, string Email);
{
public Account(User usr, string mail)
{
User = usr;
Email = mail;
}
public User User { get; init; }
public string Email { get; init; }
}
} }

@ -0,0 +1,5 @@

namespace Models
{
public record AccountRecipeRate(bool IsFavorite = false, uint Rate = 0);
}

@ -1,17 +1,4 @@
namespace Models namespace Models
{ {
public class Ingredient public record Ingredient(string Name, float Amount);
{
public Ingredient(string name, float amount, Quantity quantity)
{
Name = name;
Amount = amount;
Quantity = quantity;
}
public string Name { get; init; }
public float Amount { get; init; }
public Quantity Quantity { get; init; }
}
} }

@ -1,15 +1,5 @@
namespace Models namespace Models
{ {
public class PreparationStep public record PreparationStep(string Name, string Description);
{
public PreparationStep(string name, string description)
{
Name = name;
Description = description;
}
public string Name { get; init; }
public string Description { get; init; }
}
} }

@ -1,6 +0,0 @@
namespace Models
{
public class Quantity
{
}
}

@ -1,25 +1,10 @@
namespace Models using System.Collections.Immutable;
{
public class Recipe
{
public RecipeInfo Info { get; init; }
public User Owner { get; init; }
public List<Ingredient> Ingredients { get; init; } namespace Models
public List<PreparationStep> Steps { get; init; } {
public record Recipe(
public Recipe( RecipeInfo Info,
RecipeInfo info, User Owner,
User owner, ImmutableList<Ingredient> Ingredients,
List<Ingredient> ingredients, ImmutableList<PreparationStep> Steps);
List<PreparationStep> steps)
{
Info = info;
Owner = owner;
Ingredients = ingredients;
Steps = steps;
}
}
} }

@ -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());
}
}
}

@ -1,18 +1,10 @@
namespace Models namespace Models
{ {
public class RecipeInfo public record RecipeInfo(
{ string Name,
public string Name { get; init; } uint CalPerPers,
public string Description { get; init; } uint CookTimeMins,
public Uri Image { get; init; } Uri? Image,
public float AverageNote { get; init; } float AverageNote,
Guid Id);
public RecipeInfo(string name, string description, Uri image, float averageNote)
{
Name = name;
Description = description;
Image = image;
AverageNote = averageNote;
}
}
} }

@ -1,15 +1,4 @@
namespace Models namespace Models
{ {
public class User public record User(Uri ProfilePicture, string Name);
{
public User(Uri profilePicture, string name)
{
ProfilePicture = profilePicture;
Name = name;
}
public Uri ProfilePicture { get; init; }
public string Name { get; init; }
}
} }

@ -196,9 +196,7 @@
</ItemGroup> </ItemGroup>
<ProjectExtensions> <ProjectExtensions>
<VisualStudio> <VisualStudio><UserProperties XamarinHotReloadDebuggerTimeoutExceptionShoopNCookHideInfoBar="True" XamarinHotReloadGenericExceptionInfoBarShoopNCookHideInfoBar="True" XamarinHotReloadUnhandledDeviceExceptionShoopNCookHideInfoBar="True" /></VisualStudio>
<UserProperties XamarinHotReloadDebuggerTimeoutExceptionShoopNCookHideInfoBar="True" XamarinHotReloadUnhandledDeviceExceptionShoopNCookHideInfoBar="True" />
</VisualStudio>
</ProjectExtensions> </ProjectExtensions>
</Project> </Project>

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

@ -1,3 +1,5 @@
using Models;
namespace ShoopNCook.Views; namespace ShoopNCook.Views;
public partial class IngredientEntry : ContentView public partial class IngredientEntry : ContentView
@ -6,4 +8,17 @@ public partial class IngredientEntry : ContentView
{ {
InitializeComponent(); 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);
}
} }

@ -1,3 +1,5 @@
using Models;
namespace ShoopNCook.Views; namespace ShoopNCook.Views;
public partial class IngredientView : ContentView public partial class IngredientView : ContentView
@ -30,12 +32,13 @@ public partial class IngredientView : ContentView
set => SetValue(UnitProperty, value); set => SetValue(UnitProperty, value);
} }
public IngredientView(string name, float quantity, string unit) public IngredientView(Ingredient ingredient)
{ {
InitializeComponent(); InitializeComponent();
Name = name; Name = ingredient.Name;
Quantity = quantity; Quantity = ingredient.Amount;
Unit = unit; //TODO Unit implementation in IngredientView.xaml.cs
Unit = "TODO: Unit implementation in IngredientView.xaml.cs";
} }
} }

@ -12,13 +12,20 @@
MinimumHeightRequest="175" MinimumHeightRequest="175"
MinimumWidthRequest="150" MinimumWidthRequest="150"
RowDefinitions="*, Auto"> RowDefinitions="*, Auto">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnViewTapped"
NumberOfTapsRequired="1"/>
</Grid.GestureRecognizers>
<Border <Border
Grid.Row="0" Grid.Row="0"
Stroke="Transparent" Stroke="Transparent"
StrokeShape="RoundRectangle 20" StrokeShape="RoundRectangle 20"
BackgroundColor="{StaticResource ImageBackground}"> BackgroundColor="{StaticResource ImageBackground}">
<Grid> <Grid>
<Image /> <Image x:Name="RecipeImage" />
<HorizontalStackLayout <HorizontalStackLayout
x:Name="Stars" x:Name="Stars"
VerticalOptions="End" VerticalOptions="End"
@ -60,7 +67,7 @@
> >
<Grid.GestureRecognizers> <Grid.GestureRecognizers>
<TapGestureRecognizer <TapGestureRecognizer
Tapped="TapGestureRecognizer_Tapped" Tapped="OnRemoveButtonTapped"
NumberOfTapsRequired="1"/> NumberOfTapsRequired="1"/>
</Grid.GestureRecognizers> </Grid.GestureRecognizers>
<Image <Image

@ -1,18 +1,32 @@
using Models;
namespace ShoopNCook.Views; namespace ShoopNCook.Views;
public partial class OwnedRecipeView : ContentView public partial class OwnedRecipeView : ContentView
{ {
public OwnedRecipeView() : this(5, "Title") private readonly Action clickCallback;
{ } private readonly Action removeCallback;
private readonly RecipeInfo recipeInfo;
public OwnedRecipeView(float note, string title) public OwnedRecipeView(RecipeInfo info, Action onClickCallback, Action onRemoveCallback)
{ {
InitializeComponent(); InitializeComponent();
Note = note;
Title = title; if (info.Image != null)
RecipeImage.Source = ImageSource.FromUri(info.Image);
Note = info.AverageNote;
Title = info.Name;
this.recipeInfo = info;
this.clickCallback = onClickCallback;
this.removeCallback = onRemoveCallback;
} }
public bool IsViewing(RecipeInfo info)
{
return recipeInfo == info;
}
public float Note public float Note
{ {
set => SetNote(value); set => SetNote(value);
@ -25,20 +39,25 @@ public partial class OwnedRecipeView : ContentView
private void SetNote(float note) private void SetNote(float note)
{ {
int i = 1; note = (uint)note; //truncate integer as we currently do not handle semi stars
foreach (Image img in Stars.Children) foreach (Image img in Stars.Children.Reverse())
{ {
if (i <= note) if (note > 0)
{ {
img.Opacity = 0; img.Opacity = 1;
i++; note--;
} }
else img.Opacity = 1; else img.Opacity = 0;
} }
} }
private void TapGestureRecognizer_Tapped(object sender, TappedEventArgs e) private void OnViewTapped(object sender, TappedEventArgs e)
{
clickCallback();
}
private void OnRemoveButtonTapped(object sender, TappedEventArgs e)
{ {
Console.WriteLine("This is a test"); removeCallback();
} }
} }

@ -1,20 +1,28 @@
using ShoopNCook.Pages; using ShoopNCook.Pages;
using Models;
namespace ShoopNCook.Views; namespace ShoopNCook.Views;
public partial class RecipeView : ContentView public partial class RecipeView : ContentView
{ {
public RecipeView(): this(5, "Title", "Subtitle") private readonly Action callback;
{}
public RecipeView(float note, string title, string subtitle) public RecipeView(RecipeInfo info, Action onClickCallback)
{ {
InitializeComponent(); InitializeComponent();
Note = note;
Title = title; if (info.Image != null)
Subtitle = subtitle; RecipeImage.Source = ImageSource.FromUri(info.Image);
}
Note = info.AverageNote;
Title = info.Name;
Subtitle = info.CookTimeMins + " min";
callback = onClickCallback;
}
public float Note public float Note
{ {
@ -31,22 +39,21 @@ public partial class RecipeView : ContentView
set => SubtitleLabel.Text = value; set => SubtitleLabel.Text = value;
} }
private void SetNote(float note) private void SetNote(float note)
{ {
int i = 1; note = (uint)note; //truncate integer as we currently do not handle semi stars
foreach (Image img in Stars.Children) foreach (Image img in Stars.Children.Reverse())
{ {
if (i <= note) if (note > 0)
{ {
img.Opacity = 0; img.Opacity = 1;
i++; note--;
} }
else img.Opacity = 1; else img.Opacity = 0;
} }
} }
private async void OnRecipeTapped(object sender, EventArgs e) private void OnRecipeTapped(object sender, EventArgs e)
{ {
await Shell.Current.Navigation.PushAsync(new RecipePage()); callback();
} }
} }

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

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

@ -14,13 +14,18 @@
MinimumHeightRequest="250" MinimumHeightRequest="250"
MinimumWidthRequest="150" MinimumWidthRequest="150"
RowDefinitions="*, Auto"> RowDefinitions="*, Auto">
<Grid.GestureRecognizers>
<TapGestureRecognizer Tapped="OnRecipeTapped"/>
</Grid.GestureRecognizers>
<Border <Border
Grid.Row="0" Grid.Row="0"
Stroke="Transparent" Stroke="Transparent"
StrokeShape="RoundRectangle 20" StrokeShape="RoundRectangle 20"
BackgroundColor="{StaticResource ImageBackground}"> BackgroundColor="{StaticResource ImageBackground}">
<Grid> <Grid>
<Image /> <Image x:Name="RecipeImage"/>
<HorizontalStackLayout <HorizontalStackLayout
x:Name="Stars" x:Name="Stars"
VerticalOptions="End" VerticalOptions="End"
@ -51,8 +56,9 @@
<Label <Label
TextColor="{StaticResource TextColorPrimary}" TextColor="{StaticResource TextColorPrimary}"
x:Name="TitleLabel"/> x:Name="TitleLabel"/>
<Grid></Grid> <views:CounterView
<views:CounterView CounterText="pers"/> CounterText="pers"
x:Name="Counter"/>
</VerticalStackLayout> </VerticalStackLayout>
</Grid> </Grid>
</Border> </Border>

@ -1,16 +1,24 @@
using Models;
namespace ShoopNCook.Views; namespace ShoopNCook.Views;
public partial class StoredRecipeView : ContentView public partial class StoredRecipeView : ContentView
{ {
public StoredRecipeView() : this(5, "Title") private readonly Action<uint> clickCallback;
{ }
public StoredRecipeView(float note, string title) public StoredRecipeView(RecipeInfo info, uint personCount, Action<uint> onClickCallback)
{ {
InitializeComponent(); InitializeComponent();
Note = note;
Title = title; if (info.Image != null)
RecipeImage.Source = ImageSource.FromUri(info.Image);
Note = info.AverageNote;
Title = info.Name;
clickCallback = onClickCallback;
Counter.Count = personCount;
} }
public float Note public float Note
@ -23,19 +31,22 @@ public partial class StoredRecipeView : ContentView
set => TitleLabel.Text = value; set => TitleLabel.Text = value;
} }
private void SetNote(float note) private void SetNote(float note)
{ {
int i = 1; note = (uint)note; //truncate integer as we currently do not handle semi stars
foreach (Image img in Stars.Children) foreach (Image img in Stars.Children.Reverse())
{ {
if (i <= note) if (note > 0)
{ {
img.Opacity = 0; img.Opacity = 1;
i++; note--;
} }
else img.Opacity = 1; else img.Opacity = 0;
} }
} }
private void OnRecipeTapped(object sender, TappedEventArgs e)
{
clickCallback(Counter.Count);
}
} }

@ -21,7 +21,8 @@
HeightRequest="50" HeightRequest="50"
WidthRequest="50" WidthRequest="50"
Source="arrow_back.svg"/> Source="arrow_back.svg"
Clicked="OnBackButtonClicked"/>
</HorizontalStackLayout> </HorizontalStackLayout>
<Label <Label
Margin="-40,10,0,0" Margin="-40,10,0,0"
@ -78,7 +79,8 @@
BackgroundColor="{StaticResource ActionButton}" BackgroundColor="{StaticResource ActionButton}"
FontFamily="PoppinsMedium" FontFamily="PoppinsMedium"
TextColor="White" TextColor="White"
Text="That's been my email"/> Text="That's been my email"
/>
</Border> </Border>
</VerticalStackLayout> </VerticalStackLayout>
</Grid> </Grid>
@ -136,7 +138,8 @@
BackgroundColor="{StaticResource ActionButton}" BackgroundColor="{StaticResource ActionButton}"
FontFamily="PoppinsMedium" FontFamily="PoppinsMedium"
TextColor="White" TextColor="White"
Text="Confirm my email"/> Text="Confirm my email"
Clicked="OnRegiterButtonTapped"/>
</Border> </Border>
</VerticalStackLayout> </VerticalStackLayout>

@ -6,4 +6,12 @@ public partial class ConfirmMail : ContentPage
{ {
InitializeComponent(); InitializeComponent();
} }
private async void OnRegiterButtonTapped(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("//LoginPage");
}
private async void OnBackButtonClicked(object sender, EventArgs e)
{
await Navigation.PopAsync();
}
} }

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

@ -1,12 +1,21 @@
using Models;
using ShoopNCook.Views; using ShoopNCook.Views;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
public partial class CreateRecipePage : ContentPage 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(); InitializeComponent();
this.owner = owner;
this.onRecipeCreated = onRecipeCreated;
this.notifier = notifier;
} }
private void OnAddIngredientTapped(object sender, TappedEventArgs e) 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)); 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)
{ {
await Navigation.PopAsync(); Navigation.PopAsync();
}
private void OnUploadRecipeClicked(object sender, EventArgs e)
{
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());
} }
} }

@ -4,7 +4,8 @@
x:Class="ShoopNCook.Pages.FavoritesPage" x:Class="ShoopNCook.Pages.FavoritesPage"
Title="FavoritesPage" Title="FavoritesPage"
xmlns:views="clr-namespace:ShoopNCook.Views" xmlns:views="clr-namespace:ShoopNCook.Views"
BackgroundColor="{StaticResource BackgroundPrimary}"> BackgroundColor="{StaticResource BackgroundPrimary}"
NavigatedTo="ContentPage_NavigatedTo">
<Grid <Grid
RowDefinitions="Auto, *"> RowDefinitions="Auto, *">
@ -15,7 +16,6 @@
ColumnDefinitions="*" ColumnDefinitions="*"
MaximumHeightRequest="60"> MaximumHeightRequest="60">
<Label <Label
Grid.Column="0" Grid.Column="0"
FontSize="24" FontSize="24"
@ -35,18 +35,9 @@
AlignItems="Start" AlignItems="Start"
AlignContent="Start" AlignContent="Start"
Direction="Row" Direction="Row"
Wrap="Wrap"> Wrap="Wrap"
x:Name="RecipeViewLayout">
<views:RecipeView Margin="5" Note="4.5" Title="Spaghetti Bolognese" Subtitle="30 min"/>
<views:RecipeView Margin="5" Note="3" Title="Chickend Curry" Subtitle="45 min"/>
<views:RecipeView Margin="5" Note="0.2" Title="Beef Stroganoff" Subtitle="10 min"/>
<views:RecipeView Margin="5" Note="1.6" Title="Fish And Ships" Subtitle="15 min"/>
<views:RecipeView Margin="5" Note="5" Title="Caesar Salad" Subtitle="20 min"/>
<views:RecipeView Margin="5" Note="3.5" Title="Vegetables" Subtitle="60 min"/>
<views:RecipeView Margin="5" Note="4.6" Title="Guacamole" Subtitle="90 min"/>
<views:RecipeView Margin="5" Note="4" Title="Pad Thai" Subtitle="10 min"/>
<views:RecipeView Margin="5" Note="3" Title="French Toast" Subtitle="5 min"/>
<views:RecipeView Margin="5" Note="2" Title="Margherita Pizza" Subtitle="2 min"/>
</FlexLayout> </FlexLayout>
</ScrollView> </ScrollView>
</Grid> </Grid>

@ -1,12 +1,46 @@
using Models; using Models;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
public partial class FavoritesPage : ContentPage using Endpoint;
{ using LocalEndpoint;
public FavoritesPage(Account account, IApp app) using Models;
{ using ShoopNCook.Views;
InitializeComponent(); using System.Security.Principal;
}
public partial class FavoritesPage : ContentPage
{
private readonly Account account;
private readonly IUserNotifier notifier;
private IRecipesService service;
public FavoritesPage(Account account, IUserNotifier notifier, IRecipesService service)
{
InitializeComponent();
this.account = account;
this.notifier = notifier;
this.service = service;
UpdateFavorites();
}
private void UpdateFavorites()
{
IAccountRecipesPreferences preferences = service.GetPreferencesOf(account);
RecipeViewLayout.Children.Clear();
preferences.GetFavorites().ForEach(info =>
{
RecipeViewLayout.Children.Add(new RecipeView(info, () =>
{
Recipe recipe = service.GetRecipe(info);
Shell.Current.Navigation.PushAsync(new RecipePage(recipe, notifier, preferences, 1));
}));
});
}
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
{
UpdateFavorites();
}
} }

@ -62,7 +62,7 @@
Margin="20" Margin="20"
HeightRequest="30"> HeightRequest="30">
<Label <Label
Text="Popular recipe" Text="Popular recipes"
Style="{StaticResource h2}"/> Style="{StaticResource h2}"/>
<Label <Label
@ -75,12 +75,9 @@
Orientation="Horizontal"> Orientation="Horizontal">
<HorizontalStackLayout <HorizontalStackLayout
Spacing="10" Spacing="10"
Padding="0,0,0,40"> Padding="0,0,0,40"
<views:RecipeView/> x:Name="PopularsList">
<views:RecipeView/>
<views:RecipeView/>
<views:RecipeView/>
<views:RecipeView/>
</HorizontalStackLayout> </HorizontalStackLayout>
</ScrollView> </ScrollView>
@ -109,14 +106,9 @@
AlignItems="Start" AlignItems="Start"
AlignContent="Start" AlignContent="Start"
Direction="Row" Direction="Row"
Wrap="Wrap"> Wrap="Wrap"
<views:RecipeView Margin="2.5"/> x:Name="RecommendedList">
<views:RecipeView Margin="2.5"/>
<views:RecipeView Margin="2.5"/>
<views:RecipeView Margin="2.5"/>
<views:RecipeView Margin="2.5"/>
<views:RecipeView Margin="2.5"/>
<views:RecipeView Margin="2.5"/>
</FlexLayout> </FlexLayout>
</ScrollView> </ScrollView>
</VerticalStackLayout> </VerticalStackLayout>

@ -1,17 +1,42 @@
using Models;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
using Models;
using ShoopNCook.Views;
using Endpoint;
using LocalEndpoint;
public partial class HomePage : ContentPage public partial class HomePage : ContentPage
{ {
public HomePage(Account account, IApp app) public HomePage(Account account, IUserNotifier notifier, IEndpoint endpoint)
{ {
InitializeComponent(); InitializeComponent();
IRecipesService service = endpoint.RecipesService;
IAccountRecipesPreferences preferences = service.GetPreferencesOf(account);
//TODO this code can be factorised
void PushRecipe(Layout layout, RecipeInfo info)
{
layout.Children.Add(new RecipeView(info, () =>
{
Recipe recipe = service.GetRecipe(info);
Shell.Current.Navigation.PushAsync(new RecipePage(recipe, notifier, preferences, 1));
}));
}
service.PopularRecipes().ForEach(recipe => PushRecipe(PopularsList, recipe));
preferences.GetRecommendedRecipes().ForEach(recipe => PushRecipe(RecommendedList, recipe));
ProfilePictureImage.Source = ImageSource.FromUri(account.User.ProfilePicture); ProfilePictureImage.Source = ImageSource.FromUri(account.User.ProfilePicture);
ProfilePictureName.Text = account.User.Name; ProfilePictureName.Text = account.User.Name;
} }
private async void OnSyncButtonClicked(object sender, EventArgs e)
private void OnSyncButtonClicked(object sender, EventArgs e)
{ {
await Shell.Current.Navigation.PushAsync(new SearchPage()); Shell.Current.Navigation.PushAsync(new SearchPage());
} }
} }

@ -1,41 +1,41 @@
using Models; using Models;
using ShoopNCook.Controllers; using ShoopNCook.Controllers;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
public partial class MorePage : ContentPage public partial class MorePage : ContentPage
{ {
private readonly MorePageController controller; private readonly MorePageController controller;
public MorePage(Account account, MorePageController controller) public MorePage(Account account, MorePageController controller)
{ {
InitializeComponent(); InitializeComponent();
ProfileImage.Source = ImageSource.FromUri(account.User.ProfilePicture); ProfileImage.Source = ImageSource.FromUri(account.User.ProfilePicture);
ProfileName.Text = account.User.Name; ProfileName.Text = account.User.Name;
this.controller = controller; this.controller = controller;
} }
private async void OnMyRecipesButtonTapped(object sender, EventArgs e) private void OnMyRecipesButtonTapped(object sender, EventArgs e)
{ {
await Shell.Current.Navigation.PushAsync(new MyRecipesPage()); controller.GoToMyRecipesPage();
} }
private async void OnEditProfileButtonTapped(object sender, EventArgs e) private void OnEditProfileButtonTapped(object sender, EventArgs e)
{ {
await Shell.Current.Navigation.PushAsync(new ProfilePage()); controller.GoToProfilePage();
} }
private void OnLogoutButtonTapped(object sender, EventArgs e) private void OnLogoutButtonTapped(object sender, EventArgs e)
{ {
controller.Logout(); controller.Logout();
} }
private async void OnShareButtonClicked(object sender, EventArgs e) private async void OnShareButtonClicked(object sender, EventArgs e)
{ {
await Share.RequestAsync(new ShareTextRequest await Share.RequestAsync(new ShareTextRequest
{ {
Text = "Voici le texte à partager (à changer)", Text = "Voici le texte à partager (à changer)",
Title = "Partagez ce texte : (à modifier)" Title = "Partagez ce texte : (à modifier)"
}); });
} }
} }

@ -4,7 +4,8 @@
x:Class="ShoopNCook.Pages.MyListPage" x:Class="ShoopNCook.Pages.MyListPage"
Title="MyList" Title="MyList"
BackgroundColor="{StaticResource BackgroundPrimary}" BackgroundColor="{StaticResource BackgroundPrimary}"
xmlns:views="clr-namespace:ShoopNCook.Views"> xmlns:views="clr-namespace:ShoopNCook.Views"
NavigatedTo="ContentPage_NavigatedTo">
<Grid <Grid
RowDefinitions="Auto, *, Auto"> RowDefinitions="Auto, *, Auto">
<!-- Header label and return button --> <!-- Header label and return button -->
@ -32,23 +33,13 @@
VerticalOptions="Center"/> VerticalOptions="Center"/>
</Grid> </Grid>
<!-- Favorite items --> <!-- Account Recipe List items -->
<ScrollView <ScrollView
Grid.Row="1"> Grid.Row="1">
<VerticalStackLayout <VerticalStackLayout
Padding="30, 0, 30, 0" Padding="30, 0, 30, 0"
Spacing="12"> Spacing="12"
x:Name="RecipesLayout">
<views:StoredRecipeView Note="4.5" Title="Spaghetti Bolognese"/>
<views:StoredRecipeView Note="3" Title="Chickend Curry"/>
<views:StoredRecipeView Note="0.2" Title="Beef Stroganoff"/>
<views:StoredRecipeView Note="1.6" Title="Fish And Ships" />
<views:StoredRecipeView Note="5" Title="Caesar Salad"/>
<views:StoredRecipeView Note="3.5" Title="Vegetables"/>
<views:StoredRecipeView Note="4.6" Title="Guacamole"/>
<views:StoredRecipeView Note="4" Title="Pad Thai"/>
<views:StoredRecipeView Note="3" Title="French Toast"/>
<views:StoredRecipeView Note="2" Title="Margherita Pizza"/>
</VerticalStackLayout> </VerticalStackLayout>
</ScrollView> </ScrollView>

@ -1,11 +1,44 @@
using Endpoint;
using LocalEndpoint;
using Models; using Models;
using ShoopNCook.Views;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
public partial class MyListPage : ContentPage public partial class MyListPage : ContentPage
{ {
public MyListPage(Account account, IApp app)
{ private readonly IAccountRecipesPreferences preferences;
InitializeComponent(); private readonly IUserNotifier notifier;
} private readonly IRecipesService service;
public MyListPage(Account account, IUserNotifier notifier, IRecipesService service)
{
InitializeComponent();
this.preferences = service.GetPreferencesOf(account);
this.notifier = notifier;
this.service = service;
UpdateMyList();
}
private void UpdateMyList()
{
RecipesLayout.Children.Clear();
preferences.GetWeeklyList().ForEach(tuple =>
{
RecipeInfo info = tuple.Item1;
RecipesLayout.Children.Add(new StoredRecipeView(info, tuple.Item2, amount =>
{
Recipe recipe = service.GetRecipe(info);
Shell.Current.Navigation.PushAsync(new RecipePage(recipe, notifier, preferences, amount));
}));
});
}
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
{
UpdateMyList();
}
} }

@ -38,18 +38,9 @@
AlignItems="Start" AlignItems="Start"
AlignContent="Start" AlignContent="Start"
Direction="Row" Direction="Row"
Wrap="Wrap"> Wrap="Wrap"
x:Name="RecipesLayout">
<views:OwnedRecipeView Margin="5" Note="4.5" Title="Spaghetti Bolognese"/>
<views:OwnedRecipeView Margin="5" Note="3" Title="Chickend Curry"/>
<views:OwnedRecipeView Margin="5" Note="0.2" Title="Beef Stroganoff"/>
<views:OwnedRecipeView Margin="5" Note="1.6" Title="Fish And Ships"/>
<views:OwnedRecipeView Margin="5" Note="5" Title="Caesar Salad"/>
<views:OwnedRecipeView Margin="5" Note="3.5" Title="Vegetables"/>
<views:OwnedRecipeView Margin="5" Note="4.6" Title="Guacamole"/>
<views:OwnedRecipeView Margin="5" Note="4" Title="Pad Thai"/>
<views:OwnedRecipeView Margin="5" Note="3" Title="French Toast"/>
<views:OwnedRecipeView Margin="5" Note="2" Title="Margherita Pizza"/>
</FlexLayout> </FlexLayout>
</ScrollView> </ScrollView>
@ -61,7 +52,7 @@
Style="{StaticResource UserButton}" Style="{StaticResource UserButton}"
BackgroundColor="{StaticResource Selected}" BackgroundColor="{StaticResource Selected}"
Text="Add a new recipe" Text="Add a new recipe"
Clicked="AddRecipeButtonClicked"/> Clicked="OnAddRecipeButtonClicked"/>
</Border> </Border>
</Grid> </Grid>
</ContentPage> </ContentPage>

@ -1,19 +1,85 @@
using Endpoint;
using LocalEndpoint;
using Models;
using ShoopNCook.Views;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
public partial class MyRecipesPage : ContentPage public partial class MyRecipesPage : ContentPage
{ {
public MyRecipesPage()
{ private IUserNotifier notifier;
private IRecipesService service;
private Account account;
public MyRecipesPage(
Account account,
IRecipesService service,
IUserNotifier notifier)
{
InitializeComponent(); InitializeComponent();
} this.notifier = notifier;
private async void OnBackButtonClicked(object sender, EventArgs e) this.service = service;
this.account = account;
service
.GetRecipesOf(account)
.GetAccountRecipes()
.ForEach(AddRecipeView);
}
private void AddRecipeView(RecipeInfo info)
{ {
await Navigation.PopAsync(); RecipesLayout.Children.Add(new OwnedRecipeView(info, () =>
{
Recipe recipe = service.GetRecipe(info);
IAccountRecipesPreferences preferences = service.GetPreferencesOf(account);
Shell.Current.Navigation.PushAsync(new RecipePage(recipe, notifier, preferences, 1));
},
() => RemoveRecipe(info)
));
} }
private async void AddRecipeButtonClicked(object sender, EventArgs e)
private void RemoveRecipe(RecipeInfo info)
{ {
await Shell.Current.Navigation.PushAsync(new CreateRecipePage()); IAccountOwnedRecipes recipes = service.GetRecipesOf(account);
if (!recipes.RemoveRecipe(info))
{
notifier.Error("Could not remove recipe");
return;
}
foreach (OwnedRecipeView view in RecipesLayout.Children)
{
if (view.IsViewing(info))
{
RecipesLayout.Remove(view);
break;
}
}
notifier.Success("Recipe successfully removed");
}
private void OnBackButtonClicked(object sender, EventArgs e)
{
Navigation.PopAsync();
}
private void OnAddRecipeButtonClicked(object sender, EventArgs e)
{
IAccountOwnedRecipes recipes = service.GetRecipesOf(account);
var page = new CreateRecipePage(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
} }
} }

@ -1,8 +1,10 @@
using Models;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
public partial class ProfilePage : ContentPage public partial class ProfilePage : ContentPage
{ {
public ProfilePage() public ProfilePage(Account account)
{ {
InitializeComponent(); InitializeComponent();
} }

@ -8,7 +8,6 @@
BackgroundColor="{StaticResource BackgroundPrimary}"> BackgroundColor="{StaticResource BackgroundPrimary}">
<Grid <Grid
RowDefinitions="90*, 10*" RowDefinitions="90*, 10*"
Padding="10"> Padding="10">
@ -51,7 +50,8 @@
StrokeShape="RoundRectangle 20" StrokeShape="RoundRectangle 20"
BackgroundColor="{StaticResource ImageBackground}"> BackgroundColor="{StaticResource ImageBackground}">
<Image <Image
HeightRequest="250"/> HeightRequest="250"
x:Name="RecipeImage"/>
</Border> </Border>
<!--Steps--> <!--Steps-->
@ -146,7 +146,8 @@
BackgroundColor="{StaticResource Selected}" BackgroundColor="{StaticResource Selected}"
VerticalOptions="Center" VerticalOptions="Center"
HorizontalOptions="Center" HorizontalOptions="Center"
Text="Submit"/> Text="Submit"
Clicked="OnSubmitReviewClicked"/>
</Border> </Border>
</HorizontalStackLayout> </HorizontalStackLayout>
</Grid> </Grid>
@ -170,7 +171,8 @@
Text="Add to list" Text="Add to list"
Style="{StaticResource UserButton}" Style="{StaticResource UserButton}"
TextColor="White" TextColor="White"
BackgroundColor="Gray"> BackgroundColor="Gray"
Clicked="OnAddToMyListClicked">
</Button> </Button>
</FlexLayout> </FlexLayout>
</Grid> </Grid>

@ -1,5 +1,8 @@
using ShoopNCook.Views; using ShoopNCook.Views;
using System.Windows.Input; using System.Windows.Input;
using Models;
using LocalEndpoint;
using Endpoint;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
@ -9,53 +12,51 @@ public partial class RecipePage : ContentPage
private uint note; private uint note;
private bool isFavorite; private bool isFavorite;
public ICommand StarCommand => new Command<string>(count =>
{ private IAccountRecipesPreferences preferences;
SetNote(uint.Parse(count)); private IUserNotifier notifier;
}); private RecipeInfo info;
public RecipePage() : public ICommand StarCommand => new Command<string>(count => SetNote(uint.Parse(count)));
this("Recipe Sample", 32, 250,
true, 2, 0, public RecipePage(Recipe recipe, IUserNotifier notifier, IAccountRecipesPreferences preferences, uint amount)
new List<IngredientView> {
new IngredientView("Chocolate", 25, "g"),
new IngredientView("Flour", 250, "g"),
new IngredientView("Sugar", 0.5F, "kg")
},
new List<string> { "This is the first preparation step", "add to furnace and wait", "Enjoy !" }
)
{}
public RecipePage(
string name,
uint cookTime,
uint energy,
bool isFavorite,
uint nbPers,
uint note,
List<IngredientView> ingredients,
List<string> steps
)
{ {
InitializeComponent(); InitializeComponent();
Counter.Count = nbPers;
this.preferences = preferences;
this.notifier = notifier;
this.info = recipe.Info;
AccountRecipeRate rate = preferences.GetRate(recipe.Info);
note = rate.Rate;
isFavorite = rate.IsFavorite;
SetFavorite(isFavorite); SetFavorite(isFavorite);
SetNote(note); SetNote(note);
CookTime.Text = cookTime.ToString(); RecipeInfo info = recipe.Info;
Energy.Text = energy.ToString();
RecipeName.Text = name; Counter.Count = amount;
CookTime.Text = info.CookTimeMins.ToString();
Energy.Text = info.CalPerPers.ToString() + " cal/pers";
RecipeName.Text = info.Name;
if (info.Image != null)
RecipeImage.Source = ImageSource.FromUri(info.Image);
foreach (IngredientView iv in ingredients) foreach (Ingredient ingredient in recipe.Ingredients)
IngredientList.Add(iv); IngredientList.Add(new IngredientView(ingredient));
//retrieves the app's styles
var styles = Application.Current.Resources.MergedDictionaries.ElementAt(1); var styles = Application.Current.Resources.MergedDictionaries.ElementAt(1);
int count = 0; int count = 0;
foreach (string step in steps) { foreach (PreparationStep step in recipe.Steps) {
//TODO display name of PreparationSteps.
Label label = new Label(); Label label = new Label();
label.Style = (Style)styles["Small"]; label.Style = (Style)styles["Small"];
label.Text = "Step " + ++count + ": " + step; label.Text = "Step " + ++count + ": " + step.Description;
StepList.Add(label); StepList.Add(label);
} }
} }
@ -72,9 +73,7 @@ public partial class RecipePage : ContentPage
i++; i++;
} }
else else
{
img.Source = ImageSource.FromFile("star_empty.svg"); img.Source = ImageSource.FromFile("star_empty.svg");
}
} }
} }
@ -83,21 +82,38 @@ public partial class RecipePage : ContentPage
SetFavorite(!isFavorite); SetFavorite(!isFavorite);
} }
private void OnSubmitReviewClicked(object o, EventArgs e)
{
preferences.SetReviewScore(info, note);
notifier.Success("Your review has been successfuly submited");
}
private void OnAddToMyListClicked(object o, EventArgs e)
{
if (!preferences.AddToWeeklyList(info, Counter.Count))
notifier.Notice("You already added this recipe to you weekly list!");
else
notifier.Success("Recipe added to your weekly list.");
}
private void SetFavorite(bool isFavorite) private void SetFavorite(bool isFavorite)
{ {
this.isFavorite = isFavorite; this.isFavorite = isFavorite;
if (isFavorite) if (isFavorite)
{ {
Favorite.Source = ImageSource.FromFile("hearth_on.svg"); Favorite.Source = ImageSource.FromFile("hearth_on.svg");
preferences.AddToFavorites(info);
} }
else else
{ {
Favorite.Source = ImageSource.FromFile("hearth_off.svg"); Favorite.Source = ImageSource.FromFile("hearth_off.svg");
preferences.RemoveFromFavorites(info);
} }
} }
private async void OnBackButtonClicked(object sender, EventArgs e) private void OnBackButtonClicked(object sender, EventArgs e)
{ {
await Navigation.PopAsync();
Navigation.PopAsync();
} }
} }

@ -1,5 +1,5 @@
using ShoopNCook.Controllers; using ShoopNCook.Controllers;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
public partial class RegisterPage : ContentPage public partial class RegisterPage : ContentPage

@ -105,14 +105,9 @@
AlignItems="Start" AlignItems="Start"
AlignContent="Start" AlignContent="Start"
Direction="Row" Direction="Row"
Wrap="Wrap"> Wrap="Wrap"
<views:RecipeView Margin="5"/> x:Name="ResultSearchView">
<views:RecipeView Margin="5"/>
<views:RecipeView Margin="5"/>
<views:RecipeView Margin="5"/>
<views:RecipeView Margin="5"/>
<views:RecipeView Margin="5"/>
<views:RecipeView Margin="5"/>
</FlexLayout> </FlexLayout>
</ScrollView> </ScrollView>

@ -1,11 +1,13 @@
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
using Models;
using ShoopNCook.Views;
public partial class SearchPage : ContentPage public partial class SearchPage : ContentPage
{ {
public SearchPage() public SearchPage()
{ {
InitializeComponent(); InitializeComponent();
} //TODO
}
private async void OnBackButtonClicked(object sender, EventArgs e) private async void OnBackButtonClicked(object sender, EventArgs e)
{ {
await Navigation.PopAsync(); await Navigation.PopAsync();

Loading…
Cancel
Save