diff --git a/.drone.yml b/.drone.yml index 78af971..05189d6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,7 +3,12 @@ type: docker name: CI Workflow steps: - - name: Build + - name: Test image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dotnet7-maui:latest commands: - dotnet test --framework net7.0 'Tests\\\\Tests.csproj' + + - name: Deploy doc + image: hub.codefirst.iut.uca.fr/maxime.batista/codefirst-docdeployer:latest + commands: + - /entrypoint.sh -t doxygen -l . -d sourcecode_documentation \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8852f14 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*.cs] + +# CS0618: Le type ou le membre est obsolète +dotnet_diagnostic.CS0618.severity = silent + +# CS1998: Async method lacks 'await' operators and will run synchronously +dotnet_diagnostic.CS1998.severity = none \ No newline at end of file diff --git a/App.xaml.cs b/App.xaml.cs index c873ce2..4e4d06b 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,31 +1,34 @@ namespace ShoopNCook; using Models; -using Endpoint; -using LocalEndpoint; +using Services; +using LocalServices; + +// Classe principale de l'application qui implémente l'interface ConnectionObserver et IApp public partial class App : Application, ConnectionObserver, IApp { - + // Initialisation de l'interface IEndpoint avec une nouvelle instance de LocalEndpoint private IEndpoint Endpoint = new LocalEndpoint(); - public IUserNotifier Notifier => new ConsoleUserNotifier(); - public App() { InitializeComponent(); - ForceLogin(); //start in login shell + ForceLogin(); // Commencer l'application avec l'écran de connexion } + + // Méthode appelée lorsque l'utilisateur se connecte avec succès public void OnAccountConnected(Account account) { Shell shell = new MainAppShell(account, Endpoint, this); - shell.GoToAsync("//Home"); + shell.GoToAsync("//Home"); MainPage = shell; } + // Méthode pour forcer l'utilisateur à se connecter public void ForceLogin() { - Shell shell = new ConnectAppShell(this, Endpoint.AuthService, Notifier); + Shell shell = new ConnectAppShell(this, Endpoint.AuthService); shell.GoToAsync("//Splash"); MainPage = shell; } diff --git a/ConnectAppShell.xaml.cs b/ConnectAppShell.xaml.cs index dd44352..bbd3198 100644 --- a/ConnectAppShell.xaml.cs +++ b/ConnectAppShell.xaml.cs @@ -1,17 +1,24 @@ namespace ShoopNCook; using Microsoft.Maui.Controls; using Models; -using Endpoint; +using Services; using ShoopNCook.Controllers; using ShoopNCook.Pages; + +// Shell pour la phase de connexion de l'application public partial class ConnectAppShell : Shell { - public ConnectAppShell(ConnectionObserver observer, IAuthService accounts, IUserNotifier notifier) + // Constructeur qui prend un observateur de connexion et un service d'authentification en argument + + public ConnectAppShell(ConnectionObserver observer, IAuthService accounts) { - ConnectionController controller = new ConnectionController(observer, accounts, notifier); + // Création d'un nouveau contrôleur de connexion + ConnectionController controller = new ConnectionController(observer, accounts); InitializeComponent(); - LoginPage.ContentTemplate = new DataTemplate(() => new LoginPage(controller)); + + // Initialisation des pages de connexion et d'inscription avec le contrôleur de connexion + LoginPage.ContentTemplate = new DataTemplate(() => new LoginPage(controller)); RegisterPage.ContentTemplate = new DataTemplate(() => new RegisterPage(controller)); } } diff --git a/ConnectionObserver.cs b/ConnectionObserver.cs index 6cd2772..ec4edd3 100644 --- a/ConnectionObserver.cs +++ b/ConnectionObserver.cs @@ -2,6 +2,8 @@ namespace ShoopNCook { + // Interface définissant un observateur de connexion. + // Tout objet implémentant cette interface doit définir la méthode OnAccountConnected(). public interface ConnectionObserver { public void OnAccountConnected(Account account); diff --git a/ConsoleUserNotifier.cs b/ConsoleUserNotifier.cs deleted file mode 100644 index 1a06c28..0000000 --- a/ConsoleUserNotifier.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace ShoopNCook -{ - /// - /// A notice reporter implementation that prints in console the applications's user notices. - /// - public class ConsoleUserNotifier : - IUserNotifier - { - - public void Success(string message) - { - Console.WriteLine(" Success: " + message); - } - - - public void Error(string message) - { - Console.WriteLine(" Error: " + message); - } - - public void Notice(string message) - { - Console.WriteLine(" Notice: " + message); - } - - public void Warn(string message) - { - Console.WriteLine(" Warn: " + message); - } - } -} diff --git a/Controllers/ConnectionController.cs b/Controllers/ConnectionController.cs index 0e59bb7..23903b7 100644 --- a/Controllers/ConnectionController.cs +++ b/Controllers/ConnectionController.cs @@ -1,4 +1,4 @@ -using Endpoint; +using Services; using Models; namespace ShoopNCook.Controllers @@ -7,19 +7,27 @@ namespace ShoopNCook.Controllers { private readonly ConnectionObserver observer; private readonly IAuthService accounts; - private readonly IUserNotifier notifier; - public ConnectionController(ConnectionObserver observer, IAuthService accounts, IUserNotifier notifier) { + public ConnectionController(ConnectionObserver observer, IAuthService accounts) { this.observer = observer; this.accounts = accounts; - this.notifier = notifier; } public void Login(string email, string password) { + if (email == null) + { + UserNotifier.Notice("Please provide an email address"); + return; + } + if (password == null) + { + UserNotifier.Notice("Please provide your password"); + return; + } Account? acc = accounts.Login(email, password); if (acc == null) { - notifier.Error("Email or password invalid."); + UserNotifier.Error("Email or password invalid."); return; } observer.OnAccountConnected(acc); @@ -27,10 +35,25 @@ namespace ShoopNCook.Controllers public void Register(string username, string email, string password) { + if (email == null) + { + UserNotifier.Notice("Please provide an email address"); + return; + } + if (password == null) + { + UserNotifier.Notice("Please provide your password"); + return; + } + if (username == null) + { + UserNotifier.Notice("Please provide an username"); + return; + } Account? acc = accounts.Register(username, email, password); if (acc == null) { - notifier.Error("Invalid credentials."); + UserNotifier.Error("Invalid credentials."); return; } observer.OnAccountConnected(acc); diff --git a/Controllers/MorePageController.cs b/Controllers/MorePageController.cs index c8ecee8..d92b5d1 100644 --- a/Controllers/MorePageController.cs +++ b/Controllers/MorePageController.cs @@ -1,4 +1,4 @@ -using Endpoint; +using Services; using Models; using ShoopNCook.Pages; @@ -20,18 +20,18 @@ namespace ShoopNCook.Controllers public void Logout() { - app.Notifier.Notice("You have been loged out."); + UserNotifier.Notice("You have been loged out."); app.ForceLogin(); } - public void GoToMyRecipesPage() + public async void GoToMyRecipesPage() { - Shell.Current.Navigation.PushAsync(new MyRecipesPage(account, endpoint.RecipesService, app.Notifier)); + await Shell.Current.Navigation.PushAsync(new MyRecipesPage(account, endpoint.RecipesService)); } - public void GoToProfilePage() + public async void GoToProfilePage() { - Shell.Current.Navigation.PushAsync(new ProfilePage(account)); + await Shell.Current.Navigation.PushAsync(new ProfilePage(account)); } } } diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..6cda83d --- /dev/null +++ b/Doxyfile @@ -0,0 +1,429 @@ +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "ShopNCook" +PROJECT_NUMBER = 1.0.0 +PROJECT_BRIEF = "A Cook application" +PROJECT_LOGO = Resources/appicon.svg +OUTPUT_DIRECTORY = /docs/doxygen +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +JAVADOC_BANNER = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +PYTHON_DOCSTRING = YES +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +# Well... the one for Java looks so similar to the one for C#... +OPTIMIZE_OUTPUT_JAVA = YES +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +OPTIMIZE_OUTPUT_SLICE = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +TOC_INCLUDE_HEADINGS = 5 +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +EXTRACT_ALL = YES +# I do not like other members to see my private members... but you can set it to YES if you prefer. +EXTRACT_PRIVATE = NO +EXTRACT_PRIV_VIRTUAL = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +RESOLVE_UNNAMED_PARAMS = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_HEADERFILE = YES +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_IF_INCOMPLETE_DOC = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +INPUT = . +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = */Tests/* +EXCLUDE_PATTERNS += */bin/* +EXCLUDE_PATTERNS += */obj/* +EXCLUDE_PATTERNS += documentation/* +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +CLANG_ASSISTED_PARSING = NO +CLANG_ADD_INC_PATHS = YES +CLANG_OPTIONS = +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +ALPHABETICAL_INDEX = YES +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = images/CodeFirst.png images/clubinfo.png +HTML_COLORSTYLE_HUE = 215 +HTML_COLORSTYLE_SAT = 45 +HTML_COLORSTYLE_GAMMA = 240 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_MENUS = YES +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_FEEDURL = +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +FULL_SIDEBAR = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +OBFUSCATE_EMAILS = YES +HTML_FORMULA_FORMAT = png +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +FORMULA_MACROFILE = +USE_MATHJAX = NO +MATHJAX_VERSION = MathJax_2 +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = +MAKEINDEX_CMD_NAME = makeindex +LATEX_MAKEINDEX_CMD = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO +LATEX_EMOJI_DIRECTORY = + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +GENERATE_XML = NO +XML_OUTPUT = xml +XML_PROGRAMLISTING = YES +XML_NS_MEMB_FILE_SCOPE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +DOT_UML_DETAILS = NO +DOT_WRAP_THRESHOLD = 17 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DIR_GRAPH_MAX_DEPTH = 1 +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_CFG_FILE = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES \ No newline at end of file diff --git a/IApp.cs b/IApp.cs index dad1336..765ec6c 100644 --- a/IApp.cs +++ b/IApp.cs @@ -7,10 +7,10 @@ using System.Threading.Tasks; namespace ShoopNCook { + // Interface définissant une application. + // Tout objet implémentant cette interface doit définir la méthode ForceLogin(). public interface IApp { - public IUserNotifier Notifier { get; } - public void ForceLogin(); } } diff --git a/IUserNotifier.cs b/IUserNotifier.cs deleted file mode 100644 index 4576a70..0000000 --- a/IUserNotifier.cs +++ /dev/null @@ -1,19 +0,0 @@ -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/LocalServices/AccountOwnedRecipes.cs b/LocalServices/AccountOwnedRecipes.cs index 633d96e..34a27b2 100644 --- a/LocalServices/AccountOwnedRecipes.cs +++ b/LocalServices/AccountOwnedRecipes.cs @@ -1,19 +1,19 @@ -using LocalEndpoint; -using LocalEndpoint.Data; +using Services; +using LocalServices.Data; using Models; using System.Collections.Immutable; -namespace Endpoint +namespace Services { - internal class AccountOwnedRecipes : IAccountOwnedRecipes + public class AccountOwnedRecipes : IAccountOwnedRecipesService { public Account Account { get; init; } private readonly Dictionary ownedRecipes = new Dictionary(); - private readonly Database db; + private readonly IDatabase db; - public AccountOwnedRecipes(Account account, Database db) + public AccountOwnedRecipes(Account account, IDatabase db) { Account = account; this.db = db; @@ -48,7 +48,5 @@ namespace Endpoint { return ownedRecipes.Values.ToImmutableList().ConvertAll(r => r.Info); } - - } } diff --git a/LocalServices/AccountRecipesPreferences.cs b/LocalServices/AccountRecipesPreferences.cs index 520c767..052acfd 100644 --- a/LocalServices/AccountRecipesPreferences.cs +++ b/LocalServices/AccountRecipesPreferences.cs @@ -1,15 +1,15 @@ -using Endpoint; -using LocalEndpoint.Data; +using Services; +using LocalServices.Data; using Models; using System.Collections.Immutable; -namespace LocalEndpoint +namespace LocalServices { - internal class AccountRecipesPreferences : IAccountRecipesPreferences + public class AccountRecipesPreferences : IAccountRecipesPreferencesService { - private readonly Database db; - public AccountRecipesPreferences(Account account, Database db) + private readonly IDatabase db; + public AccountRecipesPreferences(Account account, IDatabase db) { Account = account; this.db = db; @@ -56,7 +56,7 @@ namespace LocalEndpoint public RecipeRate GetRate(RecipeInfo info) { - RecipeRate rate = null; + RecipeRate? rate = null; var ratings = db.ListRatesOf(Account.User.Id); if (!ratings.TryGetValue(info.Id, out rate)) @@ -102,7 +102,6 @@ namespace LocalEndpoint public void AddToFavorites(RecipeInfo info) { Guid userId = Account.User.Id; - var ratings = db.ListRatesOf(userId); RecipeRate rate = GetRate(info); db.InsertRate(userId, info.Id, new RecipeRate(true, rate.Rate)); @@ -111,7 +110,6 @@ namespace LocalEndpoint public void RemoveFromFavorites(RecipeInfo info) { Guid userId = Account.User.Id; - var ratings = db.ListRatesOf(userId); RecipeRate rate = GetRate(info); db.InsertRate(userId, info.Id, new RecipeRate(false, rate.Rate)); @@ -120,10 +118,9 @@ namespace LocalEndpoint public void SetReviewScore(RecipeInfo info, uint score) { Guid userId = Account.User.Id; - var ratings = db.ListRatesOf(userId); RecipeRate rate = GetRate(info); - db.InsertRate(userId, info.Id, new RecipeRate(rate.IsFavorite, score)); + db.InsertRate(userId, info.Id, new RecipeRate(rate.IsFavorite, Math.Min(score, 5))); } } } diff --git a/LocalServices/AccountServices.cs b/LocalServices/AccountServices.cs index 02d4d6a..656f3b7 100644 --- a/LocalServices/AccountServices.cs +++ b/LocalServices/AccountServices.cs @@ -1,6 +1,6 @@ -using Endpoint; +using Services; -namespace LocalEndpoint +namespace LocalServices { - internal record AccountServices(IAccountOwnedRecipes Recipes, IAccountRecipesPreferences Preferences); + internal record AccountServices(IAccountOwnedRecipesService Recipes, IAccountRecipesPreferencesService Preferences); } diff --git a/LocalServices/AuthService.cs b/LocalServices/AuthService.cs index 47ca4fb..a229afa 100644 --- a/LocalServices/AuthService.cs +++ b/LocalServices/AuthService.cs @@ -1,21 +1,15 @@ using Models; -using Endpoint; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using LocalEndpoint.Data; -using System.Security.Cryptography; +using Services; +using LocalServices.Data; -namespace LocalEndpoint +namespace LocalServices { - internal class AuthService : IAuthService + public class AuthService : IAuthService { - private readonly Database db; + private readonly IDatabase db; - public AuthService(Database db) + public AuthService(IDatabase db) { this.db = db; } diff --git a/LocalServices/Constants.cs b/LocalServices/Constants.cs index 75f80d1..cfb7bf8 100644 --- a/LocalServices/Constants.cs +++ b/LocalServices/Constants.cs @@ -1,8 +1,8 @@ using Models; -namespace LocalEndpoint +namespace LocalServices { - internal class Constants + public 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"); } diff --git a/LocalServices/Data/AccountData.cs b/LocalServices/Data/AccountData.cs index 332c7e9..7199fa4 100644 --- a/LocalServices/Data/AccountData.cs +++ b/LocalServices/Data/AccountData.cs @@ -1,12 +1,6 @@ -using Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.Serialization; -namespace LocalEndpoint.Data +namespace LocalServices.Data { [DataContract] internal record AccountData( diff --git a/LocalServices/Data/CatastrophicPerformancesDatabase.cs b/LocalServices/Data/CatastrophicPerformancesDatabase.cs index 32c48c1..755a348 100644 --- a/LocalServices/Data/CatastrophicPerformancesDatabase.cs +++ b/LocalServices/Data/CatastrophicPerformancesDatabase.cs @@ -1,160 +1,185 @@ -using Models; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; - -namespace LocalEndpoint.Data -{ - /// - /// Database implementation with catastrophic performances. - /// This database implementation persists data in xml and will save all the data in their files on each mutable requests. - /// - internal class CatastrophicPerformancesDatabase : Database - { - - private static readonly DataContractSerializer RECIPES_SERIALIZER = new DataContractSerializer(typeof(Dictionary)); - private static readonly DataContractSerializer USERS_SERIALIZER = new DataContractSerializer(typeof(Dictionary)); - private static readonly DataContractSerializer ACCOUNTS_SERIALIZER = new DataContractSerializer(typeof(Dictionary)); - - private static readonly string RECIPES_FILENAME = "recipes_data.xml"; - private static readonly string USERS_FILENAME = "users_data.xml"; - private static readonly string ACCOUNTS_FILENAME = "accounts_data.xml"; - - private readonly Dictionary recipesData; - private readonly Dictionary usersData; - private readonly Dictionary accountsData; - - private readonly string dbPath; - - - public CatastrophicPerformancesDatabase(string folderPath) - { - dbPath = folderPath; - if (!Directory.Exists(folderPath)) - Directory.CreateDirectory(folderPath); - - usersData = Load(USERS_FILENAME, USERS_SERIALIZER); - recipesData = Load(RECIPES_FILENAME, RECIPES_SERIALIZER); - accountsData = Load(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER); - } - - public bool IsEmpty() - { - return recipesData.Count == 0 && usersData.Count == 0 && accountsData.Count == 0; - } - - public Account? GetAccount(string email, string passwordHash) - { - if (!accountsData.TryGetValue(email, out AccountData? data)) - return null; - - if (data.PasswordHash != passwordHash) return null; - return new Account(usersData[data.UserId].User, data.Email); - } - - public void InsertAccount(Account account, string passwordHash) - { - accountsData[account.Email] = new AccountData(account.User.Id, account.Email, passwordHash); - Save(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER, accountsData); - } - - public Recipe GetRecipe(Guid id) - { - return ConvertRecipeDataToRecipe(recipesData[id]); - } - - public RecipeRate GetRecipeRate(Guid user, Guid recipe) - { - return usersData[user].Rates[recipe]; - } - - public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount) - { - usersData[userId].RecipesList[recipeId] = persAmount; - Save(USERS_FILENAME, USERS_SERIALIZER, usersData); - } - - public void RemoveFromUserList(Guid userId, Guid recipeId) - { - usersData[userId].RecipesList.Remove(recipeId); - Save(USERS_FILENAME, USERS_SERIALIZER, usersData); - } - - - public void InsertRecipe(Recipe recipe) - { - recipesData[recipe.Info.Id] = new RecipeData(recipe.Info, recipe.Owner.Id, recipe.Ingredients, recipe.Steps); - Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData); - } - - public void InsertUser(User user) - { - usersData[user.Id] = new UserData(user, new Dictionary(), new Dictionary()); - Save(USERS_FILENAME, USERS_SERIALIZER, usersData); - } - - public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate) - { - usersData[userId].Rates[recipeId] = rate; - Save(USERS_FILENAME, USERS_SERIALIZER, usersData); - } - - public void RemoveRecipe(Guid id) - { - recipesData.Remove(id); - Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData); - } - - public ImmutableList ListAllRecipes() - { - return recipesData.Values.ToImmutableList().ConvertAll(ConvertRecipeDataToRecipe); - } - - - public ImmutableDictionary ListRatesOf(Guid user) - { - return usersData[user].Rates.ToImmutableDictionary(); - } - - public ImmutableDictionary GetRecipeListOf(Guid user) - { - return usersData[user].RecipesList.ToImmutableDictionary(); - } - - private Recipe ConvertRecipeDataToRecipe(RecipeData rd) - { - var owner = usersData[rd.OwnerID].User; - return new Recipe(rd.Info, owner, rd.Ingredients, rd.Steps); - } - - private Dictionary Load(string fileName, DataContractSerializer deserializer) - { - var file = dbPath + "/" + fileName; - var fileInfo = new FileInfo(file); - - if (!fileInfo.Exists) - fileInfo.Create(); - - if (fileInfo.Length == 0) - return new Dictionary(); //file is empty thus there is nothing to deserialize - Console.WriteLine(File.ReadAllText(file)); - - using (var stream = File.OpenRead(file)) - return deserializer.ReadObject(stream) as Dictionary ?? throw new Exception("object read from " + file + " is not a dictionnary"); - } - - private void Save(string fileName, DataContractSerializer serializer, Dictionary dict) - { - using (var stream = File.OpenWrite(dbPath + "/" + fileName)) - { - serializer.WriteObject(stream, dict); - stream.Flush(); - } - } - - } -} +using Models; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Text; +using System.Text.Json; + +namespace LocalServices.Data +{ + /// + /// Database implementation with catastrophic performances. + /// This database implementation persists data in json and will save all the data in their files for each mutable requests. + /// + public class CatastrophicPerformancesDatabase : IDatabase + { + + private static readonly string RECIPES_FILENAME = "recipes_data.xml"; + private static readonly string USERS_FILENAME = "users_data.xml"; + private static readonly string ACCOUNTS_FILENAME = "accounts_data.xml"; + + private readonly Dictionary recipesData; + private readonly Dictionary usersData; + private readonly Dictionary accountsData; + + private readonly string dbPath; + + + public CatastrophicPerformancesDatabase(string folderPath) + { + dbPath = folderPath; + if (!Directory.Exists(folderPath)) + Directory.CreateDirectory(folderPath); + + usersData = Load(USERS_FILENAME); + recipesData = Load(RECIPES_FILENAME); + accountsData = Load(ACCOUNTS_FILENAME); + } + + public bool IsEmpty() + { + return recipesData.Count == 0 && usersData.Count == 0 && accountsData.Count == 0; + } + + public Account? GetAccount(string email, string passwordHash) + { + if (!accountsData.TryGetValue(email, out AccountData? data)) + return null; + + if (data.PasswordHash != passwordHash) return null; + return new Account(usersData[data.UserId].User, data.Email); + } + + public void InsertAccount(Account account, string passwordHash) + { + accountsData[account.Email] = new AccountData(account.User.Id, account.Email, passwordHash); + Save(ACCOUNTS_FILENAME, accountsData); + InsertUser(account.User); + } + + public Recipe? GetRecipe(Guid id) + { + if (recipesData.TryGetValue(id, out RecipeData? data)) + return ConvertRecipeDataToRecipe(data); + return null; + } + + public RecipeRate GetRecipeRate(Guid user, Guid recipe) + { + return usersData[user].Rates[recipe]; + } + + public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount) + { + usersData[userId].RecipesList[recipeId] = persAmount; + Save(USERS_FILENAME, usersData); + } + + public void RemoveFromUserList(Guid userId, Guid recipeId) + { + usersData[userId].RecipesList.Remove(recipeId); + Save(USERS_FILENAME, usersData); + } + + + public void InsertRecipe(Recipe recipe) + { + recipesData[recipe.Info.Id] = new RecipeData(recipe.Info, recipe.Owner.Id, recipe.Ingredients, recipe.Steps); + Save(RECIPES_FILENAME, recipesData); + } + + public void InsertUser(User user) + { + usersData[user.Id] = new UserData(user, new Dictionary(), new Dictionary()); + Save(USERS_FILENAME, usersData); + } + + public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate) + { + usersData[userId].Rates[recipeId] = rate; + Save(USERS_FILENAME, usersData); + } + + public void RemoveRecipe(Guid id) + { + recipesData.Remove(id); + Save(RECIPES_FILENAME, recipesData); + } + + public ImmutableList ListAllRecipes() + { + return recipesData.Values.ToImmutableList().ConvertAll(ConvertRecipeDataToRecipe); + } + + + public ImmutableDictionary ListRatesOf(Guid user) + { + return usersData[user].Rates.ToImmutableDictionary(); + } + + public ImmutableDictionary GetRecipeListOf(Guid user) + { + return usersData[user].RecipesList.ToImmutableDictionary(); + } + + private Recipe ConvertRecipeDataToRecipe(RecipeData rd) + { + var owner = usersData[rd.OwnerID].User; + return new Recipe(rd.Info, owner, rd.Ingredients, rd.Steps); + } + + private Dictionary Load(string fileName) + { + var file = dbPath + "/" + fileName; + var fileInfo = new FileInfo(file); + + if (!fileInfo.Exists) + fileInfo.Create(); + + if (fileInfo.Length == 0) + return new Dictionary(); //file is empty thus there is nothing to deserialize + + string text = File.ReadAllText(file); + + return JsonSerializer.Deserialize>(text); + } + + private async void Save(string fileName, Dictionary dict) + { + string json = JsonSerializer.Serialize(dict); + using (var stream = WaitForFile(dbPath + "/" + fileName, FileMode.Open, FileAccess.Write, FileShare.Write)) + { + var bytes = Encoding.ASCII.GetBytes(json); + await stream.WriteAsync(bytes, 0, bytes.Length); + } + } + + // This is a workaround function to wait for a file to be released before opening it. + // This was to fix the Save method that used to throw sometimes as the file were oftenly being scanned by the androids' antivirus. + // Simply wait until the file is released and return it. This function will never return until + private static FileStream WaitForFile(string fullPath, FileMode mode, FileAccess access, FileShare share) + { + for (int attempt = 0 ; attempt < 40; attempt++) + { + FileStream? fs = null; + try + { + fs = new FileStream(fullPath, mode, access, share); + return fs; + } + catch (FileNotFoundException e) + { + throw e; + } + catch (IOException e) + { + Debug.WriteLine(e.Message + " in thread " + Thread.CurrentThread.Name + " " + Thread.CurrentThread.ManagedThreadId); + if (fs != null) + fs.Dispose(); + + Thread.Sleep(200); + } + } + throw new TimeoutException("Could not access file '" + fullPath + "', maximum attempts reached."); + } + } +} diff --git a/LocalServices/Data/Database.cs b/LocalServices/Data/Database.cs deleted file mode 100644 index 130093a..0000000 --- a/LocalServices/Data/Database.cs +++ /dev/null @@ -1,36 +0,0 @@ - -using Models; -using System.Collections.Immutable; - -namespace LocalEndpoint.Data -{ - - // The database interface defines all the different kinds of requests the LocalEndpoint needs to store and retrieve data. - public interface Database - { - - public Recipe GetRecipe(Guid id); - - public RecipeRate GetRecipeRate(Guid user, Guid recipe); - - public Account? GetAccount(string email, string passwordHash); - - public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount); - public void RemoveFromUserList(Guid userId, Guid recipeId); - public void InsertAccount(Account account, string passwordHash); - - public void InsertRecipe(Recipe recipe); - - public void InsertUser(User user); - - public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate); - - public void RemoveRecipe(Guid id); - - public ImmutableList ListAllRecipes(); - - public ImmutableDictionary ListRatesOf(Guid user); - - public ImmutableDictionary GetRecipeListOf(Guid user); - } -} diff --git a/LocalServices/Data/IDatabase.cs b/LocalServices/Data/IDatabase.cs new file mode 100644 index 0000000..4e6c1e3 --- /dev/null +++ b/LocalServices/Data/IDatabase.cs @@ -0,0 +1,103 @@ + +using Models; +using System.Collections.Immutable; + +namespace LocalServices.Data +{ + /// + /// The database interface defines all the different kinds of requests the LocalEndpoint needs to store and retrieve data. + /// + public interface IDatabase + { + /// + /// Get a Recipe from its identifier + /// + /// + /// The recipe if the identifier is registered in the database + + public Recipe? GetRecipe(Guid id); + /// + /// Get the rate of a user for a given recipe + /// + /// The user identifier + /// The recipe identifier + /// Returns a rate + public RecipeRate GetRecipeRate(Guid user, Guid recipe); + + /// + /// Gets an account from an email and a password hash + /// + /// + /// + /// some account if the email and the hash is found in the database + public Account? GetAccount(string email, string passwordHash); + + /// + /// Insert a recipe in user's weekly list + /// + /// + /// + /// + public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount); + + /// + /// Remove a recipe from user's weekly list + /// + /// + /// + public void RemoveFromUserList(Guid userId, Guid recipeId); + + /// + /// Inserts an account in the database, with the given passwordhash + /// + /// + /// + public void InsertAccount(Account account, string passwordHash); + + /// + /// Inserts a recipe + /// + /// + public void InsertRecipe(Recipe recipe); + + /// + /// Inserts a user + /// + /// + public void InsertUser(User user); + + /// + /// Inserts a user rate over the given recipe identifier + /// + /// + /// + /// + public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate); + + /// + /// Removes a recipe + /// + /// + public void RemoveRecipe(Guid id); + + /// + /// Lists all recipes in the database + /// + /// + public ImmutableList ListAllRecipes(); + + /// + /// List the ratings of an user + /// + /// + /// + public ImmutableDictionary ListRatesOf(Guid user); + + /// + /// Get the weekly list of given user + /// + /// + /// + public ImmutableDictionary GetRecipeListOf(Guid user); + } +} diff --git a/LocalServices/Data/RecipeData.cs b/LocalServices/Data/RecipeData.cs index 431f9b6..852c56d 100644 --- a/LocalServices/Data/RecipeData.cs +++ b/LocalServices/Data/RecipeData.cs @@ -2,7 +2,7 @@ using System.Collections.Immutable; using System.Runtime.Serialization; -namespace LocalEndpoint.Data +namespace LocalServices.Data { [DataContract] internal record RecipeData( diff --git a/LocalServices/Data/StubDatabase.cs b/LocalServices/Data/StubDatabase.cs new file mode 100644 index 0000000..156f3ba --- /dev/null +++ b/LocalServices/Data/StubDatabase.cs @@ -0,0 +1,72 @@ +using LocalServices.Data; +using Models; +using System.Collections.Immutable; + +namespace LocalServices.Data +{ + /// + /// stub implementation for IDatabase + /// + internal class StubDatabase : IDatabase + { + private readonly User User = new User(new Uri("https://images.pexels.com/photos/37546/woman-portrait-face-studio-37546.jpeg?cs=srgb&dl=beauty-face-headshot-37546.jpg&fm=jpg"), "David", Guid.NewGuid()); + public Account? GetAccount(string email, string passwordHash) + { + return new Account(User, "david@gmail.com"); + } + + public Recipe? GetRecipe(Guid id) + { + return new Recipe(new RecipeInfo("Foo", 4, 5, new Uri("https://th.bing.com/th/id/R.e57009c4044bef3e86f8a7b18a7cf36a?rik=K3dSu3KoTgeRSw&pid=ImgRaw&r=0"), 4.5F, id), User, new List { new Ingredient("Chocolate", 4, "g") }.ToImmutableList(), new List { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList()); + } + + public ImmutableDictionary GetRecipeListOf(Guid user) + { + return new Dictionary().ToImmutableDictionary(); + } + + public RecipeRate GetRecipeRate(Guid user, Guid recipe) + { + return new RecipeRate(true, 4); + } + + public void InsertAccount(Account account, string passwordHash) + { + + } + + public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount) + { + } + + public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate) + { + } + + public void InsertRecipe(Recipe recipe) + { + } + + public void InsertUser(User user) + { + } + + public ImmutableList ListAllRecipes() + { + return new List().ToImmutableList(); + } + + public ImmutableDictionary ListRatesOf(Guid user) + { + return new Dictionary().ToImmutableDictionary(); + } + + public void RemoveFromUserList(Guid userId, Guid recipeId) + { + } + + public void RemoveRecipe(Guid id) + { + } + } +} diff --git a/LocalServices/Data/UserData.cs b/LocalServices/Data/UserData.cs index 847d659..7b5a9e1 100644 --- a/LocalServices/Data/UserData.cs +++ b/LocalServices/Data/UserData.cs @@ -3,7 +3,7 @@ using Models; using System.Runtime.Serialization; -namespace LocalEndpoint.Data +namespace LocalServices.Data { [DataContract] internal record UserData( diff --git a/LocalServices/LocalEndpoint.cs b/LocalServices/LocalEndpoint.cs index c8f4533..3e524e2 100644 --- a/LocalServices/LocalEndpoint.cs +++ b/LocalServices/LocalEndpoint.cs @@ -1,14 +1,14 @@ -using Endpoint; -using LocalEndpoint.Data; +using Services; +using LocalServices.Data; using Models; using System.Collections.Immutable; -namespace LocalEndpoint +namespace LocalServices { /// - /// The local endpoint is an implementation of the Endpoint API definition. - /// + /// The local endpoint is an implementation of the Services API. + /// This class is the _entry point_ class of the implementation /// public class LocalEndpoint : IEndpoint { @@ -20,6 +20,7 @@ namespace LocalEndpoint { var db = new CatastrophicPerformancesDatabase(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); + if (db.IsEmpty()) PrepareDatabase(db); @@ -31,7 +32,12 @@ namespace LocalEndpoint public IRecipesService RecipesService => recipesService; - private static void PrepareDatabase(Database db) + + /// + /// Inserts sample data in the local database + /// + /// + private static void PrepareDatabase(IDatabase db) { 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", MakeGuid(1)); User USER2 = new User(Constants.DEFAULT_ACCOUNT_IMAGE, "Yanis", MakeGuid(2)); @@ -45,14 +51,19 @@ namespace LocalEndpoint db.InsertAccount(new Account(USER2, "yanis@google.com"), "123456"); db.InsertAccount(new Account(USER3, "leo@google.com"), "123456"); - db.InsertRecipe(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()), USER1, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); - db.InsertRecipe(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()), USER2, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); - db.InsertRecipe(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()), USER1, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); - db.InsertRecipe(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()), USER3, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); - db.InsertRecipe(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()), USER3, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); - db.InsertRecipe(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()), USER1, new List { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList())); + db.InsertRecipe(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()), USER1, new List { new Ingredient("Ingredient 1", 6, "g") }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + db.InsertRecipe(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()), USER2, new List { new Ingredient("Ingredient 1", 6, "g") }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + db.InsertRecipe(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()), USER1, new List { new Ingredient("Ingredient 1", 6, "g") }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + db.InsertRecipe(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()), USER3, new List { new Ingredient("Ingredient 1", 6, "g") }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + db.InsertRecipe(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()), USER3, new List { new Ingredient("Ingredient 1", 6, "g") }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + db.InsertRecipe(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()), USER1, new List { new Ingredient("Chocolate", 4, "g") }.ToImmutableList(), new List { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList())); } + /// + /// helper function to Create a Guid from a given seed + /// + /// the seed to use for the generation + /// private static Guid MakeGuid(int seed) { var r = new Random(seed); diff --git a/LocalServices/RecipesService.cs b/LocalServices/RecipesService.cs index 8969e9e..89404a3 100644 --- a/LocalServices/RecipesService.cs +++ b/LocalServices/RecipesService.cs @@ -1,17 +1,17 @@ -using Endpoint; -using LocalEndpoint.Data; +using Services; +using LocalServices.Data; using Models; using System.Collections.Immutable; -namespace LocalEndpoint +namespace LocalServices { - internal class RecipesService : IRecipesService + public class RecipesService : IRecipesService { - private readonly Database db; + private readonly IDatabase db; private readonly Dictionary accountsData = new Dictionary(); - public RecipesService(Database db) + public RecipesService(IDatabase db) { this.db = db; } @@ -21,17 +21,17 @@ namespace LocalEndpoint return db.ListAllRecipes().Take(4).ToImmutableList().ConvertAll(v => v.Info); } - public Recipe GetRecipe(RecipeInfo info) + public Recipe? GetRecipe(RecipeInfo info) { return db.GetRecipe(info.Id); } - public IAccountOwnedRecipes GetRecipesOf(Account account) + public IAccountOwnedRecipesService GetRecipesOf(Account account) { return GetOrInitData(account).Recipes; } - public IAccountRecipesPreferences GetPreferencesOf(Account account) + public IAccountRecipesPreferencesService GetPreferencesOf(Account account) { return GetOrInitData(account).Preferences; } @@ -51,5 +51,11 @@ namespace LocalEndpoint return data; } + public ImmutableList SearchRecipes(string prompt) + { + return db.ListAllRecipes() + .ConvertAll(r => r.Info) + .FindAll(i => i.Name.ToLower().Contains(prompt.ToLower())); + } } } diff --git a/MainAppShell.xaml.cs b/MainAppShell.xaml.cs index d3f6bc1..1b7399c 100644 --- a/MainAppShell.xaml.cs +++ b/MainAppShell.xaml.cs @@ -3,15 +3,20 @@ using Microsoft.Maui.Controls; using Models; using ShoopNCook.Controllers; using ShoopNCook.Pages; -using Endpoint; +using Services; + +// Shell principale de l'application après connexion de l'utilisateur public partial class MainAppShell : Shell { - public MainAppShell(Account account, IEndpoint endpoint, IApp app) + // Constructeur qui prend en argument un compte, un endpoint et une application + 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)); + + // Initialisation de chaque onglet avec sa page respective + HomeTab.ContentTemplate = new DataTemplate(() => new HomePage(account, endpoint)); + FavoritesTab.ContentTemplate = new DataTemplate(() => new FavoritesPage(account, endpoint.RecipesService)); + MyListTab.ContentTemplate = new DataTemplate(() => new MyListPage(account, endpoint.RecipesService)); MoreTab.ContentTemplate = new DataTemplate(() => new MorePage(account, new MorePageController(account, endpoint, app))); } } diff --git a/MauiProgram.cs b/MauiProgram.cs index 7dc9f23..420e008 100644 --- a/MauiProgram.cs +++ b/MauiProgram.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using CommunityToolkit.Maui; +using Microsoft.Extensions.Logging; namespace ShoopNCook; @@ -9,6 +10,7 @@ public static class MauiProgram var builder = MauiApp.CreateBuilder(); builder .UseMauiApp() + .UseMauiCommunityToolkit() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); diff --git a/Models/Account.cs b/Models/Account.cs index 5a9973b..142141e 100644 --- a/Models/Account.cs +++ b/Models/Account.cs @@ -1,4 +1,9 @@ namespace Models { + /// + /// Contains the informations of an account. + /// + /// The account's public information + /// The account's email address public record Account(User User, string Email); } diff --git a/Models/Ingredient.cs b/Models/Ingredient.cs index 10e7655..f0e14ee 100644 --- a/Models/Ingredient.cs +++ b/Models/Ingredient.cs @@ -3,7 +3,11 @@ namespace Models { - + /// + /// An ingredient + /// + /// The ingredient's name + /// The ingredient's amount (in kilograms or liters) [DataContract] - public record Ingredient([property: DataMember] string Name, [property: DataMember] float Amount); + public record Ingredient([property: DataMember] string Name, [property: DataMember] float Amount, [property: DataMember] string Unit); } diff --git a/Models/PreparationStep.cs b/Models/PreparationStep.cs index 35e4734..d14aea2 100644 --- a/Models/PreparationStep.cs +++ b/Models/PreparationStep.cs @@ -2,6 +2,11 @@ namespace Models { + /// + /// A step of preparation + /// + /// The step's name + /// The step's instructions / description [DataContract] public record PreparationStep([property: DataMember] string Name, [property: DataMember] string Description); } diff --git a/Models/Recipe.cs b/Models/Recipe.cs index 021d99a..71f44e3 100644 --- a/Models/Recipe.cs +++ b/Models/Recipe.cs @@ -3,6 +3,13 @@ using System.Runtime.Serialization; namespace Models { + /// + /// A Recipe + /// + /// The essential information of the recipe + /// The creator of the recipe + /// The needed ingredients of the recipe + /// The preparation steps [DataContract] public record Recipe( [property: DataMember] RecipeInfo Info, @@ -10,5 +17,4 @@ namespace Models [property: DataMember] ImmutableList Ingredients, [property: DataMember] ImmutableList Steps ); - } \ No newline at end of file diff --git a/Models/RecipeBuilder.cs b/Models/RecipeBuilder.cs index 047a4c6..ce49872 100644 --- a/Models/RecipeBuilder.cs +++ b/Models/RecipeBuilder.cs @@ -7,6 +7,9 @@ using System.Threading.Tasks; namespace Models { + /// + /// A Simple builder to create a recipe + /// public class RecipeBuilder { private readonly string name; diff --git a/Models/RecipeInfo.cs b/Models/RecipeInfo.cs index fec0ebb..d29f89d 100644 --- a/Models/RecipeInfo.cs +++ b/Models/RecipeInfo.cs @@ -2,12 +2,21 @@ namespace Models { + /// + /// The essential information about a recipe + /// + /// The recipe's name + /// The energy input + /// Estimated time of preparation in minutes + /// An illustrative image of the recipe + /// The average rate of the recipe + /// An unique identifier [DataContract] public record RecipeInfo( [property: DataMember] string Name, [property: DataMember] uint CalPerPers, [property: DataMember] uint CookTimeMins, - [property: DataMember] Uri? Image, + [property: DataMember] Uri Image, [property: DataMember] float AverageNote, [property: DataMember] Guid Id ); diff --git a/Models/RecipeRate.cs b/Models/RecipeRate.cs index b4dc3a3..a8e82c2 100644 --- a/Models/RecipeRate.cs +++ b/Models/RecipeRate.cs @@ -3,6 +3,11 @@ using System.Runtime.Serialization; namespace Models { + /// + /// The rate of a recipe, usually, the instances are bound with an account. + /// + /// + /// a rate between 0 and 5 [DataContract] public record RecipeRate( [property: DataMember] bool IsFavorite = false, diff --git a/Models/User.cs b/Models/User.cs index b70f7b1..7e18db1 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -2,13 +2,25 @@ namespace Models { + /// + /// Publics informations of a user + /// [DataContract] public class User { + /// + /// The profile picture + /// [DataMember] public Uri ProfilePicture { get; init; } + /// + /// The username + /// [DataMember] public string Name { get; init; } + /// + /// An unique identifier + /// [DataMember] public Guid Id { get; init; } diff --git a/Resources/Images/error.png b/Resources/Images/error.png new file mode 100644 index 0000000..57c47ae Binary files /dev/null and b/Resources/Images/error.png differ diff --git a/Resources/Images/notice.png b/Resources/Images/notice.png new file mode 100644 index 0000000..85c3bfe Binary files /dev/null and b/Resources/Images/notice.png differ diff --git a/Resources/Images/success.png b/Resources/Images/success.png new file mode 100644 index 0000000..1bd8d58 Binary files /dev/null and b/Resources/Images/success.png differ diff --git a/Resources/Images/warning.png b/Resources/Images/warning.png new file mode 100644 index 0000000..fe35146 Binary files /dev/null and b/Resources/Images/warning.png differ diff --git a/Services/IAccountOwnedRecipes.cs b/Services/IAccountOwnedRecipes.cs deleted file mode 100644 index 69d6b7b..0000000 --- a/Services/IAccountOwnedRecipes.cs +++ /dev/null @@ -1,23 +0,0 @@ -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/Services/IAccountOwnedRecipesService.cs b/Services/IAccountOwnedRecipesService.cs new file mode 100644 index 0000000..c6b375e --- /dev/null +++ b/Services/IAccountOwnedRecipesService.cs @@ -0,0 +1,38 @@ +using Models; +using System.Collections.Immutable; + +namespace Services +{ + /// + /// This service handles the recipes created by an account + /// + public interface IAccountOwnedRecipesService + { + /// + /// This service's bound account + /// + public Account Account { get; } + + /// + /// Upload a new recipe, ensuring the recipe's owner matches the service's bound account user. + /// + /// The recipe to upload + /// true if the recipe could be uploaded, false instead + public bool UploadRecipe(Recipe recipe); + + /// + /// Removes a recipe + /// + /// The informations about the recipe to remove + /// true if the recipe could be removed, false instead + public bool RemoveRecipe(RecipeInfo info); + + /// + /// The living recipes created by this account. + /// If the user removes a recipe (using ) it'll no longer apear in the + /// next invocations of this recipe + /// + /// the list of all the living recipes of the account + public ImmutableList GetAccountRecipes(); + } +} diff --git a/Services/IAccountRecipesPreferences.cs b/Services/IAccountRecipesPreferences.cs deleted file mode 100644 index 7f138db..0000000 --- a/Services/IAccountRecipesPreferences.cs +++ /dev/null @@ -1,29 +0,0 @@ -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 bool RemoveFromWeeklyList(RecipeInfo info); - - public bool IsInWeeklyList(RecipeInfo info); - - public RecipeRate GetRate(RecipeInfo info); - - public ImmutableList GetFavorites(); - - public ImmutableList GetRecommendedRecipes(); - - public ImmutableList<(RecipeInfo, uint)> GetWeeklyList(); - - } -} diff --git a/Services/IAccountRecipesPreferencesService.cs b/Services/IAccountRecipesPreferencesService.cs new file mode 100644 index 0000000..57d14df --- /dev/null +++ b/Services/IAccountRecipesPreferencesService.cs @@ -0,0 +1,68 @@ +using Models; +using System.Collections.Immutable; + +namespace Services +{ + /// + /// This service handles the preferences of a bound account + /// + public interface IAccountRecipesPreferencesService + { + /// + /// The bound account + /// + public Account Account { get; } + + /// + /// Adds a recipe in the favorites of the bound account + /// + /// The information about the recipe to add in favorites + public void AddToFavorites(RecipeInfo info); + /// + /// Removes a recipe from the favorites of the bound account + /// + /// The information about the recipe to remove from favorites + public void RemoveFromFavorites(RecipeInfo info); + /// + /// Sets a score for the specified recipe + /// + /// The information about the targeted recipe + /// The score to set + public void SetReviewScore(RecipeInfo info, uint score); + /// + /// Adds a recipe to the weekly list, specifying the amount of persons that must be fed for the week + /// + /// The information about the targeted recipe + /// The amount of guests that needs to be fed by the recipe for the week + /// + public bool AddToWeeklyList(RecipeInfo info, uint persAmount); + + /// + /// Retrieves the rate of the targeted recipe + /// The rate contains the user's score and whether if the recipe is in the favorites list. + /// + /// + /// The information about the targeted recipe + /// + public RecipeRate GetRate(RecipeInfo info); + + /// + /// The favorites recipes of the account + /// + /// A list containing all the recipe info that are marked as favorite by the bound account + public ImmutableList GetFavorites(); + + /// + /// The recommended recipes for the user based on his preferences. + /// + /// A list of the recommended recipes based on the preferences of the bound account + public ImmutableList GetRecommendedRecipes(); + + /// + /// The weekly list of the bound account + /// + /// The weekly list of the bound account, containing tuples that binds a recipe to the number of guests to feed for the week + public ImmutableList<(RecipeInfo, uint)> GetWeeklyList(); + + } +} diff --git a/Services/IAuthService.cs b/Services/IAuthService.cs index 0e450e2..ad12946 100644 --- a/Services/IAuthService.cs +++ b/Services/IAuthService.cs @@ -1,11 +1,34 @@ -using Models; -namespace Endpoint -{ - public interface IAuthService - { - public Account? Login(string email, string password); - - public Account? Register(string email, string username, string password); - } -} - +using Models; + +namespace Services +{ + /// + /// Service for account authentification + /// Passwords are being passed in this service clearly as the hash function used for passwords is implementation specific + /// + public interface IAuthService + { + /// + /// Tries to login to an account using its mail address and password. + /// + /// The mail address which acts as an identifier for the targeted account + /// The (clear) password used to login. + /// + /// Returns an instance of Account representing the account that got logged in. + /// If the login credentials are invalid to log in the targeted acccount, this method returns null. + /// + public Account? Login(string email, string password); + /// + /// Tries to register to a new account, defining its mail address, username and password. + /// + /// The mail address which acts as an identifier for the targeted account + /// The username of the account + /// The (clear) password used to login on next connections attempt. + /// + /// Returns an instance of Account representing the account that got newly registered. + /// If the register credentials are invalid, or if the account already exists, this method returns null. + /// + public Account? Register(string email, string username, string password); + } +} + diff --git a/Services/IEndpoint.cs b/Services/IEndpoint.cs index b928557..a8aba6f 100644 --- a/Services/IEndpoint.cs +++ b/Services/IEndpoint.cs @@ -1,13 +1,16 @@ - - -namespace Endpoint -{ - public interface IEndpoint - { - public IAuthService AuthService { get; } - - public IRecipesService RecipesService { get; } - - } -} - + + +namespace Services +{ + /// + /// The endpoint is the central element of the 'Services' assembly. + /// + public interface IEndpoint + { + public IAuthService AuthService { get; } + + public IRecipesService RecipesService { get; } + + } +} + diff --git a/Services/IRecipesService.cs b/Services/IRecipesService.cs index 88e8b65..f2fc18c 100644 --- a/Services/IRecipesService.cs +++ b/Services/IRecipesService.cs @@ -1,18 +1,48 @@ -using LocalEndpoint; -using Models; -using System.Collections.Immutable; - -namespace Endpoint -{ - public interface IRecipesService - { - public ImmutableList PopularRecipes(); - - public Recipe GetRecipe(RecipeInfo info); - - public IAccountOwnedRecipes GetRecipesOf(Account account); - public IAccountRecipesPreferences GetPreferencesOf(Account account); - - - } -} +using Models; +using System.Collections.Immutable; + +namespace Services +{ + /// + /// The services that is in charge of handling the application's recipes. + /// + public interface IRecipesService + { + /// + /// + /// + /// A list containg the popular recipes of the week + public ImmutableList PopularRecipes(); + + /// + /// performs a search over all the recipes + /// + /// A list containg the recipes that matches the prompt + public ImmutableList SearchRecipes(string prompt); + + /// + /// Retrieves a recipe from given RecipeInfo + /// + /// the informations about the recipe that we want to retrieve + /// some recipe if the recipe was found, null else + public Recipe? GetRecipe(RecipeInfo info); + + /// + /// Gets the service that is in charge of handling the account's owned recipes. + /// The account's owned recipes are the recipes that the account created. + /// + /// The account logged in + /// The service that handles the given account's recipes + public IAccountOwnedRecipesService GetRecipesOf(Account account); + /// + /// Gets the service that handles all the preferences of the given account + /// + /// The account logged in + /// + /// The service that handles the given account's preferences + /// + public IAccountRecipesPreferencesService GetPreferencesOf(Account account); + + + } +} diff --git a/ShoopNCook.csproj b/ShoopNCook.csproj index 8a80861..1310c37 100644 --- a/ShoopNCook.csproj +++ b/ShoopNCook.csproj @@ -6,16 +6,17 @@ Exe + ShoopNCook true true enable - ShoopNCook + ShopNCook - com.companyname.shoopncook + com.companyname.shopncook bf17e1fe-a722-42f6-a24d-3327d351c924 @@ -48,26 +49,31 @@ + + + + + @@ -113,6 +119,7 @@ + diff --git a/ShoopNCook.sln b/ShoopNCook.sln index 3b6c720..17d11be 100644 --- a/ShoopNCook.sln +++ b/ShoopNCook.sln @@ -10,7 +10,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models", "Models\Models.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalServices", "LocalServices\LocalServices.csproj", "{57732316-93B9-4DA0-A212-F8892D3D968B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Services\Services.csproj", "{C976BDD8-710D-4162-8A42-973B634491F9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Services", "Services\Services.csproj", "{C976BDD8-710D-4162-8A42-973B634491F9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6DEA92EF-71CD-4A21-9CC0-67F228E1155D}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Tests/AccountOwnedRecipesTest.cs b/Tests/AccountOwnedRecipesTest.cs new file mode 100644 index 0000000..00e6a28 --- /dev/null +++ b/Tests/AccountOwnedRecipesTest.cs @@ -0,0 +1,26 @@ +using LocalServices.Data; +using Models; +using Services; +using System.Collections.Immutable; + +namespace Tests +{ + public class AccountOwnedRecipesTest + { + private static readonly User SAMPLE_USER = new User(new Uri("https://www.referenseo.com/wp-content/uploads/2019/03/image-attractive-960x540.jpg"), "user", Guid.NewGuid()); + private static readonly Account SAMPLE_ACC = new Account(SAMPLE_USER, "mail"); + private static readonly Recipe SAMPLE_RECIPE = new RecipeBuilder("foo", SAMPLE_USER).Build(); + + [Fact] + public void UploadRemove() + { + var db = new Mock(); + db.Setup(x => x.ListAllRecipes()).Returns(() => new List().ToImmutableList()); + var owned = new AccountOwnedRecipes(SAMPLE_ACC, db.Object); + owned.UploadRecipe(SAMPLE_RECIPE); + Assert.Contains(SAMPLE_RECIPE.Info, owned.GetAccountRecipes()); + owned.RemoveRecipe(SAMPLE_RECIPE.Info); + Assert.DoesNotContain(SAMPLE_RECIPE.Info, owned.GetAccountRecipes()); + } + } +} diff --git a/Tests/AccountRecipesPreferencesTests.cs b/Tests/AccountRecipesPreferencesTests.cs new file mode 100644 index 0000000..cb3f499 --- /dev/null +++ b/Tests/AccountRecipesPreferencesTests.cs @@ -0,0 +1,46 @@ +using LocalServices; +using LocalServices.Data; +using Models; +using System.Collections.Immutable; + +namespace Tests +{ + public class AccountRecipesPreferencesTests + { + private static readonly User SAMPLE_USER = new User(new Uri("https://www.referenseo.com/wp-content/uploads/2019/03/image-attractive-960x540.jpg"), "user", Guid.NewGuid()); + private static readonly Account SAMPLE_ACC = new Account(SAMPLE_USER, "mail"); + private static readonly Recipe SAMPLE_RECIPE = new RecipeBuilder("foo", SAMPLE_USER).Build(); + + [Fact] + public void Review() + { + var fav_inserted = false; + var rate_inserted = false; + var dict = new Dictionary(); + var db = new Mock(); + db.Setup(x => x.ListRatesOf(SAMPLE_USER.Id)).Returns(() => dict.ToImmutableDictionary()); + db.Setup(x => x.InsertRate(SAMPLE_USER.Id, SAMPLE_RECIPE.Info.Id, new RecipeRate(true, 0))).Callback(() => fav_inserted = true); + db.Setup(x => x.InsertRate(SAMPLE_USER.Id, SAMPLE_RECIPE.Info.Id, new RecipeRate(false, 3))).Callback(() => rate_inserted = true); + var pref = new AccountRecipesPreferences(SAMPLE_ACC, db.Object); + pref.AddToFavorites(SAMPLE_RECIPE.Info); + pref.SetReviewScore(SAMPLE_RECIPE.Info, 3); + Assert.True(fav_inserted); + Assert.True(rate_inserted); + } + + public void AddWeeklyList() + { + var inserted = false; + + var dict = new Dictionary { }; + dict.Add(SAMPLE_RECIPE.Info.Id, 88); + var db = new Mock(); + db.Setup(x => x.GetRecipeListOf(SAMPLE_USER.Id)).Returns(() => dict); + db.Setup(x => x.InsertInUserList(SAMPLE_USER.Id, SAMPLE_RECIPE.Info.Id, 88)).Callback(() => inserted = true); + var pref = new AccountRecipesPreferences(SAMPLE_ACC, db.Object); + pref.AddToWeeklyList(SAMPLE_RECIPE.Info, 88); + Assert.True(inserted); + Assert.True(pref.GetWeeklyList().Contains((SAMPLE_RECIPE.Info, 88))); + } + } +} diff --git a/Tests/AuthServiceTests.cs b/Tests/AuthServiceTests.cs new file mode 100644 index 0000000..0747e62 --- /dev/null +++ b/Tests/AuthServiceTests.cs @@ -0,0 +1,35 @@ + +using Models; +using LocalServices.Data; +using Moq; +using LocalServices; +using Services; + +namespace Tests +{ + public class AuthServiceTests + { + private static readonly User SAMPLE_USER = new User(new Uri("https://www.referenseo.com/wp-content/uploads/2019/03/image-attractive-960x540.jpg"), "user", Guid.NewGuid()); + private static readonly Account SAMPLE_ACC = new Account(SAMPLE_USER, "mail"); + + [Fact] + public void TestLogin() + { + var database = new Mock(); + database.Setup(x => x.GetAccount("mail", "1234")).Returns(SAMPLE_ACC); + var service = new AuthService(database.Object); + var acc = service.Login("mail", "1234"); + Assert.Equal(acc, SAMPLE_ACC); + } + + [Fact] + public void TestRegister() + { + var database = new Mock(); + database.Setup(x => x.GetAccount("mail", "1234")).Returns(SAMPLE_ACC); + var service = new AuthService(database.Object); + var acc = service.Register("mail", "foo", "1234"); + Assert.Equal(acc, new Account(new User(Constants.DEFAULT_ACCOUNT_IMAGE, "foo", acc.User.Id), "mail")); + } + } +} diff --git a/Tests/RecipeServicesTests.cs b/Tests/RecipeServicesTests.cs new file mode 100644 index 0000000..b598009 --- /dev/null +++ b/Tests/RecipeServicesTests.cs @@ -0,0 +1,25 @@ +using LocalServices.Data; +using Models; +using LocalServices; +using Services; +using System.Collections.Immutable; + +namespace Tests +{ + public class RecipeServicesTests + { + private static readonly User SAMPLE_USER = new User(new Uri("https://www.referenseo.com/wp-content/uploads/2019/03/image-attractive-960x540.jpg"), "user", Guid.NewGuid()); + private static readonly Account SAMPLE_ACC = new Account(SAMPLE_USER, "mail"); + private static readonly Recipe SAMPLE_RECIPE = new RecipeBuilder("foo", SAMPLE_USER).Build(); + + [Fact] + public void GetRecipe() + { + var database = new Mock(); + database.Setup(x => x.GetRecipe(SAMPLE_RECIPE.Info.Id)).Returns(SAMPLE_RECIPE); + var service = new RecipesService(database.Object); + Assert.Equal(service.GetRecipe(SAMPLE_RECIPE.Info), SAMPLE_RECIPE); + } + + } +} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 42ff44d..a268461 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,25 +1,32 @@ - - - - net7.0 - enable - enable - - false - true - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - + + + + net7.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/Tests/UnitTest1.cs b/Tests/UnitTest1.cs deleted file mode 100644 index f5782ed..0000000 --- a/Tests/UnitTest1.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Tests -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - - } - } - -} - diff --git a/Tests/Usings.cs b/Tests/Usings.cs index 8c927eb..7a8920d 100644 --- a/Tests/Usings.cs +++ b/Tests/Usings.cs @@ -1 +1,2 @@ -global using Xunit; \ No newline at end of file +global using Xunit; +global using Moq; \ No newline at end of file diff --git a/UserNotifier.cs b/UserNotifier.cs new file mode 100644 index 0000000..8432fc8 --- /dev/null +++ b/UserNotifier.cs @@ -0,0 +1,42 @@ +using CommunityToolkit.Maui.Alerts; +using CommunityToolkit.Maui.Core; + + +namespace ShoopNCook +{ + internal class UserNotifier + { + private static async Task Show(string message, string messageType) + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + // Vous pouvez configurer la durée et la taille de police ici. + ToastDuration duration = ToastDuration.Short; + double fontSize = 14; + + var toast = Toast.Make(message, duration, fontSize); + + await toast.Show(cancellationTokenSource.Token); + } + + + + public static void Error(string message) + { + Show(message, "Error"); + } + public static void Warn(string message) + { + Show(message, "Warning"); + } + public static void Notice(string message) + { + Show(message, "Notice"); + } + public static void Success(string message) + { + Show(message, "Success"); + } + } + +} diff --git a/Views/Components/CounterView.xaml.cs b/Views/Components/CounterView.xaml.cs index 9882cb2..9262ccb 100644 --- a/Views/Components/CounterView.xaml.cs +++ b/Views/Components/CounterView.xaml.cs @@ -41,3 +41,4 @@ public partial class CounterView : ContentView Count -= 1; } } + diff --git a/Views/Components/HeadedButton.xaml.cs b/Views/Components/HeadedButton.xaml.cs index 1c00ef2..1c22ef6 100644 --- a/Views/Components/HeadedButton.xaml.cs +++ b/Views/Components/HeadedButton.xaml.cs @@ -1,26 +1,29 @@ namespace ShoopNCook.Views; using Microsoft.Maui.Graphics; +// Classe représentant un bouton avec une tête (une image en préfixe) public partial class HeadedButton : ContentView { - + // Texte du bouton public string Text { set => BtnLabel.Text = value; } + // Couleur de l'image en préfixe public string HeadColor { set => PrefixBorder.BackgroundColor = Color.FromArgb(value); } + // Source de l'image en préfixe public string HeadSource { set => PrefixImage.Source = ImageSource.FromFile(value); } public HeadedButton() - { - InitializeComponent(); - } -} \ No newline at end of file + { + InitializeComponent(); + } +} diff --git a/Views/Components/IngredientEntry.xaml b/Views/Components/IngredientEntry.xaml index 0a39bdc..769bd12 100644 --- a/Views/Components/IngredientEntry.xaml +++ b/Views/Components/IngredientEntry.xaml @@ -22,7 +22,7 @@ Style="{StaticResource UserInput}" Placeholder="Ingredient Name" HeightRequest="40" - x:Name="NameEntry"/> + Text="{Binding NameText, Mode=OneWayToSource}"/> + Text="{Binding QuantityText, Mode=OneWayToSource}"/> + RowDefinitions="*, Auto, Auto"> onRecipeCreated; - private IUserNotifier notifier; - public CreateRecipePage(User owner, IUserNotifier notifier, Action onRecipeCreated) + public CreateRecipePage(User owner, Action onRecipeCreated) { InitializeComponent(); this.owner = owner; this.onRecipeCreated = onRecipeCreated; - this.notifier = notifier; } private void OnAddIngredientTapped(object sender, TappedEventArgs e) @@ -27,9 +25,9 @@ public partial class CreateRecipePage : ContentPage { StepList.Children.Add(new StepEntry((uint) StepList.Children.Count() + 1)); } - private void OnBackButtonClicked(object sender, EventArgs e) + private async void OnBackButtonClicked(object sender, EventArgs e) { - Navigation.PopAsync(); + await Navigation.PopAsync(); } private void OnUploadRecipeClicked(object sender, EventArgs e) @@ -53,7 +51,7 @@ public partial class CreateRecipePage : ContentPage if (hadErrors) { - notifier.Error("You need to fix input errors before upload."); + UserNotifier.Error("You need to fix input errors before upload."); return; } @@ -63,8 +61,12 @@ public partial class CreateRecipePage : ContentPage //TODO .SetImage(RecipeImage) ; - foreach (IngredientEntry entry in IngredientList.Children) - builder.AddIngredient(entry.MakeValue()); + foreach (IngredientEntry entry in IngredientList.Children) + { + var ingredient = entry.MakeValue(); + if (ingredient == null) return; + builder.AddIngredient(ingredient); + } foreach (StepEntry entry in StepList.Children) builder.AddStep(entry.MakeStep()); diff --git a/Views/FavoritesPage.xaml.cs b/Views/FavoritesPage.xaml.cs index cc4fff6..8375feb 100644 --- a/Views/FavoritesPage.xaml.cs +++ b/Views/FavoritesPage.xaml.cs @@ -1,8 +1,7 @@ -using Models; namespace ShoopNCook.Pages; -using Endpoint; +using Services; using Models; using ShoopNCook.Views; @@ -10,14 +9,12 @@ public partial class FavoritesPage : ContentPage { private readonly Account account; - private readonly IUserNotifier notifier; private IRecipesService service; - public FavoritesPage(Account account, IUserNotifier notifier, IRecipesService service) + public FavoritesPage(Account account, IRecipesService service) { InitializeComponent(); this.account = account; - this.notifier = notifier; this.service = service; UpdateFavorites(); @@ -25,15 +22,14 @@ public partial class FavoritesPage : ContentPage private void UpdateFavorites() { - IAccountRecipesPreferences preferences = service.GetPreferencesOf(account); + IAccountRecipesPreferencesService preferences = service.GetPreferencesOf(account); RecipeViewLayout.Children.Clear(); preferences.GetFavorites().ForEach(info => { - RecipeViewLayout.Children.Add(new RecipeView(info, () => + RecipeViewLayout.Children.Add(new RecipeView(info, async () => { Recipe recipe = service.GetRecipe(info); - - Shell.Current.Navigation.PushAsync(new RecipePage(recipe, notifier, preferences, 1)); + await Shell.Current.Navigation.PushAsync(new RecipePage(recipe, preferences, 1)); })); }); } diff --git a/Views/HomePage.xaml b/Views/HomePage.xaml index fa9eae2..afcf2b1 100644 --- a/Views/HomePage.xaml +++ b/Views/HomePage.xaml @@ -20,7 +20,7 @@ BackgroundColor="{StaticResource BackgroundSecondary}" StrokeShape="RoundRectangle 1500"> @@ -40,7 +40,8 @@ + Placeholder="Search here..." + x:Name="SearchPrompt"/> - { - Recipe recipe = service.GetRecipe(info); - Shell.Current.Navigation.PushAsync(new RecipePage(recipe, notifier, preferences, 1)); + { + Recipe recipe = recipes.GetRecipe(info); + if (recipe != null) + Shell.Current.Navigation.PushAsync(new RecipePage(recipe, preferences, 1)); + else + { + UserNotifier.Error("Could not find recipe"); + } })); } - service.PopularRecipes().ForEach(recipe => PushRecipe(PopularsList, recipe)); + recipes.PopularRecipes().ForEach(recipe => PushRecipe(PopularsList, recipe)); preferences.GetRecommendedRecipes().ForEach(recipe => PushRecipe(RecommendedList, recipe)); - - ProfilePictureImage.Source = ImageSource.FromUri(account.User.ProfilePicture); } - - - - private void OnSyncButtonClicked(object sender, EventArgs e) + private async void OnSyncButtonClicked(object sender, EventArgs e) { - Shell.Current.Navigation.PushAsync(new SearchPage()); + string prompt = SearchPrompt.Text; + if (string.IsNullOrEmpty(prompt)) + return; + + var searchPage = new SearchPage(recipes, preferences); + await Shell.Current.Navigation.PushAsync(searchPage); + searchPage.MakeSearch(SearchPrompt.Text); } } \ No newline at end of file diff --git a/Views/LoginPage.xaml.cs b/Views/LoginPage.xaml.cs index 6d2293a..31f87f7 100644 --- a/Views/LoginPage.xaml.cs +++ b/Views/LoginPage.xaml.cs @@ -1,4 +1,4 @@ -using Endpoint; +using Services; using ShoopNCook.Controllers; namespace ShoopNCook.Pages; @@ -11,7 +11,7 @@ public partial class LoginPage : ContentPage InitializeComponent(); this.controller = controller; } - private async void OnLoginButtonClicked(object sender, EventArgs e) + private void OnLoginButtonClicked(object sender, EventArgs e) { string email = EmailEntry.Text; string password = PasswordEntry.Text; diff --git a/Views/MyListPage.xaml.cs b/Views/MyListPage.xaml.cs index 87cddb5..7e51831 100644 --- a/Views/MyListPage.xaml.cs +++ b/Views/MyListPage.xaml.cs @@ -1,5 +1,5 @@ -using Endpoint; -using LocalEndpoint; +using Services; +using Services; using Models; using ShoopNCook.Views; @@ -8,16 +8,14 @@ namespace ShoopNCook.Pages; public partial class MyListPage : ContentPage { - private readonly IAccountRecipesPreferences preferences; - private readonly IUserNotifier notifier; + private readonly IAccountRecipesPreferencesService preferences; private readonly IRecipesService service; - public MyListPage(Account account, IUserNotifier notifier, IRecipesService service) + public MyListPage(Account account, IRecipesService service) { InitializeComponent(); this.preferences = service.GetPreferencesOf(account); - this.notifier = notifier; this.service = service; UpdateMyList(); @@ -32,12 +30,12 @@ public partial class MyListPage : ContentPage RecipesLayout.Children.Add(new StoredRecipeView(info, tuple.Item2, amount => { Recipe recipe = service.GetRecipe(info); - Shell.Current.Navigation.PushAsync(new RecipePage(recipe, notifier, preferences, amount)); + Shell.Current.Navigation.PushAsync(new RecipePage(recipe, preferences, amount)); })); }); } - private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e) + private async void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e) { UpdateMyList(); } diff --git a/Views/MyRecipesPage.xaml.cs b/Views/MyRecipesPage.xaml.cs index 952d786..b4210ba 100644 --- a/Views/MyRecipesPage.xaml.cs +++ b/Views/MyRecipesPage.xaml.cs @@ -1,5 +1,5 @@ -using Endpoint; -using LocalEndpoint; +using Services; +using Services; using Models; using ShoopNCook.Views; @@ -8,18 +8,15 @@ namespace ShoopNCook.Pages; public partial class MyRecipesPage : ContentPage { - private IUserNotifier notifier; private IRecipesService service; private Account account; public MyRecipesPage( Account account, - IRecipesService service, - IUserNotifier notifier) + IRecipesService service) { InitializeComponent(); - this.notifier = notifier; this.service = service; this.account = account; @@ -34,8 +31,8 @@ public partial class MyRecipesPage : ContentPage 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)); + IAccountRecipesPreferencesService preferences = service.GetPreferencesOf(account); + Shell.Current.Navigation.PushAsync(new RecipePage(recipe, preferences, 1)); }, () => RemoveRecipe(info) )); @@ -43,11 +40,11 @@ public partial class MyRecipesPage : ContentPage private void RemoveRecipe(RecipeInfo info) { - IAccountOwnedRecipes recipes = service.GetRecipesOf(account); + IAccountOwnedRecipesService recipes = service.GetRecipesOf(account); if (!recipes.RemoveRecipe(info)) { - notifier.Error("Could not remove recipe"); + UserNotifier.Error("Could not remove recipe"); return; } foreach (OwnedRecipeView view in RecipesLayout.Children) @@ -58,25 +55,25 @@ public partial class MyRecipesPage : ContentPage break; } } - notifier.Success("Recipe successfully removed"); + UserNotifier.Success("Recipe successfully removed"); } - private void OnBackButtonClicked(object sender, EventArgs e) + private async void OnBackButtonClicked(object sender, EventArgs e) { - Navigation.PopAsync(); + await Navigation.PopAsync(); } - private void OnAddRecipeButtonClicked(object sender, EventArgs e) + private async void OnAddRecipeButtonClicked(object sender, EventArgs e) { - IAccountOwnedRecipes recipes = service.GetRecipesOf(account); + IAccountOwnedRecipesService recipes = service.GetRecipesOf(account); - var page = new CreateRecipePage(account.User, notifier, recipe => + var page = new CreateRecipePage(account.User, recipe => { if (!recipes.UploadRecipe(recipe)) { - notifier.Error("Could not upload recipe."); + UserNotifier.Error("Could not upload recipe."); return; } - notifier.Success("Recipe Successfuly uploaded !"); + UserNotifier.Success("Recipe Successfuly uploaded !"); AddRecipeView(recipe.Info); Shell.Current.Navigation.PopAsync(); //go back to current recipe page. }); diff --git a/Views/RecipePage.xaml b/Views/RecipePage.xaml index 38449fb..cc77351 100644 --- a/Views/RecipePage.xaml +++ b/Views/RecipePage.xaml @@ -154,6 +154,7 @@ Grid.Column="2" Style="{StaticResource UserButton}" BackgroundColor="Gray" + Text="Add To Weekly List" x:Name="MyListStateButton" Clicked="OnFooterButtonClicked"/> diff --git a/Views/RecipePage.xaml.cs b/Views/RecipePage.xaml.cs index de43776..2dbcfea 100644 --- a/Views/RecipePage.xaml.cs +++ b/Views/RecipePage.xaml.cs @@ -1,26 +1,23 @@ using ShoopNCook.Views; using System.Windows.Input; using Models; -using LocalEndpoint; -using Endpoint; +using Services; namespace ShoopNCook.Pages; public partial class RecipePage : ContentPage { - private readonly IAccountRecipesPreferences preferences; - private readonly IUserNotifier notifier; + private readonly IAccountRecipesPreferencesService preferences; private uint note; private bool isFavorite; - private bool isInMyList; public Recipe Recipe { get; init; } - + public ICommand StarCommand => new Command(count => SetNote(uint.Parse(count))); - public RecipePage(Recipe recipe, IUserNotifier notifier, IAccountRecipesPreferences preferences, uint amount) + public RecipePage(Recipe recipe, IAccountRecipesPreferencesService preferences, uint amount) { Recipe = recipe; @@ -29,7 +26,6 @@ public partial class RecipePage : ContentPage InitializeComponent(); this.preferences = preferences; - this.notifier = notifier; RecipeRate rate = preferences.GetRate(recipe.Info); @@ -38,7 +34,6 @@ public partial class RecipePage : ContentPage SetFavorite(isFavorite); SetNote(note); - SetIsInMyListState(preferences.IsInWeeklyList(recipe.Info)); Counter.Count = amount; @@ -49,7 +44,8 @@ public partial class RecipePage : ContentPage var styles = Application.Current.Resources.MergedDictionaries.ElementAt(1); int count = 0; - foreach (PreparationStep step in recipe.Steps) { + foreach (PreparationStep step in recipe.Steps) + { //TODO display name of PreparationSteps. Label label = new Label(); label.Style = (Style)styles["Small"]; @@ -58,19 +54,6 @@ public partial class RecipePage : ContentPage } } - private void SetIsInMyListState(bool isInMyList) - { - this.isInMyList = isInMyList; - if (isInMyList) - { - MyListStateButton.Text = "Remove From My List"; - } - else - { - MyListStateButton.Text = "Add To My List"; - } - } - private void SetNote(uint note) { this.note = note; @@ -99,40 +82,20 @@ public partial class RecipePage : ContentPage private void OnSubmitReviewClicked(object o, EventArgs e) { preferences.SetReviewScore(Recipe.Info, note); - notifier.Success("Your review has been successfuly submited"); + UserNotifier.Success("Your review has been successfuly submited"); } private void OnFooterButtonClicked(object o, EventArgs e) { - SetIsInMyListState(!isInMyList); - if (isInMyList) - RemoveFromMyList(); - else - AddToMyList(); - } - - private void RemoveFromMyList() - { - if (!preferences.RemoveFromWeeklyList(Recipe.Info)) - { - notifier.Notice("This recipe does not figures in your personnal list"); - } - else - { - notifier.Success("Recipe added to your weekly list."); - } + AddToMyList(); } private void AddToMyList() { if (!preferences.AddToWeeklyList(Recipe.Info, Counter.Count)) - { - notifier.Notice("You already added this recipe to you weekly list!"); - } + UserNotifier.Notice("You already added this recipe to you weekly list!"); else - { - notifier.Success("Recipe added to your weekly list."); - } + UserNotifier.Success("Recipe added to your weekly list."); } private void SetFavorite(bool isFavorite) @@ -147,5 +110,4 @@ public partial class RecipePage : ContentPage { Navigation.PopAsync(); } - } \ No newline at end of file diff --git a/Views/RegisterPage.xaml.cs b/Views/RegisterPage.xaml.cs index b8d1929..65c3b2c 100644 --- a/Views/RegisterPage.xaml.cs +++ b/Views/RegisterPage.xaml.cs @@ -14,7 +14,7 @@ public partial class RegisterPage : ContentPage { await Shell.Current.GoToAsync("//Login"); } - private void RegisterTapped(object sender, EventArgs e) + private async void RegisterTapped(object sender, EventArgs e) { string email = EmailEntry.Text; string password = PasswordEntry.Text; diff --git a/Views/SearchPage.xaml b/Views/SearchPage.xaml index 6d2dce6..da8978b 100644 --- a/Views/SearchPage.xaml +++ b/Views/SearchPage.xaml @@ -49,18 +49,28 @@ Style="{StaticResource SecondaryBorderShadow}"> + Placeholder="Cake, Lasagna, Vegetarian..." + x:Name="SearchPrompt"/> - - + WidthRequest="40"> + + + + @@ -69,34 +79,10 @@ - - - - - - - - - - - @@ -106,9 +92,22 @@ AlignContent="Start" Direction="Row" Wrap="Wrap" - x:Name="ResultSearchView"> + BindableLayout.ItemsSource="{Binding FoundRecipes}"> + + + + + + + + + + + diff --git a/Views/SearchPage.xaml.cs b/Views/SearchPage.xaml.cs index ef27179..ff2d530 100644 --- a/Views/SearchPage.xaml.cs +++ b/Views/SearchPage.xaml.cs @@ -1,4 +1,3 @@ -using Microsoft.Maui.Storage; using Models; using Services; using ShoopNCook.Views; @@ -10,10 +9,9 @@ public partial class SearchPage : ContentPage private readonly IRecipesService recipesService; private readonly IAccountRecipesPreferencesService preferences; - public ObservableCollection FoundRecipes { get; private init; } = new ObservableCollection(); - public SearchPage(IRecipesService recipes, IAccountRecipesPreferencesService preferences) + public SearchPage(IRecipesService recipes, IAccountRecipesPreferencesService preferences) { BindingContext = this; this.recipesService = recipes; @@ -27,6 +25,7 @@ public partial class SearchPage : ContentPage { return; } + SearchPrompt.Text = prompt; FoundRecipes.Clear(); foreach (RecipeInfo info in recipesService.SearchRecipes(prompt)) { @@ -46,15 +45,8 @@ public partial class SearchPage : ContentPage await Navigation.PopAsync(); } - private void OnSortByRateClicked(object sender, EventArgs e) - { - FoundRecipes.OrderBy(view => view.Info) - } - private void OnSearchClicked(object sender, EventArgs e) { MakeSearch(SearchPrompt.Text); } - - } \ No newline at end of file diff --git a/documentation/diagrammes/Diagramme de paquetage persistence.svg b/documentation/diagrammes/Diagramme de paquetage persistence.svg new file mode 100644 index 0000000..40c8604 --- /dev/null +++ b/documentation/diagrammes/Diagramme de paquetage persistence.svg @@ -0,0 +1,4 @@ + + + +LocalServicesLocalServicesDataDataL'assembly LocalServices Contient l'espace de nom LocalServices.Data qui gère la base de donnée et sa persistenceL'assembly LocalServices Contient l'espace de nom LocalServices.Data qui gère la base de donnée et sa persi...Text is not SVG - cannot display \ No newline at end of file diff --git a/documentation/diagrammes/Diagramme de paquetage.svg b/documentation/diagrammes/Diagramme de paquetage.svg new file mode 100644 index 0000000..175146f --- /dev/null +++ b/documentation/diagrammes/Diagramme de paquetage.svg @@ -0,0 +1,4 @@ + + + +ShopNCookShopNCookViewsViewsComponentsComponentsControllersControllersModelsModelsServicesServicesLocalServicesLocalServicesUseUseUseUseUseUseText is not SVG - cannot display \ No newline at end of file diff --git a/documentation/diagrammes/FinalDiagram.svg b/documentation/diagrammes/FinalDiagram.svg new file mode 100644 index 0000000..989f2df --- /dev/null +++ b/documentation/diagrammes/FinalDiagram.svg @@ -0,0 +1,2978 @@ + + +-owner : User-onRecipeCreated : Action<Recipe>+CreateRecipePage()-OnAddIngredientTapped()-OnAddStepTapped()-OnBackButtonClicked()-OnUploadRecipeClicked()CreateRecipePageShoopNCookServices-controller : RegisterController+RegisterPage()-LoginTapped()-RegisterTapped()RegisterPage-note : uint-isFavorite : bool-preferences : IAccountRecipesPreferences-info : RecipeInfo+StarCommand : ICommand+RecipePage()-SetNote()-OnFavorite()-OnSubmitReviewClicked()-OnAddToMyListClicked()-SetFavorite()-OnBackButtonClicked()RecipePage-clickCallback : Action-removeCallback : Action-recipeInfo : RecipeInfo+Note : float+Title : string+OwnedRecipeView()+IsViewing()-SetNote()-OnViewTapped()-OnRemoveButtonTapped()OwnedRecipeView-service : IRecipesService-account : Account+MyRecipesPage()-AddRecipeView()-RemoveRecipe()-OnBackButtonClicked()-OnAddRecipeButtonClicked()MyRecipesPage-preferences : IAccountRecipesPreferences-service : IRecipesService+MyListPage()-UpdateMyList()-ContentPage_NavigatedTo()MyListPage-controller : MorePageController+MorePage()-OnMyRecipesButtonTapped()-OnEditProfileButtonTapped()-OnLogoutButtonTapped()-OnShareButtonClicked()MorePage-controller : LoginController+LoginPage()-OnLoginButtonClicked()-ForgotPasswordTapped()-RegisterLabbelTapped()LoginPageLocalServices-account : Account-service : IRecipesService+FavoritesPage()-UpdateFavorites()-ContentPage_NavigatedTo()FavoritesPage-Endpoint : IEndpoint+App()+OnAccountConnected()+ForceLogin()AppData-db : IDatabase-accountsData : Dictionary<Account, AccountServices>+RecipesService()+PopularRecipes()+GetRecipe()+GetRecipesOf()+GetPreferencesOf()-GetOrInitData()RecipesService+Account : Account-ownedRecipes : Dictionary<Guid, Recipe>-db : IDatabase+AccountOwnedRecipes()+UploadRecipe()+RemoveRecipe()+GetAccountRecipes()AccountOwnedRecipes-Show()+Error()+Warn()+Notice()+Success()UserNotifier+ForceLogin()<<interface>>IAppControllers+OnAccountConnected()<<interface>>ConnectionObserver+PopularRecipes()+GetRecipe()+GetRecipesOf()+GetPreferencesOf()<<interface>>IRecipesService+AuthService : IAuthService+RecipesService : IRecipesService<<interface>>IEndpoint+Login()+Register()<<interface>>IAuthService+Account : Account+AddToFavorites()+RemoveFromFavorites()+SetReviewScore()+AddToWeeklyList()+GetRate()+GetFavorites()+GetRecommendedRecipes()+GetWeeklyList()<<interface>>IAccountRecipesPreferences+Account : Account+UploadRecipe()+RemoveRecipe()+GetAccountRecipes()<<interface>>IAccountOwnedRecipes-authService : IAuthService-recipesService : IRecipesService+AuthService : IAuthService+RecipesService : IRecipesService+LocalEndpoint()-PrepareDatabase()-MakeGuid()LocalEndpoint-db : IDatabase+AuthService()+Login()+Register()AuthService-db : IDatabase+Account : Account+AccountRecipesPreferences()+GetRecommendedRecipes()+GetFavorites()+GetWeeklyList()+GetRate()+AddToWeeklyList()+AddToFavorites()+RemoveFromFavorites()+SetReviewScore()AccountRecipesPreferences-RECIPES_FILENAME : string-USERS_FILENAME : string-ACCOUNTS_FILENAME : string-recipesData : Dictionary<Guid, RecipeData>-usersData : Dictionary<Guid, UserData>-accountsData : Dictionary<string, AccountData>-dbPath : string+CatastrophicPerformancesDatabase()+IsEmpty()+GetAccount()+InsertAccount()+GetRecipe()+GetRecipeRate()+InsertInUserList()+RemoveFromUserList()+InsertRecipe()+InsertUser()+InsertRate()+RemoveRecipe()+ListAllRecipes()+ListRatesOf()+GetRecipeListOf()-ConvertRecipeDataToRecipe()-Load< K, V >()-Save< K, T >()-WaitForFile()CatastrophicPerformancesDatabase-User : User+GetAccount()+GetRecipe()+GetRecipeListOf()+GetRecipeRate()+InsertAccount()+InsertInUserList()+InsertRate()+InsertRecipe()+InsertUser()+ListAllRecipes()+ListRatesOf()+RemoveFromUserList()+RemoveRecipe()StubDatabase+GetRecipe()+GetRecipeRate()+GetAccount()+InsertInUserList()+RemoveFromUserList()+InsertAccount()+InsertRecipe()+InsertUser()+InsertRate()+RemoveRecipe()+ListAllRecipes()+ListRatesOf()+GetRecipeListOf()<<interface>>IDatabase+Register()<<interface>>RegisterController-app : IApp-endpoint : IEndpoint-account : Account+MorePageController()+Logout()+GoToMyRecipesPage()+GoToProfilePage()MorePageController+Login()<<interface>>LoginController-observer : ConnectionObserver-accounts : IAuthService+ConnectionController()+Login()+Register()ConnectionControllerModels+Name : string+Amount : float+Ingredient()Ingredient+Name : string+Description : string+PreparationStep()PreparationStep+Info : RecipeInfo+Owner : User+Ingredients : ImmutableList<Ingredient>+Steps : ImmutableList<PreparationStep>+Recipe()Recipe-name : string-owner : User-callPerPers : uint-cookTimeMins : uint-image : Uri-ingredients : List<Ingredient>-steps : List<PreparationStep>+RecipeBuilder()+SetCallPerPers()+SetCookTimeMins()+SetImage()+AddIngredient()+AddStep()+Build()RecipeBuilder+IsFavorite : bool+Rate : uint+RecipeRate()RecipeRate+ProfilePicture : Uri+Name : string+Id : Guid+User()+Equals()+GetHashCode()User+Name : string+CalPerPers : uint+CookTimeMins : uint+Image : Uri+AverageNote : float+Id : Guid+RecipeInfo()RecipeInfo-User : User+Email : string+Account()Account-User-steps+Info+Steps-ingredients+Ingredients+RecipesService-owner+Owner+AuthService<<use>>-info<<use>>-owner<<use>>-recipeInfo<<use>>-recipesServices<<use>><<use>>-User<<use>>-authService<<use>>-db<<use>>+ account<<use>>-db<<use>>-db<<use>>-db<<use>>+account<<use>>+account<<use>>+ account<<use>>-account<<use>>-service<<use>>-preferences<<use>>-service<<use>>-preferences<<use>>-service<<use>>-account<<use>>-endpoint<<use>>-controller<<use>>-accounts<<use>>-observer<<use>>-account<<use>>-app<<use>>- endpoint<<use>>- controller<<use>>-controller diff --git a/documentation/diagrammes/LocalServicesUML-Diagram-transformed.jpeg b/documentation/diagrammes/LocalServicesUML-Diagram-transformed.jpeg new file mode 100644 index 0000000..82ee094 Binary files /dev/null and b/documentation/diagrammes/LocalServicesUML-Diagram-transformed.jpeg differ diff --git a/documentation/diagrammes/ModelsUML-Diagram-transformed.jpeg b/documentation/diagrammes/ModelsUML-Diagram-transformed.jpeg new file mode 100644 index 0000000..a37a5e2 Binary files /dev/null and b/documentation/diagrammes/ModelsUML-Diagram-transformed.jpeg differ diff --git a/documentation/diagrammes/ServicesUML-Diagram-transformed.jpeg b/documentation/diagrammes/ServicesUML-Diagram-transformed.jpeg new file mode 100644 index 0000000..0231dfc Binary files /dev/null and b/documentation/diagrammes/ServicesUML-Diagram-transformed.jpeg differ