diff --git a/App.xaml.cs b/App.xaml.cs index fa8b710..9c150df 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -8,18 +8,18 @@ public partial class App : Application, ConnectionObserver, IApp private IEndpoint Endpoint = new LocalEndpoint(); - public UserNotifier Notifier => new ConsoleUserNotifier(); + public IUserNotifier Notifier => new ConsoleUserNotifier(); public App() { InitializeComponent(); - ForceLogin(); //start in login state + ForceLogin(); //start in login shell } public void OnAccountConnected(Account account) { - Shell shell = new MainAppShell(account, this); - shell.GoToAsync("//Main"); + Shell shell = new MainAppShell(account, Endpoint, this); + shell.GoToAsync("//Home"); MainPage = shell; } diff --git a/ConnectAppShell.xaml.cs b/ConnectAppShell.xaml.cs index 7eb3761..29f3275 100644 --- a/ConnectAppShell.xaml.cs +++ b/ConnectAppShell.xaml.cs @@ -1,17 +1,17 @@ -namespace ShoopNCook; -using Microsoft.Maui.Controls; -using Models; +namespace ShoopNCook; +using Microsoft.Maui.Controls; +using Models; using Endpoint; using ShoopNCook.Controllers; using ShoopNCook.Pages; -public partial class ConnectAppShell : Shell -{ - public ConnectAppShell(ConnectionObserver observer, IAccountManager accounts, UserNotifier notifier) - { - ConnectionController controller = new ConnectionController(observer, accounts, notifier); - InitializeComponent(); - LoginPage.ContentTemplate = new DataTemplate(() => new LoginPage(controller)); - RegisterPage.ContentTemplate = new DataTemplate(() => new RegisterPage(controller)); - } -} +public partial class ConnectAppShell : Shell +{ + public ConnectAppShell(ConnectionObserver observer, IAccountManager accounts, IUserNotifier notifier) + { + ConnectionController controller = new ConnectionController(observer, accounts, notifier); + InitializeComponent(); + LoginPage.ContentTemplate = new DataTemplate(() => new LoginPage(controller)); + RegisterPage.ContentTemplate = new DataTemplate(() => new RegisterPage(controller)); + } +} diff --git a/ConsoleUserNotifier.cs b/ConsoleUserNotifier.cs index 04b7221..1a06c28 100644 --- a/ConsoleUserNotifier.cs +++ b/ConsoleUserNotifier.cs @@ -4,8 +4,15 @@ /// A notice reporter implementation that prints in console the applications's user notices. /// public class ConsoleUserNotifier : - UserNotifier + IUserNotifier { + + public void Success(string message) + { + Console.WriteLine(" Success: " + message); + } + + public void Error(string message) { Console.WriteLine(" Error: " + message); diff --git a/Controllers/ConnectionController.cs b/Controllers/ConnectionController.cs index 2ed88de..6ecfd51 100644 --- a/Controllers/ConnectionController.cs +++ b/Controllers/ConnectionController.cs @@ -7,8 +7,8 @@ namespace ShoopNCook.Controllers { private readonly ConnectionObserver observer; private readonly IAccountManager accounts; - private readonly UserNotifier notifier; - public ConnectionController(ConnectionObserver observer, IAccountManager accounts, UserNotifier notifier) { + private readonly IUserNotifier notifier; + public ConnectionController(ConnectionObserver observer, IAccountManager accounts, IUserNotifier notifier) { this.observer = observer; this.accounts = accounts; this.notifier = notifier; diff --git a/Controllers/MorePageController.cs b/Controllers/MorePageController.cs index f4698fa..c8ecee8 100644 --- a/Controllers/MorePageController.cs +++ b/Controllers/MorePageController.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Endpoint; +using Models; +using ShoopNCook.Pages; namespace ShoopNCook.Controllers { @@ -10,9 +8,14 @@ namespace ShoopNCook.Controllers { 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.endpoint = endpoint; + this.account = account; } public void Logout() @@ -20,5 +23,15 @@ namespace ShoopNCook.Controllers app.Notifier.Notice("You have been loged out."); 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)); + } } } diff --git a/Endpoint/IAccountOwnedRecipes.cs b/Endpoint/IAccountOwnedRecipes.cs new file mode 100644 index 0000000..69d6b7b --- /dev/null +++ b/Endpoint/IAccountOwnedRecipes.cs @@ -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 GetAccountRecipes(); + } +} diff --git a/Endpoint/IAccountRecipesPreferences.cs b/Endpoint/IAccountRecipesPreferences.cs new file mode 100644 index 0000000..1a40745 --- /dev/null +++ b/Endpoint/IAccountRecipesPreferences.cs @@ -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 GetFavorites(); + + public ImmutableList GetRecommendedRecipes(); + + public ImmutableList<(RecipeInfo, uint)> GetWeeklyList(); + + } +} diff --git a/Endpoint/IEndpoint.cs b/Endpoint/IEndpoint.cs index 649e501..10906ee 100644 --- a/Endpoint/IEndpoint.cs +++ b/Endpoint/IEndpoint.cs @@ -6,6 +6,8 @@ namespace Endpoint { public IAccountManager AccountManager { get; } + public IRecipesService RecipesService { get; } + } } diff --git a/Endpoint/IRecipesService.cs b/Endpoint/IRecipesService.cs new file mode 100644 index 0000000..f9159c2 --- /dev/null +++ b/Endpoint/IRecipesService.cs @@ -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 PopularRecipes(); + + public Recipe GetRecipe(RecipeInfo info); + + public IAccountOwnedRecipes GetRecipesOf(Account account); + public IAccountRecipesPreferences GetPreferencesOf(Account account); + + + } +} diff --git a/IApp.cs b/IApp.cs index 32a77b0..dad1336 100644 --- a/IApp.cs +++ b/IApp.cs @@ -9,7 +9,7 @@ namespace ShoopNCook { public interface IApp { - public UserNotifier Notifier { get; } + public IUserNotifier Notifier { get; } public void ForceLogin(); } diff --git a/UserNotifier.cs b/IUserNotifier.cs similarity index 78% rename from UserNotifier.cs rename to IUserNotifier.cs index bfd4c07..4576a70 100644 --- a/UserNotifier.cs +++ b/IUserNotifier.cs @@ -1,18 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ShoopNCook -{ - public interface UserNotifier - - { - public void Notice(string message); - - public void Error(string message); - - public void Warn(string message); - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ShoopNCook +{ + public interface IUserNotifier + { + public void Success(string message); + + public void Notice(string message); + + public void Error(string message); + + public void Warn(string message); + } +} diff --git a/LocalEndpoint/AccountData.cs b/LocalEndpoint/AccountData.cs new file mode 100644 index 0000000..cf84b07 --- /dev/null +++ b/LocalEndpoint/AccountData.cs @@ -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); +} diff --git a/LocalEndpoint/AccountManager.cs b/LocalEndpoint/AccountManager.cs deleted file mode 100644 index 2a3dbc2..0000000 --- a/LocalEndpoint/AccountManager.cs +++ /dev/null @@ -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; - } - } -} diff --git a/LocalEndpoint/AccountOwnedRecipes.cs b/LocalEndpoint/AccountOwnedRecipes.cs new file mode 100644 index 0000000..e3d8059 --- /dev/null +++ b/LocalEndpoint/AccountOwnedRecipes.cs @@ -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 ownedRecipes = new Dictionary(); + 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 GetAccountRecipes() + { + return ownedRecipes.Values.ToImmutableList().ConvertAll(r => r.Info); + } + + + } +} diff --git a/LocalEndpoint/AccountRecipesPreferences.cs b/LocalEndpoint/AccountRecipesPreferences.cs new file mode 100644 index 0000000..ce8f296 --- /dev/null +++ b/LocalEndpoint/AccountRecipesPreferences.cs @@ -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 ratings = new Dictionary(); + + //Binds a recipe's id to its amount of person stored in the account's weekly list + private readonly Dictionary weekly = new Dictionary(); + + private readonly RecipesDatabase db; + public AccountRecipesPreferences(Account account, RecipesDatabase db) + { + Account = account; + this.db = db; + } + + public Account Account { get; init; } + + public ImmutableList GetRecommendedRecipes() + { + return db.ListAll().ConvertAll(recipe => recipe.Info); + } + + public ImmutableList GetFavorites() + { + List favorites = new List(); + 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); + } + } +} diff --git a/LocalEndpoint/ConnectionManager.cs b/LocalEndpoint/ConnectionManager.cs new file mode 100644 index 0000000..427814e --- /dev/null +++ b/LocalEndpoint/ConnectionManager.cs @@ -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; + } + } +} diff --git a/LocalEndpoint/Constants.cs b/LocalEndpoint/Constants.cs new file mode 100644 index 0000000..249a016 --- /dev/null +++ b/LocalEndpoint/Constants.cs @@ -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"); + + } +} diff --git a/LocalEndpoint/LocalEndpoint.cs b/LocalEndpoint/LocalEndpoint.cs index d2696f7..9b27f1e 100644 --- a/LocalEndpoint/LocalEndpoint.cs +++ b/LocalEndpoint/LocalEndpoint.cs @@ -1,10 +1,37 @@ using Endpoint; +using Models; +using System.Collections.Immutable; namespace LocalEndpoint { + + /// + /// The local endpoint is an implementation of the Endpoint API definition. + /// + /// 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 { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { 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 { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { 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 { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { 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 { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { 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 { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + + recipesService = new RecipesService(db); + } + + public IAccountManager AccountManager => accountManager; + + public IRecipesService RecipesService => recipesService; } } \ No newline at end of file diff --git a/LocalEndpoint/RecipesDatabase.cs b/LocalEndpoint/RecipesDatabase.cs new file mode 100644 index 0000000..511cd80 --- /dev/null +++ b/LocalEndpoint/RecipesDatabase.cs @@ -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 recipes = new Dictionary(); + + 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 ListAll() + { + return recipes.Values.ToImmutableList(); + } + + } +} diff --git a/LocalEndpoint/RecipesService.cs b/LocalEndpoint/RecipesService.cs new file mode 100644 index 0000000..9684e02 --- /dev/null +++ b/LocalEndpoint/RecipesService.cs @@ -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 accountsData = new Dictionary(); + + public RecipesService(RecipesDatabase db) + { + this.db = db; + } + + public ImmutableList 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 { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List { 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; + } + + } +} diff --git a/MainAppShell.xaml.cs b/MainAppShell.xaml.cs index d8df598..d3f6bc1 100644 --- a/MainAppShell.xaml.cs +++ b/MainAppShell.xaml.cs @@ -1,17 +1,17 @@ -namespace ShoopNCook; -using Microsoft.Maui.Controls; -using Models; +namespace ShoopNCook; +using Microsoft.Maui.Controls; +using Models; using ShoopNCook.Controllers; using ShoopNCook.Pages; - -public partial class MainAppShell : Shell -{ - public MainAppShell(Account account, IApp app) - { - InitializeComponent(); - HomeTab.ContentTemplate = new DataTemplate(() => new HomePage(account, app)); - FavoritesTab.ContentTemplate = new DataTemplate(() => new FavoritesPage(account, app)); - MyListTab.ContentTemplate = new DataTemplate(() => new MyListPage(account, app)); - MoreTab.ContentTemplate = new DataTemplate(() => new MorePage(account, new MorePageController(app))); - } -} +using Endpoint; +public partial class MainAppShell : Shell +{ + public MainAppShell(Account account, IEndpoint endpoint, IApp app) + { + InitializeComponent(); + HomeTab.ContentTemplate = new DataTemplate(() => new HomePage(account, app.Notifier, endpoint)); + FavoritesTab.ContentTemplate = new DataTemplate(() => new FavoritesPage(account, app.Notifier, endpoint.RecipesService)); + MyListTab.ContentTemplate = new DataTemplate(() => new MyListPage(account, app.Notifier, endpoint.RecipesService)); + MoreTab.ContentTemplate = new DataTemplate(() => new MorePage(account, new MorePageController(account, endpoint, app))); + } +} diff --git a/Models/Account.cs b/Models/Account.cs index 6d5a31c..5a9973b 100644 --- a/Models/Account.cs +++ b/Models/Account.cs @@ -1,15 +1,4 @@ namespace Models { - public class Account - { - - public Account(User usr, string mail) - { - User = usr; - Email = mail; - } - - public User User { get; init; } - public string Email { get; init; } - } + public record Account(User User, string Email); } diff --git a/Models/AccountRecipeRate.cs b/Models/AccountRecipeRate.cs new file mode 100644 index 0000000..caf7658 --- /dev/null +++ b/Models/AccountRecipeRate.cs @@ -0,0 +1,5 @@ + +namespace Models +{ + public record AccountRecipeRate(bool IsFavorite = false, uint Rate = 0); +} diff --git a/Models/Ingredient.cs b/Models/Ingredient.cs index 48f1f21..3a40a75 100644 --- a/Models/Ingredient.cs +++ b/Models/Ingredient.cs @@ -1,17 +1,4 @@ namespace Models { - public class Ingredient - { - - 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; } - } + public record Ingredient(string Name, float Amount); } diff --git a/Models/PreparationStep.cs b/Models/PreparationStep.cs index d85cf91..40899a7 100644 --- a/Models/PreparationStep.cs +++ b/Models/PreparationStep.cs @@ -1,15 +1,5 @@ namespace Models { - public class PreparationStep - { - public PreparationStep(string name, string description) - { - Name = name; - Description = description; - } - - public string Name { get; init; } - public string Description { get; init; } - } + public record PreparationStep(string Name, string Description); } diff --git a/Models/Quantity.cs b/Models/Quantity.cs deleted file mode 100644 index 51433d5..0000000 --- a/Models/Quantity.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Models -{ - public class Quantity - { - } -} diff --git a/Models/Recipe.cs b/Models/Recipe.cs index 15d09ae..e077c22 100644 --- a/Models/Recipe.cs +++ b/Models/Recipe.cs @@ -1,25 +1,10 @@ -namespace Models -{ - public class Recipe - { - - public RecipeInfo Info { get; init; } - - public User Owner { get; init; } +using System.Collections.Immutable; - public List Ingredients { get; init; } - public List Steps { get; init; } - - public Recipe( - RecipeInfo info, - User owner, - List ingredients, - List steps) - { - Info = info; - Owner = owner; - Ingredients = ingredients; - Steps = steps; - } - } +namespace Models +{ + public record Recipe( + RecipeInfo Info, + User Owner, + ImmutableList Ingredients, + ImmutableList Steps); } \ No newline at end of file diff --git a/Models/RecipeBuilder.cs b/Models/RecipeBuilder.cs new file mode 100644 index 0000000..047a4c6 --- /dev/null +++ b/Models/RecipeBuilder.cs @@ -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 ingredients = new List(); + private List steps = new List(); + + + 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()); + } + } +} diff --git a/Models/RecipeInfo.cs b/Models/RecipeInfo.cs index 4640626..1265c96 100644 --- a/Models/RecipeInfo.cs +++ b/Models/RecipeInfo.cs @@ -1,18 +1,10 @@ namespace Models { - public class RecipeInfo - { - public string Name { get; init; } - public string Description { get; init; } - public Uri Image { get; init; } - public float AverageNote { get; init; } - - public RecipeInfo(string name, string description, Uri image, float averageNote) - { - Name = name; - Description = description; - Image = image; - AverageNote = averageNote; - } - } + public record RecipeInfo( + string Name, + uint CalPerPers, + uint CookTimeMins, + Uri? Image, + float AverageNote, + Guid Id); } diff --git a/Models/User.cs b/Models/User.cs index 2f19f35..981cef6 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -1,15 +1,4 @@ namespace Models { - public class User - { - - public User(Uri profilePicture, string name) - { - ProfilePicture = profilePicture; - Name = name; - } - - public Uri ProfilePicture { get; init; } - public string Name { get; init; } - } + public record User(Uri ProfilePicture, string Name); } diff --git a/ShoopNCook.csproj b/ShoopNCook.csproj index 8f865ed..063bc5b 100644 --- a/ShoopNCook.csproj +++ b/ShoopNCook.csproj @@ -196,9 +196,7 @@ - - - + diff --git a/Views/Components/IngredientEntry.xaml b/Views/Components/IngredientEntry.xaml index e18d75a..0a39bdc 100644 --- a/Views/Components/IngredientEntry.xaml +++ b/Views/Components/IngredientEntry.xaml @@ -21,7 +21,8 @@ + HeightRequest="40" + x:Name="NameEntry"/> + HeightRequest="40" + Keyboard="Numeric" + x:Name="QuantityEntry"/> + FontFamily="PoppinsMedium" + x:Name="UnitPicker"> G diff --git a/Views/Components/IngredientEntry.xaml.cs b/Views/Components/IngredientEntry.xaml.cs index 2d777b1..72a0bc3 100644 --- a/Views/Components/IngredientEntry.xaml.cs +++ b/Views/Components/IngredientEntry.xaml.cs @@ -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); + } } \ No newline at end of file diff --git a/Views/Components/IngredientView.xaml.cs b/Views/Components/IngredientView.xaml.cs index 2013447..466d557 100644 --- a/Views/Components/IngredientView.xaml.cs +++ b/Views/Components/IngredientView.xaml.cs @@ -1,3 +1,5 @@ +using Models; + namespace ShoopNCook.Views; public partial class IngredientView : ContentView @@ -30,12 +32,13 @@ public partial class IngredientView : ContentView set => SetValue(UnitProperty, value); } - public IngredientView(string name, float quantity, string unit) + public IngredientView(Ingredient ingredient) { InitializeComponent(); - Name = name; - Quantity = quantity; - Unit = unit; + Name = ingredient.Name; + Quantity = ingredient.Amount; + //TODO Unit implementation in IngredientView.xaml.cs + Unit = "TODO: Unit implementation in IngredientView.xaml.cs"; } } \ No newline at end of file diff --git a/Views/Components/OwnedRecipeView.xaml b/Views/Components/OwnedRecipeView.xaml index 627cc7c..dc14df3 100644 --- a/Views/Components/OwnedRecipeView.xaml +++ b/Views/Components/OwnedRecipeView.xaml @@ -12,13 +12,20 @@ MinimumHeightRequest="175" MinimumWidthRequest="150" RowDefinitions="*, Auto"> + + + + + - + SetNote(value); @@ -25,20 +39,25 @@ 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; } } - 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(); } } \ No newline at end of file diff --git a/Views/Components/RecipeView.xaml.cs b/Views/Components/RecipeView.xaml.cs index ed1b99c..e30c89e 100644 --- a/Views/Components/RecipeView.xaml.cs +++ b/Views/Components/RecipeView.xaml.cs @@ -1,20 +1,28 @@ using ShoopNCook.Pages; - +using Models; namespace ShoopNCook.Views; 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(); - Note = note; - Title = title; - Subtitle = subtitle; - } + + if (info.Image != null) + RecipeImage.Source = ImageSource.FromUri(info.Image); + + Note = info.AverageNote; + Title = info.Name; + Subtitle = info.CookTimeMins + " min"; + + callback = onClickCallback; + + } + + public float Note { @@ -31,22 +39,21 @@ public partial class RecipeView : ContentView set => SubtitleLabel.Text = value; } - 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 async void OnRecipeTapped(object sender, EventArgs e) + } + private void OnRecipeTapped(object sender, EventArgs e) { - await Shell.Current.Navigation.PushAsync(new RecipePage()); + callback(); } } \ No newline at end of file diff --git a/Views/Components/StepEntry.xaml b/Views/Components/StepEntry.xaml index 3bff27a..f07c71e 100644 --- a/Views/Components/StepEntry.xaml +++ b/Views/Components/StepEntry.xaml @@ -22,7 +22,8 @@ MaxLength="10000" Style="{StaticResource UserInput}" AutoSize="TextChanges" - FontSize="15"/> + FontSize="15" + x:Name="StepEditor"/> diff --git a/Views/Components/StepEntry.xaml.cs b/Views/Components/StepEntry.xaml.cs index a2a229c..cc7203e 100644 --- a/Views/Components/StepEntry.xaml.cs +++ b/Views/Components/StepEntry.xaml.cs @@ -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(); diff --git a/Views/Components/StoredRecipeView.xaml b/Views/Components/StoredRecipeView.xaml index 84ecb88..23e6035 100644 --- a/Views/Components/StoredRecipeView.xaml +++ b/Views/Components/StoredRecipeView.xaml @@ -14,13 +14,18 @@ MinimumHeightRequest="250" MinimumWidthRequest="150" RowDefinitions="*, Auto"> + + + + + - + - - + diff --git a/Views/Components/StoredRecipeView.xaml.cs b/Views/Components/StoredRecipeView.xaml.cs index 1eb7ae1..1915216 100644 --- a/Views/Components/StoredRecipeView.xaml.cs +++ b/Views/Components/StoredRecipeView.xaml.cs @@ -1,16 +1,24 @@ +using Models; + namespace ShoopNCook.Views; public partial class StoredRecipeView : ContentView { - public StoredRecipeView() : this(5, "Title") - { } + private readonly Action clickCallback; + - public StoredRecipeView(float note, string title) + public StoredRecipeView(RecipeInfo info, uint personCount, Action onClickCallback) { 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 @@ -23,19 +31,22 @@ public partial class StoredRecipeView : ContentView set => TitleLabel.Text = value; } - - 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, TappedEventArgs e) + { + clickCallback(Counter.Count); + } } \ No newline at end of file diff --git a/Views/ConfirmMail.xaml b/Views/ConfirmMail.xaml index acc8df7..ae22bca 100644 --- a/Views/ConfirmMail.xaml +++ b/Views/ConfirmMail.xaml @@ -21,7 +21,8 @@ HeightRequest="50" WidthRequest="50" - Source="arrow_back.svg"/> + Source="arrow_back.svg" + Clicked="OnBackButtonClicked"/> @@ -136,7 +138,8 @@ BackgroundColor="{StaticResource ActionButton}" FontFamily="PoppinsMedium" TextColor="White" - Text="Confirm my email"/> + Text="Confirm my email" + Clicked="OnRegiterButtonTapped"/> diff --git a/Views/ConfirmMail.xaml.cs b/Views/ConfirmMail.xaml.cs index 7d186ea..94418e9 100644 --- a/Views/ConfirmMail.xaml.cs +++ b/Views/ConfirmMail.xaml.cs @@ -6,4 +6,12 @@ public partial class ConfirmMail : ContentPage { 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(); + } } \ No newline at end of file diff --git a/Views/CreateRecipePage.xaml b/Views/CreateRecipePage.xaml index 63cd5f7..0f1bbc9 100644 --- a/Views/CreateRecipePage.xaml +++ b/Views/CreateRecipePage.xaml @@ -56,9 +56,10 @@ VerticalOptions="End" TranslationY="20" TranslationX="-20"> + + Source="edit.svg" + WidthRequest="30"/> @@ -66,7 +67,8 @@ + Placeholder="Specify your recipe name" + x:Name="RecipeNameEntry"/>