add documentation, setup doxygen and drone CI
continuous-integration/drone/push Build is passing Details

master
Maxime BATISTA 2 years ago
parent 18304de6a4
commit 7cb2b30216

@ -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

@ -1,29 +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 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);
Shell shell = new ConnectAppShell(this, Endpoint.AuthService);
shell.GoToAsync("//Splash");
MainPage = shell;
}

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

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

@ -1,4 +1,4 @@
using Endpoint;
using Services;
using Models;
namespace ShoopNCook.Controllers

@ -1,4 +1,4 @@
using Endpoint;
using Services;
using Models;
using ShoopNCook.Pages;

@ -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

@ -7,6 +7,8 @@ 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 void ForceLogin();

@ -1,11 +1,11 @@
using LocalServices;
using Services;
using LocalServices.Data;
using Models;
using System.Collections.Immutable;
namespace Endpoint
namespace Services
{
internal class AccountOwnedRecipes : IAccountOwnedRecipes
internal class AccountOwnedRecipes : IAccountOwnedRecipesService
{
public Account Account { get; init; }

@ -1,11 +1,11 @@
using Endpoint;
using Services;
using LocalServices.Data;
using Models;
using System.Collections.Immutable;
namespace LocalServices
{
internal class AccountRecipesPreferences : IAccountRecipesPreferences
internal class AccountRecipesPreferences : IAccountRecipesPreferencesService
{
private readonly IDatabase db;
@ -103,7 +103,7 @@ namespace LocalServices
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)));
}
}
}

@ -1,6 +1,6 @@
using Endpoint;
using Services;
namespace LocalServices
{
internal record AccountServices(IAccountOwnedRecipes Recipes, IAccountRecipesPreferences Preferences);
internal record AccountServices(IAccountOwnedRecipesService Recipes, IAccountRecipesPreferencesService Preferences);
}

@ -1,5 +1,5 @@
using Models;
using Endpoint;
using Services;
using System;
using System.Collections.Generic;
using System.Linq;

@ -1,10 +1,4 @@
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 LocalServices.Data
{

@ -1,27 +1,17 @@
using Models;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace LocalServices.Data
{
/// <summary>
/// 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.
/// This database implementation persists data in json and will save all the data in their files for each mutable requests.
/// </summary>
internal class CatastrophicPerformancesDatabase : IDatabase
{
private static readonly DataContractSerializer RECIPES_SERIALIZER = new DataContractSerializer(typeof(Dictionary<Guid, RecipeData>));
private static readonly DataContractSerializer USERS_SERIALIZER = new DataContractSerializer(typeof(Dictionary<Guid, UserData>));
private static readonly DataContractSerializer ACCOUNTS_SERIALIZER = new DataContractSerializer(typeof(Dictionary<string, AccountData>));
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";
@ -39,9 +29,9 @@ namespace LocalServices.Data
if (!Directory.Exists(folderPath))
Directory.CreateDirectory(folderPath);
usersData = Load<Guid, UserData>(USERS_FILENAME, USERS_SERIALIZER);
recipesData = Load<Guid, RecipeData>(RECIPES_FILENAME, RECIPES_SERIALIZER);
accountsData = Load<string, AccountData>(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER);
usersData = Load<Guid, UserData>(USERS_FILENAME);
recipesData = Load<Guid, RecipeData>(RECIPES_FILENAME);
accountsData = Load<string, AccountData>(ACCOUNTS_FILENAME);
}
public bool IsEmpty()
@ -61,7 +51,7 @@ namespace LocalServices.Data
public void InsertAccount(Account account, string passwordHash)
{
accountsData[account.Email] = new AccountData(account.User.Id, account.Email, passwordHash);
Save(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER, accountsData);
Save(ACCOUNTS_FILENAME, accountsData);
}
public Recipe? GetRecipe(Guid id)
@ -79,38 +69,38 @@ namespace LocalServices.Data
public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount)
{
usersData[userId].RecipesList[recipeId] = persAmount;
Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
Save(USERS_FILENAME, usersData);
}
public void RemoveFromUserList(Guid userId, Guid recipeId)
{
usersData[userId].RecipesList.Remove(recipeId);
Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
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, RECIPES_SERIALIZER, recipesData);
Save(RECIPES_FILENAME, recipesData);
}
public void InsertUser(User user)
{
usersData[user.Id] = new UserData(user, new Dictionary<Guid, RecipeRate>(), new Dictionary<Guid, uint>());
Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
Save(USERS_FILENAME, usersData);
}
public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate)
{
usersData[userId].Rates[recipeId] = rate;
Save(USERS_FILENAME, USERS_SERIALIZER, usersData);
Save(USERS_FILENAME, usersData);
}
public void RemoveRecipe(Guid id)
{
recipesData.Remove(id);
Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData);
Save(RECIPES_FILENAME, recipesData);
}
public ImmutableList<Recipe> ListAllRecipes()
@ -135,7 +125,7 @@ namespace LocalServices.Data
return new Recipe(rd.Info, owner, rd.Ingredients, rd.Steps);
}
private Dictionary<K, V> Load<K, V>(string fileName, DataContractSerializer deserializer)
private Dictionary<K, V> Load<K, V>(string fileName)
{
var file = dbPath + "/" + fileName;
var fileInfo = new FileInfo(file);
@ -151,10 +141,10 @@ namespace LocalServices.Data
return JsonSerializer.Deserialize<Dictionary<K, V>>(text);
}
private async void Save<K, T>(string fileName, DataContractSerializer serializer, Dictionary<K, T> dict)
private async void Save<K, T>(string fileName, Dictionary<K, T> dict)
{
string json = JsonSerializer.Serialize(dict);
using (var stream = WaitForFile(fileName, FileMode.Open, FileAccess.Write, FileShare.Write))
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);
@ -166,7 +156,7 @@ namespace LocalServices.Data
// 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 < 20; attempt++) //20 attempts equals to 2 seconds of wait
for (int attempt = 0 ; attempt < 40; attempt++)
{
FileStream? fs = null;
try
@ -174,7 +164,11 @@ namespace LocalServices.Data
fs = new FileStream(fullPath, mode, access, share);
return fs;
}
catch (IOException)
catch (FileNotFoundException e)
{
throw e;
}
catch (IOException e)
{
if (fs != null)
fs.Dispose();

@ -4,33 +4,100 @@ 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.
/// <summary>
/// The database interface defines all the different kinds of requests the LocalEndpoint needs to store and retrieve data.
/// </summary>
public interface IDatabase
{
/// <summary>
/// Get a Recipe from its identifier
/// </summary>
/// <param name="id"></param>
/// <returns>The recipe if the identifier is registered in the database</returns>
public Recipe? GetRecipe(Guid id);
/// <summary>
/// Get the rate of a user for a given recipe
/// </summary>
/// <param name="user">The user identifier</param>
/// <param name="recipe">The recipe identifier</param>
/// <returns>Returns a rate</returns>
public RecipeRate GetRecipeRate(Guid user, Guid recipe);
/// <summary>
/// Gets an account from an email and a password hash
/// </summary>
/// <param name="email"></param>
/// <param name="passwordHash"></param>
/// <returns>some account if the email and the hash is found in the database</returns>
public Account? GetAccount(string email, string passwordHash);
/// <summary>
/// Insert a recipe in user's weekly list
/// </summary>
/// <param name="userId"></param>
/// <param name="recipeId"></param>
/// <param name="persAmount"></param>
public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount);
/// <summary>
/// Remove a recipe from user's weekly list
/// </summary>
/// <param name="userId"></param>
/// <param name="recipeId"></param>
public void RemoveFromUserList(Guid userId, Guid recipeId);
/// <summary>
/// Inserts an account in the database, with the given passwordhash
/// </summary>
/// <param name="account"></param>
/// <param name="passwordHash"></param>
public void InsertAccount(Account account, string passwordHash);
/// <summary>
/// Inserts a recipe
/// </summary>
/// <param name="recipe"></param>
public void InsertRecipe(Recipe recipe);
/// <summary>
/// Inserts a user
/// </summary>
/// <param name="user"></param>
public void InsertUser(User user);
/// <summary>
/// Inserts a user rate over the given recipe identifier
/// </summary>
/// <param name="userId"></param>
/// <param name="recipeId"></param>
/// <param name="rate"></param>
public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate);
/// <summary>
/// Removes a recipe
/// </summary>
/// <param name="id"></param>
public void RemoveRecipe(Guid id);
/// <summary>
/// Lists all recipes in the database
/// </summary>
/// <returns></returns>
public ImmutableList<Recipe> ListAllRecipes();
/// <summary>
/// List the ratings of an user
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public ImmutableDictionary<Guid, RecipeRate> ListRatesOf(Guid user);
/// <summary>
/// Get the weekly list of given user
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public ImmutableDictionary<Guid, uint> GetRecipeListOf(Guid user);
}
}

@ -4,6 +4,9 @@ using System.Collections.Immutable;
namespace LocalServices.Data
{
/// <summary>
/// stub implementation for IDatabase
/// </summary>
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());

@ -1,4 +1,4 @@
using Endpoint;
using Services;
using LocalServices.Data;
using Models;
using System.Collections.Immutable;
@ -7,8 +7,8 @@ namespace LocalServices
{
/// <summary>
/// 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
/// </summary>
public class LocalEndpoint : IEndpoint
{
@ -20,6 +20,7 @@ namespace LocalServices
{
var db = new CatastrophicPerformancesDatabase(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
if (db.IsEmpty())
PrepareDatabase(db);
@ -31,6 +32,11 @@ namespace LocalServices
public IRecipesService RecipesService => recipesService;
/// <summary>
/// Inserts sample data in the local database
/// </summary>
/// <param name="db"></param>
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));
@ -53,6 +59,11 @@ namespace LocalServices
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<Ingredient> { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList()));
}
/// <summary>
/// helper function to Create a Guid from a given seed
/// </summary>
/// <param name="seed">the seed to use for the generation</param>
/// <returns></returns>
private static Guid MakeGuid(int seed)
{
var r = new Random(seed);

@ -1,4 +1,4 @@
using Endpoint;
using Services;
using LocalServices.Data;
using Models;
using System.Collections.Immutable;
@ -27,11 +27,11 @@ namespace LocalServices
}
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;
}

@ -3,13 +3,18 @@ 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, endpoint));
// 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)));

@ -1,4 +1,9 @@
namespace Models
{
/// <summary>
/// Contains the informations of an account.
/// </summary>
/// <param name="User">The account's public information</param>
/// <param name="Email">The account's email address</param>
public record Account(User User, string Email);
}

@ -3,7 +3,11 @@
namespace Models
{
/// <summary>
/// An ingredient
/// </summary>
/// <param name="Name">The ingredient's name</param>
/// <param name="Amount">The ingredient's amount (in kilograms or liters)</param>
[DataContract]
public record Ingredient([property: DataMember] string Name, [property: DataMember] float Amount);
}

@ -2,6 +2,11 @@
namespace Models
{
/// <summary>
/// A step of preparation
/// </summary>
/// <param name="Name">The step's name</param>
/// <param name="Description">The step's instructions / description</param>
[DataContract]
public record PreparationStep([property: DataMember] string Name, [property: DataMember] string Description);
}

@ -3,6 +3,13 @@ using System.Runtime.Serialization;
namespace Models
{
/// <summary>
/// A Recipe
/// </summary>
/// <param name="Info">The essential information of the recipe</param>
/// <param name="Owner">The creator of the recipe</param>
/// <param name="Ingredients">The needed ingredients of the recipe</param>
/// <param name="Steps">The preparation steps</param>
[DataContract]
public record Recipe(
[property: DataMember] RecipeInfo Info,
@ -10,5 +17,4 @@ namespace Models
[property: DataMember] ImmutableList<Ingredient> Ingredients,
[property: DataMember] ImmutableList<PreparationStep> Steps
);
}

@ -7,6 +7,9 @@ using System.Threading.Tasks;
namespace Models
{
/// <summary>
/// A Simple builder to create a recipe
/// </summary>
public class RecipeBuilder
{
private readonly string name;

@ -2,12 +2,21 @@
namespace Models
{
/// <summary>
/// The essential information about a recipe
/// </summary>
/// <param name="Name">The recipe's name</param>
/// <param name="CalPerPers">The energy input</param>
/// <param name="CookTimeMins">Estimated time of preparation in minutes</param>
/// <param name="Image">An illustrative image of the recipe</param>
/// <param name="AverageNote">The average rate of the recipe</param>
/// <param name="Id">An unique identifier</param>
[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
);

@ -3,6 +3,11 @@ using System.Runtime.Serialization;
namespace Models
{
/// <summary>
/// The rate of a recipe, usually, the instances are bound with an account.
/// </summary>
/// <param name="IsFavorite"></param>
/// <param name="Rate">a rate between 0 and 5</param>
[DataContract]
public record RecipeRate(
[property: DataMember] bool IsFavorite = false,

@ -2,13 +2,25 @@
namespace Models
{
/// <summary>
/// Publics informations of a user
/// </summary>
[DataContract]
public class User
{
/// <summary>
/// The profile picture
/// </summary>
[DataMember]
public Uri ProfilePicture { get; init; }
/// <summary>
/// The username
/// </summary>
[DataMember]
public string Name { get; init; }
/// <summary>
/// An unique identifier
/// </summary>
[DataMember]
public Guid Id { get; init; }

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

@ -0,0 +1,38 @@
using Models;
using System.Collections.Immutable;
namespace Services
{
/// <summary>
/// This service handles the recipes created by an account
/// </summary>
public interface IAccountOwnedRecipesService
{
/// <summary>
/// This service's bound account
/// </summary>
public Account Account { get; }
/// <summary>
/// Upload a new recipe, ensuring the recipe's owner matches the service's bound account user.
/// </summary>
/// <param name="recipe">The recipe to upload</param>
/// <returns>true if the recipe could be uploaded, false instead</returns>
public bool UploadRecipe(Recipe recipe);
/// <summary>
/// Removes a recipe
/// </summary>
/// <param name="info">The informations about the recipe to remove</param>
/// <returns>true if the recipe could be removed, false instead</returns>
public bool RemoveRecipe(RecipeInfo info);
/// <summary>
/// The living recipes created by this account.
/// If the user removes a recipe (using <see cref="RemoveRecipe(RecipeInfo)"/>) it'll no longer apear in the
/// next invocations of this recipe
/// </summary>
/// <returns>the list of all the living recipes of the account</returns>
public ImmutableList<RecipeInfo> GetAccountRecipes();
}
}

@ -1,25 +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 RecipeRate GetRate(RecipeInfo info);
public ImmutableList<RecipeInfo> GetFavorites();
public ImmutableList<RecipeInfo> GetRecommendedRecipes();
public ImmutableList<(RecipeInfo, uint)> GetWeeklyList();
}
}

@ -0,0 +1,68 @@
using Models;
using System.Collections.Immutable;
namespace Services
{
/// <summary>
/// This service handles the preferences of a bound account
/// </summary>
public interface IAccountRecipesPreferencesService
{
/// <summary>
/// The bound account
/// </summary>
public Account Account { get; }
/// <summary>
/// Adds a recipe in the favorites of the bound account
/// </summary>
/// <param name="info">The information about the recipe to add in favorites</param>
public void AddToFavorites(RecipeInfo info);
/// <summary>
/// Removes a recipe from the favorites of the bound account
/// </summary>
/// <param name="info">The information about the recipe to remove from favorites</param>
public void RemoveFromFavorites(RecipeInfo info);
/// <summary>
/// Sets a score for the specified recipe
/// </summary>
/// <param name="info">The information about the targeted recipe</param>
/// <param name="score">The score to set</param>
public void SetReviewScore(RecipeInfo info, uint score);
/// <summary>
/// Adds a recipe to the weekly list, specifying the amount of persons that must be fed for the week
/// </summary>
/// <param name="info">The information about the targeted recipe</param>
/// <param name="persAmount">The amount of guests that needs to be fed by the recipe for the week</param>
/// <returns></returns>
public bool AddToWeeklyList(RecipeInfo info, uint persAmount);
/// <summary>
/// Retrieves the rate of the targeted recipe
/// The rate contains the user's score and whether if the recipe is in the favorites list.
/// <see cref="RecipeInfo"/>
/// </summary>
/// <param name="info">The information about the targeted recipe</param>
/// <returns></returns>
public RecipeRate GetRate(RecipeInfo info);
/// <summary>
/// The favorites recipes of the account
/// </summary>
/// <returns>A list containing all the recipe info that are marked as favorite by the bound account</returns>
public ImmutableList<RecipeInfo> GetFavorites();
/// <summary>
/// The recommended recipes for the user based on his preferences.
/// </summary>
/// <returns>A list of the recommended recipes based on the preferences of the bound account</returns>
public ImmutableList<RecipeInfo> GetRecommendedRecipes();
/// <summary>
/// The weekly list of the bound account
/// </summary>
/// <returns>The weekly list of the bound account, containing tuples that binds a recipe to the number of guests to feed for the week</returns>
public ImmutableList<(RecipeInfo, uint)> GetWeeklyList();
}
}

@ -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
{
/// <summary>
/// Service for account authentification
/// Passwords are being passed in this service clearly as the hash function used for passwords is implementation specific
/// </summary>
public interface IAuthService
{
/// <summary>
/// Tries to login to an account using its mail address and password.
/// </summary>
/// <param name="email"> The mail address which acts as an identifier for the targeted account </param>
/// <param name="password"> The (clear) password used to login.</param>
/// <returns>
/// 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.
/// </returns>
public Account? Login(string email, string password);
/// <summary>
/// Tries to register to a new account, defining its mail address, username and password.
/// </summary>
/// <param name="email"> The mail address which acts as an identifier for the targeted account </param>
/// <param name="username"> The username of the account </param>
/// <param name="password"> The (clear) password used to login on next connections attempt.</param>
/// <returns>
/// 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.
/// </returns>
public Account? Register(string email, string username, string password);
}
}

@ -1,13 +1,16 @@

namespace Endpoint
{
public interface IEndpoint
{
public IAuthService AuthService { get; }
public IRecipesService RecipesService { get; }
}
}

namespace Services
{
/// <summary>
/// The endpoint is the central element of the 'Services' assembly.
/// </summary>
public interface IEndpoint
{
public IAuthService AuthService { get; }
public IRecipesService RecipesService { get; }
}
}

@ -1,18 +1,42 @@
using LocalEndpoint;
using Models;
using System.Collections.Immutable;
namespace Endpoint
{
public interface IRecipesService
{
public ImmutableList<RecipeInfo> PopularRecipes();
public Recipe? GetRecipe(RecipeInfo info);
public IAccountOwnedRecipes GetRecipesOf(Account account);
public IAccountRecipesPreferences GetPreferencesOf(Account account);
}
}
using Models;
using System.Collections.Immutable;
namespace Services
{
/// <summary>
/// The services that is in charge of handling the application's recipes.
/// </summary>
public interface IRecipesService
{
/// <summary>
///
/// </summary>
/// <returns>A list containg the popular recipes of the week</returns>
public ImmutableList<RecipeInfo> PopularRecipes();
/// <summary>
/// Retrieves a recipe from given RecipeInfo
/// </summary>
/// <param name="info">the informations about the recipe that we want to retrieve</param>
/// <returns>some recipe if the recipe was found, null else</returns>
public Recipe? GetRecipe(RecipeInfo info);
/// <summary>
/// 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.
/// </summary>
/// <param name="account">The account logged in</param>
/// <returns>The service that handles the given account's recipes</returns>
public IAccountOwnedRecipesService GetRecipesOf(Account account);
/// <summary>
/// Gets the service that handles all the preferences of the given account
/// </summary>
/// <param name="account">The account logged in</param>
/// <returns>
/// The service that handles the given account's preferences
/// </returns>
public IAccountRecipesPreferencesService GetPreferencesOf(Account account);
}
}

@ -6,16 +6,17 @@
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType>
<RootNamespace>ShoopNCook</RootNamespace>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Display name -->
<ApplicationTitle>ShoopNCook</ApplicationTitle>
<ApplicationTitle>ShopNCook</ApplicationTitle>
<!-- App Identifier -->
<ApplicationId>com.companyname.shoopncook</ApplicationId>
<ApplicationId>com.companyname.shopncook</ApplicationId>
<ApplicationIdGuid>bf17e1fe-a722-42f6-a24d-3327d351c924</ApplicationIdGuid>
<!-- Versions -->
@ -48,26 +49,31 @@
<ItemGroup>
<AndroidResource Remove="ShopNCookTests\**" />
<AndroidResource Remove="Tests\**" />
<Compile Remove="Foo\**" />
<Compile Remove="Services\**" />
<Compile Remove="LocalServices\**" />
<Compile Remove="Models\**" />
<Compile Remove="ShopNCookTests\**" />
<Compile Remove="Tests\**" />
<EmbeddedResource Remove="Foo\**" />
<EmbeddedResource Remove="Services\**" />
<EmbeddedResource Remove="LocalServices\**" />
<EmbeddedResource Remove="Models\**" />
<EmbeddedResource Remove="ShopNCookTests\**" />
<EmbeddedResource Remove="Tests\**" />
<MauiCss Remove="Foo\**" />
<MauiCss Remove="Services\**" />
<MauiCss Remove="LocalServices\**" />
<MauiCss Remove="Models\**" />
<MauiCss Remove="ShopNCookTests\**" />
<MauiCss Remove="Tests\**" />
<MauiXaml Remove="Foo\**" />
<MauiXaml Remove="Services\**" />
<MauiXaml Remove="LocalServices\**" />
<MauiXaml Remove="Models\**" />
<MauiXaml Remove="ShopNCookTests\**" />
<MauiXaml Remove="Tests\**" />
<None Remove="Foo\**" />
<None Remove="Services\**" />
<None Remove="LocalServices\**" />
<None Remove="Models\**" />

@ -1,25 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LocalServices\LocalServices.csproj" />
<ProjectReference Include="..\Models\Models.csproj" />
<ProjectReference Include="..\Services\Services.csproj" />
</ItemGroup>
</Project>

@ -1,3 +1,5 @@
using Models;
namespace Tests
{
public class UnitTest1
@ -5,7 +7,7 @@ namespace Tests
[Fact]
public void Test1()
{
new RecipeRate(true, 4);
}
}

@ -1,34 +1,40 @@
namespace ShoopNCook.Views;
// Classe représentant une vue avec un compteur
public partial class CounterView : ContentView
{
// Propriété liée pour le nombre à afficher
private readonly BindableProperty CountProperty =
BindableProperty.Create(nameof(CountLabel), typeof(uint), typeof(CounterView), default(uint) + 1);
// Propriété liée pour le texte à afficher
private readonly BindableProperty CounterLabelProperty =
BindableProperty.Create(nameof(CounterLabel), typeof(string), typeof(CounterView), default(string));
public CounterView()
{
{
InitializeComponent();
// Liaison des propriétés à leurs étiquettes respectives
CountLabel.BindingContext = this;
CountLabel.SetBinding(Label.TextProperty, nameof(Count));
CounterLabel.BindingContext = this;
CounterLabel.SetBinding(Label.TextProperty, nameof(CounterText));
}
// Accesseurs pour Count
public uint Count
{
get => (uint)GetValue(CountProperty);
set
{
// Assure que la valeur est toujours au minimum 1
SetValue(CountProperty, value <= 1 ? 1 : uint.Parse(value.ToString()));
OnPropertyChanged(nameof(Count));
}
}
// Accesseurs pour CounterText
public string CounterText
{
get => (string)GetValue(CounterLabelProperty);
@ -39,6 +45,7 @@ public partial class CounterView : ContentView
}
}
// Méthodes pour augmenter et diminuer le compteur
private void OnPlus(object o, EventArgs e)
{
Count += 1;
@ -48,4 +55,4 @@ public partial class CounterView : ContentView
{
Count -= 1;
}
}
}

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

@ -2,17 +2,20 @@ using Models;
namespace ShoopNCook.Views;
// Classe représentant une entrée d'ingrédient
public partial class IngredientEntry : ContentView
{
public IngredientEntry()
{
InitializeComponent();
}
public IngredientEntry()
{
InitializeComponent();
}
public Ingredient MakeValue()
{
// Renvoie une nouvelle instance de Ingredient à partir des informations entrées par l'utilisateur
public Ingredient MakeValue()
{
float quantity;
// Tente de convertir la quantité en float, sinon, attribue une valeur par défaut de 0
if (!float.TryParse(QuantityEntry.Text, out quantity))
{
quantity = 0;
@ -21,4 +24,4 @@ public partial class IngredientEntry : ContentView
return new Ingredient(NameEntry.Text, quantity);
}
}
}

@ -2,9 +2,10 @@ using Models;
namespace ShoopNCook.Views;
// Classe représentant une vue d'ingrédient
public partial class IngredientView : ContentView
{
// Propriétés liées pour le nom, la quantité et l'unité de l'ingrédient
private readonly BindableProperty NameProperty =
BindableProperty.Create(nameof(Name), typeof(string), typeof(IngredientView), default(string));
@ -36,9 +37,10 @@ public partial class IngredientView : ContentView
{
InitializeComponent();
// Initialisation des valeurs de l'ingrédient
Name = ingredient.Name;
Quantity = ingredient.Amount;
//TODO Unit implementation in IngredientView.xaml.cs
Unit = "TODO: Unit implementation in IngredientView.xaml.cs";
}
}
}

@ -2,7 +2,7 @@ using Models;
namespace ShoopNCook.Pages;
using Endpoint;
using Services;
using Models;
using ShoopNCook.Views;
@ -23,7 +23,7 @@ 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 =>
{

@ -1,8 +1,8 @@
namespace ShoopNCook.Pages;
using Models;
using ShoopNCook.Views;
using Endpoint;
using LocalEndpoint;
using Services;
using Services;
public partial class HomePage : ContentPage
{
@ -11,7 +11,7 @@ public partial class HomePage : ContentPage
InitializeComponent();
IRecipesService service = endpoint.RecipesService;
IAccountRecipesPreferences preferences = service.GetPreferencesOf(account);
IAccountRecipesPreferencesService preferences = service.GetPreferencesOf(account);
//TODO this code can be factorised

@ -1,4 +1,4 @@
using Endpoint;
using Services;
using ShoopNCook.Controllers;
namespace ShoopNCook.Pages;

@ -1,5 +1,5 @@
using Endpoint;
using LocalEndpoint;
using Services;
using Services;
using Models;
using ShoopNCook.Views;
@ -8,7 +8,7 @@ namespace ShoopNCook.Pages;
public partial class MyListPage : ContentPage
{
private readonly IAccountRecipesPreferences preferences;
private readonly IAccountRecipesPreferencesService preferences;
private readonly IRecipesService service;
public MyListPage(Account account, IRecipesService service)

@ -1,5 +1,5 @@
using Endpoint;
using LocalEndpoint;
using Services;
using Services;
using Models;
using ShoopNCook.Views;
@ -31,7 +31,7 @@ public partial class MyRecipesPage : ContentPage
RecipesLayout.Children.Add(new OwnedRecipeView(info, () =>
{
Recipe recipe = service.GetRecipe(info);
IAccountRecipesPreferences preferences = service.GetPreferencesOf(account);
IAccountRecipesPreferencesService preferences = service.GetPreferencesOf(account);
Shell.Current.Navigation.PushAsync(new RecipePage(recipe, preferences, 1));
},
() => RemoveRecipe(info)
@ -40,7 +40,7 @@ public partial class MyRecipesPage : ContentPage
private void RemoveRecipe(RecipeInfo info)
{
IAccountOwnedRecipes recipes = service.GetRecipesOf(account);
IAccountOwnedRecipesService recipes = service.GetRecipesOf(account);
if (!recipes.RemoveRecipe(info))
{
@ -64,7 +64,7 @@ public partial class MyRecipesPage : ContentPage
}
private async void OnAddRecipeButtonClicked(object sender, EventArgs e)
{
IAccountOwnedRecipes recipes = service.GetRecipesOf(account);
IAccountOwnedRecipesService recipes = service.GetRecipesOf(account);
var page = new CreateRecipePage(account.User, recipe =>
{

@ -1,8 +1,8 @@
using ShoopNCook.Views;
using System.Windows.Input;
using Models;
using LocalEndpoint;
using Endpoint;
using Services;
using Services;
namespace ShoopNCook.Pages;
@ -13,12 +13,12 @@ public partial class RecipePage : ContentPage
private bool isFavorite;
private IAccountRecipesPreferences preferences;
private IAccountRecipesPreferencesService preferences;
private RecipeInfo info;
public ICommand StarCommand => new Command<string>(count => SetNote(uint.Parse(count)));
public RecipePage(Recipe recipe, IAccountRecipesPreferences preferences, uint amount)
public RecipePage(Recipe recipe, IAccountRecipesPreferencesService preferences, uint amount)
{
InitializeComponent();

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

Loading…
Cancel
Save