Merge pull request 'final' (#68) from dev into master
continuous-integration/drone/push Build is passing Details

Reviewed-on: #68
master
Alexandre AGOSTINHO 2 years ago
commit f3a0873422

@ -5,20 +5,18 @@ name: CI-pipeline
trigger: trigger:
branch: branch:
- master - master
- dev* - dev
- feature/* - feature/*
- gestion/*
event: event:
- push - push
ref:
exclude:
- refs/tags/no-ci
steps: steps:
# Build Check # Build Check
- name: build - name: build
image: mcr.microsoft.com/dotnet/sdk:7.0 image: mcr.microsoft.com/dotnet/sdk:7.0
volumes:
- name: docs
path: /docs
commands: commands:
- cd MCTG/ - cd MCTG/
- dotnet restore CI-CD.slnf - dotnet restore CI-CD.slnf
@ -39,11 +37,11 @@ steps:
image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dronesonarplugin-dotnet7 image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dronesonarplugin-dotnet7
secrets: [ SECRET_SONAR_LOGIN ] secrets: [ SECRET_SONAR_LOGIN ]
environment: environment:
sonar_host: https://codefirst.iut.uca.fr/sonar/ sonar_host: 'https://codefirst.iut.uca.fr/sonar/'
sonar_token: sonar_token:
from_secret: SECRET_SONAR_LOGIN from_secret: SECRET_SONAR_LOGIN
project_key: "SAE-2.01_MCTG" project_key: 'SAE-2.01_MCTG'
coverage_exclusions: "Tests/**" coverage_exclusions: 'Tests/**'
commands: commands:
- cd MCTG/ - cd MCTG/
- dotnet restore CI-CD.slnf - dotnet restore CI-CD.slnf
@ -57,26 +55,18 @@ steps:
# Documentation generation # Documentation generation
- name: generate-and-deploy-docs - name: generate-and-deploy-docs
image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-docdeployer image: hub.codefirst.iut.uca.fr/alexandre.agostinho/codefirst-docdeployer:latest
failure: ignore failure: ignore
volumes: volumes:
- name: docs - name: docs
path: /docs path: /docs
commands: commands:
- /entrypoint.sh - /entrypoint.sh -l ./Documentation/doxygen -t doxygen -d SAE-2.01_MCTG-documentation
when: when:
branch: branch:
- master - master
- gestion/*
event:
- push
- pull_request
depends_on: [ build ] depends_on: [ build ]
volumes:
- name: docs
temp: {}
--- ---
kind: pipeline kind: pipeline
@ -87,17 +77,15 @@ trigger:
branch: branch:
- master - master
- dev - dev
# - gestion/*
event: event:
- push - push
steps: steps:
# Docker build and push image console-app
# Docker image build and push - name: docker-image-console-app
- name: docker-build-and-push
image: plugins/docker image: plugins/docker
settings: settings:
dockerfile: MCTG/Dockerfile dockerfile: MCTG/ConsoleApp/Dockerfile
context: MCTG/ context: MCTG/
registry: hub.codefirst.iut.uca.fr registry: hub.codefirst.iut.uca.fr
repo: hub.codefirst.iut.uca.fr/alexandre.agostinho/console-mctg repo: hub.codefirst.iut.uca.fr/alexandre.agostinho/console-mctg
@ -105,13 +93,3 @@ steps:
from_secret: SECRET_REGISTRY_USERNAME from_secret: SECRET_REGISTRY_USERNAME
password: password:
from_secret: SECRET_REGISTRY_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 ]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

@ -5,7 +5,7 @@ DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "SAE 2.01 - MCTG" PROJECT_NAME = "SAE 2.01 - MCTG"
PROJECT_NUMBER = 1.0.0 PROJECT_NUMBER = 1.0.0
PROJECT_BRIEF = "Ma Cuisine Trop Géniale - Application MAUI" PROJECT_BRIEF = "Ma Cuisine Trop Géniale - Application MAUI"
PROJECT_LOGO = images/CodeFirst.png PROJECT_LOGO = images/iut-logo.png
OUTPUT_DIRECTORY = /docs/doxygen OUTPUT_DIRECTORY = /docs/doxygen
CREATE_SUBDIRS = NO CREATE_SUBDIRS = NO
ALLOW_UNICODE_NAMES = NO ALLOW_UNICODE_NAMES = NO
@ -222,11 +222,16 @@ IGNORE_PREFIX =
GENERATE_HTML = YES GENERATE_HTML = YES
HTML_OUTPUT = html HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html HTML_FILE_EXTENSION = .html
HTML_HEADER = HTML_HEADER = header.html
HTML_FOOTER = footer.html HTML_FOOTER = footer.html
HTML_STYLESHEET = HTML_STYLESHEET =
HTML_EXTRA_STYLESHEET = HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css
HTML_EXTRA_FILES = images/CodeFirst.png images/clubinfo.png HTML_EXTRA_FILES = images/CodeFirst.png \
images/iut-logo \
doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js \
doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js \
doxygen-awesome-css/doxygen-awesome-paragraph-link.js \
doxygen-awesome-css/doxygen-awesome-interactive-toc.js
HTML_COLORSTYLE_HUE = 215 HTML_COLORSTYLE_HUE = 215
HTML_COLORSTYLE_SAT = 45 HTML_COLORSTYLE_SAT = 45
HTML_COLORSTYLE_GAMMA = 240 HTML_COLORSTYLE_GAMMA = 240

@ -0,0 +1,3 @@
docs/html
.DS_Store
.idea

File diff suppressed because it is too large Load Diff

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,124 @@
# Doxygen Awesome
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/jothepro/doxygen-awesome-css)](https://github.com/jothepro/doxygen-awesome-css/releases/latest)
[![GitHub](https://img.shields.io/github/license/jothepro/doxygen-awesome-css)](https://github.com/jothepro/doxygen-awesome-css/blob/main/LICENSE)
![GitHub Repo stars](https://img.shields.io/github/stars/jothepro/doxygen-awesome-css)
<div class="title_screenshot">
![Screenshot of Doxygen Awesome CSS](img/screenshot.png)
</div>
**Doxygen Awesome** is a custom **CSS theme for Doxygen HTML-documentation** with lots of customization parameters.
## Motivation
I really like how the Doxygen HTML-documentation is structured! But IMHO it looks a bit outdated.
This theme is an attempt to update the visuals of Doxygen without changing its overall layout too much.
## Features
- 🌈 Clean, modern design
- 🚀 Heavily customizable by adjusting CSS-variables
- 🧩 No changes to the HTML structure of Doxygen required
- 📱 Improved mobile usability
- 🌘 Dark mode support!
- 🥇 Works best with **doxygen 1.9.1** - **1.9.4**
## Examples
Some websites using this theme:
- [Documentation of this repository](https://jothepro.github.io/doxygen-awesome-css/)
- [wxWidgets](https://docs.wxwidgets.org/3.2/)
- [OpenCV 5.x](https://docs.opencv.org/5.x/)
- [Zephyr](https://docs.zephyrproject.org/latest/doxygen/html/index.html)
- [FELTOR](https://mwiesenberger.github.io/feltor/dg/html/modules.html)
- [Spatial Audio Framework (SAF)](https://leomccormack.github.io/Spatial_Audio_Framework/index.html)
- [libCloudSync](https://jothepro.github.io/libCloudSync/)
- [libsl3](https://a4z.github.io/libsl3/)
## Installation
To use the theme in your documentation, copy the required CSS and JS files from this repository into your project or add the repository as submodule and check out the latest release:
```bash
git submodule add https://github.com/jothepro/doxygen-awesome-css.git
cd doxygen-awesome-css
git checkout v2.1.0
```
All theme files are located in the root of this repository and start with the prefix `doxygen-awesome-`. You may not need all of them. Follow the install instructions to figure out what files are required for your setup.
### Choosing a layout
There is two layout options. Choose one of them and configure Doxygen accordingly:
<div class="darkmode_inverted_image">
![Available theme variants](img/theme-variants.drawio.svg)
</div>
#### Base Theme (1)
Comes with the typical Doxygen titlebar. Optionally the treeview in the sidebar can be enabled.
Required files: `doxygen-awesome.css`
Required `Doxyfile` configuration:
```
GENERATE_TREEVIEW = YES # optional. Also works without treeview
DISABLE_INDEX = NO
FULL_SIDEBAR = NO
HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css
```
#### Sidebar-Only Theme (2)
Hides the top titlebar to give more space to the content. The treeview must be enabled in order for this theme to work.
Required files: `doxygen-awesome.css`, `doxygen-awesome-sidebar-only.css`
Required `Doxyfile` configuration:
```
GENERATE_TREEVIEW = YES # required!
DISABLE_INDEX = NO
FULL_SIDEBAR = NO
HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css \
doxygen-awesome-css/doxygen-awesome-sidebar-only.css
```
**Caution**: This theme is not compatible with the `FULL_SIDEBAR = YES` option provided by Doxygen!
### Further installation instructions:
- [Installing extensions](docs/extensions.md)
- [Customizing the theme (colors, spacing, border-radius, ...)](docs/customization.md)
- [Tips and Tricks for further configuration](docs/tricks.md)
## Browser support
Tested with
- Chrome 104, Chrome 104 for Android, Chrome 103 for iOS
- Safari 15, Safari for iOS 15
- Firefox 103, Firefox 103 for Android, Firefox Daylight 102 for iOS
- Edge 104
The theme does not strive to be backwards compatible to (significantly) older browser versions.
## Credits
- This theme was initially inspired by the [vuepress](https://vuepress.vuejs.org/) static site generator default theme.
- Thank you for all the bug reports, pull requests and inspiring feedback on github!
<span class="next_section_button">
Read Next: [Extensions](docs/extensions.md)
</span>

@ -0,0 +1,110 @@
# Customization
[TOC]
## CSS-Variables
This theme is highly customizable because a lot of things are parameterized with CSS variables.
Just to give you an idea on how flexible the styling is, click this button:
<div class="alter-theme-button" onclick="toggle_alternative_theme()" onkeypress="if (event.keyCode == 13) toggle_alternative_theme()" tabindex=0>Alter theme</div>
### Setup
It is recommended to add your own `custom.css` and overwrite the variables there:
```
HTML_EXTRA_STYLESHEET = doxygen-awesome.css custom.css
```
Make sure to override the variables in the correct spot. All variables should be customized where they have been defined, in the `html` tag selector:
```css
html {
/* override light-mode variables here */
}
```
For dark-mode overrides you have to choose where to put them, depending on wether the dark-mode toggle extension is installed or not:
- dark-mode toggle is installed:
```css
html.dark-mode {
/* define dark-mode variable overrides here if you DO use doxygen-awesome-darkmode-toggle.js */
}
```
- dark-mode toggle is **NOT** installed. The dark-mode is enabled automatically depending on the system preference:
```css
@media (prefers-color-scheme: dark) {
html:not(.light-mode) {
/* define dark-mode variable overrides here if you DON'T use doxygen-awesome-darkmode-toggle.js */
}
}
```
### Available variables
The following list gives an overview of the variables defined in [`doxygen-awesome.css`](https://github.com/jothepro/doxygen-awesome-css/blob/main/doxygen-awesome.css).
The list is not complete. To explore all available variables, have a look at the CSS starting from [here](https://github.com/jothepro/doxygen-awesome-css/blob/main/doxygen-awesome.css#L30).
All variables are defined at the beginning of the stylesheet.
| Parameter | Default (Light) | Default (Dark) |
| :---------------------------------- | :---------------------------------------------------------- | :---------------------------------------------------------- |
| **Color Scheme**:<br>primary theme colors. This will affect the entire websites color scheme: links, arrows, labels, ... |||
| `--primary-color` | <code style="background:#1779c4;color:white">#1779c4</code> | <code style="background:#1982d2;color:white">#1982d2</code> |
| `--primary-dark-color` | <code style="background:#335c80;color:white">#335c80</code> | <code style="background:#5ca8e2;color:black">#5ca8e2</code> |
| `--primary-light-color` | <code style="background:#70b1e9;color:black">#70b1e9</code> | <code style="background:#4779ac;color:white">#4779ac</code> |
| **Page Colors**:<br>background and foreground (text-color) of the documentation. |||
| `--page-background-color` | <code style="background:#ffffff;color:black">#ffffff</code> | <code style="background:#1C1D1F;color:white">#1C1D1F</code> |
| `--page-foreground-color` | <code style="background:#2f4153;color:white">#2f4153</code> | <code style="background:#d2dbde;color:black">#d2dbde</code> |
| `--page-secondary-foreground-color` | <code style="background:#6f7e8e;color:white">#6f7e8e</code> | <code style="background:#859399;color:white">#859399</code> |
| **Spacing:**<br>default spacings. Most ui components reference these values for spacing, to provide uniform spacing on the page. |||
| `--spacing-small` | `5px` | |
| `--spacing-medium` | `10px` | |
| `--spacing-large` | `16px` | |
| **Border Radius**:<br>border radius for all rounded ui components. Will affect many components, like dropdowns, memitems, codeblocks, ... |||
| `--border-radius-small` | `4px` | |
| `--border-radius-medium` | `6px` | |
| `--border-radius-large` | `8px` | |
| **Content Width**:<br>The content is centered and constrained in its width. To make the content fill the whole page, set the following variable to `auto`. |||
| `--content-maxwidth` | `1000px` | |
| **Code Fragment Colors**:<br>Color-Scheme of multiline codeblocks |||
| `--fragment-background` | <code style="background:#F8F9FA;color:black">#F8F9FA</code> | <code style="background:#282c34;color:white">#282c34</code> |
| `--fragment-foreground` | <code style="background:#37474F;color:white">#37474F</code> | <code style="background:#dbe4eb;color:black">#dbe4eb</code> |
| **Arrow Opacity**:<br>By default the arrows in the sidebar are only visible on hover. You can override this behaviour so they are visible all the time. |||
| `--side-nav-arrow-opacity` | `0` | |
| `--side-nav-arrow-hover-opacity` | `0.9` | |
| ...and many more |||
If you miss a configuration option or find a bug, please consider [opening an issue](https://github.com/jothepro/doxygen-awesome-css/issues)!
## Doxygen generator
The theme overrides most colors with the `--primary-color-*` variables.
But there is a few small images and graphics that the theme cannot adjust or replace. To make these blend in better with
the rest, it is recommended to adjust the [doxygen color settings](https://www.doxygen.nl/manual/customize.html#minor_tweaks_colors)
to something that matches the chosen color-scheme.
For the default color-scheme, these values work out quite well:
```
# Doxyfile
HTML_COLORSTYLE_HUE = 209
HTML_COLORSTYLE_SAT = 255
HTML_COLORSTYLE_GAMMA = 113
```
## Share your customizations
If you customized the theme with custom colors, spacings, font-sizes, etc. and you want to share your creation with others, you can to this [here](https://github.com/jothepro/doxygen-awesome-css/discussions/13).
I am always curious to learn about how you made the theme look even better!
<span class="next_section_button">
Read Next: [Tips & Tricks](tricks.md)
</span>

@ -0,0 +1,194 @@
# Extensions
[TOC]
On top of the base theme provided by `doxygen-awesome.css`, this repository comes with Javascript extensions that require additional setup steps to get them running.
The extensions require customizations in the header HTML-template.
This is how you can create the default template with Doxygen:
1. Create default header template:
```sh
doxygen -w html header.html delete_me.html delete_me.css
```
2. Reference the template in your `Doxyfile`:
```
HTML_HEADER = header.html
```
[More details on header customization](https://www.doxygen.nl/manual/customize.html#minor_tweaks_header_css)
## Dark Mode Toggle
Adds a button next to the search bar to enable and disable the dark theme variant manually:
<div class="darkmode_inverted_image bordered_image">
<img width=250 src="darkmode_toggle.png" />
</div>
### Installation
1. Add the required resources in your `Doxyfile`:
- **HTML_EXTRA_FILES:** `doxygen-awesome-darkmode-toggle.js`
- **HTML_EXTRA_STYLESHEET:** `doxygen-awesome-sidebar-only-darkmode-toggle.css`
<em>(ONLY required for the sidebar-only theme variant!)</em>
2. In the `header.html` template, include `doxygen-awesome-darkmode-toggle.js` at the end of the `<head>` and then initialize it:
```html
<html>
<head>
<!-- ... other metadata & script includes ... -->
<script type="text/javascript" src="$relpath^doxygen-awesome-darkmode-toggle.js"></script>
<script type="text/javascript">
DoxygenAwesomeDarkModeToggle.init()
</script>
</head>
<body>
```
### Customizing
Changing the tooltip of the button:
```js
DoxygenAwesomeDarkModeToggle.title = "Zwischen hellem/dunklem Modus wechseln"
```
Changing Icons. Both Emoji or SVG icons are supported:
```js
DoxygenAwesomeDarkModeToggle.lightModeIcon = '🌞'
// icon from https://fonts.google.com/icons
DoxygenAwesomeDarkModeToggle.darkModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#009793"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M8.1,14.15C9.77,14.63,11,16.17,11,18c0,0.68-0.19,1.31-0.48,1.87c0.48,0.09,0.97,0.14,1.48,0.14 c1.48,0,2.9-0.41,4.13-1.15c-2.62-0.92-5.23-2.82-6.8-5.86C7.74,9.94,7.78,7.09,8.29,4.9c-2.57,1.33-4.3,4.01-4.3,7.1c0,0,0,0,0,0 c0.01,0,0.01,0,0.02,0C5.66,12,7.18,12.83,8.1,14.15z" opacity=".3"/><path d="M19.78,17.51c-2.47,0-6.57-1.33-8.68-5.43C8.77,7.57,10.6,3.6,11.63,2.01C6.27,2.2,1.98,6.59,1.98,12 c0,0.14,0.02,0.28,0.02,0.42C2.61,12.16,3.28,12,3.98,12c0,0,0,0,0,0c0-3.09,1.73-5.77,4.3-7.1C7.78,7.09,7.74,9.94,9.32,13 c1.57,3.04,4.18,4.95,6.8,5.86c-1.23,0.74-2.65,1.15-4.13,1.15c-0.5,0-1-0.05-1.48-0.14c-0.37,0.7-0.94,1.27-1.64,1.64 c0.98,0.32,2.03,0.5,3.11,0.5c3.5,0,6.58-1.8,8.37-4.52C20.18,17.5,19.98,17.51,19.78,17.51z"/><path d="M7,16l-0.18,0C6.4,14.84,5.3,14,4,14c-1.66,0-3,1.34-3,3s1.34,3,3,3c0.62,0,2.49,0,3,0c1.1,0,2-0.9,2-2 C9,16.9,8.1,16,7,16z"/></g></g></svg>`
```
All customizations must be applied before calling `DoxygenAwesomeDarkModeToggle.init()`!
## Fragment Copy Button
***This feature is experimental!***
Shows a copy button when the user hovers over a code fragment:
<div class="darkmode_inverted_image bordered_image">
<img width=490 src="fragment_copy_button.png"/>
</div>
### Installation
1. Add the required resources in your `Doxyfile`:
- **HTML_EXTRA_FILES:** `doxygen-awesome-fragment-copy-button.js`
2. In the `header.html` template, include `doxygen-awesome-fragment-copy-button.js` at the end of the `<head>` and then initialize it:
```html
<html>
<head>
<!-- ... other metadata & script includes ... -->
<script type="text/javascript" src="$relpath^doxygen-awesome-fragment-copy-button.js"></script>
<script type="text/javascript">
DoxygenAwesomeFragmentCopyButton.init()
</script>
</head>
<body>
```
### Customizing
The tooltip of the button can be changed:
```js
DoxygenAwesomeFragmentCopyButton.title = "In die Zwischenablage kopieren"
```
The icon can be changed. It must be an SVG:
```js
DoxygenAwesomeFragmentCopyButton.copyIcon = `<svg ...>`
DoxygenAwesomeFragmentCopyButton.successIcon = `<svg ...>`
```
All customizations must be applied before calling `DoxygenAwesomeDarkModeToggle.init()`!
## Paragraph Linking
***This feature is experimental!***
Provides a button on hover behind every headline to allow easy creation of a permanent link to the headline:
<div class="darkmode_inverted_image bordered_image">
<img width=220 src="paragraph_link.png"/>
</div>
Works for all headlines and for many documentation section titles.
### Installation
1. Add the required resources in your `Doxyfile`:
- **HTML_EXTRA_FILES:** `doxygen-awesome-paragraph-link.js`
2. In the `header.html` template, include `doxygen-awesome-paragraph-link.js` at the end of the `<head>` and then initialize it:
```html
<html>
<head>
<!-- ... other metadata & script includes ... -->
<script type="text/javascript" src="$relpath^doxygen-awesome-paragraph-link.js"></script>
<script type="text/javascript">
DoxygenAwesomeParagraphLink.init()
</script>
</head>
<body>
```
### Customizing
The button tooltip can be changed:
```js
DoxygenAwesomeParagraphLink.title = "Abschnitt verknüpfen"
```
The icon of the button can be changed. Both plain characters or SVG icons are supported:
```js
DoxygenAwesomeParagraphLink.icon = "¶"
```
All customizations must be applied before calling `DoxygenAwesomeParagraphLink.init()`!
## Interactive TOC
On large screens the Table of Contents (TOC) is anchored on the top right of the page. This extension visualizes the reading progress by dynamically highlighting the currently active section.
On small screens the extension hides the TOC by default. The user can open it manually when needed:
<div class="darkmode_inverted_image bordered_image">
<img width=380 src="interactive_toc_mobile.png" />
</div>
### Installation
1. Add the required resources in your `Doxyfile`:
- **HTML_EXTRA_FILES:** `doxygen-awesome-interactive-toc.js`
2. In the `header.html` template, include `doxygen-awesome-interactive-toc.js` at the end of the `<head>` and then initialize it:
```html
<html>
<head>
<!-- ... other metadata & script includes ... -->
<script type="text/javascript" src="$relpath^doxygen-awesome-interactive-toc.js"></script>
<script type="text/javascript">
DoxygenAwesomeInteractiveToc.init()
</script>
</head>
<body>
```
### Customizing
The offset for when a headline is considered active can be changed. A smaller value means that the headline of the section must be closer to the top of the viewport before it is highlighted in the TOC:
```js
DoxygenAwesomeInteractiveToc.topOffset = 45
```
Hiding the TOC on small screens can be disabled. It is still interactive and can be hidden by the user but will now be open by default:
```js
DoxygenAwesomeInteractiveToc.hideMobileMenu = false
```
<span class="next_section_button">
Read Next: [Customization](customization.md)
</span>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -0,0 +1,57 @@
# Tips & Tricks
[TOC]
## Diagrams with Graphviz
To get the best looking class diagrams for your documentation, generate them with Graphviz as vector graphics with transparent background:
```
# Doxyfile
HAVE_DOT = YES
DOT_IMAGE_FORMAT = svg
DOT_TRANSPARENT = YES
```
## Disable Dark Mode
If for some reason you don't want the theme to automatically switch to dark mode depending on the browser preference,
you can disable dark mode by adding the `light-mode` class to the html-tag in the header template:
```html
<html xmlns="http://www.w3.org/1999/xhtml" class="light-mode">
```
The same can be done to always enable dark-mode:
```html
<html xmlns="http://www.w3.org/1999/xhtml" class="dark-mode">
```
**This only works if you don't use the dark-mode toggle extension.**
## Choosing Sidebar Width
If you have enabled the sidebar-only theme variant, make sure to carefully choose a proper width for your sidebar.
It should be wide enough to hold the icon, project title and version number. If the content is too wide, it will be
cut off.
```css
html {
/* Make sure sidebar is wide enough to contain the page title (logo + title + version) */
--side-nav-fixed-width: 335px;
}
```
The choosen width should also be set in the Doxyfile:
```
# Doxyfile
TREEVIEW_WIDTH = 335
```
<span class="next_section_button">
Read Next: [Example](https://jothepro.github.io/doxygen-awesome-css/class_my_library_1_1_example.html)
</span>

@ -0,0 +1,157 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 - 2022 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
class DoxygenAwesomeDarkModeToggle extends HTMLElement {
// SVG icons from https://fonts.google.com/icons
// Licensed under the Apache 2.0 license:
// https://www.apache.org/licenses/LICENSE-2.0.html
static lightModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FCBF00"><rect fill="none" height="24" width="24"/><circle cx="12" cy="12" opacity=".3" r="3"/><path d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"/></svg>`
static darkModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FE9700"><rect fill="none" height="24" width="24"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27 C17.45,17.19,14.93,19,12,19c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z" opacity=".3"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/></svg>`
static title = "Toggle Light/Dark Mode"
static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode"
static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode"
static _staticConstructor = function() {
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.userPreference)
// Update the color scheme when the browsers preference changes
// without user interaction on the website.
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
})
// Update the color scheme when the tab is made visible again.
// It is possible that the appearance was changed in another tab
// while this tab was in the background.
document.addEventListener("visibilitychange", visibilityState => {
if (document.visibilityState === 'visible') {
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
}
});
}()
static init() {
$(function() {
$(document).ready(function() {
const toggleButton = document.createElement('doxygen-awesome-dark-mode-toggle')
toggleButton.title = DoxygenAwesomeDarkModeToggle.title
toggleButton.updateIcon()
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
toggleButton.updateIcon()
})
document.addEventListener("visibilitychange", visibilityState => {
if (document.visibilityState === 'visible') {
toggleButton.updateIcon()
}
});
$(document).ready(function(){
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
})
$(window).resize(function(){
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
})
})
})
}
constructor() {
super();
this.onclick=this.toggleDarkMode
}
/**
* @returns `true` for dark-mode, `false` for light-mode system preference
*/
static get systemPreference() {
return window.matchMedia('(prefers-color-scheme: dark)').matches
}
/**
* @returns `true` for dark-mode, `false` for light-mode user preference
*/
static get userPreference() {
return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) ||
(DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey))
}
static set userPreference(userPreference) {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference
if(!userPreference) {
if(DoxygenAwesomeDarkModeToggle.systemPreference) {
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true)
} else {
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)
}
} else {
if(!DoxygenAwesomeDarkModeToggle.systemPreference) {
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true)
} else {
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)
}
}
DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged()
}
static enableDarkMode(enable) {
if(enable) {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = true
document.documentElement.classList.add("dark-mode")
document.documentElement.classList.remove("light-mode")
} else {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = false
document.documentElement.classList.remove("dark-mode")
document.documentElement.classList.add("light-mode")
}
}
static onSystemPreferenceChanged() {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
}
static onUserPreferenceChanged() {
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
}
toggleDarkMode() {
DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference
this.updateIcon()
}
updateIcon() {
if(DoxygenAwesomeDarkModeToggle.darkModeEnabled) {
this.innerHTML = DoxygenAwesomeDarkModeToggle.darkModeIcon
} else {
this.innerHTML = DoxygenAwesomeDarkModeToggle.lightModeIcon
}
}
}
customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle);

@ -0,0 +1,85 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2022 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
class DoxygenAwesomeFragmentCopyButton extends HTMLElement {
constructor() {
super();
this.onclick=this.copyContent
}
static title = "Copy to clipboard"
static copyIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`
static successIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>`
static successDuration = 980
static init() {
$(function() {
$(document).ready(function() {
if(navigator.clipboard) {
const fragments = document.getElementsByClassName("fragment")
for(const fragment of fragments) {
const fragmentWrapper = document.createElement("div")
fragmentWrapper.className = "doxygen-awesome-fragment-wrapper"
const fragmentCopyButton = document.createElement("doxygen-awesome-fragment-copy-button")
fragmentCopyButton.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon
fragmentCopyButton.title = DoxygenAwesomeFragmentCopyButton.title
fragment.parentNode.replaceChild(fragmentWrapper, fragment)
fragmentWrapper.appendChild(fragment)
fragmentWrapper.appendChild(fragmentCopyButton)
}
}
})
})
}
copyContent() {
const content = this.previousSibling.cloneNode(true)
// filter out line number from file listings
content.querySelectorAll(".lineno, .ttc").forEach((node) => {
node.remove()
})
let textContent = content.textContent
// remove trailing newlines that appear in file listings
let numberOfTrailingNewlines = 0
while(textContent.charAt(textContent.length - (numberOfTrailingNewlines + 1)) == '\n') {
numberOfTrailingNewlines++;
}
textContent = textContent.substring(0, textContent.length - numberOfTrailingNewlines)
navigator.clipboard.writeText(textContent);
this.classList.add("success")
this.innerHTML = DoxygenAwesomeFragmentCopyButton.successIcon
window.setTimeout(() => {
this.classList.remove("success")
this.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon
}, DoxygenAwesomeFragmentCopyButton.successDuration);
}
}
customElements.define("doxygen-awesome-fragment-copy-button", DoxygenAwesomeFragmentCopyButton)

@ -0,0 +1,81 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2022 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
class DoxygenAwesomeInteractiveToc {
static topOffset = 38
static hideMobileMenu = true
static headers = []
static init() {
window.addEventListener("load", () => {
let toc = document.querySelector(".contents > .toc")
if(toc) {
toc.classList.add("interactive")
if(!DoxygenAwesomeInteractiveToc.hideMobileMenu) {
toc.classList.add("open")
}
document.querySelector(".contents > .toc > h3")?.addEventListener("click", () => {
if(toc.classList.contains("open")) {
toc.classList.remove("open")
} else {
toc.classList.add("open")
}
})
document.querySelectorAll(".contents > .toc > ul a").forEach((node) => {
let id = node.getAttribute("href").substring(1)
DoxygenAwesomeInteractiveToc.headers.push({
node: node,
headerNode: document.getElementById(id)
})
document.getElementById("doc-content")?.addEventListener("scroll", () => {
DoxygenAwesomeInteractiveToc.update()
})
})
DoxygenAwesomeInteractiveToc.update()
}
})
}
static update() {
let active = DoxygenAwesomeInteractiveToc.headers[0]?.node
DoxygenAwesomeInteractiveToc.headers.forEach((header) => {
let position = header.headerNode.getBoundingClientRect().top
header.node.classList.remove("active")
header.node.classList.remove("aboveActive")
if(position < DoxygenAwesomeInteractiveToc.topOffset) {
active = header.node
active?.classList.add("aboveActive")
}
})
active?.classList.add("active")
active?.classList.remove("aboveActive")
}
}

@ -0,0 +1,51 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2022 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
class DoxygenAwesomeParagraphLink {
// Icon from https://fonts.google.com/icons
// Licensed under the Apache 2.0 license:
// https://www.apache.org/licenses/LICENSE-2.0.html
static icon = `<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z"/></svg>`
static title = "Permanent Link"
static init() {
$(function() {
$(document).ready(function() {
document.querySelectorAll(".contents a.anchor[id], .contents .groupheader > a[id]").forEach((node) => {
let anchorlink = document.createElement("a")
anchorlink.setAttribute("href", `#${node.getAttribute("id")}`)
anchorlink.setAttribute("title", DoxygenAwesomeParagraphLink.title)
anchorlink.classList.add("anchorlink")
node.classList.add("anchor")
anchorlink.innerHTML = DoxygenAwesomeParagraphLink.icon
node.parentElement.appendChild(anchorlink)
})
})
})
}
}

@ -0,0 +1,40 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
@media screen and (min-width: 768px) {
#MSearchBox {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px);
}
#MSearchField {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height));
}
}

@ -0,0 +1,115 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
html {
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
* Make sure it is wide enough to contain the page title (logo + title + version)
*/
--side-nav-fixed-width: 335px;
--menu-display: none;
--top-height: 120px;
--toc-sticky-top: -25px;
--toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px);
}
#projectname {
white-space: nowrap;
}
@media screen and (min-width: 768px) {
html {
--searchbar-background: var(--page-background-color);
}
#side-nav {
min-width: var(--side-nav-fixed-width);
max-width: var(--side-nav-fixed-width);
top: var(--top-height);
overflow: visible;
}
#nav-tree, #side-nav {
height: calc(100vh - var(--top-height)) !important;
}
#nav-tree {
padding: 0;
}
#top {
display: block;
border-bottom: none;
height: var(--top-height);
margin-bottom: calc(0px - var(--top-height));
max-width: var(--side-nav-fixed-width);
overflow: hidden;
background: var(--side-nav-background);
}
#main-nav {
float: left;
padding-right: 0;
}
.ui-resizable-handle {
cursor: default;
width: 1px !important;
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
}
#nav-path {
position: fixed;
right: 0;
left: var(--side-nav-fixed-width);
bottom: 0;
width: auto;
}
#doc-content {
height: calc(100vh - 31px) !important;
padding-bottom: calc(3 * var(--spacing-large));
padding-top: calc(var(--top-height) - 80px);
box-sizing: border-box;
margin-left: var(--side-nav-fixed-width) !important;
}
#MSearchBox {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
}
#MSearchField {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
}
#MSearchResultsWindow {
left: var(--spacing-medium) !important;
right: auto;
}
}

@ -0,0 +1,54 @@
html.alternative {
/* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */
--primary-color: #AF7FE4;
--primary-dark-color: #9270E4;
--primary-light-color: #7aabd6;
--primary-lighter-color: #cae1f1;
--primary-lightest-color: #e9f1f8;
/* page base colors */
--page-background-color: white;
--page-foreground-color: #2c3e50;
--page-secondary-foreground-color: #67727e;
--border-radius-large: 22px;
--border-radius-small: 9px;
--border-radius-medium: 14px;
--spacing-small: 8px;
--spacing-medium: 14px;
--spacing-large: 19px;
--top-height: 125px;
--side-nav-background: #324067;
--side-nav-foreground: #F1FDFF;
--header-foreground: var(--side-nav-foreground);
--searchbar-background: var(--side-nav-foreground);
--searchbar-border-radius: var(--border-radius-medium);
--header-background: var(--side-nav-background);
--header-foreground: var(--side-nav-foreground);
--toc-background: rgb(243, 240, 252);
--toc-foreground: var(--page-foreground-color);
}
html.alternative.dark-mode {
color-scheme: dark;
--primary-color: #AF7FE4;
--primary-dark-color: #9270E4;
--primary-light-color: #4779ac;
--primary-lighter-color: #191e21;
--primary-lightest-color: #191a1c;
--page-background-color: #1C1D1F;
--page-foreground-color: #d2dbde;
--page-secondary-foreground-color: #859399;
--separator-color: #3a3246;
--side-nav-background: #171D32;
--side-nav-foreground: #F1FDFF;
--toc-background: #20142C;
--searchbar-background: var(--page-background-color);
}

@ -0,0 +1,101 @@
.github-corner svg {
fill: var(--primary-light-color);
color: var(--page-background-color);
width: 72px;
height: 72px;
}
@media screen and (max-width: 767px) {
.github-corner svg {
width: 50px;
height: 50px;
}
#projectnumber {
margin-right: 22px;
}
}
.alter-theme-button {
display: inline-block;
cursor: pointer;
background: var(--primary-color);
color: var(--page-background-color) !important;
border-radius: var(--border-radius-medium);
padding: var(--spacing-small) var(--spacing-medium);
text-decoration: none;
}
.next_section_button {
display: block;
padding: var(--spacing-large) 0 var(--spacing-small) 0;
color: var(--page-background-color);
user-select: none;
}
.next_section_button::after {
/* clearfix */
content: "";
clear: both;
display: table;
}
.next_section_button a {
overflow: hidden;
float: right;
border: 1px solid var(--separator-color);
padding: var(--spacing-medium) calc(var(--spacing-large) / 2) var(--spacing-medium) var(--spacing-large);
border-radius: var(--border-radius-medium);
color: var(--page-secondary-foreground-color) !important;
text-decoration: none;
background-color: var(--page-background-color);
transition: color .08s ease-in-out, background-color .1s ease-in-out;
}
.next_section_button a:hover {
color: var(--page-foreground-color) !important;
background-color: var(--odd-color);
}
.next_section_button a::after {
content: '〉';
color: var(--page-secondary-foreground-color) !important;
padding-left: var(--spacing-large);
display: inline-block;
transition: color .08s ease-in-out, transform .09s ease-in-out;
}
.next_section_button a:hover::after {
color: var(--page-foreground-color) !important;
transform: translateX(3px);
}
.alter-theme-button:hover {
background: var(--primary-dark-color);
}
html.dark-mode .darkmode_inverted_image img, /* < doxygen 1.9.3 */
html.dark-mode .darkmode_inverted_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ {
filter: brightness(87%) hue-rotate(180deg) invert();
}
.bordered_image {
border-radius: var(--border-radius-small);
border: 1px solid var(--separator-color);
display: inline-block;
overflow: hidden;
}
html.dark-mode .bordered_image img, /* < doxygen 1.9.3 */
html.dark-mode .bordered_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ {
border-radius: var(--border-radius-small);
}
.title_screenshot {
filter: drop-shadow(0px 3px 10px rgba(0,0,0,0.22));
max-width: 500px;
margin: var(--spacing-large) 0;
}
.title_screenshot .caption {
display: none;
}

@ -0,0 +1,88 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!-- BEGIN opengraph metadata -->
<meta property="og:title" content="Doxygen Awesome" />
<meta property="og:image" content="https://repository-images.githubusercontent.com/348492097/4f16df80-88fb-11eb-9d31-4015ff22c452" />
<meta property="og:description" content="Custom CSS theme for doxygen html-documentation with lots of customization parameters." />
<meta property="og:url" content="https://jothepro.github.io/doxygen-awesome-css/" />
<!-- END opengraph metadata -->
<!-- BEGIN twitter metadata -->
<meta name="twitter:image:src" content="https://repository-images.githubusercontent.com/348492097/4f16df80-88fb-11eb-9d31-4015ff22c452" />
<meta name="twitter:title" content="Doxygen Awesome" />
<meta name="twitter:description" content="Custom CSS theme for doxygen html-documentation with lots of customization parameters." />
<!-- END twitter metadata -->
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<link rel="icon" type="image/svg+xml" href="logo.drawio.svg"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
<script type="text/javascript" src="$relpath^doxygen-awesome-darkmode-toggle.js"></script>
<script type="text/javascript" src="$relpath^doxygen-awesome-fragment-copy-button.js"></script>
<script type="text/javascript" src="$relpath^doxygen-awesome-paragraph-link.js"></script>
<script type="text/javascript" src="$relpath^doxygen-awesome-interactive-toc.js"></script>
<script type="text/javascript" src="$relpath^toggle-alternative-theme.js"></script>
<script type="text/javascript">
DoxygenAwesomeFragmentCopyButton.init()
DoxygenAwesomeDarkModeToggle.init()
DoxygenAwesomeParagraphLink.init()
DoxygenAwesomeInteractiveToc.init()
</script>
$treeview
$search
$mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
</head>
<body>
<!-- https://tholman.com/github-corners/ -->
<a href="https://github.com/jothepro/doxygen-awesome-css" class="github-corner" title="View source on GitHub" target="_blank">
<svg viewBox="0 0 250 250" width="40" height="40" style="position: absolute; top: 0; border: 0; right: 0; z-index: 99;" aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px;">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign" style="padding-left: 0.5em;">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em;">
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

@ -0,0 +1,12 @@
let original_theme_active = true;
function toggle_alternative_theme() {
if(original_theme_active) {
document.documentElement.classList.add("alternative")
original_theme_active = false;
} else {
document.documentElement.classList.remove("alternative")
original_theme_active = true;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

@ -0,0 +1,240 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1001px" height="301px" viewBox="-0.5 -0.5 1001 301" content="&lt;mxfile&gt;&lt;diagram id=&quot;6E4AiNPWWr3a8GvC3Ypl&quot; name=&quot;Page-1&quot;&gt;7Vlbk5owGP01ztiHMiSBAI/rbfvS9sHO9DkrWWAaiMW4an99EwgCBna1orsPhRmFLxeSc06+HHSEpun+MSfr+CsPKRtBO9yP0GwEoR/48lMFDmXAcWAZiPIkLEOgDiyTP1QHbR3dJiHdtCoKzplI1u3gimcZXYlWjOQ537WrPXPWfuqaRNQILFeEmdGfSShiPS3XruNfaBLF1ZOBrUtSUlXWgU1MQr5rhNB8hKY556K8SvdTyhR2FS5lu0VP6XFgOc3EWQ3cssULYdtqcpa8n5ANlV8/YppSPVJxqKYv6F52PolFymQAyEvCkiiT1yv5WJrLwAvNRSIBe9AFaRKGqvlkFyeCLtdkpfraSXXIWM63WUhD3dczz4SmXI5O3+uHF+UJY1POeF4MBi0e1KnrNeJ2cci4CYlGSY2R7hshDdEj5SkV+UFWqUo9TZfWK/T1/a5mHyANZdxgvqKZaMFFx65rTuSFpqWHIt+gCCqKlnINPJH88/eMHS6gaiNy/otWQGU8o/fmr4enPl4H4A+7vuW5b1PoedatSAwMEg2uIonj+vzZHtMbeap6sF9FwQXmlJ3A7pgwHmDGZ0y41o3do6xe3UqB4Lk6uwUS9GBmYnMXLCrgG2BM5SpQ4x0Wk3lxXotJVRr4lguD+vBaKwhgC3uGoGQrC+MOHBGyEBoASmBAqRPhSMkfM5XunuQNjtTV+EciGC2KJCb2N/KSREQkPPt0HfKnycpT511UCiC2PHgroUID3YX0IlSBO57klISrfJuuN1eidyOU9k2EHIC8AAEbOS5qZ35ZjFuHIWKEkQU918fQxn6A3Y5NoZBz88ADoI9MbVOSr+JX0AbXoH2yFUOszsFYkOmhwYLfzh4OaJNgZhJgA6tq7PiSjsBkAZxS6Q/AgtOxc5V5ReFV7LsVZPj3Vjn1SQ1eI1RmIJ2Ayg7ks8s+yjKD1zPtWjP7nDg4Rp/Fv/s3u+3fYI9/HloMAHTT2FySyGpqwccda/IWaoDm1m2wVhq3UxzlwtUwuhe52PN93V28C357/gPl/oiRzUZ3IiEQJMnUu8isW4RHYj6IxzOzxt0t3gWQNC1ee3t0HMs/39YB37XAALbO9B1F3ixt3bi2bUcnV26Mw7q42Xw+Wywu9CcD6rCAHniBU31ej2uHozi65fGHcsMX4PiqUgv3h26nVPMHs/d2yJcjZ1jgwICxU6C3ML2eqdAP4nkvT6fIA+03j7bLOV3e0BTvO3le8yfG/5b32q0VW8WrI6gEAS8Vw50sr7yt/28oyhp/2qD5Xw==&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<rect x="170" y="280" width="135" height="20" rx="3" ry="3" fill="#fafafa" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 133px; height: 1px; padding-top: 290px; margin-left: 171px;">
<div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 15px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
1. Base Theme
</div>
</div>
</div>
</foreignObject>
<text x="238" y="295" fill="#000000" font-family="Helvetica" font-size="15px" text-anchor="middle" font-weight="bold">
1. Base Theme
</text>
</switch>
</g>
<rect x="658.75" y="280" width="177.5" height="20" rx="3" ry="3" fill="#fafafa" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 176px; height: 1px; padding-top: 290px; margin-left: 660px;">
<div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 15px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
2. Sidebar-Only Theme
</div>
</div>
</div>
</foreignObject>
<text x="748" y="295" fill="#000000" font-family="Helvetica" font-size="15px" text-anchor="middle" font-weight="bold">
2. Sidebar-Only Theme
</text>
</switch>
</g>
<rect x="510" y="0" width="490" height="260" fill="rgb(255, 255, 255)" stroke="#6e6e6e" pointer-events="none"/>
<rect x="708.53" y="16.67" width="219.66" height="233.33" fill="rgb(255, 255, 255)" stroke="#e3e3e3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 218px; height: 1px; padding-top: 133px; margin-left: 710px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
Content
</div>
</div>
</div>
</foreignObject>
<text x="818" y="137" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Content
</text>
</switch>
</g>
<rect x="510" y="0" width="126.72" height="260" fill="#f7f7f7" stroke="#6e6e6e" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 125px; height: 1px; padding-top: 130px; margin-left: 511px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
Sidebar
<br/>
(Title + Navigation)
</div>
</div>
</div>
</foreignObject>
<text x="573" y="134" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Sidebar...
</text>
</switch>
</g>
<rect x="636.72" y="226.67" width="363.28" height="33.33" fill="rgb(255, 255, 255)" stroke="#6e6e6e" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 361px; height: 1px; padding-top: 243px; margin-left: 638px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
Footer (Breadcrumps)
</div>
</div>
</div>
</foreignObject>
<text x="818" y="247" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Footer (Breadcrumps)
</text>
</switch>
</g>
<rect x="522.67" y="41.67" width="101.38" height="16.67" rx="2.5" ry="2.5" fill="rgb(255, 255, 255)" stroke="#6e6e6e" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 99px; height: 1px; padding-top: 50px; margin-left: 524px;">
<div data-drawio-colors="color: #262626; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(38, 38, 38); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
Search
</div>
</div>
</div>
</foreignObject>
<text x="573" y="54" fill="#262626" font-family="Helvetica" font-size="12px" text-anchor="middle">
Search
</text>
</switch>
</g>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 32px; height: 1px; padding-top: 20px; margin-left: 525px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;">
<div style="display: inline-block; font-size: 20px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
<font color="#262626">
Title
</font>
</div>
</div>
</div>
</foreignObject>
<text x="525" y="26" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="20px">
Tit...
</text>
</switch>
</g>
<rect x="0" y="0" width="490" height="260" fill="rgb(255, 255, 255)" stroke="#6e6e6e" pointer-events="none"/>
<rect x="198.53" y="44.87" width="219.66" height="185.13" fill="rgb(255, 255, 255)" stroke="#e3e3e3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 218px; height: 1px; padding-top: 137px; margin-left: 200px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
Content
</div>
</div>
</div>
</foreignObject>
<text x="308" y="141" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Content
</text>
</switch>
</g>
<rect x="0" y="0" width="490" height="44.87" fill="#deedff" stroke="#6e6e6e" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 488px; height: 1px; padding-top: 22px; margin-left: 1px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
Titlebar (Navigation + Search)
</div>
</div>
</div>
</foreignObject>
<text x="245" y="26" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Titlebar (Navigation + Search)
</text>
</switch>
</g>
<rect x="0" y="44.87" width="126.73" height="185.13" fill="#f7f7f7" stroke="#6e6e6e" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 125px; height: 1px; padding-top: 137px; margin-left: 1px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
Sidebar (Navigation)
</div>
</div>
</div>
</foreignObject>
<text x="63" y="141" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Sidebar (Navigation)
</text>
</switch>
</g>
<rect x="0" y="226.67" width="490" height="33.33" fill="rgb(255, 255, 255)" stroke="#6e6e6e" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 488px; height: 1px; padding-top: 243px; margin-left: 1px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
Footer (Breadcrumps)
</div>
</div>
</div>
</foreignObject>
<text x="245" y="247" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Footer (Breadcrumps)
</text>
</switch>
</g>
<rect x="371.72" y="14.87" width="101.38" height="16.67" rx="2.5" ry="2.5" fill="rgb(255, 255, 255)" stroke="#6e6e6e" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 99px; height: 1px; padding-top: 23px; margin-left: 373px;">
<div data-drawio-colors="color: #262626; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(38, 38, 38); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
Search
</div>
</div>
</div>
</foreignObject>
<text x="422" y="27" fill="#262626" font-family="Helvetica" font-size="12px" text-anchor="middle">
Search
</text>
</switch>
</g>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 32px; height: 1px; padding-top: 23px; margin-left: 19px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;">
<div style="display: inline-block; font-size: 20px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
<font color="#262626">
Title
</font>
</div>
</div>
</div>
</foreignObject>
<text x="19" y="29" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="20px">
Tit...
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Viewer does not support full SVG 1.1
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

@ -0,0 +1,139 @@
#pragma once
#include <string>
namespace MyLibrary {
enum Color { red, green, blue };
/**
* @brief Example class to demonstrate the features of the custom CSS.
*
* @author jothepro
*
*/
class Example {
public:
/**
* @brief brief summary
*
* doxygen test documentation
*
* @param test this is the only parameter of this test function. It does nothing!
*
* # Supported elements
*
* These elements have been tested with the custom CSS.
*
* ## Tables
*
* The table content is scrollable if the table gets too wide.
*
* | first_column | second_column | third_column | fourth_column | fifth_column | sixth_column | seventh_column | eighth_column | ninth_column |
* |--------------|---------------|--------------|---------------|--------------|--------------|----------------|---------------|--------------|
* | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
*
* A table can contain images:
*
* | Column 1 | Column 2 |
* |---------------------------|-------------------------------------------------|
* | ![doxygen](testimage.png) | the image should not be inverted in dark-mode |
*
* Complex [Doxygen tables](https://www.doxygen.nl/manual/tables.html) are also supported as seen in @ref multi_row "this example":
*
* <table>
* <caption id="multi_row">Complex table</caption>
* <tr><th>Column 1 <th>Column 2 <th>Column 3
* <tr><td rowspan="2">cell row=1+2,col=1<td>cell row=1,col=2<td>cell row=1,col=3
* <tr><td rowspan="2">cell row=2+3,col=2 <td>cell row=2,col=3
* <tr><td>cell row=3,col=1 <td rowspan="2">cell row=3+4,col=3
* <tr><td colspan="2">cell row=4,col=1+2
* <tr><td>cell row=5,col=1 <td colspan="2">cell row=5,col=2+3
* <tr><td colspan="2" rowspan="2">cell row=6+7,col=1+2 <td>cell row=6,col=3
* <tr> <td>cell row=7,col=3
* <tr><td>cell row=8,col=1 <td>cell row=8,col=2\n
* <table>
* <tr><td>Inner cell row=1,col=1<td>Inner cell row=1,col=2
* <tr><td>Inner cell row=2,col=1<td>Inner cell row=2,col=2
* </table>
* <td>cell row=8,col=3
* <ul>
* <li>Item 1
* <li>Item 2
* </ul>
* </table>
*
* ## Lists
*
* - element 1
* - element 2
*
* 1. element 1
* ```
* code in lists
* ```
* 2. element 2
*
* ## Quotes
*
* > Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
* > ut labore et dolore magna aliqua. Vitae proin sagittis nisl rhoncus mattis rhoncus urna neque viverra.
* > Velit sed ullamcorper morbi tincidunt ornare.
* >
* > Lorem ipsum dolor sit amet consectetur adipiscing elit duis.
* *- jothepro*
*
* ## Code block
*
* ```cpp
* auto x = "code within md fences (```)";
* ```
*
* @code{.cpp}
* // code within @code block
* while(true) {
* auto example = std::make_shared<Example>(5);
* example->test("test");
* }
*
* @endcode
*
* // code within indented code block
* auto test = std::shared_ptr<Example(5);
*
*
* Inline `code` elements in a text. *Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.* This also works within multiline text and does not break the `layout`.
*
*
* ## Special hints
*
* @warning this is a warning only for demonstration purposes
*
* @note this is a note to show that notes work. They can also include `code`:
* @code{.c}
* void this_looks_awesome();
* @endcode
*
* @bug example bug
*
* @deprecated None of this will be deprecated, because it's beautiful!
*
* @invariant This is an invariant
*
* @pre This is a precondition
*
* @todo This theme is never finished!
*
* @remark This is awesome!
*
*/
std::string test(const std::string& test);
virtual int virtualfunc() = 0;
static bool staticfunc();
};
}

@ -0,0 +1,46 @@
#pragma once
#include <string>
#include "example.hpp"
#include <iostream>
namespace MyLibrary {
/**
* @brief some subclass
*/
template<typename TemplatedClass>
class SubclassExample : public Example {
public:
/**
* @bug second bug
* @return
*/
int virtualfunc() override;
/**
* @brief Template function function
*/
template <typename T>
std::shared_ptr<std::string> function_template_test(std::shared_ptr<T>& param);
/**
* @brief Extra long function with lots of parameters and many template types.
*
* Also has a long return type.
*
* @param param1 first parameter
* @param param2 second parameter
* @param parameter3 third parameter
*/
template <typename T, typename Foo, typename Bar, typename Alice, typename Bob, typename Charlie, typename Hello, typename World>
std::pair<std::string, std::string> long_function_with_many_parameters(std::shared_ptr<T>& param1, std::shared_ptr<std::string>& param2, bool parameter3, Alice paramater4 Bob parameter 5) {
if(true) {
std::cout << "this even has some code." << std::endl;
}
}
};
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="61px" height="74px" viewBox="-0.5 -0.5 61 74" content="&lt;mxfile host=&quot;drawio-plugin&quot; modified=&quot;2021-03-16T23:58:23.462Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36&quot; version=&quot;13.7.9&quot; etag=&quot;JoeaGLJ54FcERO7YrWLQ&quot; type=&quot;embed&quot;&gt;&lt;diagram id=&quot;JMB9aH8b_oZ7EWDuqJgx&quot; name=&quot;Page-1&quot;&gt;7VdNc5swEP01HDsjkGPDsSVJe+lMZnzoWYENaAwsI8ux6a+vCCtA4KSu62kmSS+M9LT7tB9P0uDxuDx8VaLOv2MKhRew9ODxay8Igigy3xZoCOC8AzIl0w7yB2AtfwKBjNCdTGHrGGrEQsvaBROsKki0gwmlcO+aPWDh7lqLDGbAOhHFHP0hU513aHjFBvwbyCy3O/uMVkphjQnY5iLF/QjiNx6PFaLuRuUhhqKtna1L53f7zGofmIJKn+RAcTyKYkfJUWC6sdlmCnc1mYHScDhWY3Fvzdk8Br/PzCgCsAStGmNCRJy2JDH4pIV8VMG+edS4rCcZcjMDSu+ZVP3fpwpV+rnVh5ndF5hsPP4l16VhvPbN8AErTWI0re7mMRaonpw5Y8tlHBvcsNzKwnpttVDaslZYgcXIhj3NFW56LS1bbrM44l6m4Wq5MLhxzEDfgZKmAKDWtUhklRFNgqVM7LYb0Enu8I9j9dkVC80KtgS6Lb3fGnYVgXSm/1Ez2fFu7oeTYA/CuIUWU1AILR9d/mN9pR3uUJqde7F88leOWhYLl2GLO5UAOY2FP+GxMm3c6CwNlXlKY9oompFZ3Rps59EOkuw8BoH2BTtNs8EfaZbUdYZkXQGuXhDgR9DYRBycXURj00D+UmMT2ktJLnr9B8HG0IzFcPkHYfUe3oPZqfOjMEiDs1+KEw5n9P/+/1f3f/gq1394lt7erqQ+0HVvpsPPRWc+/KHxm18=&lt;/diagram&gt;&lt;/mxfile&gt;"><defs/><g><path d="M 13 57 L 13.01 57.01 L 15.87 50.14 L 18.37 43.14 L 20.91 36.15 L 23.67 29.25 L 26.4 22.33 Q 30 13 33.71 22.28 L 33.55 22.22 L 35.48 26.91 L 37.49 31.64 L 39.48 36.36 L 41.2 40.97 L 43.05 45.63" fill="none" stroke="#010508" stroke-opacity="0.1" stroke-width="6" stroke-linejoin="round" stroke-linecap="round" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 47.51 56.77 L 47.65 56.93 L 45.43 54.91 L 43.41 53.11 L 41.43 51.35 L 39.63 49.8 L 37.48 47.86 L 37.39 47.64 L 39.79 47.17 L 41.9 45.98 L 44.24 45.37 L 46.48 44.52 L 48.62 43.4 L 48.54 43.39 L 48.58 46.09 L 48.04 48.74 L 48.04 51.43 L 47.8 54.1 L 47.51 56.77 Z Z" fill-opacity="0.1" fill="#010508" stroke="#010508" stroke-opacity="0.1" stroke-width="6" stroke-linejoin="round" stroke-linecap="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 10 43 L 9.94 42.88 L 12.16 41.98 L 14.31 40.96 L 16.51 40.01 L 18.62 38.89 L 20.88 38.1 Q 30 34 40 34 L 40 33.75 L 42 33.83 L 44 33.8 L 46 33.79 L 48 34.05 L 50 34" fill="none" stroke="#010508" stroke-opacity="0.1" stroke-width="7" stroke-linejoin="round" stroke-linecap="round" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 10 54 L 9.97 53.99 L 12.69 47.07 L 15.43 40.16 L 18.07 33.21 L 20.65 26.24 L 23.4 19.33 Q 27 10 30.71 19.28 L 30.66 19.26 L 32.46 23.91 L 34.55 28.66 L 36.26 33.27 L 38.35 38.03 L 40.05 42.63" fill="none" stroke="#1982d2" stroke-width="6" stroke-linejoin="round" stroke-linecap="round" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 44.51 53.77 L 44.56 53.83 L 42.48 51.97 L 40.5 50.21 L 38.48 48.41 L 36.41 46.56 L 34.48 44.86 L 34.55 45.02 L 36.72 44 L 39 43.24 L 41.21 42.28 L 43.48 41.51 L 45.62 40.4 L 45.78 40.42 L 45.51 43.09 L 45.01 45.74 L 44.87 48.42 L 44.94 51.12 L 44.51 53.77 Z Z" fill="#1982d2" stroke="#1982d2" stroke-width="6" stroke-linejoin="round" stroke-linecap="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 7 40 L 7.02 40.05 L 9.28 39.25 L 11.33 38 L 13.48 36.96 L 15.73 36.14 L 17.88 35.1 Q 27 31 37 31 L 37 30.79 L 39 31.11 L 41 30.85 L 43 30.78 L 45 30.89 L 47 31" fill="none" stroke="#1982d2" stroke-width="8" stroke-linejoin="round" stroke-linecap="round" stroke-miterlimit="10" pointer-events="stroke"/></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

@ -0,0 +1,76 @@
<!-- HTML header for doxygen 1.9.1-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview
$search
$mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
<script type="text/javascript" src="$relpath^doxygen-awesome-darkmode-toggle.js"></script>
<script type="text/javascript">
DoxygenAwesomeDarkModeToggle.init()
</script>
<script type="text/javascript" src="$relpath^doxygen-awesome-fragment-copy-button.js"></script>
<script type="text/javascript">
DoxygenAwesomeFragmentCopyButton.init()
</script>
<script type="text/javascript" src="$relpath^doxygen-awesome-paragraph-link.js"></script>
<script type="text/javascript">
DoxygenAwesomeParagraphLink.init()
</script>
<script type="text/javascript" src="$relpath^doxygen-awesome-interactive-toc.js"></script>
<script type="text/javascript">
DoxygenAwesomeInteractiveToc.init()
</script>
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px;">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign" style="padding-left: 0.5em;">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em;">
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppException
{
public class NoRecipeSelectedException : RecipeException
{
public NoRecipeSelectedException() : base("No recipe is currently selected to perform this action.") { }
}
}

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
namespace AppException
{
public class RecipeException : Exception
{
public RecipeException() : base("Something went wrong with a recipe or a collection of recipe.") { }
public RecipeException(string message) : base(message) { }
}
}

@ -0,0 +1,9 @@

namespace AppException
{
public class RecipeNotFoundException : RecipeException
{
public RecipeNotFoundException() : base("Recipe not found.") { }
public RecipeNotFoundException(int id) : base($"Recipe id: '{id}'not found.") { }
}
}

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppException
{
public class BadMailFormatException : UserException
{
public BadMailFormatException() : base("Invalid mail format.") { }
public BadMailFormatException(string mail) : base($"'{mail}' is an invalid format.") { }
}
}

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppException
{
public class NoUserConnectedException : UserException
{
public NoUserConnectedException() : base("No user is currently connected.") { }
}
}

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppException
{
public class UserAlreadyConnectedException : UserException
{
public UserAlreadyConnectedException() : base("An user is already connected.") { }
}
}

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppException
{
public class UserException : Exception
{
public UserException() : base("Somthing went wrong with an User.") { }
public UserException(string message) : base(message) { }
}
}

@ -0,0 +1,8 @@
namespace AppException
{
public class UserNotFoundException : UserException
{
public UserNotFoundException() : base("User not found.") { }
public UserNotFoundException(string userMail) : base($"User with mail: '{userMail}' not found.") { }
}
}

@ -3,7 +3,11 @@
"path": "SAE-2.01.sln", "path": "SAE-2.01.sln",
"projects": [ "projects": [
"ConsoleApp\\ConsoleApp.csproj", "ConsoleApp\\ConsoleApp.csproj",
"AppException\\AppException.csproj",
"Managers\\Managers.csproj",
"Model\\Model.csproj", "Model\\Model.csproj",
"Persistance\\DataPersistence\\DataPersistence.csproj",
"Persistance\\FakePersistance\\FakePersistance.csproj",
"Tests\\Model_UnitTests\\Model_UnitTests.csproj" "Tests\\Model_UnitTests\\Model_UnitTests.csproj"
] ]
} }

@ -7,9 +7,12 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DataPersistence\DataPersistence.csproj" /> <ProjectReference Include="..\Managers\Managers.csproj" />
<ProjectReference Include="..\Model\Model.csproj" /> <ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\Persistance\DataPersistence\DataPersistence.csproj" />
<ProjectReference Include="..\Persistance\FakePersistance\FakePersistance.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -1,11 +1,5 @@
using ConsoleApp.Menu.Core; using ConsoleApp.Menu.Core;
using Model; using Model;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu namespace ConsoleApp.Menu
{ {
@ -39,14 +33,16 @@ namespace ConsoleApp.Menu
Recipe recipe = new Recipe( Recipe recipe = new Recipe(
title: title, title: title,
type: RecipeType.Unspecified,
priority: Priority.Fast,
id: null, id: null,
authorMail: masterMgr.CurrentConnectedUser?.Mail, authorMail: masterMgr.User.CurrentConnected?.Mail,
picture: "", picture: null)
ingredients: new List<Ingredient>(), {
preparationSteps: steps.ToArray() PreparationSteps = steps
); };
masterMgr.AddRecipe(recipe); masterMgr.Recipe.AddRecipeToData(recipe);
return null; return null;
} }
} }

@ -1,11 +1,5 @@
using ConsoleApp.Menu.Core; using ConsoleApp.Menu.Core;
using Model; using Model;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu namespace ConsoleApp.Menu
{ {
@ -30,14 +24,9 @@ namespace ConsoleApp.Menu
string surname = _selectList[2].Item.Input; string surname = _selectList[2].Item.Input;
string passwd = _selectList[3].Item.Input; string passwd = _selectList[3].Item.Input;
User user = new User( User user = masterMgr.User.CreateUser(mail, passwd, name, surname);
name: name,
surname: surname,
mail: mail,
password: passwd
);
masterMgr.Register(user); masterMgr.User.AddUserToData(user);
return null; return null;
} }
} }

@ -1,10 +1,5 @@
using System; using ConsoleApp.Menu.Core;
using System.Collections.Generic; using Model;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ConsoleApp.Menu.Core;
using Model.Managers;
namespace ConsoleApp.Menu namespace ConsoleApp.Menu
{ {
@ -38,7 +33,7 @@ namespace ConsoleApp.Menu
string mail = _selectList[0].Item.Input; string mail = _selectList[0].Item.Input;
string password = _selectList[1].Item.Input; string password = _selectList[1].Item.Input;
if (!_masterMgr.Login(mail, password)) if (!_masterMgr.User.LogIn(mail, password))
{ {
_wrongInput = true; _wrongInput = true;
return this; return this;

@ -1,12 +1,5 @@
using ConsoleApp.Menu.Core; using ConsoleApp.Menu.Core;
using Model.Managers;
using Model; using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataPersistence;
namespace ConsoleApp.Menu namespace ConsoleApp.Menu
{ {
@ -36,7 +29,7 @@ namespace ConsoleApp.Menu
try try
{ {
_masterMgr.DataMgr.Export(recipe, path); _masterMgr.Data.Export(recipe, path);
} }
catch (ArgumentNullException e) catch (ArgumentNullException e)
{ {

@ -1,11 +1,5 @@
using ConsoleApp.Menu.Core; using ConsoleApp.Menu.Core;
using Model; using Model;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu namespace ConsoleApp.Menu
{ {
@ -25,7 +19,7 @@ namespace ConsoleApp.Menu
string path = _selectList[0].Item.Input; string path = _selectList[0].Item.Input;
try try
{ {
masterMgr.DataMgr.Import<Recipe>(path); masterMgr.Data.Import<Recipe>(path);
} }
catch(ArgumentNullException e) catch(ArgumentNullException e)
{ {

@ -1,10 +1,5 @@
using ConsoleApp.Menu.Core; 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 namespace ConsoleApp.Menu
{ {
@ -20,7 +15,7 @@ namespace ConsoleApp.Menu
public override IMenu? Return() public override IMenu? Return()
{ {
_masterMgr.Logout(); _masterMgr.User.LogOut();
return base.Return(); return base.Return();
} }
} }

@ -1,12 +1,5 @@
using Model; 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 ConsoleApp.Menu.Core;
using Model.Managers;
namespace ConsoleApp.Menu namespace ConsoleApp.Menu
{ {
@ -47,7 +40,7 @@ namespace ConsoleApp.Menu
{ {
List<Selector<IMenu>> selectors = base.SearchInSelection(); List<Selector<IMenu>> selectors = base.SearchInSelection();
if (_masterMgr.CurrentConnectedUser == null) if (_masterMgr.User.CurrentConnected == null)
return selectors.Except(selectors.Where(s => s.Line == "User profile")) return selectors.Except(selectors.Where(s => s.Line == "User profile"))
.Except(selectors.Where(s => s.Line == "Logout")) .Except(selectors.Where(s => s.Line == "Logout"))
.Except(selectors.Where(s => s.Line == "Add recipe")).ToList(); .Except(selectors.Where(s => s.Line == "Add recipe")).ToList();

@ -1,11 +1,5 @@
using ConsoleApp.Menu.Core; using ConsoleApp.Menu.Core;
using Model.Managers;
using Model; using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu namespace ConsoleApp.Menu
{ {

@ -1,10 +1,4 @@
using Model; using Model;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu namespace ConsoleApp.Menu
{ {
@ -16,7 +10,10 @@ namespace ConsoleApp.Menu
public override void Update() public override void Update()
{ {
_recipeCollectionOnSearch = _masterMgr.GetCurrentUserRecipes(); if (_masterMgr.User.CurrentConnected is null)
throw new ArgumentNullException();
_recipeCollectionOnSearch = _masterMgr.Recipe.GetRecipeByAuthor(_masterMgr.User.CurrentConnected.Mail);
_allSelectors = ConvertRecipeCollectionInSelectors(); _allSelectors = ConvertRecipeCollectionInSelectors();
_selectList = SearchInSelection(); _selectList = SearchInSelection();

@ -1,12 +1,5 @@
using System; using ConsoleApp.Menu.Core;
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;
using Model.Managers;
namespace ConsoleApp.Menu namespace ConsoleApp.Menu
{ {
@ -36,7 +29,7 @@ namespace ConsoleApp.Menu
public override void Update() public override void Update()
{ {
_recipeCollectionOnSearch = _masterMgr.DataMgr.GetRecipes("all recipes"); _recipeCollectionOnSearch = _masterMgr.Recipe.GetAllRecipes();
_allSelectors = ConvertRecipeCollectionInSelectors(); _allSelectors = ConvertRecipeCollectionInSelectors();
base.Update(); base.Update();
} }
@ -46,7 +39,7 @@ namespace ConsoleApp.Menu
if (CurrentSelected == null) if (CurrentSelected == null)
return this; return this;
return new PlainText(CurrentSelected.ToString()); return new ShowRecipeInfos(CurrentSelected);
} }
#endregion #endregion
} }

@ -0,0 +1,37 @@
using ConsoleApp.Menu.Core;
using Model;
using System.Text;
namespace ConsoleApp.Menu
{
internal class ShowRecipeInfos : PlainText
{
public Recipe Recipe { get; private set; }
public ShowRecipeInfos(Recipe recipe)
: base("")
{
Recipe = recipe;
}
public override void Display()
{
StringBuilder sb = new StringBuilder($"[Recipe n°{Recipe.Id}] - {Recipe.Title}\n");
foreach (PreparationStep ps in Recipe.PreparationSteps)
{
sb.AppendFormat("\t* {0}\n", ps.ToString());
}
sb.AppendLine();
sb.AppendLine(Recipe.ConcatIngredients());
sb.AppendLine();
foreach (Review review in Recipe.Reviews)
{
sb.AppendLine(review.ToString());
}
sb.AppendLine();
sb.AppendLine($"Posted by: {Recipe.AuthorMail?.ToString()}");
Console.WriteLine(sb);
}
}
}

@ -1,11 +1,5 @@
using ConsoleApp.Menu.Core; using ConsoleApp.Menu.Core;
using Model; using Model;
using Model.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp.Menu namespace ConsoleApp.Menu
{ {
@ -22,10 +16,10 @@ namespace ConsoleApp.Menu
public override void Display() public override void Display()
{ {
Console.WriteLine( Console.WriteLine(
$"\nUser: {_masterMgr.CurrentConnectedUser}\n\n" $"\nUser: {_masterMgr.User.CurrentConnected}\n\n"
+ $"\tMail: {_masterMgr.CurrentConnectedUser?.Mail}\n" + $"\tMail: {_masterMgr.User.CurrentConnected?.Mail}\n"
+ $"\tName: {_masterMgr.CurrentConnectedUser?.Name}\n" + $"\tName: {_masterMgr.User.CurrentConnected?.Name}\n"
+ $"\tSurname: {_masterMgr.CurrentConnectedUser?.Surname}\n"); + $"\tSurname: {_masterMgr.User.CurrentConnected?.Surname}\n");
} }
} }
} }

@ -1,13 +1,7 @@
using ConsoleApp.Menu; using ConsoleApp.Menu;
using Model; using Model;
using DataPersistence; using DataPersistence;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ConsoleApp.Menu.Core; using ConsoleApp.Menu.Core;
using Model.Managers;
namespace ConsoleApp namespace ConsoleApp
{ {
@ -37,7 +31,6 @@ namespace ConsoleApp
public MenuManager(MasterManager masterManager, IMenu firstMenu) public MenuManager(MasterManager masterManager, IMenu firstMenu)
{ {
MasterMgr = masterManager; MasterMgr = masterManager;
MenuCallStack = new Stack<IMenu>(); MenuCallStack = new Stack<IMenu>();
MenuCallStack.Push(firstMenu); MenuCallStack.Push(firstMenu);
} }

@ -1,31 +1,52 @@
using ConsoleApp; using ConsoleApp;
using Model; using Model;
using DataPersistence; using DataPersistence;
using Model.Managers; using FakePersistance;
using Managers;
Console.WriteLine("Hello, World!\n\n");
string path = ""; // - path to the save file namespace ConsoleApp;
string strategy = "xml"; // - strategy is 'xml' or 'json' (/!\ this is case sensitive)
MasterManager masterMgr; public static class Program
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); public static void Main(string[] args)
} {
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)
// Initialize the data serializer
IDataSerializer dataSerializer = (strategy == "xml") ?
new DataContractXML(path)
: new DataContractJSON(path);
MenuManager menuMgr = new MenuManager(masterMgr); // Initialize the data manager
menuMgr.Loop(); IDataManager dataManager = (!File.Exists(Path.Combine(path, $"data.{strategy}"))) ?
new DataDefaultManager(new Stubs())
: new DataDefaultManager(dataSerializer);
// Save data. // Initialize the other managers
Console.Write("[ --SAVE-- ]:\t"); masterMgr.DataMgr.Save(); Console.WriteLine("Done."); IRecipeManager recipeManager = new RecipeDefaultManager(dataManager);
IPasswordManager passwordManager = new PasswordSHA256Manager();
IUserManager userManager = new UserDefaultManager(dataManager, passwordManager);
// Initialize the master manager
MasterManager Master = new MasterManager(dataManager, recipeManager, userManager);
Master.Setup();
MenuManager menuMgr = new MenuManager(Master);
menuMgr.Loop();
// Change the data serializer if the one in place is 'Stubs'
if (Master.Data.Serializer.GetType() == typeof(Stubs))
{
var data = Master.Data.Data;
dataManager = new DataDefaultManager(dataSerializer, data);
Master = new MasterManager(dataManager, recipeManager, userManager);
}
// Save data.
Console.Write("[ --SAVE-- ]:\t"); Master.Data.SaveData(); Console.WriteLine("Done.");
}
}

@ -1,147 +0,0 @@
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,56 @@
using Model;
namespace Managers
{
/// <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 DataDefaultManager : IDataManager
{
#region Attributes & Properties
public IDataSerializer Serializer { get; set; }
public IDictionary<string, List<object>> Data { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Constructor of the DataDefaultManager class. Take a IDataManager that will provide methods for the serialisation of the data.
/// </summary>
/// <param name="dataSerializer">The data manager that know how to serialize a file.</param>
/// <param name="data">The data set of the application.</param>
public DataDefaultManager(IDataSerializer dataSerializer,
IDictionary<string, List<object>>? data = null)
{
Serializer = dataSerializer;
if (data is null)
Data = new Dictionary<string, List<object>>();
else
Data = data;
}
#endregion
#region Methods
public void LoadData()
=> Data = Serializer.Load();
public void SaveData()
=> Serializer.Save(Data);
public void Import<T>(string pathOfTheFile)
where T : class
{
KeyValuePair<string, T> import = Serializer.Import<T>(pathOfTheFile);
Data[import.Key].Add(import.Value);
}
public void Export<T>(T obj, string pathToExport)
where T : class
=> Serializer.Export<T>(obj, pathToExport);
public ICollection<T> GetFromData<T>() where T : class
=> new List<T>(Data[typeof(T).Name].Cast<T>());
#endregion
}
}

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

@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace Model namespace Model
{ {
[DataContract(Name = "passmgr")] [DataContract(Name = "passmgr")]
public class PasswordSHA256 : IPasswordManager public class PasswordSHA256Manager : IPasswordManager
{ {
public string HashPassword(string password) public string HashPassword(string password)
{ {

@ -0,0 +1,112 @@
using AppException;
using Model;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Managers
{
public class RecipeDefaultManager : IRecipeManager
{
private IDataManager _dataManager;
public Recipe? CurrentSelected { get; set; } = null;
public RecipeDefaultManager(IDataManager dataManager)
{
_dataManager = dataManager;
}
public bool AddRecipeToData(Recipe recipe)
{
var recipeList = _dataManager.Data[nameof(Recipe)];
if (recipeList.Exists(r => r.Equals(recipe)))
return false;
_dataManager.Data[nameof(Recipe)].Add(recipe);
return true;
}
public RecipeCollection GetAllRecipes()
{
return new RecipeCollection(
"All recipes",
_dataManager.GetFromData<Recipe>().ToArray());
}
public RecipeCollection GetRecipeByAuthor(string authorMail)
{
User? author = _dataManager.GetFromData<User>()
.ToList()
.Find(u => u.Mail == authorMail);
if (author is null)
throw new UserNotFoundException(authorMail);
IEnumerable<Recipe> recipes = from Recipe r in _dataManager.GetFromData<Recipe>()
where r.AuthorMail == author.Mail
select r;
return new RecipeCollection(
$"{author.Name} {author.Surname}'s recipes", recipes.ToArray());
}
public RecipeCollection SearchRecipeByTitle(string title)
{
IEnumerable<Recipe> recipes = from Recipe recipe in _dataManager.GetFromData<Recipe>()
where recipe.Title.ToLower().Contains(title.ToLower())
select recipe;
return new RecipeCollection(
$"Search for '{title}'", recipes.ToArray());
}
public Recipe GetRecipeFromId(int id)
{
Recipe? recipe = _dataManager.GetFromData<Recipe>()
.ToList()
.Find(r => r.Id == id);
if (recipe is null)
throw new RecipeNotFoundException();
return recipe;
}
public RecipeCollection GetRecipesByPriorityOrder(IEnumerable<Priority> priorities)
{
List<Recipe> recipes = new List<Recipe>();
IEnumerable<Recipe> recipesWithCurrentPriority;
foreach (Priority priority in priorities)
{
recipesWithCurrentPriority = from Recipe recipe in _dataManager.GetFromData<Recipe>()
where recipe.Priority == priority
select recipe;
recipes.AddRange(recipesWithCurrentPriority);
}
return new RecipeCollection(
$"Suggestions", recipes.ToArray());
}
public bool ModifyCurrentSelected(Recipe newRecipe)
{
if (CurrentSelected is null)
throw new NoRecipeSelectedException();
try
{
var index = _dataManager.GetFromData<Recipe>().ToList()
.FindIndex(u => u.Equals(CurrentSelected));
_dataManager.Data[nameof(Recipe)][index] = newRecipe;
}
catch (ArgumentNullException e)
{
Debug.WriteLine("Recipe to modify not found.");
return false;
}
return true;
}
}
}

@ -0,0 +1,148 @@
using AppException;
using Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Managers
{
public class UserDefaultManager : IUserManager, INotifyPropertyChanged
{
private IDataManager _dataManager;
private IPasswordManager _passwordManager;
private User? _currentConnected = null;
public UserDefaultManager(IDataManager dataManager, IPasswordManager passwordManager)
{
_dataManager = dataManager;
_passwordManager = passwordManager;
}
public User? CurrentConnected
{
get => _currentConnected;
private set
{
_currentConnected = value;
OnPropertyChanged();
}
}
public IPasswordManager PasswordManager => _passwordManager;
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string pname = "")
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pname));
public bool AddUserToData(User user)
{
var userList = _dataManager.Data[nameof(User)];
if (userList.Exists(r => r.Equals(user)))
return false;
_dataManager.Data[nameof(User)].Add(user);
return true;
}
public User CreateUser(string mail, string password,
string? name = null, string? surname = null, string? profilePict = null)
{
if (name is null) name = $"user{GetRandomInt()}";
if (surname is null) surname = "";
if (profilePict is null) profilePict = "default_user_picture.png";
const string mailRegex = @"^([\w\.-]+)@([\w\.-]+\.\w+)$";
if (!Regex.Match(mail, mailRegex).Success)
throw new BadMailFormatException();
string hashedPassword = PasswordManager.HashPassword(password);
return new User(name, surname, mail, hashedPassword, profilePict);
}
public ICollection<User> GetAllUsers()
{
return _dataManager.GetFromData<User>();
}
public User GetUserFromMail(string mail)
{
User? user = _dataManager.GetFromData<User>().ToList()
.Find(u => u.Mail == mail);
if (user is null)
throw new UserNotFoundException();
return user;
}
public bool LogIn(string mail, string password)
{
if (CurrentConnected is not null)
throw new UserAlreadyConnectedException();
#if DEBUG
if (mail == "admin")
{
CurrentConnected = _dataManager.GetFromData<User>()
.FirstOrDefault(u => u.Mail == "admin@mctg.fr");
return true;
}
#endif
User? user = _dataManager.GetFromData<User>().ToList()
.Find(u => u.Mail == mail);
if (user is null)
return false;
if (!_passwordManager.VerifyPassword(user.Password, password))
return false;
CurrentConnected = user;
return true;
}
public void LogOut()
{
if (CurrentConnected is null)
throw new NoUserConnectedException();
CurrentConnected = null;
}
public bool ModifyCurrentConnected(User newUser)
{
try
{
var index = _dataManager.GetFromData<User>().ToList()
.FindIndex(u => u.Equals(CurrentConnected));
_dataManager.Data[nameof(User)][index] = newUser;
}
catch (ArgumentNullException e)
{
Debug.WriteLine("User to modify not found.\n" + e.Message);
return false;
}
return true;
}
private int GetRandomInt()
{
var randomGenerator = RandomNumberGenerator.Create();
byte[] data = new byte[16];
randomGenerator.GetBytes(data);
return Math.Abs(BitConverter.ToInt16(data));
}
}
}

@ -1,105 +0,0 @@
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
}
}

@ -1,44 +1,64 @@
using System; using System;
using System.Collections.Generic; using System.Collections;
using System.Linq; using System.Collections.Generic;
using System.Text; using System.Linq;
using System.Threading.Tasks; using System.Text;
using System.Threading.Tasks;
namespace Model
{ namespace Model
/// <summary> {
/// Interface that define the methods of a data serializer. /// <summary>
/// </summary> /// Define how to manage data.
public interface IDataManager /// </summary>
{ public interface IDataManager
/// <summary> {
/// Save all the data in a file. /// <summary>
/// </summary> /// The data manager injected that know how to serialize the data.
/// <param name="elements">The data to save.</param> /// <br/><remarks><i>The setter is actually public for testing purpose. It will be private after.</i></remarks>
void Save(Dictionary<string, List<object>> elements); /// <br/>See: <see cref="IDataSerializer"/>
/// </summary>
/// <summary> IDataSerializer Serializer { get; set; }
/// Load all the data from a file.
/// </summary> /// <summary>
/// <returns>The data loaded.</returns> /// The collection of all data. Each line of this dictionary has the type of the data as it key and the data for values.
Dictionary<string, List<object>> Load(); /// </summary>
IDictionary<string, List<object>> Data { get; }
/// <summary>
/// Import an element to the collection of data.
/// </summary> /// <summary>
/// <typeparam name="T">The type of the element to impoert</typeparam> /// Load the data. Used to update data written in the save file.
/// <param name="pathToImport">The path containing the name of the file.</param> /// <br/>See: <see cref="IDataSerializer.Load"/>
/// <returns>A pair where the key is the entry in the data and the value is the value to add on this entry.</returns> /// </summary>
public KeyValuePair<string, T> Import<T>(string pathToImport) void LoadData();
where T : class;
/// <summary>
/// <summary> /// Save the data. Call the Save method of the serializer.
/// Export an element from the collection of data. /// <br/>See: <see cref="IDataSerializer.Save(Dictionary{string, List{object}})"/>
/// </summary> /// </summary>
/// <typeparam name="T">The type of the exported object.</typeparam> void SaveData();
/// <param name="obj">The object to export.</param>
/// <param name="pathToExport">The path containing the name of the file created.</param> /// <summary>
public void Export<T>(T obj, string pathToExport) /// Import data from a file.
where T : class; /// <br/>See: <see cref="IDataSerializer.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>
void Import<T>(string pathOfTheFile) where T : class;
/// <summary>
/// Export the data from the collection of data.
/// <br/>See: <see cref="IDataSerializer.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>
void Export<T>(T obj, string pathToExport) where T : class;
/// <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>
ICollection<T> GetFromData<T>() where T : class;
}
}

@ -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 IDataSerializer
{
/// <summary>
/// Save all the data in a file.
/// </summary>
/// <param name="elements">The data to save.</param>
void Save(IDictionary<string, List<object>> elements);
/// <summary>
/// Load all the data from a file.
/// </summary>
/// <returns>The data loaded.</returns>
IDictionary<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,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
/// <summary>
/// Define how to manage passwords.
/// </summary>
public interface IPasswordManager
{
/// <summary>
/// Hash a plain text password.
/// </summary>
/// <param name="password">The plain password to hash.</param>
/// <returns>The hashed password.</returns>
public string HashPassword(string password);
/// <summary>
/// Verify a plain text password with a hashed one.
/// </summary>
/// <param name="hashedPassword">The hashed password.</param>
/// <param name="password">The plain text password.</param>
/// <returns>True is the password is correct, false otherwise.</returns>
public bool VerifyPassword(string hashedPassword,string password);
}
}

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
/// <summary>
/// Define how to manage recipes.
/// </summary>
public interface IRecipeManager
{
/// <summary>
/// Get or set the currently selected recipe.
/// </summary>
Recipe? CurrentSelected { get; set; }
/// <summary>
/// Get all the recipe in the data.
/// </summary>
/// <returns>The RecipeCollection containing the recipes stored in the data.</returns>
RecipeCollection GetAllRecipes();
/// <summary>
/// Get the recipe corresponding to the id.
/// </summary>
/// <param name="id">The id of the recipe we want.</param>
/// <returns>The recipe corresponding to the id.</returns>
Recipe GetRecipeFromId(int id);
/// <summary>
/// Search in data the recipes containing this part of title.
/// </summary>
/// <param name="title">Search string.</param>
/// <returns>The RecipeCollection of recipes corresponding to the search.</returns>
RecipeCollection SearchRecipeByTitle(string title);
/// <summary>
/// Get all the recipes created by the author.
/// </summary>
/// <param name="authorMail">The author's mail.</param>
/// <returns>The RecipeCollection of the recipe created by the author.</returns>
RecipeCollection GetRecipeByAuthor(string authorMail);
/// <summary>
/// Get an ordored list of the recipes sorted by the priority list.
/// </summary>
/// <param name="priority">The priority list.</param>
/// <returns>The RecipeCollection ordored by the priority list.</returns>
RecipeCollection GetRecipesByPriorityOrder(IEnumerable<Priority> priority);
/// <summary>
/// Add a recipe to the data.
/// </summary>
/// <param name="recipe">The recipe to add.</param>
/// <returns>Weither the adding succed or not.</returns>
bool AddRecipeToData(Recipe recipe);
/// <summary>
/// Modify the <see cref="CurrentSelected"/> recipe with a new one in the data.
/// </summary>
/// <param name="newRecipe">The new recipe</param>
/// <returns>Weither the modification succed or not.</returns>
bool ModifyCurrentSelected(Recipe newRecipe);
}
}

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
/// <summary>
/// Define how to manage an user.
/// </summary>
public interface IUserManager : INotifyPropertyChanged
{
/// <summary>
/// Get the current connected user. Null if no user is connected.
/// </summary>
User? CurrentConnected { get; }
/// <summary>
/// Get the password manager used to hash passords.
/// </summary>
IPasswordManager PasswordManager { get; }
/// <summary>
/// Get a collection of all users in the data.
/// </summary>
/// <returns>A collection of all user.</returns>
ICollection<User> GetAllUsers();
/// <summary>
/// Get an user by his email.
/// </summary>
/// <param name="mail">The user's mail.</param>
/// <returns>The user corresponding to the mail.</returns>
User GetUserFromMail(string mail);
/// <summary>
/// Create a new user. The mail and the password are required. Other can be null.
/// <br/>This function use the password manager to hash the plain text password.
/// </summary>
/// <param name="mail">The user's mail address.</param>
/// <param name="password">The user's plain text password.</param>
/// <param name="name">The user's name.</param>
/// <param name="surname">The user's surname.</param>
/// <param name="profilePict">The user's profile picture.</param>
/// <returns>A new user.</returns>
User CreateUser(string mail, string password,
string? name = null, string? surname = null, string? profilePict = null);
/// <summary>
/// Add an user in the data.
/// </summary>
/// <param name="user">The user to add.</param>
/// <returns>True is the user was correctly added to the data. False otherwise.</returns>
bool AddUserToData(User user);
/// <summary>
/// Modify the currently connected user.
/// </summary>
/// <param name="newUser">An user containing new user's properties to changes.</param>
/// <returns></returns>
bool ModifyCurrentConnected(User newUser);
/// <summary>
/// Log in an user. If the connection succed, pass the connected user to <see cref="CurrentConnected"/>.
/// </summary>
/// <param name="mail">The User's mail.</param>
/// <param name="password">The User's (plain text) password.</param>
/// <returns>True if the connection succed, false otherwise.</returns>
bool LogIn(string mail, string password);
/// <summary>
/// Log out the current connected user.
/// </summary>
void LogOut();
}
}

@ -1,13 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Model.Managers
namespace Model
{ {
/// <summary> /// <summary>
/// The Main manager of the model. /// The Main manager of the model.
@ -16,120 +12,46 @@ namespace Model.Managers
{ {
#region Attributes & Properties #region Attributes & Properties
/// <summary> /// <summary>
/// The currently connected user. 'null' if no user is connected. /// Manage the data of the application.
/// </summary>
public User? CurrentConnectedUser { get; private set; }
/// <summary>
/// The collection of all recipes loaded.
/// </summary> /// </summary>
public RecipeCollection Recipes { get; private set; } public IDataManager Data { get; private set; }
/// <summary> /// <summary>
/// The collection of all users loaded. /// Manage the recipes of the application.
/// </summary> /// </summary>
public List<User> Users { get; private set; } public IRecipeManager Recipe { get; private set; }
/// <summary> /// <summary>
/// The data manager for load, save, export and import data. /// Manage the users of the application.
/// </summary> /// </summary>
public DataManager DataMgr { get; private set; } public IUserManager User { get; private set; }
#endregion #endregion
#region Constructors #region Constructors
/// <summary> /// <summary>
/// Constructor of the MasterManager. /// Constructor of the MasterManager.
/// </summary> /// </summary>
/// <param name="dataManager">The serializer for the data.</param> /// <param name="dataManager">The data manager.</param>
public MasterManager(IDataManager dataManager) /// <param name="recipeManager">The recipes manager.</param>
/// <param name="userManager">The users manager.</param>
public MasterManager(IDataManager dataManager,
IRecipeManager recipeManager,
IUserManager userManager)
{ {
DataMgr = new DataManager(dataManager); Data = dataManager;
CurrentConnectedUser = null; Recipe = recipeManager;
Recipes = DataMgr.GetRecipes("all recipes"); User = userManager;
Users = DataMgr.GetUsers();
} }
#endregion #endregion
#region Methods #region Methods
/// <summary> /// <summary>
/// Log in an user. Test if the log in information are correct then connect the user. /// Setup all the necessary parameters before start.
/// </summary> /// </summary>
/// <param name="mail">The user's mail</param> public void Setup()
/// <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) Data.LoadData();
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;
} }
#endregion
/// <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
} }

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

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -8,7 +10,7 @@ using System.Threading.Tasks;
namespace Model namespace Model
{ {
[DataContract(Name = "ingredient")] [DataContract(Name = "ingredient")]
public class Ingredient public class Ingredient : INotifyPropertyChanged
{ {
#region Attributes #region Attributes
[DataMember(Name = "id")] [DataMember(Name = "id")]
@ -17,6 +19,7 @@ namespace Model
[DataMember(Name = "quantity")] [DataMember(Name = "quantity")]
private Quantity _quantity = new Quantity(1, Unit.unit); private Quantity _quantity = new Quantity(1, Unit.unit);
public event PropertyChangedEventHandler? PropertyChanged;
#endregion #endregion
#region Properties #region Properties
@ -33,6 +36,7 @@ namespace Model
throw new ArgumentNullException("Impossible de ne pas avoir de nom pour l'ingrédient"); throw new ArgumentNullException("Impossible de ne pas avoir de nom pour l'ingrédient");
} }
_name = value; _name = value;
OnPropertyChanged();
} }
} }
@ -41,8 +45,12 @@ namespace Model
/// </summary> /// </summary>
public Quantity QuantityI public Quantity QuantityI
{ {
get => _quantity; get { return _quantity; }
set => _quantity = value; set
{
_quantity = value;
OnPropertyChanged();
}
} }
@ -62,6 +70,12 @@ namespace Model
Name = name; Name = name;
QuantityI = quantity; QuantityI = quantity;
} }
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion #endregion
public override string ToString() public override string ToString()

@ -67,7 +67,7 @@ namespace Model
public override string ToString() public override string ToString()
{ {
return $"{Number}{UnitQ}"; return $"{Number} {UnitQ} de : ";
} }
} }
} }

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,10 +13,13 @@ namespace Model
/// Define a step of the preparation of a recipe. /// Define a step of the preparation of a recipe.
/// </summary> /// </summary>
[DataContract(Name = "preparation-step")] [DataContract(Name = "preparation-step")]
public class PreparationStep : IEquatable<PreparationStep> public class PreparationStep : IEquatable<PreparationStep> , INotifyPropertyChanged
{ {
#region Attributes #region Attributes
private string _description = ""; private string _description = "";
private int _order = 1;
public event PropertyChangedEventHandler? PropertyChanged;
#endregion #endregion
#region Properties #region Properties
@ -22,7 +27,15 @@ namespace Model
/// The order of this step in the preparation of the meal. /// The order of this step in the preparation of the meal.
/// </summary> /// </summary>
[DataMember(Name = "order")] [DataMember(Name = "order")]
public int Order { get; init; } public int Order
{
get { return _order; }
set
{
_order = value;
OnPropertyChanged();
}
}
/// <summary> /// <summary>
/// The description of the task the user need to do for this step of the preparation. <br/> /// The description of the task the user need to do for this step of the preparation. <br/>
@ -40,6 +53,12 @@ namespace Model
_description = value; _description = value;
} }
} }
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion #endregion
#region Constructors #region Constructors

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
public class ReadOnlyObservableRecipeCollection : ReadOnlyObservableCollection<Recipe>
{
private RecipeCollection _recipeObserved;
public string Description => _recipeObserved.Description;
public ReadOnlyObservableRecipeCollection(RecipeCollection recipeCollection)
: base(recipeCollection)
{
_recipeObserved = recipeCollection;
}
}
}

@ -19,6 +19,9 @@ namespace Model
[DataMember(Name = "image")] [DataMember(Name = "image")]
private string _image = ""; private string _image = "";
[DataMember(Name = "authorMail")]
private string _authorMail = "";
#endregion #endregion
#region Properties #region Properties
@ -32,13 +35,28 @@ namespace Model
/// List of reviews of this recipe. /// List of reviews of this recipe.
/// </summary> /// </summary>
[DataMember(Name = "reviews")] [DataMember(Name = "reviews")]
public List<Review> Reviews { get; private set; } public List<Review> Reviews { get; set; }
/// <summary> /// <summary>
/// AuthorMail's mail of the recipe. /// AuthorMail's mail of the recipe.
/// </summary> /// </summary>
[DataMember(Name = "authorMail")] public string? AuthorMail
public string? AuthorMail { get; private set; } {
get => _authorMail;
set
{
if (string.IsNullOrEmpty(value))
_authorMail = "admin@mctg.fr";
else
_authorMail = value;
}
}
/// <summary>
/// Priority of this recipe.
/// </summary>
[DataMember(Name = "priority")]
public Priority Priority { get; set; }
/// <summary> /// <summary>
/// The Title of the recipe. <br/> /// The Title of the recipe. <br/>
@ -65,12 +83,14 @@ namespace Model
get => _image; get => _image;
set set
{ {
if (!string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
_image = "room_service_icon.png"; _image = "room_service_icon.png";
_image = value; else
_image = value;
} }
} }
/// <summary>
/// The list of ingredients. /// The list of ingredients.
/// </summary> /// </summary>
[DataMember(Name = "ingredient")] [DataMember(Name = "ingredient")]
@ -82,30 +102,36 @@ namespace Model
/// </summary> /// </summary>
[DataMember(Name = "preparation-steps")] [DataMember(Name = "preparation-steps")]
public List<PreparationStep> PreparationSteps { get; set; } public List<PreparationStep> PreparationSteps { get; set; }
/// <summary>
/// The type of recipe.
/// </summary>
[DataMember(Name = "type")]
public RecipeType Type { get; set; }
#endregion #endregion
#region Constructors #region Constructors
/// <summary> /// <summary>
/// Construct a new recipe. /// Construct a new recipe.
/// </summary> /// </summary>
/// <param _name="title">The title of the recipe</param> /// <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="type">The type of the recipe.</param>
/// <param _name="authorMail">The name of the user that create this recipe.</param> /// <param name="priority">The priority of this recipe.</param>
/// <param _name="picture"> The image that represent the recipe</param> /// <param name="id">The id of the recipe. If not given, get a new id.</param>
/// <param _name="reviews">Thr list of reviews.</param> /// <param name="authorMail">The name of the user that create this recipe.</param>
/// <param _name="ingredients">Thr list of ingredients.</param> /// <param name="picture"> The image that represent the recipe</param>
/// <param _name="preparationSteps">The steps of the preparation of the meal</param> public Recipe(string title, RecipeType type, Priority priority, int? id, string? authorMail, string? picture)
public Recipe(string title, int? id, string? authorMail, string? picture,
List<Review> reviews, List<Ingredient> ingredients,
params PreparationStep[] preparationSteps)
{ {
Title = title; Title = title;
Type = type;
Priority = priority;
Image = picture; Image = picture;
PreparationSteps = new List<PreparationStep>(preparationSteps);
Ingredients = ingredients;
Reviews = reviews;
AuthorMail = authorMail; AuthorMail = authorMail;
PreparationSteps = new List<PreparationStep>();
Ingredients = new List<Ingredient>();
Reviews = new List<Review>();
if (id == null) if (id == null)
{ {
var randomGenerator = RandomNumberGenerator.Create(); var randomGenerator = RandomNumberGenerator.Create();
@ -116,46 +142,23 @@ namespace Model
else Id = (int)id; else Id = (int)id;
} }
/// <inheritdoc cref="Recipe.Recipe(string, RecipeType, Priority, int?, string?, string?)"/>
/// <summary> public Recipe(string title, RecipeType type, Priority priority, int? id, string? authorMail)
/// <inheritdoc cref="Recipe.Recipe(string, int?, List{Review}, PreparationStep[])"/> : this(title, type, priority, id, authorMail, null)
/// </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, RecipeType, Priority, int?, string?, string?)"/>
/// <inheritdoc cref="Recipe.Recipe(string, int?, List{Review}, PreparationStep[])"/> public Recipe(string title, RecipeType type, Priority priority)
/// </summary> : this(title, type, priority, null, null)
/// <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, RecipeType, Priority, int?, string?, string?)"/>
///// <inheritdoc cref="Recipe.Recipe(string, int?, List{Review}, PreparationStep[])"/> public Recipe(string title)
///// </summary> : this(title, RecipeType.Unspecified, Priority.Fast)
///// <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 #endregion
#region Methods #region Methods
@ -184,7 +187,7 @@ namespace Model
/// Concatenate the list of ingredients in a single string /// Concatenate the list of ingredients in a single string
/// </summary> /// </summary>
/// <returns>The list of ingredients in string format</returns> /// <returns>The list of ingredients in string format</returns>
private string ConcatIngredients() public string ConcatIngredients()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
foreach (Ingredient ingredient in Ingredients) foreach (Ingredient ingredient in Ingredients)
@ -205,9 +208,11 @@ namespace Model
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
var item = obj as Recipe; if (ReferenceEquals(obj, null)) return false;
if (item == null) return false; if (ReferenceEquals(obj, this)) return true;
return Equals(obj); if (GetType() != obj.GetType()) return false;
return Equals(obj as Recipe);
} }
public override int GetHashCode() public override int GetHashCode()
@ -217,21 +222,7 @@ namespace Model
public override string ToString() public override string ToString()
{ {
StringBuilder sb = new StringBuilder($"[Recipe n°{Id}] - {Title}\n"); return $"'{Title}' [{Id}] - {Type}, {Priority} | {AuthorMail} | {Image}";
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 #endregion
} }

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

@ -1,4 +1,4 @@
using Model.Managers; using Model;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

@ -1,15 +0,0 @@
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);
}
}

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

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

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

@ -6,8 +6,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" /> <ProjectReference Include="..\..\Model\Model.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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

@ -0,0 +1,205 @@
using Managers;
using Model;
using System;
using System.Collections.Generic;
namespace FakePersistance
{
/// <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 : IDataSerializer
{
private IPasswordManager psswdMgr = new PasswordSHA256Manager();
public IDictionary<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("Cookies classiques", RecipeType.Dessert, Priority.Easy, null, "admin@mctg.fr", "")
{
Ingredients = new List<Ingredient>
{
new Ingredient("Patates", new Quantity(23, Unit.unit)),
new Ingredient("Farine", new Quantity(23, Unit.G))
},
PreparationSteps = new List<PreparationStep>
{
new PreparationStep(1, "Faire cuire."),
new PreparationStep(2, "Manger.")
},
Reviews = new List<Review>
{
new Review(4, "Bonne recette, je recommande !"),
new Review(3, "Bof bof, mais mangeable...")
}
},
new Recipe(
title: "Cookies au chocolat",
type: RecipeType.Dessert,
priority: Priority.Fast)
{
Ingredients = new List<Ingredient>
{
new Ingredient("Farine", new Quantity(200, Unit.G))
},
PreparationSteps = new List<PreparationStep>()
{
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",
type: RecipeType.Dessert,
priority: Priority.Gourmet)
{
Ingredients = new List<Ingredient>
{
new Ingredient("Farine", new Quantity(200, Unit.G)),
new Ingredient("Lait", new Quantity(2, Unit.L))
},
PreparationSteps = new List<PreparationStep>
{
new PreparationStep(1, "Achetez les ingrédients."),
new PreparationStep(2, "Préparez le matériel. Ustensiles et tout."),
new PreparationStep(3, "Pleurez.")
},
Reviews = new List<Review>
{
new Review("pedrosamigos@hotmail.com", 5, "C'était vraiment IN-CROY-ABLE !!!")
}
},
new Recipe(
title: "Gateau au pommes",
type: RecipeType.Dessert,
priority: Priority.Light)
{
PreparationSteps = new List<PreparationStep>
{
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",
type: RecipeType.Dessert,
priority: Priority.Economic,
id: null, authorMail: "pedrosamigos@hotmail.com")
{
Ingredients = new List<Ingredient>
{
new Ingredient("Mais", new Quantity(2, Unit.kG)),
new Ingredient("Sachet pépites de chocolat", new Quantity(1, Unit.unit)),
new Ingredient("Dinde", new Quantity(2, Unit.G))
},
PreparationSteps = new List<PreparationStep>
{
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",
type: RecipeType.Dish,
priority: Priority.Easy,
id: null, authorMail: "pedrosamigos@hotmail.com")
{
Ingredients = new List<Ingredient>
{
new Ingredient("Morceaux de bois", new Quantity(2, Unit.unit)),
new Ingredient("Sachet gélatine", new Quantity(1, Unit.unit)),
new Ingredient("Jambon", new Quantity(2, Unit.kG))
},
PreparationSteps = new List<PreparationStep>
{
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",
type: RecipeType.Dish,
priority: Priority.Gourmet,
id: null, authorMail: "pedrosamigos@hotmail.com")
{
Ingredients = new List<Ingredient>
{
new Ingredient("Pissenlis", new Quantity(200, Unit.unit)),
new Ingredient("Boule de pétanque", new Quantity(10, Unit.unit)),
new Ingredient("Poivre", new Quantity(4, Unit.mG))
},
PreparationSteps = new List<PreparationStep>
{
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 !")
},
Reviews = new List<Review>
{
new Review(5, "Meilleure recette que j'ai avalé de tout les temps !!!!!!!")
}
}
})
#endregion
},
{
#region Data: User
nameof(User),
new List<object>(new[]
{
new User(
name: "Admin",
surname: "Admin",
mail: "admin@mctg.fr",
hashedPassword: psswdMgr.HashPassword("admin")),
new User(
name: "Pedros",
surname: "Amigos",
mail: "pedrosamigos@hotmail.com",
hashedPassword: psswdMgr.HashPassword("pamigos"))
})
#endregion
}
};
return data;
}
#region Not supported methods
public void Save(IDictionary<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
}
}

@ -1,55 +1,77 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.5.33516.290 VisualStudioVersion = 17.5.33516.290
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "ConsoleApp\ConsoleApp.csproj", "{666C2211-8EBB-4FC8-9484-CB93BC854153}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "ConsoleApp\ConsoleApp.csproj", "{666C2211-8EBB-4FC8-9484-CB93BC854153}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{42FF86BD-92F9-4A32-A938-68515905378F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{42FF86BD-92F9-4A32-A938-68515905378F}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Views", "Views\Views.csproj", "{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Views", "Views\Views.csproj", "{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model_UnitTests", "Tests\Model_UnitTests\Model_UnitTests.csproj", "{45AB746A-194B-4E43-81EB-83B06F35AA33}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model_UnitTests", "Tests\Model_UnitTests\Model_UnitTests.csproj", "{45AB746A-194B-4E43-81EB-83B06F35AA33}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{08B80CE8-A01D-4D86-8989-AF225D5DA48C}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{08B80CE8-A01D-4D86-8989-AF225D5DA48C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPersistence", "DataPersistence\DataPersistence.csproj", "{432F9D12-B1F7-4A79-8720-4971BB10B831}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataPersistence", "Persistance\DataPersistence\DataPersistence.csproj", "{432F9D12-B1F7-4A79-8720-4971BB10B831}"
EndProject EndProject
Global Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managers", "Managers\Managers.csproj", "{A3703A19-687C-4F63-A5DE-18E6D8995C77}"
GlobalSection(SolutionConfigurationPlatforms) = preSolution EndProject
Debug|Any CPU = Debug|Any CPU Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppException", "AppException\AppException.csproj", "{77E6BD97-B1E5-45F5-ABFB-9A1D985A8EDE}"
Release|Any CPU = Release|Any CPU EndProject
EndGlobalSection Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FakePersistance", "Persistance\FakePersistance\FakePersistance.csproj", "{7C340CB2-8925-4BC4-9D8C-9058D9657F3F}"
GlobalSection(ProjectConfigurationPlatforms) = postSolution EndProject
{666C2211-8EBB-4FC8-9484-CB93BC854153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Persistance", "Persistance", "{F6413DA3-CE67-4097-8FF7-8D221AF2A5E8}"
{666C2211-8EBB-4FC8-9484-CB93BC854153}.Debug|Any CPU.Build.0 = Debug|Any CPU EndProject
{666C2211-8EBB-4FC8-9484-CB93BC854153}.Release|Any CPU.ActiveCfg = Release|Any CPU Global
{666C2211-8EBB-4FC8-9484-CB93BC854153}.Release|Any CPU.Build.0 = Release|Any CPU GlobalSection(SolutionConfigurationPlatforms) = preSolution
{42FF86BD-92F9-4A32-A938-68515905378F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
{42FF86BD-92F9-4A32-A938-68515905378F}.Debug|Any CPU.Build.0 = Debug|Any CPU Release|Any CPU = Release|Any CPU
{42FF86BD-92F9-4A32-A938-68515905378F}.Release|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection
{42FF86BD-92F9-4A32-A938-68515905378F}.Release|Any CPU.Build.0 = Release|Any CPU GlobalSection(ProjectConfigurationPlatforms) = postSolution
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {666C2211-8EBB-4FC8-9484-CB93BC854153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Debug|Any CPU.Build.0 = Debug|Any CPU {666C2211-8EBB-4FC8-9484-CB93BC854153}.Debug|Any CPU.Build.0 = Debug|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {666C2211-8EBB-4FC8-9484-CB93BC854153}.Release|Any CPU.ActiveCfg = Release|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Release|Any CPU.ActiveCfg = Release|Any CPU {666C2211-8EBB-4FC8-9484-CB93BC854153}.Release|Any CPU.Build.0 = Release|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Release|Any CPU.Build.0 = Release|Any CPU {42FF86BD-92F9-4A32-A938-68515905378F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Release|Any CPU.Deploy.0 = Release|Any CPU {42FF86BD-92F9-4A32-A938-68515905378F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45AB746A-194B-4E43-81EB-83B06F35AA33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {42FF86BD-92F9-4A32-A938-68515905378F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45AB746A-194B-4E43-81EB-83B06F35AA33}.Debug|Any CPU.Build.0 = Debug|Any CPU {42FF86BD-92F9-4A32-A938-68515905378F}.Release|Any CPU.Build.0 = Release|Any CPU
{45AB746A-194B-4E43-81EB-83B06F35AA33}.Release|Any CPU.ActiveCfg = Release|Any CPU {508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{45AB746A-194B-4E43-81EB-83B06F35AA33}.Release|Any CPU.Build.0 = Release|Any CPU {508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Debug|Any CPU.Build.0 = Debug|Any CPU
{432F9D12-B1F7-4A79-8720-4971BB10B831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{432F9D12-B1F7-4A79-8720-4971BB10B831}.Debug|Any CPU.Build.0 = Debug|Any CPU {508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Release|Any CPU.ActiveCfg = Release|Any CPU
{432F9D12-B1F7-4A79-8720-4971BB10B831}.Release|Any CPU.ActiveCfg = Release|Any CPU {508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Release|Any CPU.Build.0 = Release|Any CPU
{432F9D12-B1F7-4A79-8720-4971BB10B831}.Release|Any CPU.Build.0 = Release|Any CPU {508B5600-AFD0-4AE4-A3CF-5FA8BE3ECE75}.Release|Any CPU.Deploy.0 = Release|Any CPU
EndGlobalSection {45AB746A-194B-4E43-81EB-83B06F35AA33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
GlobalSection(SolutionProperties) = preSolution {45AB746A-194B-4E43-81EB-83B06F35AA33}.Debug|Any CPU.Build.0 = Debug|Any CPU
HideSolutionNode = FALSE {45AB746A-194B-4E43-81EB-83B06F35AA33}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection {45AB746A-194B-4E43-81EB-83B06F35AA33}.Release|Any CPU.Build.0 = Release|Any CPU
GlobalSection(NestedProjects) = preSolution {432F9D12-B1F7-4A79-8720-4971BB10B831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{45AB746A-194B-4E43-81EB-83B06F35AA33} = {08B80CE8-A01D-4D86-8989-AF225D5DA48C} {432F9D12-B1F7-4A79-8720-4971BB10B831}.Debug|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection {432F9D12-B1F7-4A79-8720-4971BB10B831}.Release|Any CPU.ActiveCfg = Release|Any CPU
GlobalSection(ExtensibilityGlobals) = postSolution {432F9D12-B1F7-4A79-8720-4971BB10B831}.Release|Any CPU.Build.0 = Release|Any CPU
SolutionGuid = {ADEA5603-1EF6-4D43-9493-7D6D9DE7FA3F} {A3703A19-687C-4F63-A5DE-18E6D8995C77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
EndGlobalSection {A3703A19-687C-4F63-A5DE-18E6D8995C77}.Debug|Any CPU.Build.0 = Debug|Any CPU
EndGlobal {A3703A19-687C-4F63-A5DE-18E6D8995C77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3703A19-687C-4F63-A5DE-18E6D8995C77}.Release|Any CPU.Build.0 = Release|Any CPU
{77E6BD97-B1E5-45F5-ABFB-9A1D985A8EDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77E6BD97-B1E5-45F5-ABFB-9A1D985A8EDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77E6BD97-B1E5-45F5-ABFB-9A1D985A8EDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77E6BD97-B1E5-45F5-ABFB-9A1D985A8EDE}.Release|Any CPU.Build.0 = Release|Any CPU
{7C340CB2-8925-4BC4-9D8C-9058D9657F3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C340CB2-8925-4BC4-9D8C-9058D9657F3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C340CB2-8925-4BC4-9D8C-9058D9657F3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C340CB2-8925-4BC4-9D8C-9058D9657F3F}.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}
{432F9D12-B1F7-4A79-8720-4971BB10B831} = {F6413DA3-CE67-4097-8FF7-8D221AF2A5E8}
{7C340CB2-8925-4BC4-9D8C-9058D9657F3F} = {F6413DA3-CE67-4097-8FF7-8D221AF2A5E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {ADEA5603-1EF6-4D43-9493-7D6D9DE7FA3F}
EndGlobalSection
EndGlobal

@ -1,27 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
<Configurations>Debug;Release;CI</Configurations> <Configurations>Debug;Release;CI</Configurations>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2"> <PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Model\Model.csproj" /> <ItemGroup>
<ProjectReference Include="..\..\DataPersistence\DataPersistence.csproj" /> <ProjectReference Include="..\..\Managers\Managers.csproj" />
</ItemGroup> <ProjectReference Include="..\..\Persistance\DataPersistence\DataPersistence.csproj" />
</Project> <ProjectReference Include="..\..\Persistance\FakePersistance\FakePersistance.csproj" />
</ItemGroup>
</Project>

@ -1,25 +0,0 @@
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);
}
}
}

@ -8,10 +8,24 @@ namespace Model_UnitTests
public void TestVoidConstructor() public void TestVoidConstructor()
{ {
Recipe r = new Recipe( Recipe r = new Recipe(
title: "test recipe", title: "test recipe", type: RecipeType.Unspecified, priority: Priority.Easy);
id: null,
authorMail: "test@test.fr");
Assert.NotNull(r.Title); Assert.NotNull(r.Title);
} }
[Theory]
[InlineData("recipe", RecipeType.Dish, Priority.Light, "recipe", RecipeType.Dish, Priority.Light)]
[InlineData("No title.", RecipeType.Unspecified, Priority.Light, "", RecipeType.Unspecified, Priority.Light)]
[InlineData("re cipe", RecipeType.Unspecified, Priority.Light, "re cipe", RecipeType.Unspecified, Priority.Light)]
public void TestValuesConstructor(
string expectedTitle, RecipeType expectedType, Priority expectedPriority,
string title, RecipeType type, Priority priority)
{
Recipe rc = new Recipe(title, type, priority);
Assert.Equal(expectedTitle, rc.Title);
Assert.Equal(expectedType, rc.Type);
Assert.Equal(expectedPriority, rc.Priority);
}
} }
} }

@ -12,8 +12,8 @@ namespace Model_UnitTests
[Fact] [Fact]
public void TestConstructUser() public void TestConstructUser()
{ {
PasswordSHA256 passwordManager = new PasswordSHA256(); PasswordSHA256Manager passwordManager = new PasswordSHA256Manager();
User user = new User("Bob", "Dylan", "bd@gmail.com", "bobby"); User user = new User("Bob", "Dylan", "bd@gmail.com", passwordManager.HashPassword("bobby"));
Assert.Equal("Bob", user.Name); Assert.Equal("Bob", user.Name);
Assert.Equal("Dylan", user.Surname); Assert.Equal("Dylan", user.Surname);
Assert.Equal("bd@gmail.com", user.Mail); Assert.Equal("bd@gmail.com", user.Mail);

@ -1,80 +0,0 @@
<?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>

@ -1,18 +0,0 @@
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();
}
}
}

@ -1,55 +1,126 @@
#if WINDOWS using Model;
using Microsoft.UI; using FakePersistance;
using Microsoft.UI.Windowing;
using Windows.Graphics;
#endif
using DataPersistence; using DataPersistence;
using Model; using Managers;
using System.Collections.ObjectModel; using System.Diagnostics;
using Model.Managers; using System.Runtime.Serialization;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization.Json;
namespace Views namespace Views
{ {
public partial class App : Application public partial class App : Application
{ {
//Point d'entrée de l'application /// <summary>
public MasterManager MasterMgr { get; private set; } = new MasterManager(new Stubs()); /// Master manager - access to the Model.
/// </summary>
//L'utilisateur courant de l'application public MasterManager Master { get; private set; }
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() public App()
{ {
CurrentUser = MasterMgr.DataMgr.GetUsers().Last(); Debug.WriteLine("Hello, World!\n\n");
AllRecipes = MasterMgr.DataMgr.GetRecipes("All recipes");
string path = FileSystem.Current.AppDataDirectory; // - path to the save file
string strategy = "json"; // - strategy is 'xml' or 'json' (/!\ this is case sensitive)
// Initialize the data serializer
IDataSerializer dataSerializer = (strategy == "xml") ?
new DataContractXML(path)
: new DataContractJSON(path);
// Initialize the data manager
IDataManager dataManager;
if (!File.Exists(Path.Combine(path, $"data.{strategy}")))
{
var data = LoadJSONBundledFilesAsync("data.json");
dataManager = new DataDefaultManager(dataSerializer, data);
}
else
{
dataManager = new DataDefaultManager(dataSerializer);
dataManager.LoadData();
}
// Initialize the other managers
IRecipeManager recipeManager = new RecipeDefaultManager(dataManager);
IPasswordManager passwordManager = new PasswordSHA256Manager();
IUserManager userManager = new UserDefaultManager(dataManager, passwordManager);
// Initialize the master manager
Master = new MasterManager(dataManager, recipeManager, userManager);
// Save data.
Debug.Write($"[ {DateTime.Now:H:mm:ss} ] Saving...\t");
Master.Data.SaveData();
Debug.WriteLine("Done.");
InitializeComponent(); 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; UserAppTheme = AppTheme.Light;
MainPage = new Home(); MainPage = new Home();
//MainPage = new MyPosts(); //MainPage = new MyPosts();
} }
protected override void OnSleep()
{
// Save data.
Debug.Write($"[ {DateTime.Now:H:mm:ss} ] Saving...\t");
Master.Data.SaveData();
Debug.WriteLine("Done.");
Debug.WriteLine(FileSystem.Current.AppDataDirectory);
base.OnSleep();
}
/// <summary>
/// Load XML raw assets from data.
/// </summary>
/// <param name="path">The path in the raw assets directory.</param>
/// <returns>A dictionary containing the data loaded.</returns>
private static IDictionary<string, List<object>> LoadXMLBundledFilesAsync(string path)
{
DataContractSerializerSettings _dataContractSerializerSettings =
new DataContractSerializerSettings()
{
KnownTypes = new Type[]
{
typeof(Recipe), typeof(RecipeType), typeof(Priority), typeof(Review), typeof(User), typeof(Ingredient), typeof(Quantity)
},
PreserveObjectReferences = true
};
var serializer = new DataContractSerializer(typeof(Dictionary<string, List<object>>), _dataContractSerializerSettings);
IDictionary<string, List<Object>> data;
using Stream stream = FileSystem.Current.OpenAppPackageFileAsync(path).Result;
data = serializer.ReadObject(stream) as IDictionary<string, List<Object>>;
return data;
}
/// <summary>
/// Load JSON raw assets from data.
/// </summary>
/// <param name="path">The path in the raw assets directory.</param>
/// <returns>A dictionary containing the data loaded.</returns>
private static IDictionary<string, List<object>> LoadJSONBundledFilesAsync(string path)
{
DataContractJsonSerializerSettings _dataContractJsonSerializerSettings =
new DataContractJsonSerializerSettings()
{
KnownTypes = new Type[]
{
typeof(Recipe), typeof(RecipeType), typeof(Priority), typeof(Review), typeof(User), typeof(Ingredient), typeof(Quantity)
}
};
var jsonSerializer = new DataContractJsonSerializer(typeof(Dictionary<string, List<object>>), _dataContractJsonSerializerSettings);
IDictionary<string, List<Object>> data;
using Stream stream = FileSystem.Current.OpenAppPackageFileAsync(path).Result;
data = jsonSerializer.ReadObject(stream) as IDictionary<string, List<Object>>;
return data;
}
} }
} }

@ -1,62 +0,0 @@
<?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>

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

Loading…
Cancel
Save