Merge pull request 'Version de Model et ConsolApp fonctionnelle et prête pour une évaluation blanche' (#49) from dev into master
continuous-integration/drone/push Build is passing Details

Reviewed-on: #49
pull/53/head eval-blanche/2
Alexandre AGOSTINHO 2 years ago
commit 0aee8f070e

@ -0,0 +1,117 @@
kind: pipeline
type: docker
name: CI-pipeline
trigger:
branch:
- master
- dev*
- feature/*
- gestion/*
event:
- push
steps:
# Build Check
- name: build
image: mcr.microsoft.com/dotnet/sdk:7.0
volumes:
- name: docs
path: /docs
commands:
- cd MCTG/
- dotnet restore CI-CD.slnf
- dotnet build CI-CD.slnf -c Release --no-restore
- dotnet publish CI-CD.slnf -c Release --no-restore -o CI_PROJECT_DIR/build/release
# Unit Testing Check
- name: tests
image: mcr.microsoft.com/dotnet/sdk:7.0
commands:
- cd MCTG/
- dotnet restore CI-CD.slnf
- dotnet test CI-CD.slnf --no-restore
depends_on: [ build ]
# Code Inspection (Sonar)
- name: code-analysis
image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dronesonarplugin-dotnet7
secrets: [ SECRET_SONAR_LOGIN ]
environment:
sonar_host: https://codefirst.iut.uca.fr/sonar/
sonar_token:
from_secret: SECRET_SONAR_LOGIN
project_key: "SAE-2.01_MCTG"
coverage_exclusions: "Tests/**"
commands:
- cd MCTG/
- dotnet restore CI-CD.slnf
- dotnet sonarscanner begin /k:$${project_key} /d:sonar.host.url=$${sonar_host} /d:sonar.coverageReportPaths="coveragereport/SonarQube.xml" /d:sonar.coverage.exclusions=$${coverage_exclusions} /d:sonar.login=$${sonar_token}
- dotnet build CI-CD.slnf -c Release --no-restore
- dotnet test CI-CD.slnf --logger trx --no-restore /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura --collect "XPlat Code Coverage"
- reportgenerator -reports:"**/coverage.cobertura.xml" -reporttypes:SonarQube -targetdir:"coveragereport"
- dotnet publish CI-CD.slnf -c Release --no-restore -o CI_PROJECT_DIR/build/release
- dotnet sonarscanner end /d:sonar.login=$${sonar_token}
depends_on: [ tests ]
# Documentation generation
- name: generate-and-deploy-docs
image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-docdeployer
failure: ignore
volumes:
- name: docs
path: /docs
commands:
- /entrypoint.sh
when:
branch:
- master
- gestion/*
event:
- push
- pull_request
depends_on: [ build ]
volumes:
- name: docs
temp: {}
---
kind: pipeline
type: docker
name: CD-pipeline
trigger:
branch:
- master
- dev
# - gestion/*
event:
- push
steps:
# Docker image build and push
- name: docker-build-and-push
image: plugins/docker
settings:
dockerfile: MCTG/Dockerfile
context: MCTG/
registry: hub.codefirst.iut.uca.fr
repo: hub.codefirst.iut.uca.fr/alexandre.agostinho/console-mctg
username:
from_secret: SECRET_REGISTRY_USERNAME
password:
from_secret: SECRET_REGISTRY_PASSWORD
# Docker container deployement
# - name: deploy-container
# image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest
# environment:
# IMAGENAME: hub.codefirst.iut.uca.fr/alexandre.agostinho/sae-2.01:latest
# CONTAINERNAME: console-mctg
# COMMAND: create
# OVERWRITE: true
# depends_on: [ docker-build-and-push ]

4
.gitignore vendored

@ -4,6 +4,10 @@
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-spacific editor config
.editorconfig
.vscode
# User-specific files
*.rsuser
*.suo

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

@ -0,0 +1,429 @@
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "SAE 2.01 - MCTG"
PROJECT_NUMBER = 1.0.0
PROJECT_BRIEF = "Ma Cuisine Trop Géniale - Application MAUI"
PROJECT_LOGO = images/CodeFirst.png
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 = ../../MCTG/
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_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_FOOTER = footer.html
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

@ -0,0 +1,8 @@
<html><body>
<p>
<hr size="1"/><address style="text-align: right;"><small>Generated on $datetime with &nbsp;
<img src="CodeFirst.png" alt="Code#0" align="middle" border="0" height="40px"/>
by Doxygen version $doxygenversion</small></address>
</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

@ -0,0 +1,10 @@
{
"solution": {
"path": "SAE-2.01.sln",
"projects": [
"ConsoleApp\\ConsoleApp.csproj",
"Model\\Model.csproj",
"Tests\\Model_UnitTests\\Model_UnitTests.csproj"
]
}
}

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Configurations>Debug;Release;CI</Configurations>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DataPersistence\DataPersistence.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DataPersistence\DataPersistence.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,53 @@
using ConsoleApp.Menu.Core;
using Model;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu
{
internal class AddRecipeMenu : Entry
{
MasterManager masterMgr;
public AddRecipeMenu(MasterManager masterManager)
: base("Add recipe",
new Entry.EntryStep("Title: ", typeof(string)),
new Entry.EntryStep("new step: ", typeof(string)),
new Entry.EntryStep("new step: ", typeof(string)),
new Entry.EntryStep("new step: ", typeof(string)),
new Entry.EntryStep("new step: ", typeof(string)))
{
masterMgr = masterManager;
}
public override IMenu? Return()
{
string title = _selectList[0].Item.Input;
int order = 1;
List<PreparationStep> steps = new List<PreparationStep>();
for (int i = 1; i <= 4; i++)
{
if (string.IsNullOrEmpty(_selectList[i].Item.Input))
continue;
steps.Add(new PreparationStep(order++, _selectList[i].Item.Input));
}
Recipe recipe = new Recipe(
title: title,
id: null,
authorMail: masterMgr.CurrentConnectedUser?.Mail,
picture: "",
ingredients: new List<Ingredient>(),
preparationSteps: steps.ToArray()
);
masterMgr.AddRecipe(recipe);
return null;
}
}
}

@ -0,0 +1,44 @@
using ConsoleApp.Menu.Core;
using Model;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu
{
internal class AddUserMenu : Entry
{
MasterManager masterMgr;
public AddUserMenu(MasterManager masterManager)
: base("Add Manager",
new Entry.EntryStep("Mail: ", typeof(string)),
new Entry.EntryStep("Name: ", typeof(string)),
new Entry.EntryStep("Surname: ", typeof(string)),
new Entry.EntryStep("Password: ", typeof(string), true))
{
masterMgr = masterManager;
}
public override IMenu? Return()
{
string mail = _selectList[0].Item.Input;
string name = _selectList[1].Item.Input;
string surname = _selectList[2].Item.Input;
string passwd = _selectList[3].Item.Input;
User user = new User(
name: name,
surname: surname,
mail: mail,
password: passwd
);
masterMgr.Register(user);
return null;
}
}
}

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ConsoleApp.Menu.Core;
using Model.Managers;
namespace ConsoleApp.Menu
{
internal class ConnectionMenu : Entry
{
private MasterManager _masterMgr;
private bool _wrongInput = false;
public ConnectionMenu(MasterManager masterManager)
: base("Connection",
new Entry.EntryStep("Mail: ", typeof(string)),
new Entry.EntryStep("Password: ", typeof(string), true))
{
_masterMgr = masterManager;
}
public override void Display()
{
base.Display();
if (_wrongInput)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Wrong input...");
Console.ResetColor();
}
}
public override IMenu? Return()
{
string mail = _selectList[0].Item.Input;
string password = _selectList[1].Item.Input;
if (!_masterMgr.Login(mail, password))
{
_wrongInput = true;
return this;
}
else
return null;
}
}
}

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu.Core
{
internal abstract partial class Entry
{
/// <summary>
/// Define a step of the Entry menu, or in other word, an entry itself.
/// </summary>
public class EntryStep
{
#region Attributes & Properties
private readonly Type _entryType;
/// <summary>
/// The entry description. This text is generally placed before the input field.
/// </summary>
public string Description { get; private set; }
/// <summary>
/// Contain the input gave by the menu.
/// </summary>
public string Input { get; internal set; }
/// <summary>
/// Define whether the input need to be hidden. Useful for password.
/// </summary>
public bool Hidden { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Constructor of the entry step.
/// </summary>
/// <param name="description">The text generally placed before the input in the menu.</param>
/// <param name="type">The type of the returned input.</param>
public EntryStep(string description, Type type, bool hidden = false)
{
Description = description;
Input = "";
_entryType = type;
Hidden = hidden;
}
#endregion
#region Methods
/// <summary>
/// Get the inputed string converted on this entry type.
/// </summary>
/// <returns>The converted string on the entry type.</returns>
/// <exception cref="NotImplementedException">Throw when the entry type converter does not exist here.</exception>
public object GetEntry()
{
try
{
if (_entryType == typeof(string))
return Input;
if (_entryType == typeof(int))
return Int32.Parse(Input);
if (_entryType == typeof(DateTime))
return DateTime.Parse(Input);
}
catch (FormatException fe)
{
Console.Error.WriteLine(fe);
}
throw new NotImplementedException("Error: parse of this type is not implemented.");
}
#endregion
}
}
}

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ConsoleApp.Menu.Core;
namespace ConsoleApp.Menu.Core
{
/// <summary>
/// Define an Entry menu.
/// <br/>It allows you to navigate through the entries and completes them with a console input.
/// </summary>
internal abstract partial class Entry : Menu<Entry.EntryStep>
{
#region Attributes & Properties
private List<EntryStep> _steps;
#endregion
#region Constructors
/// <summary>
/// Constructor of the entry menu, based on the Menu constructor.
/// </summary>
/// <param name="title">The title of this menu.</param>
/// <param name="entrySteps">All the entries of this menu.</param>
protected Entry(string title, params EntryStep[] entrySteps)
: base(title)
{
_steps = entrySteps.ToList();
_allSelectors = ConvertEntryStepsInSelector();
_selectList = _allSelectors;
}
#endregion
#region Methods
private List<Selector<EntryStep>> ConvertEntryStepsInSelector()
{
List<Selector<EntryStep>> newSelectors = new List<Selector<EntryStep>>();
foreach (EntryStep step in _steps)
{
newSelectors.Add(new Selector<EntryStep>(step, step.Description));
}
return newSelectors;
}
#endregion
#region IMenu implementation
public override void WriteMenuMode(ConsoleKeyInfo cki)
{
if (!WriteMode && cki.Key == ConsoleKey.R)
{
EnableWriteMode();
if (CurrentSelected is null)
return;
InputStr.Append(CurrentSelected.Input);
CurrentSelected.Input = "";
return;
}
if (WriteMode)
{
if (cki.Key == ConsoleKey.Escape)
{
if (CurrentSelected is null)
throw new ArgumentNullException("CurrentSelected");
CurrentSelected.Input = InputStr.ToString();
DisableWriteMode();
InputStr.Clear();
return;
}
if (cki.Key == ConsoleKey.Backspace)
{
if (InputStr.Length > 0) InputStr.Remove(InputStr.Length - 1, 1);
return;
}
InputStr.Append(cki.KeyChar);
}
}
public override void Update()
{
if (_selectList.Count == 0)
{
CurrentSelected = default;
return;
}
CurrentSelected = _selectList[CurrentLine].Item;
}
public override void Display()
{
StringBuilder displayItem = new StringBuilder();
_screenDisplay.Clear();
Console.Clear();
_screenDisplay.AppendLine($"[ {Title} ]");
_screenDisplay.AppendLine("-------------------------------------------");
for (int i = 0; i < _selectList.Count; i++)
{
if (_selectList[i].Item.Hidden)
for (int _ = 0; _ < _selectList[i].Item.Input.Length; _++)
displayItem.Append('*');
else
displayItem.Append(_selectList[i].Item.Input);
if (CurrentLine == i)
{
if (WriteMode)
_screenDisplay.Append($"W ");
else
_screenDisplay.Append($"> ");
}
else
_screenDisplay.Append($" ");
_screenDisplay.Append($"{_selectList[i].Line} {displayItem}");
if (CurrentLine == i && WriteMode)
{
if (_selectList[i].Item.Hidden)
for (int _ = 0; _ < InputStr.Length; _++) _screenDisplay.Append('*');
else
_screenDisplay.Append(InputStr);
}
_screenDisplay.AppendLine();
displayItem.Clear();
}
if (_selectList.Count == 0)
_screenDisplay.AppendLine("Empty...");
_screenDisplay.AppendLine(
"\n\nHint:\n^:previous, v:next, <:back, -enter-:return, r:write, -escape-:exit search mode");
Console.WriteLine(_screenDisplay);
}
#endregion
}
}

@ -0,0 +1,72 @@
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu.Core
{
/// <summary>
/// Define a console menu with element selection.
/// </summary>
internal interface IMenu
{
/// <summary>
/// True when enable, False otherwise.
/// </summary>
bool WriteMode { get; set; }
/// <summary>
/// A string input. Used for search string or entry input.
/// </summary>
StringBuilder InputStr { get; set; }
/// <summary>
/// Refresh the console with the updated display. This function dose not call Update().
/// </summary>
void Display();
/// <summary>
/// Update the parameters of the menu. Generally called before Display() to refresh the informations.
/// </summary>
void Update();
/// <summary>
/// Select the next element in the selection list.
/// </summary>
void SelectNext();
/// <summary>
/// Select the previous element in the selection list.
/// </summary>
void SelectPrevious();
/// <summary>
/// Enable the write mode.
/// </summary>
void EnableWriteMode();
/// <summary>
/// Disable the write mode.
/// </summary>
void DisableWriteMode();
/// <summary>
/// Toogle the write mode.
/// </summary>
void ToggleWriteMode();
/// <summary>
/// Define the comportement of the write mode. For instence: in the standard menu, it is used for the research of a line.
/// </summary>
/// <param name="cki">The key to deal with.</param>
void WriteMenuMode(ConsoleKeyInfo cki);
/// <summary>
/// Execute some actions and then return.
/// </summary>
/// <returns>'null' when there is no menu after this selection. Otherwise the next menu.</returns>
IMenu? Return();
}
}

@ -0,0 +1,201 @@
using System.Data;
using Model;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu.Core
{
/// <summary>
/// Define a selection menu.
/// <br/>It allows you to navigate through the selections and search with a console input.
/// </summary>
/// <typeparam name="T">The type (or the implementation) of the selection.</typeparam>
internal abstract class Menu<T> : IMenu
where T : notnull
{
#region Attributes & Properties
protected StringBuilder _screenDisplay;
protected List<Selector<T>> _allSelectors = new List<Selector<T>>();
protected List<Selector<T>> _selectList = new List<Selector<T>>();
private int _currentLine;
/// <summary>
/// Title of the menu.
/// </summary>
public string Title { get; private set; }
/// <summary>
/// The current line of the selection list.
/// </summary>
public int CurrentLine
{
get
{
if (_currentLine >= _selectList.Count) _currentLine = _selectList.Count - 1;
return _currentLine;
}
protected set
{
_currentLine = value;
if (_currentLine <= 0) _currentLine = 0;
else if (_currentLine >= _selectList.Count) _currentLine = _selectList.Count - 1;
}
}
/// <summary>
/// The currently selected object.
/// </summary>
public T? CurrentSelected { get; protected set; }
#endregion
#region Constructors
/// <summary>
/// Base constructor of the Menu class.
/// <br/>This one is incomplete and need to be completed in the inherited class constructors.
/// <br/>Basically, the '_allSelection' and '_selectList' attribute initialization are missing.
/// </summary>
/// <param name="title">The title of the Menu.</param>
protected Menu(string title)
{
Title = title;
CurrentLine = 0;
WriteMode = false;
_screenDisplay = new StringBuilder();
InputStr = new StringBuilder();
}
/// <summary>
/// Constructor of the Menu class. This constructor allows you to directly pass the selections.
/// </summary>
/// <param name="title">The title of the menu.</param>
/// <param name="selections">The selections of the menu.</param>
protected Menu(string title, params Selector<T>[] selections) : this(title)
{
if (selections == null || selections.Length == 0)
{
Console.WriteLine("Empty menu...");
return;
}
_allSelectors = selections.ToList();
_selectList = _allSelectors;
CurrentSelected = _allSelectors[0].Item;
}
#endregion
#region IMenu implementation
public StringBuilder InputStr { get; set; }
public bool WriteMode { get; set; }
public virtual IMenu? Return()
{
if (CurrentSelected is null)
throw new ArgumentNullException("CurrentSelected");
return (IMenu)CurrentSelected;
}
protected virtual List<Selector<T>> SearchInSelection()
{
if (_allSelectors is null)
throw new ArgumentNullException("_allSelectors");
return _allSelectors.FindAll(x =>
x.Line.ToLower().Contains(InputStr.ToString().ToLower()));
}
public virtual void WriteMenuMode(ConsoleKeyInfo cki)
{
if (!WriteMode && cki.Key == ConsoleKey.R)
{
EnableWriteMode();
return;
}
if (WriteMode)
{
if (cki.Key == ConsoleKey.Escape)
{
DisableWriteMode();
InputStr.Clear();
return;
}
if (cki.Key == ConsoleKey.Backspace)
{
if (InputStr.Length > 0) InputStr.Remove(InputStr.Length - 1, 1);
return;
}
InputStr.Append(cki.KeyChar);
}
}
public virtual void Update()
{
_selectList = SearchInSelection();
if (_selectList.Count == 0)
{
CurrentSelected = default;
return;
}
CurrentSelected = _selectList[CurrentLine].Item;
}
public virtual void Display()
{
_screenDisplay.Clear();
Console.Clear();
_screenDisplay.AppendLine($"[ {Title} ]");
_screenDisplay.AppendLine("-------------------------------------------");
if (WriteMode)
{
_screenDisplay.Append("Search: ");
_screenDisplay.AppendLine(InputStr.ToString());
}
for (int i = 0; i < _selectList.Count; i++)
{
if (CurrentLine == i)
_screenDisplay.Append($"> ");
else
_screenDisplay.Append($" ");
_screenDisplay.AppendLine($"{_selectList[i].Line}");
}
if (_selectList.Count == 0)
_screenDisplay.AppendLine("Empty...");
_screenDisplay.AppendLine(
"\n\nHint:\n^:previous, v:next, <:back, -enter-:select, r:search, -escape-:exit search mode");
Console.WriteLine(_screenDisplay);
}
public void SelectNext() => ++CurrentLine;
public void SelectPrevious() => --CurrentLine;
public void EnableWriteMode() => WriteMode = true;
public void DisableWriteMode() => WriteMode = false;
public void ToggleWriteMode()
{
if (WriteMode)
DisableWriteMode();
else
EnableWriteMode();
}
#endregion
}
}

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu.Core
{
/// <summary>
/// Define a Plain text menu.
/// <br/>This menu is a bit special. It display some text, and then, the only action that can be performed is the back return. Usefull for testing.
/// </summary>
internal class PlainText : IMenu
{
#region Constructors
/// <summary>
/// Constructor of the Plain text menu.
/// </summary>
/// <param name="text">The text buffer to display.</param>
public PlainText(string text)
{
InputStr = new StringBuilder(text);
WriteMode = false;
}
#endregion
#region IMenu implementation
public virtual IMenu? Return() { return null; }
public virtual void Display()
{
Console.Clear();
Console.WriteLine(InputStr);
}
public bool WriteMode { get; set; }
public StringBuilder InputStr { get; set; }
public void DisableWriteMode()
{
// Plain text does not need to do anything for this.
}
public void EnableWriteMode()
{
// Plain text does not need to do anything for this.
}
public void SelectNext()
{
// Plain text does not need to do anything for this.
}
public void SelectPrevious()
{
// Plain text does not need to do anything for this.
}
public void ToggleWriteMode()
{
// Plain text does not need to do anything for this.
}
public void Update()
{
// Plain text does not need to do anything for this.
}
public void WriteMenuMode(ConsoleKeyInfo cki)
{
// Plain text does not need to do anything for this.
}
#endregion
}
}

@ -0,0 +1,77 @@
using Model;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu.Core
{
/// <summary>
/// The selector of a menu.
/// </summary>
/// <typeparam name="T">The type of the selector.</typeparam>
internal class Selector<T> : IEquatable<Selector<T>>
where T : notnull
{
#region Attributes & Properties
private string _line = "";
/// <summary>
/// The string that are displayed on the menu.
/// </summary>
public string Line
{
get => _line;
private set
{
if (string.IsNullOrEmpty(value))
_line = "no data";
else
_line = value;
}
}
/// <summary>
/// The item contained in the selector.
/// </summary>
public T Item { get; private set; }
#endregion
#region Constructors
/// <summary>
/// The constructor of the selector.
/// </summary>
/// <param name="item">The item to place inside.</param>
/// <param name="line">The string to display in the menu.</param>
public Selector(T item, string line = "")
{
Line = line;
Item = item;
}
#endregion
#region IEquatable implementation
public virtual bool Equals(Selector<T>? other)
{
if (other == null) return false;
if (other == this) return true;
return other.Line.Equals(Line);
}
public override bool Equals(object? obj)
{
var item = obj as Selector<T>;
if (item == null) return false;
return Equals(obj);
}
public override int GetHashCode()
{
return Line.GetHashCode();
}
#endregion
}
}

@ -0,0 +1,51 @@
using ConsoleApp.Menu.Core;
using Model.Managers;
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataPersistence;
namespace ConsoleApp.Menu
{
internal class ExportRecipeMenu : SearcherRecipe
{
public ExportRecipeMenu(MasterManager masterManager)
: base(masterManager)
{
}
public override IMenu? Return()
{
if (CurrentSelected is null)
throw new ArgumentNullException();
Recipe recipe = CurrentSelected;
string path = $"export_{string.Concat(CurrentSelected.Title.Where(c => !char.IsWhiteSpace(c)))}";
// -- trying to put the right extention by checking the type of the DataManager...
//if (object.ReferenceEquals(_masterMgr.DataMgr.GetType(), typeof(DataContractXML)))
// path += ".xml";
//else if (object.ReferenceEquals(_masterMgr.DataMgr.GetType(), typeof(DataContractJSON)))
// path += ".json";
path += ".xml";
try
{
_masterMgr.DataMgr.Export(recipe, path);
}
catch (ArgumentNullException e)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("error: " + e.Message);
Console.ResetColor();
return this;
}
return null;
}
}
}

@ -0,0 +1,40 @@
using ConsoleApp.Menu.Core;
using Model;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu
{
internal class ImportRecipeMenu : Entry
{
MasterManager masterMgr;
public ImportRecipeMenu(MasterManager masterManager)
: base("Import recipe",
new Entry.EntryStep("Path file: ", typeof(string)))
{
masterMgr = masterManager;
}
public override IMenu? Return()
{
string path = _selectList[0].Item.Input;
try
{
masterMgr.DataMgr.Import<Recipe>(path);
}
catch(ArgumentNullException e)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("error: " + e.Message);
Console.ResetColor();
return this;
}
return null;
}
}
}

@ -0,0 +1,27 @@
using ConsoleApp.Menu.Core;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu
{
internal class LogoutButton : PlainText
{
MasterManager _masterMgr;
public LogoutButton(MasterManager masterManager)
: base("Logout ? ('ENTER' yes, '<' no)")
{
_masterMgr = masterManager;
}
public override IMenu? Return()
{
_masterMgr.Logout();
return base.Return();
}
}
}

@ -0,0 +1,58 @@
using Model;
using DataPersistence;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ConsoleApp.Menu.Core;
using Model.Managers;
namespace ConsoleApp.Menu
{
/// <summary>
/// Main menu of the console. Contain the first interaction menus.
/// </summary>
internal class MainMenu : Menu<IMenu>
{
private MasterManager _masterMgr;
public MainMenu(MasterManager masterManager)
: base("Main menu")
{
_masterMgr = masterManager;
_allSelectors.Add(
new Selector<IMenu>(new SearcherRecipe(_masterMgr), "Recipe search"));
_allSelectors.Add(
new Selector<IMenu>(new ConnectionMenu(_masterMgr), "Connection"));
_allSelectors.Add(
new Selector<IMenu>(new ProfileMenu(_masterMgr), "User profile"));
_allSelectors.Add(
new Selector<IMenu>(new LogoutButton(_masterMgr), "Logout"));
_allSelectors.Add(
new Selector<IMenu>(new AddRecipeMenu(_masterMgr), "Add recipe"));
_allSelectors.Add(
new Selector<IMenu>(new AddUserMenu(_masterMgr), "Add user"));
_allSelectors.Add(
new Selector<IMenu>(new ImportRecipeMenu(_masterMgr), "Import recipe"));
_allSelectors.Add(
new Selector<IMenu>(new ExportRecipeMenu(_masterMgr), "Export recipe"));
}
protected override List<Selector<IMenu>> SearchInSelection()
{
List<Selector<IMenu>> selectors = base.SearchInSelection();
if (_masterMgr.CurrentConnectedUser == null)
return selectors.Except(selectors.Where(s => s.Line == "User profile"))
.Except(selectors.Where(s => s.Line == "Logout"))
.Except(selectors.Where(s => s.Line == "Add recipe")).ToList();
else
return selectors.Except(selectors.Where(s => s.Line == "Connection")).ToList();
}
}
}

@ -0,0 +1,27 @@
using ConsoleApp.Menu.Core;
using Model.Managers;
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu
{
internal class ProfileMenu : Menu<IMenu>
{
public ProfileMenu(MasterManager masterManager)
: base("Profile")
{
_allSelectors.Add(new Selector<IMenu>(
new ShowUserInfos(masterManager),
"My informations"));
_allSelectors.Add(new Selector<IMenu>(
new SearchUserRecipes(masterManager),
"My recipes"));
}
}
}

@ -0,0 +1,32 @@
using Model;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu
{
internal class SearchUserRecipes : SearcherRecipe
{
public SearchUserRecipes(MasterManager masterManager) : base(masterManager)
{
}
public override void Update()
{
_recipeCollectionOnSearch = _masterMgr.GetCurrentUserRecipes();
_allSelectors = ConvertRecipeCollectionInSelectors();
_selectList = SearchInSelection();
if (_selectList.Count == 0)
{
CurrentSelected = default;
return;
}
CurrentSelected = _selectList[CurrentLine].Item;
}
}
}

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using ConsoleApp.Menu.Core;
using Model;
using Model.Managers;
namespace ConsoleApp.Menu
{
/// <summary>
/// An utility to find a recipe.
/// </summary>
internal class SearcherRecipe : Menu<Recipe>
{
protected MasterManager _masterMgr;
protected RecipeCollection _recipeCollectionOnSearch = new RecipeCollection("search");
public SearcherRecipe(MasterManager masterManager) : base("Search recipe")
{
_masterMgr = masterManager;
}
#region Methods
protected List<Selector<Recipe>> ConvertRecipeCollectionInSelectors()
{
List<Selector<Recipe>> newSelectors = new List<Selector<Recipe>>();
foreach (Recipe recipe in _recipeCollectionOnSearch)
{
newSelectors.Add(new Selector<Recipe>(recipe, $"[{recipe.Id}]: {recipe.Title}"));
}
return newSelectors;
}
public override void Update()
{
_recipeCollectionOnSearch = _masterMgr.DataMgr.GetRecipes("all recipes");
_allSelectors = ConvertRecipeCollectionInSelectors();
base.Update();
}
public override IMenu? Return()
{
if (CurrentSelected == null)
return this;
return new PlainText(CurrentSelected.ToString());
}
#endregion
}
}

@ -0,0 +1,31 @@
using ConsoleApp.Menu.Core;
using Model;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu
{
internal class ShowUserInfos : PlainText
{
private MasterManager _masterMgr;
public ShowUserInfos(MasterManager masterManager)
: base("")
{
_masterMgr = masterManager;
}
public override void Display()
{
Console.WriteLine(
$"\nUser: {_masterMgr.CurrentConnectedUser}\n\n"
+ $"\tMail: {_masterMgr.CurrentConnectedUser?.Mail}\n"
+ $"\tName: {_masterMgr.CurrentConnectedUser?.Name}\n"
+ $"\tSurname: {_masterMgr.CurrentConnectedUser?.Surname}\n");
}
}
}

@ -0,0 +1,93 @@
using ConsoleApp.Menu;
using Model;
using DataPersistence;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ConsoleApp.Menu.Core;
using Model.Managers;
namespace ConsoleApp
{
/// <summary>
/// Manage the menus of the console application.
/// </summary>
internal class MenuManager
{
#region Attributes & Properties
/// <summary>
/// The manager that contains usefull data taken from the model.
/// </summary>
public MasterManager MasterMgr { get; private set; }
/// <summary>
/// Each menu called are push in this stack. Then, to return back, we pop this stack to retrive the previous menu.
/// </summary>
public Stack<IMenu> MenuCallStack { get; set; }
#endregion
#region Constructors
/// <summary>
/// Constructor of the MenuManager class. This constructor allows you to give the first menu of the call stack, wich is usefull for testing.
/// </summary>
/// <param name="masterManager">The data manager needed by the menus inside.</param>
/// <param name="firstMenu">The starting menu, the first that will be push on the call stack.</param>
public MenuManager(MasterManager masterManager, IMenu firstMenu)
{
MasterMgr = masterManager;
MenuCallStack = new Stack<IMenu>();
MenuCallStack.Push(firstMenu);
}
/// <summary>
/// Constructor of the MenuManager class.
/// </summary>
/// <param name="masterManager">The data manager needed by the menus inside.</param>
public MenuManager(MasterManager masterManager) : this(masterManager, new MainMenu(masterManager))
{ }
#endregion
#region Methods
/// <summary>
/// Main loop. Loop while the menu call stack is not empty.
/// </summary>
public void Loop()
{
ConsoleKeyInfo cki;
IMenu menuOnHead;
do
{
menuOnHead = MenuCallStack.Peek();
menuOnHead.Update();
menuOnHead.Display();
cki = Console.ReadKey(true);
switch (cki.Key)
{
case ConsoleKey.DownArrow:
menuOnHead.SelectNext();
break;
case ConsoleKey.UpArrow:
menuOnHead.SelectPrevious();
break;
case ConsoleKey.Enter:
IMenu? retMenu = menuOnHead.Return();
if (retMenu is null) MenuCallStack.Pop();
else if (ReferenceEquals(retMenu, menuOnHead)) break;
else MenuCallStack.Push(retMenu);
break;
case ConsoleKey.LeftArrow:
MenuCallStack.Pop();
break;
default:
menuOnHead.WriteMenuMode(cki);
break;
}
} while (MenuCallStack.Count > 0);
}
#endregion
}
}

@ -0,0 +1,31 @@
using ConsoleApp;
using Model;
using DataPersistence;
using Model.Managers;
Console.WriteLine("Hello, World!\n\n");
string path = ""; // - path to the save file
string strategy = "xml"; // - strategy is 'xml' or 'json' (/!\ this is case sensitive)
MasterManager masterMgr;
IDataManager dataManager = (strategy == "xml") ?
new DataContractXML(path)
: new DataContractJSON(path);
if (!File.Exists(Path.Combine(path, $"data.{strategy}")))
{
masterMgr = new MasterManager(new Stubs());
masterMgr.DataMgr.Serializer = dataManager;
}
else
{
masterMgr = new MasterManager(dataManager);
}
MenuManager menuMgr = new MenuManager(masterMgr);
menuMgr.Loop();
// Save data.
Console.Write("[ --SAVE-- ]:\t"); masterMgr.DataMgr.Save(); Console.WriteLine("Done.");

@ -0,0 +1,118 @@
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
namespace DataPersistence
{
/// <summary>
/// Define a serializer to manage JSON files.
/// </summary>
public class DataContractJSON : IDataManager
{
#region Attributes
private string _jsonFolderPath;
private DataContractJsonSerializerSettings _dataContractJsonSerializerSettings;
#endregion
#region Constructors
/// <summary>
/// Constructor of the DataContractJSON serializer.
/// </summary>
/// <param name="jsonFolderPath">Give the default path where to load and save the data (by default is the current execution dir).</param>
/// <param name="dataContractJsonSerializerSettings">Give another set of DataContractJson serializer setting to write file</param>
public DataContractJSON(string jsonFolderPath = "",
DataContractJsonSerializerSettings? dataContractJsonSerializerSettings = null)
{
_jsonFolderPath = jsonFolderPath;
if (dataContractJsonSerializerSettings is null)
_dataContractJsonSerializerSettings = new DataContractJsonSerializerSettings()
{
KnownTypes = new[]
{
typeof(Recipe),
typeof(Review),
typeof(User),
typeof(Ingredient),
typeof(Quantity)
}
};
else
_dataContractJsonSerializerSettings = dataContractJsonSerializerSettings;
}
#endregion
#region IDataManager implementation
public void Export<T>(T obj, string pathToExport)
where T : class
{
var jsonSerializer = new DataContractJsonSerializer(typeof(T), _dataContractJsonSerializerSettings);
using (FileStream stream = File.Create(Path.Combine(_jsonFolderPath, pathToExport)))
{
using (var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(
stream: stream,
encoding: System.Text.Encoding.UTF8,
ownsStream: false,
indent: true))
{
jsonSerializer.WriteObject(jsonWriter, obj);
}
}
}
public KeyValuePair<string, T> Import<T>(string pathToImport)
where T : class
{
T? obj;
var jsonSerializer = new DataContractJsonSerializer(typeof(T), _dataContractJsonSerializerSettings);
using (FileStream stream = File.OpenRead(Path.Combine(_jsonFolderPath, pathToImport)))
{
obj = jsonSerializer.ReadObject(stream) as T;
}
if (obj is null)
throw new ArgumentNullException("obj");
string typeName = typeof(T).Name;
return new KeyValuePair<string, T>(typeName, obj);
}
public Dictionary<string, List<object>> Load()
{
Dictionary<string, List<object>>? elements = new Dictionary<string, List<object>>();
var jsonSerializer = new DataContractJsonSerializer(typeof(Dictionary<string, List<object>>), _dataContractJsonSerializerSettings);
using (FileStream stream = File.OpenRead(Path.Combine(_jsonFolderPath, "data.json")))
{
elements = jsonSerializer.ReadObject(stream) as Dictionary<string, List<object>>;
}
if (elements is null)
throw new ArgumentNullException("elements");
return elements;
}
public void Save(Dictionary<string, List<object>> elements)
{
var jsonSerializer = new DataContractJsonSerializer(typeof(Dictionary<string, List<object>>), _dataContractJsonSerializerSettings);
using (FileStream stream = File.Create(Path.Combine(_jsonFolderPath, "data.json")))
{
using (var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(
stream: stream,
encoding: System.Text.Encoding.UTF8,
ownsStream: false,
indent: true))
{
jsonSerializer.WriteObject(jsonWriter, elements);
}
}
}
#endregion
}
}

@ -0,0 +1,123 @@
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
namespace DataPersistence
{
/// <summary>
/// Define a serializer to manage XML files.
/// </summary>
public class DataContractXML : IDataManager
{
#region Attributes
private string _xmlFolderPath;
private XmlWriterSettings _xmlWriterSettings;
private DataContractSerializerSettings _dataContractSerializerSettings;
#endregion
#region Constructors
/// <summary>
/// Constructor of a DataContractXML serializer.
/// </summary>
/// <param name="xmlFolderPath">Give the default path where to load and save the data (by default is the current execution dir).</param>
/// <param name="xmlWriterSettings">Give another set of XML setting to write file.</param>
/// <param name="dataContractSerializerSettings">Give another set of DataContract serializer setting to write file</param>
public DataContractXML(string xmlFolderPath = "",
XmlWriterSettings? xmlWriterSettings = null,
DataContractSerializerSettings? dataContractSerializerSettings = null)
{
_xmlFolderPath = xmlFolderPath;
if (xmlWriterSettings is null)
_xmlWriterSettings = new XmlWriterSettings() { Indent = true };
else
_xmlWriterSettings = xmlWriterSettings;
if (dataContractSerializerSettings is null)
_dataContractSerializerSettings = new DataContractSerializerSettings()
{
KnownTypes = new Type[]
{
typeof(Recipe),
typeof(Review),
typeof(User),
typeof(Ingredient),
typeof(Quantity),
typeof(PasswordSHA256)
},
PreserveObjectReferences = true
};
else
_dataContractSerializerSettings = dataContractSerializerSettings;
}
#endregion
#region IDataManager implementation
public void Export<T>(T obj, string pathToExport)
where T : class
{
bool restore = _dataContractSerializerSettings.PreserveObjectReferences;
_dataContractSerializerSettings.PreserveObjectReferences = false;
var serializer = new DataContractSerializer(typeof(T), _dataContractSerializerSettings);
using (TextWriter textWriter = File.CreateText(Path.Combine(_xmlFolderPath, pathToExport)))
{
using (XmlWriter xmlWriter = XmlWriter.Create(textWriter, _xmlWriterSettings))
{
serializer.WriteObject(xmlWriter, obj);
}
}
_dataContractSerializerSettings.PreserveObjectReferences = restore;
}
public KeyValuePair<string, T> Import<T>(string pathToImport)
where T : class
{
T? obj;
var serializer = new DataContractSerializer(typeof(T), _dataContractSerializerSettings);
using (Stream s = File.OpenRead(Path.Combine(_xmlFolderPath, pathToImport)))
{
obj = serializer.ReadObject(s) as T;
}
if (obj is null)
throw new ArgumentNullException("obj");
string typeName = typeof(T).Name;
return new KeyValuePair<string, T>(typeName, obj);
}
public Dictionary<string, List<object>> Load()
{
Dictionary<string, List<object>>? elements;
var serializer = new DataContractSerializer(typeof(Dictionary<string, List<object>>), _dataContractSerializerSettings);
using (Stream s = File.OpenRead(Path.Combine(_xmlFolderPath, "data.xml")))
{
elements = serializer.ReadObject(s) as Dictionary<string, List<object>>;
}
if (elements is null)
throw new ArgumentNullException("elements");
return elements;
}
public void Save(Dictionary<string, List<object>> elements)
{
var serializer = new DataContractSerializer(typeof(Dictionary<string, List<object>>), _dataContractSerializerSettings);
using (TextWriter textWriter = File.CreateText(Path.Combine(_xmlFolderPath, "data.xml")))
{
using (XmlWriter xmlWriter = XmlWriter.Create(textWriter, _xmlWriterSettings))
{
serializer.WriteObject(xmlWriter, elements);
}
}
}
#endregion
}
}

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,147 @@
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataPersistence
{
/// <summary>
/// The subs class is a group of prefabricated object that can only be loaded. It only use is for testing.
/// </summary>
public class Stubs : IDataManager
{
public Dictionary<string, List<object>> Load()
{
Dictionary<string, List<object>> data = new Dictionary<string, List<object>>
{
{
#region Data: Recipes
nameof(Recipe),
new List<object>(new[]
{
new Recipe(
title: "Cookies classiques",
id: 50,
authorMail: "admin@mctg.fr",
picture : "room_service_icon.png",
ingredients: new List<Ingredient>(new[]
{
new Ingredient("Patates", new Quantity(23, Unit.unit)),
new Ingredient("Farine", new Quantity(23, Unit.G))
}),
preparationSteps: new[]
{
new PreparationStep(1, "Faire cuire."),
new PreparationStep(2, "Manger.")
}),
new Recipe(
authorMail: "admin@mctg.fr",
title: "Cookies au chocolat", id: null,
preparationSteps: new[]
{
new PreparationStep(1, "Moulinez la pâte."),
new PreparationStep(2, "Faire cuire pendant une bonne heure."),
new PreparationStep(3, "Sortir du four et mettre dans un plat.")
}),
new Recipe(
title: "Gateau nature", id: null,
authorMail: "admin@mctg.fr",
preparationSteps: new[]
{
new PreparationStep(1, "Achetez les ingrédients."),
new PreparationStep(2, "Préparez le matériel. Ustensiles et tout."),
new PreparationStep(3, "Pleurez.")
}),
new Recipe(
title: "Gateau au pommes", id: null,
authorMail: "admin@mctg.fr",
preparationSteps: new[]
{
new PreparationStep(1, "Achetez les légumes."),
new PreparationStep(2, "Préparez le plat. Ustensiles et préchauffez le four."),
new PreparationStep(3, "Coupez les pommes en morceaux et disposez-les sur le plat."),
new PreparationStep(4, "Mettez enfin le plat au four, puis une fois cuit, dégustez !")
}),
new Recipe(
title: "Gateau au chocolat", id: null,
authorMail: "pedrosamigos@hotmail.com",
preparationSteps: new[]
{
new PreparationStep(1, "Ajouter les oeufs."),
new PreparationStep(2, "Ajouter la farine."),
new PreparationStep(3, "Ajouter 100g de chocolat fondu."),
new PreparationStep(4, "Mélanger le tout."),
new PreparationStep(5, "Faire cuire 45h au four traditionnel.")
}),
new Recipe(
title: "Dinde au jambon",
id: null,
authorMail: "pedrosamigos@hotmail.com",
preparationSteps: new[]
{
new PreparationStep(1, "Faire une cuisson bien sec de la dinde à la poêle"),
new PreparationStep(2, "Mettre la dinde au frigo."),
new PreparationStep(3, "Mettre le jambon dans le micro-onde."),
new PreparationStep(4, "Faire chauffer 3min."),
new PreparationStep(5, "Présentez sur un plat la dinde et le jambon : Miam !")
}),
new Recipe(
title: "Poulet au curry", id: null,
authorMail: "pedrosamigos@hotmail.com",
preparationSteps: new[]
{
new PreparationStep(1, "Trouvez des épices de curry."),
new PreparationStep(2, "Trouvez maintenant du poulet."),
new PreparationStep(3, "Coupez la tête du poulet et posez-la dans un plat."),
new PreparationStep(4, "Parsemez d'épices curry la tête de la poule."),
new PreparationStep(5, "Mettre le tout au four traditionnel 30min."),
new PreparationStep(6, "Dégustez en famille !")
})
})
#endregion
},
{
#region Data: User
nameof(User),
new List<object>(new[]
{
new User(
name: "Admin",
surname: "Admin",
mail: "admin@mctg.fr",
password: "admin"),
new User(
name: "Pedros",
surname: "Amigos",
mail: "pedrosamigos@hotmail.com",
password: "pamigos")
})
#endregion
}
};
return data;
}
#region Not supported methods
public void Save(Dictionary<string, List<object>> elements)
{
throw new NotSupportedException();
}
public void Export<T>(T obj, string pathToExport) where T : class
{
throw new NotSupportedException();
}
public KeyValuePair<string, T> Import<T>(string pathToImport) where T : class
{
throw new NotSupportedException();
}
#endregion
}
}

@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /MCTG
ENV DOTNET_EnableDiagnostics=0
COPY . ./
RUN dotnet restore CI-CD.slnf
RUN dotnet publish CI-CD.slnf -c Release -o out --no-restore
# Build runtime image
FROM mcr.microsoft.com/dotnet/runtime:7.0
WORKDIR /MCTG
COPY --from=build-env /MCTG/out .
ENTRYPOINT ["dotnet", "ConsoleApp.dll"]

@ -0,0 +1,105 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
/// <summary>
/// Define the manager of the data. This is where all the data are put, and where we call the loading and the saving of them.
/// </summary>
public class DataManager
{
#region Attributes & Properties
/// <summary>
/// The data manager injected that know how to serialize the data.
/// <br/><remarks><i>The setter is actually public for testing purpose. It will be private after.</i></remarks>
/// <br/>See: <see cref="IDataManager"/>
/// </summary>
public IDataManager Serializer { get; set; }
/// <summary>
/// The collection of all data. Each line of this dictionary has the type of the data as it key and the data for values.
/// </summary>
public Dictionary<string, List<object>> Data { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Constructor of the DataManager class. Take a IDataManager that will provide methods for the serialisation of the data.
/// </summary>
/// <param name="dataMgr">The data manager that know how to serialize a file.</param>
public DataManager(IDataManager dataMgr)
{
Serializer = dataMgr;
Data = Serializer.Load();
}
#endregion
#region Methods
/// <summary>
/// Reload the data. Useful to update new data written in the save file.
/// <br/>See: <see cref="IDataManager.Load"/>
/// </summary>
public void Reload()
=> Data = Serializer.Load();
/// <summary>
/// Save the data. Call the Save method of the serializer.
/// <br/>See: <see cref="IDataManager.Save(Dictionary{string, List{object}})"/>
/// </summary>
public void Save()
=> Serializer.Save(Data);
/// <summary>
/// Import data from a file.
/// <br/>See: <see cref="IDataManager.Import{T}(string)"/>
/// </summary>
/// <typeparam name="T">The type of data to import.</typeparam>
/// <param name="pathOfTheFile">The path containing the name of the file created.</param>
public void Import<T>(string pathOfTheFile)
where T : class
{
KeyValuePair<string, T> import = Serializer.Import<T>(pathOfTheFile);
Data[import.Key].Add(import.Value);
}
/// <summary>
/// Export the data from the collection of data.
/// <br/>See: <see cref="IDataManager.Export{T}(T, string)"/>
/// </summary>
/// <typeparam name="T">The type of data to export</typeparam>
/// <param name="obj">The object to export</param>
/// <param name="pathToExport">The path containing the name of the file created.</param>
public void Export<T>(T obj, string pathToExport)
where T : class
=> Serializer.Export<T>(obj, pathToExport);
/// <summary>
/// Get all the recipe from the data.
/// </summary>
/// <param name="rcTitle">The title to give for the Recipe Collection</param>
/// <returns>A RecipeCollection that contain all the recipe in the data.</returns>
public RecipeCollection GetRecipes(string rcTitle = "default")
=> new RecipeCollection(rcTitle, Data[nameof(Recipe)].Cast<Recipe>().ToArray());
/// <summary>
/// Get all the Users from the data.
/// </summary>
/// <returns>A list of all Users.</returns>
public List<User> GetUsers()
=> new List<User>(Data[nameof(User)].Cast<User>());
/// <summary>
/// Get a list of an item in the data.
/// </summary>
/// <typeparam name="T">The type of the item</typeparam>
/// <returns>The list of all the item found in the data.</returns>
public ICollection<T> GetFromData<T>() where T : class
=> new List<T>(Data[typeof(T).Name].Cast<T>());
#endregion
}
}

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
/// <summary>
/// Interface that define the methods of a data serializer.
/// </summary>
public interface IDataManager
{
/// <summary>
/// Save all the data in a file.
/// </summary>
/// <param name="elements">The data to save.</param>
void Save(Dictionary<string, List<object>> elements);
/// <summary>
/// Load all the data from a file.
/// </summary>
/// <returns>The data loaded.</returns>
Dictionary<string, List<object>> Load();
/// <summary>
/// Import an element to the collection of data.
/// </summary>
/// <typeparam name="T">The type of the element to impoert</typeparam>
/// <param name="pathToImport">The path containing the name of the file.</param>
/// <returns>A pair where the key is the entry in the data and the value is the value to add on this entry.</returns>
public KeyValuePair<string, T> Import<T>(string pathToImport)
where T : class;
/// <summary>
/// Export an element from the collection of data.
/// </summary>
/// <typeparam name="T">The type of the exported object.</typeparam>
/// <param name="obj">The object to export.</param>
/// <param name="pathToExport">The path containing the name of the file created.</param>
public void Export<T>(T obj, string pathToExport)
where T : class;
}
}

@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Model.Managers
{
/// <summary>
/// The Main manager of the model.
/// </summary>
public class MasterManager
{
#region Attributes & Properties
/// <summary>
/// The currently connected user. 'null' if no user is connected.
/// </summary>
public User? CurrentConnectedUser { get; private set; }
/// <summary>
/// The collection of all recipes loaded.
/// </summary>
public RecipeCollection Recipes { get; private set; }
/// <summary>
/// The collection of all users loaded.
/// </summary>
public List<User> Users { get; private set; }
/// <summary>
/// The data manager for load, save, export and import data.
/// </summary>
public DataManager DataMgr { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Constructor of the MasterManager.
/// </summary>
/// <param name="dataManager">The serializer for the data.</param>
public MasterManager(IDataManager dataManager)
{
DataMgr = new DataManager(dataManager);
CurrentConnectedUser = null;
Recipes = DataMgr.GetRecipes("all recipes");
Users = DataMgr.GetUsers();
}
#endregion
#region Methods
/// <summary>
/// Log in an user. Test if the log in information are correct then connect the user.
/// </summary>
/// <param name="mail">The user's mail</param>
/// <param name="password">The user's password.</param>
/// <returns>True if the user was correctly connected, false if there is something wrong.</returns>
/// <exception cref="ArgumentNullException"></exception>
public bool Login(string mail, string password)
{
if (Users is null || Users.Count == 0)
throw new ArgumentNullException("There is no users registred.");
if (mail == "admin")
{
CurrentConnectedUser = Users.FirstOrDefault(u => u.Mail == "admin@mctg.fr");
return true;
}
User? user = Users.Find(u => u.Mail == mail);
if (user is null || !user.psswMgr.VerifyPassword(user.Password, password))
return false;
CurrentConnectedUser = user;
return true;
}
/// <summary>
/// Log out an user.
/// </summary>
/// <returns>True if the user is correctly diconnected, false otherwise.</returns>
public bool Logout()
{
if (CurrentConnectedUser is null)
return false;
CurrentConnectedUser = null;
return true;
}
/// <summary>
/// Register an user.
/// </summary>
/// <param name="newuser">The new user to add in the database.</param>
/// <returns>False if there is a problem with the registerement, true otherwise.</returns>
public bool Register(User newuser)
{
try
{
User user = newuser;
DataMgr.Data[nameof(User)].Add(user);
Users = DataMgr.GetUsers();
}
catch (ArgumentException e)
{
Debug.WriteLine(e.Message);
return false;
}
return true;
}
/// <summary>
/// Add a recipe to the database.
/// </summary>
/// <param name="recipe">The recipe to add.</param>
public void AddRecipe(Recipe recipe)
{
DataMgr.Data[nameof(Recipe)].Add(recipe);
Recipes = DataMgr.GetRecipes();
}
/// <summary>
/// Get the current connected user's personal recipes.
/// </summary>
/// <returns>The current connected user's personal recipes.</returns>
public RecipeCollection GetCurrentUserRecipes()
=> new RecipeCollection("User recipes",
DataMgr.GetRecipes().FindAll(r => r.AuthorMail == CurrentConnectedUser?.Mail).ToArray());
}
#endregion
}

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Configurations>Debug;Release;CI</Configurations>
</PropertyGroup>
</Project>

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
[DataContract(Name = "ingredient")]
public class Ingredient
{
#region Attributes
[DataMember(Name = "id")]
private string _name = "";
[DataMember(Name = "quantity")]
private Quantity _quantity = new Quantity(1, Unit.unit);
#endregion
#region Properties
/// <summary>
/// Property of the Ingredient's attribute _name. It raises an ArgumentNullException to prevent a nullable field.
/// </summary>
public string Name
{
get => _name;
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException("Impossible de ne pas avoir de nom pour l'ingrédient");
}
_name = value;
}
}
/// <summary>
/// Property of the Ingredient's attribute Quantity.
/// </summary>
public Quantity QuantityI
{
get => _quantity;
set => _quantity = value;
}
public bool Equals(Ingredient? other)
{
if (other == null) { return false; }
if (this == other) { return true; }
return Name.Equals(other.Name);
}
#endregion
#region Constructor
public Ingredient(string name, Quantity quantity)
{
Name = name;
QuantityI = quantity;
}
#endregion
public override string ToString()
{
return $"{Name} ({QuantityI})";
}
}
}

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
/// <summary>
/// Quantity is a class which is associate to Ingedients. It represents the _quantity of every ingredient with an enum
/// to indicate the unit of the _quantity.
/// </summary>
[DataContract(Name = "quantity")]
public class Quantity
{
#region Attributes
/// <summary>
/// get the quatity of ingredient
/// </summary>
[DataMember(Name = "digit")]
private int number;
/// <summary>
/// have the Unit enum of the _quantity of ingredient
/// </summary>
[DataMember(Name = "unit")]
private Unit unit;
#endregion
#region Properties
/// <summary>
/// Represents the _quantity in digits. The null value raise an argumentException.
/// </summary>
public int Number
{
get { return number; }
set
{
if (value < 0)
{
throw new ArgumentException("Si la quantité est inférieur à 0, enlever l'ingrédient!");
}
number = value;
}
}
public Unit UnitQ
{
get => unit;
set => unit = value;
}
#endregion
#region Constructor
/// <summary>
/// Constructor of Quantity
/// </summary>
/// <param _name="number"></param>
/// <param _name="unit"></param>
public Quantity(int number, Unit unit)
{
Number = number;
UnitQ = unit;
}
#endregion
public override string ToString()
{
return $"{Number}{UnitQ}";
}
}
}

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
/// <summary>
/// Unit is an Enum that represents the units of quantities
/// <list type="Unit, kG, mG, G, L, cL, mL"/>
/// </summary>
public enum Unit
{ unit, kG, mG, G, L, cL, mL };
}

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
/// <summary>
/// Define a step of the preparation of a recipe.
/// </summary>
[DataContract(Name = "preparation-step")]
public class PreparationStep : IEquatable<PreparationStep>
{
#region Attributes
private string _description = "";
#endregion
#region Properties
/// <summary>
/// The order of this step in the preparation of the meal.
/// </summary>
[DataMember(Name = "order")]
public int Order { get; init; }
/// <summary>
/// The description of the task the user need to do for this step of the preparation. <br/>
/// Set to "No description." when the value passed is null, empty or contain white spaces.
/// </summary>
[DataMember(Name = "description")]
public string Description
{
get => _description;
private set
{
if (string.IsNullOrWhiteSpace(value))
_description = "No description.";
else
_description = value;
}
}
#endregion
#region Constructors
/// <summary>
/// Construct a new step of preparation.
/// </summary>
/// <param _name="order">The number of the order in preparation</param>
/// <param _name="description">The description of the task</param>
public PreparationStep(int order, string description = "")
{
Order = order;
Description = description;
}
#endregion
#region Methods
public virtual bool Equals(PreparationStep? other)
{
if (other == null) return false;
if (other == this) return true;
return Order.Equals(other.Order) && Description.Equals(other.Description);
}
public override bool Equals(object? obj)
{
var item = obj as PreparationStep;
if (item == null) return false;
return Equals(obj);
}
public override int GetHashCode()
{
return Order.GetHashCode() + Description.GetHashCode();
}
public override string ToString()
{
return $"{Order}- {Description}";
}
#endregion
}
}

@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
namespace Model
{
/// <summary>
/// Define a Recipe for the preparation of a meal.
/// </summary>
[DataContract(Name = "recipe")]
public class Recipe : IEquatable<Recipe>
{
#region Attributes
[DataMember(Name = "title")]
private string _title = "";
[DataMember(Name = "image")]
private string _image = "";
#endregion
#region Properties
/// <summary>
/// The ID of the recipe - allows you to compare and/or get this item in an easier way.
/// </summary>
[DataMember(Name = "id")]
public int Id { get; init; }
/// <summary>
/// List of reviews of this recipe.
/// </summary>
[DataMember(Name = "reviews")]
public List<Review> Reviews { get; private set; }
/// <summary>
/// AuthorMail's mail of the recipe.
/// </summary>
[DataMember(Name = "authorMail")]
public string? AuthorMail { get; private set; }
/// <summary>
/// The Title of the recipe. <br/>
/// Set to "No title." when the value passed is null, empty or contain white spaces.
/// </summary>
public string Title
{
get => _title;
set
{
if (string.IsNullOrWhiteSpace(value))
_title = "No title.";
else
_title = value;
}
}
/// <summary>
/// The image of the recipe. <br/>
/// Set to "room_service_icon.png" when the value passed is null, empty or contain white space.
/// </summary>
public string? Image
{
get => _image;
set
{
if (!string.IsNullOrWhiteSpace(value))
_image = "room_service_icon.png";
_image = value;
}
}
/// The list of ingredients.
/// </summary>
[DataMember(Name = "ingredient")]
public List<Ingredient> Ingredients { get; set; }
/// <summary>
/// The steps of the preparation. See: <see cref="PreparationStep"/>.
/// </summary>
[DataMember(Name = "preparation-steps")]
public List<PreparationStep> PreparationSteps { get; set; }
#endregion
#region Constructors
/// <summary>
/// Construct a new recipe.
/// </summary>
/// <param _name="title">The title of the recipe</param>
/// <param _name="id">The id of the recipe. If not given, get a new id.</param>
/// <param _name="authorMail">The name of the user that create this recipe.</param>
/// <param _name="picture"> The image that represent the recipe</param>
/// <param _name="reviews">Thr list of reviews.</param>
/// <param _name="ingredients">Thr list of ingredients.</param>
/// <param _name="preparationSteps">The steps of the preparation of the meal</param>
public Recipe(string title, int? id, string? authorMail, string? picture,
List<Review> reviews, List<Ingredient> ingredients,
params PreparationStep[] preparationSteps)
{
Title = title;
Image = picture;
PreparationSteps = new List<PreparationStep>(preparationSteps);
Ingredients = ingredients;
Reviews = reviews;
AuthorMail = authorMail;
if (id == null)
{
var randomGenerator = RandomNumberGenerator.Create();
byte[] data = new byte[16];
randomGenerator.GetBytes(data);
Id = Math.Abs(BitConverter.ToInt16(data));
}
else Id = (int)id;
}
/// <summary>
/// <inheritdoc cref="Recipe.Recipe(string, int?, List{Review}, PreparationStep[])"/>
/// </summary>
/// <param _name="title">The title of the recipe.</param>
/// <param _name="id">The id of the recipe. If not given, get a new id.</param>
/// <param _name="authorMail">Mail of the user that create the recipe</param>
/// <param _name="preparationSteps">The steps of the preparation of the meal.</param>
public Recipe(string title, int? id, string? authorMail, params PreparationStep[] preparationSteps)
: this(title, id, authorMail, null, new List<Review>(), new List<Ingredient>(), preparationSteps)
{
}
/// <summary>
/// <inheritdoc cref="Recipe.Recipe(string, int?, List{Review}, PreparationStep[])"/>
/// </summary>
/// <param _name="title">The title of the recipe.</param>
/// <param _name="id">The id of the recipe. If not given, get a new id.</param>
/// <param _name="authorMail">Mail of the user that create the recipe</param>
/// <param _name="picture">Mail of the user that create the recipe</param>
/// <param _name="ingredients">List of ingredients that compose the recipe. </param>
/// <param _name="preparationSteps">The steps of the preparation of the meal.</param>
public Recipe(string title, int? id, string? authorMail, string? picture, List<Ingredient> ingredients, params PreparationStep[] preparationSteps)
: this(title, id, authorMail, picture, new List<Review>(), ingredients, preparationSteps)
{
}
///// <summary>
///// <inheritdoc cref="Recipe.Recipe(string, int?, List{Review}, PreparationStep[])"/>
///// </summary>
///// <param _name="title">The title of the recipe.</param>
///// <param _name="id">The id of the recipe. If not given, get a new id.</param>
///// <param _name="picture">Image that reppresent the recipe.</param>
///// <param _name="preparationSteps">The steps of the preparation of the meal.</param>
//public Recipe(string title, int? id, string picture, params PreparationStep[] preparationSteps)
// : this(title, id, null, picture, new List<Review>(), new List<Ingredient>(), preparationSteps)
//{
//}
#endregion
#region Methods
/// <summary>
/// Add a review for the recipe.
/// </summary>
/// <param _name="review">The new review to add.</param>
public void AddReview(Review review)
=> Reviews.Add(review);
/// <summary>
/// Get a string representing all the reviews and their informations.
/// </summary>
/// <returns></returns>
public string GetReviews()
{
StringBuilder sb = new StringBuilder("Reviews:\n------------------------------\n");
foreach (Review review in Reviews)
{
sb.AppendLine(review.ToString());
}
return sb.ToString();
}
/// <summary>
/// Concatenate the list of ingredients in a single string
/// </summary>
/// <returns>The list of ingredients in string format</returns>
private string ConcatIngredients()
{
StringBuilder sb = new StringBuilder();
foreach (Ingredient ingredient in Ingredients)
{
sb.Append("\t- " + ingredient.ToString() + "\n");
}
return sb.ToString();
}
public virtual bool Equals(Recipe? other)
{
if (other == null) return false;
if (other == this) return true;
return Title.Equals(other.Title) && PreparationSteps.Equals(other.PreparationSteps);
}
public override bool Equals(object? obj)
{
var item = obj as Recipe;
if (item == null) return false;
return Equals(obj);
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
public override string ToString()
{
StringBuilder sb = new StringBuilder($"[Recipe n°{Id}] - {Title}\n");
foreach (PreparationStep ps in PreparationSteps)
{
sb.AppendFormat("\t* {0}\n", ps.ToString());
}
sb.AppendLine();
sb.AppendLine(ConcatIngredients());
sb.AppendLine();
foreach (Review review in Reviews)
{
sb.AppendLine(review.ToString());
}
sb.AppendLine();
sb.AppendLine($"Posted by: {AuthorMail?.ToString()}");
return sb.ToString();
}
#endregion
}
}

@ -0,0 +1,106 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
namespace Model
{
/// <summary>
/// Define a collection of <see cref="Recipe"/>.
/// <br/>This class implement <see cref="IList"/> and <see cref="IEquatable{T}"/>.
/// </summary>
public class RecipeCollection : List<Recipe>, IEquatable<RecipeCollection>
{
#region Attributes
private string _description = "";
#endregion
#region Properties
/// <summary>
/// A short description of what this collection contain. <br/>
/// Set to "No description." when the value passed is null, empty or contain white spaces.
/// </summary>
public string Description
{
get => _description;
set
{
if (string.IsNullOrWhiteSpace(value))
_description = "No description.";
else
_description = value;
}
}
#endregion
#region Constructors
/// <summary>
/// Construct a new collection of _recipes.
/// </summary>
/// <param _name="description">A short description of what this list will contain</param>
/// <param _name="recipes">Recipes to add in this new collection</param>
public RecipeCollection(string description, params Recipe[] recipes)
: base(recipes)
{
Description = description;
}
#endregion
#region Methods
/// <summary>
/// Find a recipe in this list by giving the id.
/// </summary>
/// <param _name="id">The id of the list we are looking for</param>
/// <returns>The recipe of the id given</returns>
/// <exception cref="ArgumentException"/>
public Recipe? GetRecipeById(int id)
{
Recipe? recipe = this.Find(r => r.Id == id);
if (recipe == null) throw new ArgumentException("No _recipes match the given id.");
return recipe;
}
/// <summary>
/// Utility to find a recipe by his _name.
/// </summary>
/// <param _name="str">The string for the search</param>
/// <returns>A collection of Recipe where their Title contain the string.</returns>
public RecipeCollection ResearchByName(string str)
{
return new RecipeCollection(
description: $"Results of the research: {str}",
recipes: this.FindAll(x => x.Title.ToLower().Contains(str.ToLower())).ToArray());
}
public virtual bool Equals(RecipeCollection? other)
{
if (other == null) return false;
if (other == this) return true;
return this.Description.Equals(other.Description);
}
public override bool Equals(object? obj)
{
var item = obj as RecipeCollection;
if (item == null) return false;
return Equals(obj);
}
public override int GetHashCode()
{
return Description.GetHashCode();
}
public override string ToString()
{
StringBuilder sb = new StringBuilder($"[RecipeCollection] - {Description}:\n");
foreach (Recipe r in this)
{
sb.AppendFormat("\t - {0}\n", r.ToString());
}
return sb.ToString();
}
#endregion
}
}

@ -0,0 +1,133 @@
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Model
{
/// <summary>
/// Define a Review of a recipe.
/// </summary>
[DataContract(Name = "review")]
public class Review : IEquatable<Review>
{
#region Attributes & Properties
[DataMember(Name = "stars")]
private int _stars;
[DataMember(Name = "content")]
private string _content = "";
/// <summary>
/// The Id of the review.
/// </summary>
[DataMember(Name = "id")]
public int Id { get; init; }
/// <summary>
/// The mail of the author of the review.
/// </summary>
[DataMember(Name = "authorMail")]
public string AuthorMail { get; private set; }
/// <summary>
/// The number of stars the review give.
/// </summary>
public int Stars
{
get => _stars;
set
{
if (value < 0 || value > 5) throw new ArgumentException(nameof(Stars));
else _stars = value;
}
}
/// <summary>
/// The comment in the review.
/// </summary>
public string Content
{
get => _content;
set
{
if (string.IsNullOrEmpty(value)) _content = "No data...";
else _content = value;
}
}
#endregion
#region Constructors
/// <summary>
/// Constructor of a review.
/// </summary>
/// <param name="authorMail">The review's author's mail.</param>
/// <param name="id">The id of the review.</param>
/// <param name="stars">The mark to give for the recipe.</param>
/// <param name="content">A comment about the recipe.</param>
public Review(string authorMail, int? id, int stars, string content)
{
if (id == null)
{
var randomGenerator = RandomNumberGenerator.Create();
byte[] data = new byte[16];
randomGenerator.GetBytes(data);
Id = Math.Abs(BitConverter.ToInt16(data));
}
else Id = (int)id;
AuthorMail = authorMail;
Stars = stars;
Content = content;
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public Review(string authorMail, int stars, string content)
: this(authorMail, null, stars, content)
{
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public Review(int stars, string content)
: this("admin@mctg.fr", null, stars, content)
{
}
#endregion
#region Methods
public override string ToString()
{
return $"{AuthorMail}: [ {Stars} stars ]\n{Content}";
}
public bool Equals(Review? other)
{
if (other is null) return false;
return Id.Equals(other.Id);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(obj, null)) return false;
if (ReferenceEquals(obj, this)) return true;
if (GetType() != obj.GetType()) return false;
return Equals(obj);
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
#endregion
}
}

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
public interface IPasswordManager
{
public string HashPassword(string password);
public bool VerifyPassword(string hashedPassword,string password);
}
}

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
[DataContract(Name = "passmgr")]
public class PasswordSHA256 : IPasswordManager
{
public string HashPassword(string password)
{
byte[] data;
using (SHA256 sha256Hash = SHA256.Create())
data = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(password));
var sb = new StringBuilder();
foreach (byte b in data) sb.Append(b.ToString("x2"));
return sb.ToString();
}
public bool VerifyPassword(string hashedPassword, string passwordEntered)
{
string hashedInput = HashPassword(passwordEntered);
StringComparer strcmp = StringComparer.OrdinalIgnoreCase;
return strcmp.Compare(hashedPassword, hashedInput) == 0;
}
}
}

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
/// <summary>
/// This is the list of priorities that user can define in his profil. Priorities
/// are also present in recipes to match the first user's priority with the recipe's priority.
/// </summary>
public enum Priority
{ Economic, Fast, Easy, Light, Gourmet };
}

@ -0,0 +1,205 @@
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.ComponentModel;
namespace Model
{
/// <summary>
/// A user is an entity with a _name, a surname, mail, profilePict and a list of priority.
/// This user can login with an Id and a password
/// </summary>
[DataContract(Name = "user")]
public class User : IEquatable<User> , INotifyPropertyChanged
{
#region Private Attributes
[DataMember] private string name="";
[DataMember] private string surname="";
[DataMember] private string mail = "";
[DataMember] private string picture = "";
[DataMember] private string password = "";
[DataMember] private List<Priority> priorities;
public event PropertyChangedEventHandler? PropertyChanged;
#endregion
#region Properties
/// <summary>
/// Property to get Name of users and a setter
/// </summary>
/// <exception cref="ArgumentException" >Setter have Exception which is trigger when Name is null</exception>
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
/// <summary>
/// Property to get Surname of users and a setter
/// </summary>
/// <exception cref="ArgumentException" >Setter have Exception which is trigger when Surname is null</exception>
public string Surname
{
get { return surname; }
set
{
surname = value;
OnPropertyChanged();
}
}
/// <summary>
/// Property to get mail of users and a setter
/// </summary>
/// <exception cref="ArgumentException" >User's mail will serve to log the user. So there's no setter, just an init. User will enter one time his email at his
/// account creation.</exception>
public string Mail
{
get { return mail; }
private init
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Impossible d'avoir un champ Email vide!");
}
mail = value;
}
}
public string Password
{
get => password;
set => password = value;
}
/// <summary>
/// For now, we define the ProfilPict as a string which is "PhotoParDefaut"
/// when the value is null.
/// </summary>
public string ProfilPict
{
get => picture;
set => picture = value;
}
/// <summary>
/// This is the list of priorities specific tu the user. This list is initiate
/// by default. User could change it at will.
/// </summary>
public List<Priority> Priorities
{
get => priorities;
set=> priorities = value;
}
public override bool Equals(object? other)
{
if (other == null) return false;
if (other == this) return true;
return Equals(other);
}
public bool Equals(User? other)
{
if (other == null) return false;
return Name.Equals(other.Name) && Surname.Equals(other.Surname) && Mail.Equals(other.Mail);
}
public override int GetHashCode()
{
throw new NotImplementedException();
}
protected void OnPropertyChanged ([CallerMemberName] string? propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public override string ToString()
{
return $"{Name} {Surname}";
}
[DataMember(Name = "passmgr")]
public IPasswordManager psswMgr { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Construtors of user.
/// </summary>
/// <param name="name">The name of the user</param>
/// <param name="surname">The surname of the user</param>
/// <param name="mail">The user needs an email to login.</param>
/// <param name="password">The password of the new user.</param>
/// <param name="passwordManager">The password manager to manage the user password.</param>
public User(string name, string surname, string mail, string password, IPasswordManager passwordManager)
{
Name = name;
Surname = surname;
Mail = mail;
psswMgr = passwordManager;
Password = psswMgr.HashPassword(password);
priorities = new List<Priority> {
Priority.Gourmet,
Priority.Economic,
Priority.Fast,
Priority.Light,
Priority.Easy};
ProfilPict = picture;
}
/// <summary>
/// <inheritdoc cref="User.User"/>
/// </summary>
public User(string name, string surname, string mail, string password)
: this(name, surname,mail, password, new PasswordSHA256())
{
}
/// <summary>
/// <inheritdoc cref="User.User"/>
/// </summary>
public User()
: this("John", "Doe", "truc@gmail.com", "mdp")
{
}
/// <summary>
/// <inheritdoc cref="User.User"/>
/// </summary>
public User (User user)
{
Name = user.Name;
Surname = user.Surname;
Mail = user.Mail;
psswMgr = user.psswMgr;
Password = user.Password;
priorities = user.Priorities;
ProfilPict = user.ProfilPict;
}
#endregion
}
}

@ -0,0 +1,55 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33516.290
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "ConsoleApp\ConsoleApp.csproj", "{666C2211-8EBB-4FC8-9484-CB93BC854153}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{42FF86BD-92F9-4A32-A938-68515905378F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Views", "Views\Views.csproj", "{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model_UnitTests", "Tests\Model_UnitTests\Model_UnitTests.csproj", "{45AB746A-194B-4E43-81EB-83B06F35AA33}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{08B80CE8-A01D-4D86-8989-AF225D5DA48C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPersistence", "DataPersistence\DataPersistence.csproj", "{432F9D12-B1F7-4A79-8720-4971BB10B831}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{666C2211-8EBB-4FC8-9484-CB93BC854153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{666C2211-8EBB-4FC8-9484-CB93BC854153}.Debug|Any CPU.Build.0 = Debug|Any CPU
{666C2211-8EBB-4FC8-9484-CB93BC854153}.Release|Any CPU.ActiveCfg = Release|Any CPU
{666C2211-8EBB-4FC8-9484-CB93BC854153}.Release|Any CPU.Build.0 = Release|Any CPU
{42FF86BD-92F9-4A32-A938-68515905378F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42FF86BD-92F9-4A32-A938-68515905378F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42FF86BD-92F9-4A32-A938-68515905378F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42FF86BD-92F9-4A32-A938-68515905378F}.Release|Any CPU.Build.0 = Release|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Debug|Any CPU.Build.0 = Debug|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Release|Any CPU.ActiveCfg = Release|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Release|Any CPU.Build.0 = Release|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Release|Any CPU.Deploy.0 = Release|Any CPU
{45AB746A-194B-4E43-81EB-83B06F35AA33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{45AB746A-194B-4E43-81EB-83B06F35AA33}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45AB746A-194B-4E43-81EB-83B06F35AA33}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45AB746A-194B-4E43-81EB-83B06F35AA33}.Release|Any CPU.Build.0 = Release|Any CPU
{432F9D12-B1F7-4A79-8720-4971BB10B831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{432F9D12-B1F7-4A79-8720-4971BB10B831}.Debug|Any CPU.Build.0 = Debug|Any CPU
{432F9D12-B1F7-4A79-8720-4971BB10B831}.Release|Any CPU.ActiveCfg = Release|Any CPU
{432F9D12-B1F7-4A79-8720-4971BB10B831}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{45AB746A-194B-4E43-81EB-83B06F35AA33} = {08B80CE8-A01D-4D86-8989-AF225D5DA48C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {ADEA5603-1EF6-4D43-9493-7D6D9DE7FA3F}
EndGlobalSection
EndGlobal

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model;
namespace Model_UnitTests
{
public class Ingredient_UT
{
public void TestConstructIngredient()
{
Quantity quantity = new Quantity(200, Unit.mL);
Ingredient patate = new Ingredient("Patate", quantity);
Assert.Equal("Patate", patate.Name);
}
}
}

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<Configurations>Debug;Release;CI</Configurations>
</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="..\..\Model\Model.csproj" />
<ProjectReference Include="..\..\DataPersistence\DataPersistence.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataPersistence;
using Model;
using Model.Managers;
namespace Model_UnitTests
{
public class RecipeCollection_UT
{
[Fact]
public void TestResearchByName()
{
MasterManager masterManager = new MasterManager(new Stubs());
RecipeCollection recipes = masterManager.DataMgr.GetRecipes("test rc");
Recipe? search_result = recipes.ResearchByName("chocolat").FirstOrDefault();
Assert.NotNull(search_result);
Assert.Equal("Cookies au chocolat", search_result.Title);
}
}
}

@ -0,0 +1,17 @@
using Model;
namespace Model_UnitTests
{
public class Recipe_UT
{
[Fact]
public void TestVoidConstructor()
{
Recipe r = new Recipe(
title: "test recipe",
id: null,
authorMail: "test@test.fr");
Assert.NotNull(r.Title);
}
}
}

@ -0,0 +1,24 @@
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model_UnitTests
{
public class test_unit_user
{
[Fact]
public void TestConstructUser()
{
PasswordSHA256 passwordManager = new PasswordSHA256();
User user = new User("Bob", "Dylan", "bd@gmail.com", "bobby");
Assert.Equal("Bob", user.Name);
Assert.Equal("Dylan", user.Surname);
Assert.Equal("bd@gmail.com", user.Mail);
Assert.Equal(passwordManager.HashPassword("bobby"), user.Password);
Assert.NotNull(user.Priorities);
}
}
}

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Views.AddRecipe"
Title="AddRecipe"
xmlns:local="clr-namespace:Views">
<VerticalStackLayout>
<local:MiniHeader
TitleMini="Ajouter une recette"
NeedReturn="True"
HeightRequest="100"/>
<Grid ColumnDefinitions="auto, *"
RowDefinitions="auto,auto,auto,auto,auto,auto, auto, auto, auto"
Margin="50,20,20,20">
<Label Text="Titre de la recette :"/>
<Entry Placeholder="Saisie du texte de la recette correspondante"
Grid.Row="1"
Margin="10"/>
<Label Text="Type de la recette" Grid.Row="2"/>
<CheckBox x:Name="CheckEntree" Grid.Row="3" Margin="10,0,20,0" />
<Label Text="Entrée" Grid.Row="3" Margin="40,20"/>
<CheckBox x:Name="CheckPlat" Grid.Row="3" Margin="90,0" />
<Label Text="Plat" Grid.Row="3" Margin="120,20"/>
<CheckBox x:Name="CheckDessert" Grid.Row="3" Margin="155,0" />
<Label Text="Dessert" Grid.Row="3" Margin="185,20"/>
<Label Text="Type de priorité" Grid.Row="4"/>
<Grid BackgroundColor="#D1E8E2"
MinimumHeightRequest="100"
MaximumWidthRequest="300"
Padding="20"
Grid.Row="5">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Text="Recettes économiques" Grid.Row="0" Padding="5,0,0,0"/>
<BoxView Color="Black" HeightRequest="1" Margin="10,10,10,10" Grid.Row="1" />
<Label Text="Recettes rapides" Grid.Row="2"/>
<BoxView Color="Black" HeightRequest="1" Margin="10,10,10,10" Grid.Row="3" />
<Label Text="Recettes simples" Grid.Row="4"/>
<BoxView Color="Black" HeightRequest="1" Margin="10,10,10,10" Grid.Row="5" />
<Label Text="Recettes légères" Grid.Row="6"/>
<BoxView Color="Black" HeightRequest="1" Margin="10,10,10,10" Grid.Row="7" />
<Label Text="Recettes gourmandes" Grid.Row="8"/>
</Grid>
<Label Text="Saisir les étapes de la recette " Grid.Row="6" Margin="0,15"/>
<Entry Placeholder="Etape de la recette" Grid.Row="7" Margin="12,0"/>
<HorizontalStackLayout Grid.Row="8" Margin="20">
<Button WidthRequest="100" Text="Précédent" TextColor="Black" Margin="20,0,20,0"/>
<Button WidthRequest="100" Text="Ajouter" TextColor="Black" Margin="20,0"/>
</HorizontalStackLayout>
<Label Text="Saisir les ingrédients de la recette" Grid.Row="6" Grid.Column="1" Margin="50,15"/>
<HorizontalStackLayout Grid.Row="7" Grid.Column="1">
<Entry Placeholder="Nom de l'ingrédient" Margin="12,0,50,0" WidthRequest="500"/>
<Picker Title="Unité">
</Picker>
</HorizontalStackLayout>
<HorizontalStackLayout Grid.Row="8" Grid.Column="1" Margin="20">
<Button WidthRequest="100" Text="Précédent" TextColor="Black" Margin="20,0,20,0"/>
<Button WidthRequest="100" Text="Ajouter" TextColor="Black" Margin="20,0"/>
</HorizontalStackLayout>
</Grid>
</VerticalStackLayout>
</ContentPage>

@ -0,0 +1,18 @@
using CommunityToolkit.Maui.Behaviors;
using DataPersistence;
using Model;
using Model.Managers;
using System.Diagnostics;
namespace Views
{
public partial class AddRecipe : ContentPage
{
public MasterManager MasterMgr => (App.Current as App).MasterMgr;
public AddRecipe()
{
InitializeComponent();
}
}
}

@ -0,0 +1,14 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Views"
x:Class="Views.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

@ -0,0 +1,55 @@
#if WINDOWS
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Windows.Graphics;
#endif
using DataPersistence;
using Model;
using System.Collections.ObjectModel;
using Model.Managers;
namespace Views
{
public partial class App : Application
{
//Point d'entrée de l'application
public MasterManager MasterMgr { get; private set; } = new MasterManager(new Stubs());
//L'utilisateur courant de l'application
public User CurrentUser { get; set; }
//collection de recette de l'application
public RecipeCollection AllRecipes { get; set; }
//const int WindowWidth = 1200;
//const int WindowHeight = 800;
public App()
{
CurrentUser = MasterMgr.DataMgr.GetUsers().Last();
AllRecipes = MasterMgr.DataMgr.GetRecipes("All recipes");
InitializeComponent();
// Microsoft.Maui.Handlers.WindowHandler.Mapper.AppendToMapping(nameof(IWindow), (handler, view) =>
// {
//#if WINDOWS
// var mauiWindow = handler.VirtualView;
// var nativeWindow = handler.PlatformView;
// nativeWindow.Activate();
// IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(nativeWindow);
// WindowId windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(windowHandle);
// AppWindow appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);
// appWindow.Resize(new SizeInt32(WindowWidth, WindowHeight));
//#endif
// });
/* - Comment(ctrl-k + ctrl-c)/Uncomment(ctrl-k + ctrl-u) to change page - */
UserAppTheme = AppTheme.Light;
MainPage = new Home();
//MainPage = new MyPosts();
}
}
}

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="Views.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Views"
FlyoutBackgroundColor="DarkGray"
Shell.FlyoutBehavior="Disabled"
Shell.NavBarIsVisible="False">
<ShellContent
ContentTemplate="{DataTemplate local:Home}"
Route="Home"/>
</Shell>

@ -0,0 +1,10 @@
namespace Views
{
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}
}

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Views"
x:Class="Views.ContainerBase"
x:Name="root">
<Grid RowDefinitions="80, *"
ColumnDefinitions="300, *">
<local:CustomHeader
Grid.Column="1"
MinimumHeightRequest="80"
VerticalOptions="StartAndExpand"/>
<local:ContainerFlyout
Grid.RowSpan="2"
MinimumWidthRequest="300"
HorizontalOptions="StartAndExpand"
IsNotConnected="{Binding IsNotConnected, Source={x:Reference root}}"
NeedReturn="{Binding NeedReturn, Source={x:Reference root}}">
<local:ContainerFlyout.MyFlyoutContent>
<ContentView
Content="{Binding MyFlyoutContent, Source={x:Reference root}}"/>
</local:ContainerFlyout.MyFlyoutContent>
</local:ContainerFlyout>
<ContentView
VerticalOptions="StartAndExpand"
Grid.Row="1" Grid.Column="1"
Content="{Binding MyContent, Source={x:Reference root}}"/>
</Grid>
</ContentView>

@ -0,0 +1,49 @@
namespace Views;
public partial class ContainerBase : ContentView
{
public ContainerBase()
{
InitializeComponent();
}
// Bind MyContent
public static readonly BindableProperty MyContentProperty =
BindableProperty.Create("MyContent", typeof(View), typeof(ContainerBase), new Grid());
public View MyContent
{
get => (View)GetValue(MyContentProperty);
set => SetValue(MyContentProperty, value);
}
// Bind MyFlyoutContent
public static readonly BindableProperty MyFlyoutContentProperty =
BindableProperty.Create("MyFlyoutContent", typeof(View), typeof(ContainerBase), new Grid());
public View MyFlyoutContent
{
get => (View)GetValue(MyFlyoutContentProperty);
set => SetValue(MyFlyoutContentProperty, value);
}
// Bind IsNotConnected
public static readonly BindableProperty IsNotConnectedProperty =
BindableProperty.Create("IsNotConnected", typeof(bool), typeof(Button), true);
public bool IsNotConnected
{
get => (bool)GetValue(IsNotConnectedProperty);
set => SetValue(IsNotConnectedProperty, value);
}
// bind NeedReturn
public static readonly BindableProperty NeedReturnProperty =
BindableProperty.Create("NeedReturn", typeof(bool), typeof(Border), false);
public bool NeedReturn
{
get => (bool)GetValue(NeedReturnProperty);
set => SetValue(NeedReturnProperty, value);
}
}

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Views"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="Views.ContainerFlyout"
x:Name="fl"
BackgroundColor="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray600}}">
<Grid RowDefinitions="250, *, 100">
<VerticalStackLayout Grid.Row="0">
<Grid RowDefinitions="auto, *">
<!-- Return -->
<local:ReturnButton NeedReturn="{Binding NeedReturn, Source={x:Reference fl}}" Grid.Row="0"
HorizontalOptions="Start" Padding="10, 10, 0, 0"/>
<!-- Header -->
<ImageButton Source="person_default.png" HorizontalOptions="Center"
BackgroundColor="{StaticResource Secondary}"
WidthRequest="100" HeightRequest="100"
CornerRadius="50" Margin="0, 30, 0, 10"
BorderWidth="5" BorderColor="Black"
IsEnabled="{Binding IsNotConnected, Source={x:Reference fl}}"
Grid.RowSpan="2"/>
</Grid>
<Button Text="Connection" ImageSource="login_icon.png"
Style="{StaticResource button2}"
IsVisible="{Binding IsNotConnected, Source={x:Reference fl}}"
IsEnabled="{Binding IsNotConnected, Source={x:Reference fl}}"/>
<StackLayout BindingContext="{Binding user}">
<Label Text="{Binding Name}"
HorizontalOptions="Center" Margin="0,15"
FontSize="20" FontAttributes="Bold" HorizontalTextAlignment="Center"
TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}"
IsVisible="{Binding IsNotConnected, Converter={toolkit:InvertedBoolConverter} ,Source={x:Reference fl}}"/>
<Label Text="{Binding Surname}"
HorizontalOptions="Center"
FontSize="20" FontAttributes="Bold" HorizontalTextAlignment="Center"
TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}"
IsVisible="{Binding IsNotConnected, Converter={toolkit:InvertedBoolConverter} ,Source={x:Reference fl}}"/>
</StackLayout>
</VerticalStackLayout>
<!-- Content -->
<ContentView
VerticalOptions="Fill"
Grid.Row="1"
Content="{Binding MyFlyoutContent, Source={x:Reference fl}}"/>
<VerticalStackLayout Grid.Row="2">
<!-- Footer -->
<Button Text="Déconnection" ImageSource="logout_icon.png"
Style="{StaticResource button2}"
IsVisible="{Binding IsNotConnected, Converter={toolkit:InvertedBoolConverter}, Source={x:Reference fl}}"/>
</VerticalStackLayout>
</Grid>
</ContentView>

@ -0,0 +1,49 @@
using DataPersistence;
using Model;
using Model.Managers;
namespace Views;
public partial class ContainerFlyout : ContentView
{
public MasterManager MasterMgr => (App.Current as App).MasterMgr;
public User user => (App.Current as App).CurrentUser;
public ContainerFlyout()
{
InitializeComponent();
BindingContext = this;
}
// Bind MyFlyoutContent
public static readonly BindableProperty MyFlyoutContentProperty =
BindableProperty.Create("MyFlyoutContent", typeof(View), typeof(ContainerFlyout), new Grid());
public View MyFlyoutContent
{
get => (View)GetValue(MyFlyoutContentProperty);
set => SetValue(MyFlyoutContentProperty, value);
}
// Bind IsNotConnected
public static readonly BindableProperty IsNotConnectedProperty =
BindableProperty.Create("IsNotConnected", typeof(bool), typeof(Button), true);
public bool IsNotConnected
{
get => (bool)GetValue(IsNotConnectedProperty);
set => SetValue(IsNotConnectedProperty, value);
}
// bind NeedReturn
public static readonly BindableProperty NeedReturnProperty =
BindableProperty.Create("NeedReturn", typeof(bool), typeof(Border), false);
public bool NeedReturn
{
get => (bool)GetValue(NeedReturnProperty);
set => SetValue(NeedReturnProperty, value);
}
}

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Views.CustomHeader"
BackgroundColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray900}}">
<Grid ColumnDefinitions="*">
<Label Text="Ma cuisine trop géniale"
FontAttributes="Bold" FontSize="30" FontFamily="Forte"
TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}"
Margin="20, 10, 0, 0"
VerticalOptions="Start" HorizontalOptions="Start"/>
</Grid>
</ContentView>

@ -0,0 +1,14 @@
namespace Views;
public partial class CustomHeader : ContentView
{
public CustomHeader()
{
InitializeComponent();
}
private void ImageButton_Clicked(object sender, EventArgs e)
{
(App.Current.MainPage as Shell).FlyoutIsPresented ^= true;
}
}

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:local="clr-namespace:Views"
xmlns:model="clr-namespace:Model;assembly=Model"
x:Class="Views.Home">
<local:ContainerBase
IsNotConnected="True">
<!-- Flyout -->
<local:ContainerBase.MyFlyoutContent>
<VerticalStackLayout Grid.Row="1">
<!-- Research -->
<Button
Text="Recherche"
ImageSource="search_icon.png"
MaximumHeightRequest="20"
Style="{StaticResource button1}"/>
<SearchBar
Placeholder="Mots-clés (ex.: rapide, fromage)"
FontAttributes="Italic" TextColor="Black"
BackgroundColor="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray300}}"
Margin="15, 10, 15, 40"/>
<!-- Direct research -->
<Button
Text="Entrées"
ImageSource="flatware_icon.png"
Style="{StaticResource button1}"/>
<Button
Text="Plats"
ImageSource="room_service_icon.png"
Style="{StaticResource button1}"/>
<Button
Text="Desserts"
ImageSource="coffee_icon.png"
Style="{StaticResource button1}"/>
</VerticalStackLayout>
</local:ContainerBase.MyFlyoutContent>
<!-- Master -->
<local:ContainerBase.MyContent>
<ScrollView>
<StackLayout BindingContext="{Binding AllRecipes}" MinimumWidthRequest="400">
<!--Modification du prof apportée sur le stacklayout pour empecher l'affichage d'une seule case recipe-->
<Label
Text="{Binding Description}"
TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}"
FontSize="24"
Padding="15"/>
<FlexLayout
Margin="0, 15"
Wrap="Wrap"
JustifyContent="Start"
AlignItems="Center"
AlignContent="SpaceEvenly"
HorizontalOptions="Center"
BindableLayout.ItemsSource="{Binding}">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="model:Recipe">
<Border Style="{StaticResource recipeCase}">
<Grid RowDefinitions="*, 40">
<!--<local:RecipeCase
CaseImageSource="room_service_icon.png"
Title="{Binding Title}"/>-->
<Image
Grid.Row="0" VerticalOptions="Fill"
Source="{Binding Image}"/>
<Label
Text="{Binding Title}" FontSize="18"
Grid.Row="1" HorizontalOptions="Center"/>
</Grid>
</Border>
</DataTemplate>
</BindableLayout.ItemTemplate>
<!--<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>-->
</FlexLayout>
</StackLayout>
</ScrollView>
</local:ContainerBase.MyContent>
</local:ContainerBase>
</ContentPage>

@ -0,0 +1,22 @@
using DataPersistence;
using Model;
using Model.Managers;
namespace Views
{
public partial class Home : ContentPage
{
public MasterManager MasterMgr => (App.Current as App).MasterMgr;
public User user => (App.Current as App).CurrentUser;
public RecipeCollection AllRecipes => (App.Current as App).AllRecipes;
public Home()
{
//DataMgr = new DataManager(new Stubs());
//AllRecipes = new RecipeCollection("Toutes les recettes", DataMgr.Data[nameof(Recipe)].Cast<Recipe>().ToArray());
InitializeComponent();
BindingContext = this;
}
}
}

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Views.Login"
xmlns:local="clr-namespace:Views"
Title="Login"
x:Name="nlogin">
<VerticalStackLayout>
<local:MiniHeader
TitleMini="Connection"
NeedReturn="True"
HeightRequest="100"/>
<Grid ColumnDefinitions="350,*,350">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Grid.Column="1"
Grid.Row="0"
Text="Login :"
FontAttributes="Bold"
FontSize="Medium"
Padding="0,50,0,10"
MinimumWidthRequest="200"/>
<Entry Grid.Column="1"
Grid.Row="1"
BackgroundColor="#D1E8E2"
Margin="0,0,0,20"/>
<Label Grid.Column="1"
Grid.Row="2"
Text="Mot de passe :"
FontAttributes="Bold"
FontSize="Medium"
Padding="0,50,0,10"
MinimumWidthRequest="200"/>
<Entry Grid.Column="1"
Grid.Row="3"
BackgroundColor="#D1E8E2"
Margin="0,0,0,50"/>
<CheckBox Grid.Column="1"
Grid.Row="4"/>
<Label Grid.Column="1"
Grid.Row="4"
Text="première connection"
Margin="50,0,0,0"
FontAttributes="Italic"/>
<Button BackgroundColor="#D1E8E2"
Grid.Column="1"
Grid.Row="5"/>
</Grid>
</VerticalStackLayout>
</ContentPage>

@ -0,0 +1,9 @@
namespace Views;
public partial class Login : ContentPage
{
public Login()
{
InitializeComponent();
}
}

@ -0,0 +1,27 @@
using CommunityToolkit.Maui;
using Microsoft.Extensions.Logging;
namespace Views
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkit()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
}

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Views"
x:Class="Views.MiniHeader"
x:Name="miniheader">
<Grid BackgroundColor="#116466" ColumnDefinitions="*,*,*">
<local:ReturnButton NeedReturn="{Binding NeedReturn, Source={x:Reference miniheader}}"
HorizontalOptions="Start" Padding="10, 10, 0, 0"
WidthRequest="60"
/>
<Label Grid.Column="1"
Text="{Binding TitleMini, Source={Reference miniheader}}"
FontSize="Medium"
FontAttributes="Bold"
LineBreakMode="WordWrap"
HorizontalTextAlignment="Center"
VerticalOptions="Center"
HorizontalOptions="Center" />
<ImageButton Grid.Column="2"
Source="person_default.svg"
WidthRequest="80" HeightRequest="80"
CornerRadius="50"
BorderWidth="4" BorderColor="Black"/>
</Grid>
</ContentView>

@ -0,0 +1,27 @@
namespace Views;
public partial class MiniHeader : ContentView
{
public MiniHeader()
{
InitializeComponent();
}
public readonly BindableProperty TitleMiniProperty =
BindableProperty.Create("TitleMini", typeof(string), typeof(MiniHeader), "Erreur de titre");
public string TitleMini
{
get => (string)GetValue(TitleMiniProperty);
set => SetValue(TitleMiniProperty, value);
}
// bind NeedReturn
public static readonly BindableProperty NeedReturnProperty =
BindableProperty.Create("NeedReturn", typeof(bool), typeof(Border), false);
public bool NeedReturn
{
get => (bool)GetValue(NeedReturnProperty);
set => SetValue(NeedReturnProperty, value);
}
}

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Views"
x:Class="Views.MyPosts"
Title="MyPosts">
<local:ContainerBase
IsNotConnected="False"
NeedReturn="True">
<local:ContainerBase.MyFlyoutContent>
<Grid RowDefinitions="250, *, *" VerticalOptions="Fill">
<VerticalStackLayout Grid.Row="1">
<Button Text="Mes informations" ImageSource="person_default.png" Style="{StaticResource button1}" Grid.Row="1"/>
<Button Text="Modifier" ImageSource="settings_icon.png" Style="{StaticResource button1}" Grid.Row="2"/>
</VerticalStackLayout>
</Grid>
</local:ContainerBase.MyFlyoutContent>
<local:ContainerBase.MyContent>
<ScrollView>
<StackLayout>
<Label Text="Mon profil" TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}"
FontAttributes="Bold"
FontSize="24" Padding="15, 15, 20, 5"/>
<Label Text="Mes publications" TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}"
FontSize="20" Padding="15"/>
<FlexLayout
Margin="0, 15"
Wrap="Wrap"
JustifyContent="Start"
AlignItems="Center"
AlignContent="SpaceEvenly"
HorizontalOptions="Center">
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
</FlexLayout>
</StackLayout>
</ScrollView>
</local:ContainerBase.MyContent>
</local:ContainerBase>
</ContentPage>

@ -0,0 +1,9 @@
namespace Views;
public partial class MyPosts : ContentPage
{
public MyPosts()
{
InitializeComponent();
}
}

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Views"
xmlns:model="clr-namespace:Model;assembly=Model"
x:Class="Views.MyProfil"
Title="MyProfil">
<local:ContainerBase
IsNotConnected="False"
NeedReturn="True">
<local:ContainerBase.MyFlyoutContent>
<Grid RowDefinitions="250, *, *" VerticalOptions="Fill">
<VerticalStackLayout Grid.Row="1">
<Button Text="Mes Recettes" ImageSource="person_default.png" Style="{StaticResource button1}" Grid.Row="1"/>
<Button Text="Ajouter Recette" ImageSource="settings_icon.png" Style="{StaticResource button1}" Grid.Row="2"/>
</VerticalStackLayout>
</Grid>
</local:ContainerBase.MyFlyoutContent>
<local:ContainerBase.MyContent>
<ScrollView>
<StackLayout >
<!--user's informations-->
<Label Text="Mon profil" TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}"
FontAttributes="Bold"
FontSize="24" Padding="15, 15, 20, 5"/>
<HorizontalStackLayout>
<VerticalStackLayout >
<Label Text="Informations personnelles :" TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}"
FontSize="20" Padding="15"/>
<Label Text="Nom :"
Padding="50,0,0,0"
FontSize="18"/>
<Entry BackgroundColor="#D1E8E2"
Margin="50,10,0,20"
Text="{Binding CurrentUser.Name}"/>
<Label Text="Prénom :"
Padding="50,0,0,0"
FontSize="18"/>
<Entry BackgroundColor="#D1E8E2"
Margin="50,10,0,20"
Text="{Binding CurrentUser.Surname} "/>
<Label Text="Mail :"
Padding="50,0,0,0"
FontSize="18"/>
<Entry BackgroundColor="#D1E8E2"
Margin="50,10,0,20"
IsEnabled="False"
Text="{Binding CurrentUser.Mail}"/>
<!--liste drag and drop-->
</VerticalStackLayout>
<VerticalStackLayout Padding="100,0,0,0">
<Label Text="Priorités du compte : " TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}"
FontSize="20" Padding="15"/>
<Grid BackgroundColor="#D1E8E2"
MinimumHeightRequest="300"
Padding="20">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Text="Recettes économiques" Grid.Row="0" Padding="5,0,0,0"/>
<BoxView Color="Black" HeightRequest="1" Margin="10,10,10,10" Grid.Row="1" />
<Label Text="Recettes rapides" Grid.Row="2"/>
<BoxView Color="Black" HeightRequest="1" Margin="10,10,10,10" Grid.Row="3" />
<Label Text="Recettes simples" Grid.Row="4"/>
<BoxView Color="Black" HeightRequest="1" Margin="10,10,10,10" Grid.Row="5" />
<Label Text="Recettes légères" Grid.Row="6"/>
<BoxView Color="Black" HeightRequest="1" Margin="10,10,10,10" Grid.Row="7" />
<Label Text="Recettes gourmandes" Grid.Row="8"/>
</Grid>
</VerticalStackLayout>
</HorizontalStackLayout>
</StackLayout>
</ScrollView>
</local:ContainerBase.MyContent>
</local:ContainerBase>
</ContentPage>

@ -0,0 +1,20 @@
using CommunityToolkit.Maui.Behaviors;
using DataPersistence;
using Model;
using Model.Managers;
using System.Diagnostics;
namespace Views;
public partial class MyProfil : ContentPage
{
public MasterManager MasterMgr => (App.Current as App).MasterMgr;
public User user => (App.Current as App).CurrentUser;
public MyProfil()
{
InitializeComponent();
BindingContext = this;
}
}

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

@ -0,0 +1,11 @@
using Android.App;
using Android.Content.PM;
using Android.OS;
namespace Vue
{
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
}
}

@ -0,0 +1,17 @@
using Android.App;
using Android.Runtime;
using Views;
namespace Vue
{
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
}

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#512BD4</color>
<color name="colorPrimaryDark">#2B0B98</color>
<color name="colorAccent">#2B0B98</color>
</resources>

@ -0,0 +1,11 @@
using Foundation;
using Views;
namespace Vue
{
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
}

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>

@ -0,0 +1,16 @@
using ObjCRuntime;
using UIKit;
namespace Vue
{
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}

@ -0,0 +1,17 @@
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
using System;
namespace Vue
{
internal class Program : MauiApplication
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
static void Main(string[] args)
{
var app = new Program();
app.Run(args);
}
}
}

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="7" xmlns="http://tizen.org/ns/packages">
<profile name="common" />
<ui-application appid="maui-application-id-placeholder" exec="Vue.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
<label>maui-application-title-placeholder</label>
<icon>maui-appicon-placeholder</icon>
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
</ui-application>
<shortcut-list />
<privileges>
<privilege>http://tizen.org/privilege/internet</privilege>
</privileges>
<dependencies />
<provides-appdefined-privileges />
</manifest>

@ -0,0 +1,8 @@
<maui:MauiWinUIApplication
x:Class="Vue.WinUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:maui="using:Microsoft.Maui"
xmlns:local="using:Vue.WinUI">
</maui:MauiWinUIApplication>

@ -0,0 +1,25 @@
using Microsoft.UI.Xaml;
using Views;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Vue.WinUI
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : MauiWinUIApplication
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
}

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
<mp:PhoneIdentity PhoneProductId="4BDED58C-168B-44A1-9464-BB39916D6439" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>$placeholder$</DisplayName>
<PublisherDisplayName>User Name</PublisherDisplayName>
<Logo>$placeholder$.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="$placeholder$"
Description="$placeholder$"
Square150x150Logo="$placeholder$.png"
Square44x44Logo="$placeholder$.png"
BackgroundColor="transparent">
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
<uap:SplashScreen Image="$placeholder$.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="Vue.WinUI.app"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>

@ -0,0 +1,11 @@
using Foundation;
using Views;
namespace Vue
{
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
}

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>

@ -0,0 +1,16 @@
using ObjCRuntime;
using UIKit;
namespace Vue
{
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}

@ -0,0 +1,8 @@
{
"profiles": {
"Windows Machine": {
"commandName": "MsixPackage",
"nativeDebugging": false
}
}
}

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Views"
x:Class="Views.RecipeCase"
x:Name="rCase">
<Border Style="{StaticResource recipeCase}">
<Grid RowDefinitions="*, 40">
<Image
Grid.Row="0" VerticalOptions="Fill"
Source="{Binding CaseImageSource, Source={x:Reference rCase}}"/>
<Label Text="Recette 1" FontSize="18"
Grid.Row="1" HorizontalOptions="Center"/>
</Grid>
</Border>
</ContentView>

@ -0,0 +1,18 @@
namespace Views;
public partial class RecipeCase : ContentView
{
public RecipeCase()
{
InitializeComponent();
}
public static readonly BindableProperty CaseImageSourceProperty =
BindableProperty.Create("CaseImageSource", typeof(ImageSource), typeof(Image));
public ImageSource CaseImageSource
{
get => (ImageSource)GetValue(CaseImageSourceProperty);
set => SetValue(CaseImageSourceProperty, value);
}
}

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Views"
x:Class="Views.RecipeReviews"
Title="RecipeReviews">
<VerticalStackLayout>
<local:MiniHeader
TitleMini="Liste des avis de la recette n°5616548"
NeedReturn="True"
HeightRequest="100"/>
<VerticalStackLayout>
<ScrollView MaximumHeightRequest="500">
<VerticalStackLayout>
<local:UserReview>
<local:UserReview.Comment>
Recette facile, non prise de tête et agréable à réaliser avec ses grand-parents. Le résultat est délicieux, ma famille a adorée.
</local:UserReview.Comment>
</local:UserReview>
<local:UserReview>
<local:UserReview.Comment>
Très bonne recette à lexeption près quil est compliqué doffrir ces cookies à des enfants. En effet, ils ne sont pas très friant de fruit ou de légumes... Mais ils ont eu beaucoup de succès auprès de mes vieux !
</local:UserReview.Comment>
</local:UserReview>
<local:UserReview>
<local:UserReview.Comment>
Je suis plutôt vin blanc.
</local:UserReview.Comment>
</local:UserReview>
<local:UserReview>
<local:UserReview.Comment>
Très bonne recette à lexeption près quil est compliqué doffrir ces cookies à des enfants. En effet, ils ne sont pas très friant de fruit ou de légumes... Mais ils ont eu beaucoup de succès auprès de mes vieux !
Très bonne recette à lexeption près quil est compliqué doffrir ces cookies à des enfants. En effet, ils ne sont pas très friant de fruit ou de légumes... Mais ils ont eu beaucoup de succès auprès de mes vieux !
Très bonne recette à lexeption près quil est compliqué doffrir ces cookies à des enfants. En effet, ils ne sont pas très friant de fruit ou de légumes... Mais ils ont eu beaucoup de succès auprès de mes vieux !
Très bonne recette à lexeption près quil est compliqué doffrir ces cookies à des enfants. En effet, ils ne sont pas très friant de fruit ou de légumes... Mais ils ont eu beaucoup de succès auprès de mes vieux !
</local:UserReview.Comment>
</local:UserReview>
</VerticalStackLayout>
</ScrollView>
<Border>
<AbsoluteLayout>
<Grid AbsoluteLayout.LayoutBounds="0, 0.5" AbsoluteLayout.LayoutFlags="PositionProportional"
Margin="20"
ColumnDefinitions="50, 50, 50, 50"
RowDefinitions="50, 50">
<Image Source="person_default.png" Grid.Row="0"/>
<Label Text="Charles Jacob"
Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="0"
HorizontalOptions="StartAndExpand" VerticalOptions="Center"
HorizontalTextAlignment="Center"/>
<Image Style="{StaticResource Key=starCommentImage}" Grid.Row="1" Grid.Column="0"/>
<Image Style="{StaticResource Key=starCommentImage}" Grid.Row="1" Grid.Column="1"/>
<Image Style="{StaticResource Key=starCommentImage}" Grid.Row="1" Grid.Column="2"/>
<Image Style="{StaticResource Key=starCommentImage}" Grid.Row="1" Grid.Column="3"/>
</Grid>
<Editor AbsoluteLayout.LayoutBounds="0.5, 0.5, 700, 150" AbsoluteLayout.LayoutFlags="PositionProportional"
Margin="20"
Placeholder="Laisser un commentaire..." PlaceholderColor="Black" FontSize="16"
BackgroundColor="{StaticResource Gray100}" Keyboard="Text"
IsSpellCheckEnabled="True"
IsTextPredictionEnabled="True"/>
<Button AbsoluteLayout.LayoutBounds="1, 6, 270, 150" AbsoluteLayout.LayoutFlags="PositionProportional"
Margin="50"
Text="Valider" VerticalOptions="Center" TextColor="Black"
BackgroundColor="LightGreen"/>
</AbsoluteLayout>
</Border>
</VerticalStackLayout>
</VerticalStackLayout>
</ContentPage>

@ -0,0 +1,9 @@
namespace Views;
public partial class RecipeReviews : ContentPage
{
public RecipeReviews()
{
InitializeComponent();
}
}

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
</svg>

After

Width:  |  Height:  |  Size: 228 B

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save