Merge pull request 'fix/app-ready' (#66) from fix/app-ready into dev
continuous-integration/drone/push Build is passing Details

Reviewed-on: #66
pull/67/head^2
Alexandre AGOSTINHO 2 years ago
commit c3fb6f1994

@ -12,5 +12,20 @@ namespace Model_UnitTests
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);
}
} }
} }

@ -5,6 +5,7 @@ using Managers;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization.Json;
namespace Views namespace Views
{ {
@ -20,7 +21,7 @@ namespace Views
Debug.WriteLine("Hello, World!\n\n"); Debug.WriteLine("Hello, World!\n\n");
string path = FileSystem.Current.AppDataDirectory; // - path to the save file string path = FileSystem.Current.AppDataDirectory; // - path to the save file
string strategy = "xml"; // - strategy is 'xml' or 'json' (/!\ this is case sensitive) string strategy = "json"; // - strategy is 'xml' or 'json' (/!\ this is case sensitive)
// Initialize the data serializer // Initialize the data serializer
IDataSerializer dataSerializer = (strategy == "xml") ? IDataSerializer dataSerializer = (strategy == "xml") ?
@ -31,7 +32,7 @@ namespace Views
IDataManager dataManager; IDataManager dataManager;
if (!File.Exists(Path.Combine(path, $"data.{strategy}"))) if (!File.Exists(Path.Combine(path, $"data.{strategy}")))
{ {
var data = LoadXMLBundledFilesAsync("data.xml"); var data = LoadJSONBundledFilesAsync("data.json");
dataManager = new DataDefaultManager(dataSerializer, data); dataManager = new DataDefaultManager(dataSerializer, data);
} }
else else
@ -72,6 +73,7 @@ namespace Views
base.OnSleep(); base.OnSleep();
} }
/// <summary> /// <summary>
/// Load XML raw assets from data. /// Load XML raw assets from data.
/// </summary> /// </summary>
@ -79,9 +81,8 @@ namespace Views
/// <returns>A dictionary containing the data loaded.</returns> /// <returns>A dictionary containing the data loaded.</returns>
private static IDictionary<string, List<object>> LoadXMLBundledFilesAsync(string path) private static IDictionary<string, List<object>> LoadXMLBundledFilesAsync(string path)
{ {
//using Stream stream = await FileSystem.Current.OpenAppPackageFileAsync(path); DataContractSerializerSettings _dataContractSerializerSettings =
DataContractSerializerSettings _dataContractSerializerSettings new DataContractSerializerSettings()
= new DataContractSerializerSettings()
{ {
KnownTypes = new Type[] KnownTypes = new Type[]
{ {
@ -97,5 +98,29 @@ namespace Views
return data; 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;
}
} }
} }

@ -13,6 +13,9 @@ namespace Views
private Ingredient ingredient; private Ingredient ingredient;
private PreparationStep preparationStep; private PreparationStep preparationStep;
private string titleRecipe; private string titleRecipe;
public FileResult ImageSource { get; private set; } = null;
public string? ImageSourcePath { get; private set; } = null;
public MasterManager Master => (Application.Current as App).Master; public MasterManager Master => (Application.Current as App).Master;
public User CurrentUser => Master.User.CurrentConnected; public User CurrentUser => Master.User.CurrentConnected;
public Recipe RecipeToAdd{ get=> recipeToAdd; set => recipeToAdd = value; } public Recipe RecipeToAdd{ get=> recipeToAdd; set => recipeToAdd = value; }
@ -38,20 +41,30 @@ namespace Views
IngredientList = new List<Ingredient>(); IngredientList = new List<Ingredient>();
PreparationStepList = new List<PreparationStep>(); PreparationStepList = new List<PreparationStep>();
} }
private void PickPhoto(object sender, EventArgs e) private async void PickPhoto(object sender, EventArgs e)
{ {
MediaPicker.PickPhotoAsync(); ImageSource = await MediaPicker.Default.PickPhotoAsync();
} }
private void AddRecipeValidation(object sender, EventArgs e) private async void AddRecipeValidation(object sender, EventArgs e)
{ {
if (string.IsNullOrWhiteSpace(TitleRecipe)) if (string.IsNullOrWhiteSpace(TitleRecipe))
{ {
DisplayAlert("Erreur", "Entrez un nom de recette.", "Ok"); await DisplayAlert("Erreur", "Entrez un nom de recette.", "Ok");
return; return;
} }
if (ImageSource != null)
{
// save the file into local storage
ImageSourcePath = Path.Combine(FileSystem.Current.AppDataDirectory, $"{TitleRecipe.Replace(" ", "")}.{ImageSource.FileName}");
using Stream sourceStream = await ImageSource.OpenReadAsync();
using FileStream localFileStream = File.OpenWrite(ImageSourcePath);
await sourceStream.CopyToAsync(localFileStream);
}
RecipeType newRecipeType = GetSelectedRecipeType(); RecipeType newRecipeType = GetSelectedRecipeType();
Priority selectedPriority = GetSelectedPriority(); Priority selectedPriority = GetSelectedPriority();
string authorMail = CurrentUser.Mail; string authorMail = CurrentUser.Mail;
@ -62,26 +75,35 @@ namespace Views
newRecipeType, newRecipeType,
selectedPriority, selectedPriority,
null, null,
authorMail authorMail,
ImageSourcePath
); );
newRecipe.PreparationSteps.AddRange(PreparationStepList); newRecipe.PreparationSteps.AddRange(PreparationStepList);
newRecipe.Ingredients.AddRange(IngredientList); newRecipe.Ingredients.AddRange(IngredientList);
bool isRecipeSave = Master.Recipe.AddRecipeToData(newRecipe); bool isRecipeSave = Master.Recipe.AddRecipeToData(newRecipe);
// Save data.
Debug.Write($"[ {DateTime.Now:H:mm:ss} ] Saving...\t");
Master.Data.SaveData();
Debug.WriteLine("Done.");
Debug.WriteLine(FileSystem.Current.AppDataDirectory);
if (isRecipeSave) if (isRecipeSave)
{ {
DisplayAlert("Succès", "La recette a été ajoutée avec succès", "OK"); await DisplayAlert("Succès", "La recette a été ajoutée avec succès", "OK");
} }
else else
{ {
DisplayAlert("Echec", "La recette n'a pas été ajoutée", "OK"); await DisplayAlert("Echec", "La recette n'a pas été ajoutée", "OK");
} }
newRecipe = new Recipe("Nouvelle Recette"); newRecipe = new Recipe("Nouvelle Recette");
PreparationStepList.Clear(); PreparationStepList.Clear();
IngredientList.Clear(); IngredientList.Clear();
Navigation.PopAsync(); await Navigation.PopModalAsync();
} }
private void AddStepRecipe(object sender, EventArgs e) private void AddStepRecipe(object sender, EventArgs e)
@ -115,6 +137,12 @@ namespace Views
private void AddIngredient(object sender, EventArgs e) private void AddIngredient(object sender, EventArgs e)
{ {
if (nameIngredient.Text is null || quantityNumber.Text is null)
{
DisplayAlert("Warning", "some values are null, please provide correct values.", "Ok");
return;
}
string ingredientName = nameIngredient.Text; string ingredientName = nameIngredient.Text;
int numberQuantity = Convert.ToInt32(quantityNumber.Text); int numberQuantity = Convert.ToInt32(quantityNumber.Text);
Unit unitQuantity = (Unit)UnitPicker.SelectedItem; Unit unitQuantity = (Unit)UnitPicker.SelectedItem;

@ -1,5 +1,6 @@
using AppException; using AppException;
using Model; using Model;
using System.Diagnostics;
namespace Views; namespace Views;
@ -24,6 +25,13 @@ public partial class Login : ContentPage
try try
{ {
usermgr.AddUserToData(usermgr.CreateUser(mail, password)); usermgr.AddUserToData(usermgr.CreateUser(mail, password));
// Save data.
Debug.Write($"[ {DateTime.Now:H:mm:ss} ] Saving...\t");
Master.Data.SaveData();
Debug.WriteLine("Done.");
Debug.WriteLine(FileSystem.Current.AppDataDirectory);
} }
catch (BadMailFormatException) catch (BadMailFormatException)
{ {

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:Model;assembly=Model"
xmlns:local="clr-namespace:Views" xmlns:local="clr-namespace:Views"
x:Class="Views.MyPosts" x:Class="Views.MyPosts"
Title="MyPosts"> Title="MyPosts">
@ -8,45 +9,59 @@
<local:ContainerBase <local:ContainerBase
NeedReturn="True"> NeedReturn="True">
<!-- Flyout -->
<local:ContainerBase.MyFlyoutContent> <local:ContainerBase.MyFlyoutContent>
<Grid RowDefinitions="250, *, *" VerticalOptions="Fill"> <Grid RowDefinitions="Auto, *, *" VerticalOptions="Center">
<VerticalStackLayout Grid.Row="1"> <VerticalStackLayout Grid.Row="1">
<Button Text="Mes informations" ImageSource="person_default.png" Style="{StaticResource button1}" Grid.Row="1"/> <Button Text="Mes informations"
<Button Text="Modifier" ImageSource="settings_icon.png" Style="{StaticResource button1}" Grid.Row="2"/> ImageSource="person_default.png"
Style="{StaticResource button1}"
Grid.Row="1"
Clicked="MyInformations_Clicked"/>
<Button Text="Ajouter une recette"
ImageSource="add_icon.png"
Style="{StaticResource button1}"
Grid.Row="2"
Clicked="AddRecipe_Clicked"/>
</VerticalStackLayout> </VerticalStackLayout>
</Grid> </Grid>
</local:ContainerBase.MyFlyoutContent> </local:ContainerBase.MyFlyoutContent>
<!-- Master -->
<local:ContainerBase.MyContent> <local:ContainerBase.MyContent>
<ScrollView> <ScrollView>
<StackLayout> <StackLayout MinimumWidthRequest="400">
<Label Text="Mon profil" TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}" <Label
FontAttributes="Bold" Text="{Binding RecipesDisplayed.Description}"
FontSize="24" Padding="15, 15, 20, 5"/> TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}"
<Label Text="Mes publications" TextColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}" FontSize="24"
FontSize="20" Padding="15"/> Padding="15"/>
<FlexLayout <FlexLayout
Margin="0, 15" Margin="0, 15"
Wrap="Wrap" Wrap="Wrap"
JustifyContent="Start" JustifyContent="Start"
AlignItems="Center" AlignItems="Center"
AlignContent="SpaceEvenly" AlignContent="SpaceEvenly"
HorizontalOptions="Center"> HorizontalOptions="Center"
BindableLayout.ItemsSource="{Binding RecipesDisplayed}">
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/> <BindableLayout.ItemTemplate>
<local:RecipeCase CaseImageSource="room_service_icon.png"/> <DataTemplate x:DataType="model:Recipe">
<local:RecipeCase CaseImageSource="room_service_icon.png"/>
<local:RecipeCase CaseImageSource="room_service_icon.png"/> <local:RecipeCase
CaseImageSource="{Binding Image}"
RecipeTitle="{Binding Title}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</FlexLayout> </FlexLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</local:ContainerBase.MyContent> </local:ContainerBase.MyContent>
</local:ContainerBase> </local:ContainerBase>

@ -1,9 +1,34 @@
using Model;
namespace Views; namespace Views;
public partial class MyPosts : ContentPage public partial class MyPosts : ContentPage
{ {
public MyPosts() public MasterManager Master => (Application.Current as App).Master;
private readonly RecipeCollection _recipesDisplayed;
public ReadOnlyObservableRecipeCollection RecipesDisplayed { get; private set; }
public Recipe Recipe => Master.Recipe.CurrentSelected;
public MyPosts()
{ {
InitializeComponent(); _recipesDisplayed = Master.Recipe.GetRecipeByAuthor(Master.User.CurrentConnected.Mail);
} RecipesDisplayed = new ReadOnlyObservableRecipeCollection(_recipesDisplayed);
}
InitializeComponent();
BindingContext = this;
}
private void MyInformations_Clicked(object sender, EventArgs e)
{
Navigation.PopModalAsync();
}
private void AddRecipe_Clicked(object sender, EventArgs e)
{
Navigation.PushModalAsync(new AddRecipe());
}
}

@ -9,17 +9,17 @@
<local:EnumToValuesConverter x:Key="musicTypeToValueConverter" <local:EnumToValuesConverter x:Key="musicTypeToValueConverter"
x:TypeArguments="model:Priority"/> x:TypeArguments="model:Priority"/>
</ContentPage.Resources> </ContentPage.Resources>
<local:ContainerBase> <local:ContainerBase NeedReturn="True">
<local:ContainerBase.MyFlyoutContent NeedReturn="True"> <local:ContainerBase.MyFlyoutContent>
<Grid RowDefinitions="250, *, *" VerticalOptions="Fill"> <Grid RowDefinitions="Auto, *, *" VerticalOptions="Center">
<VerticalStackLayout Grid.Row="1"> <VerticalStackLayout Grid.Row="1">
<Button Text="Mes Recettes" <Button Text="Mes Recettes"
ImageSource="person_default.png" ImageSource="person_default.png"
Style="{StaticResource button1}" Style="{StaticResource button1}"
Grid.Row="1" Grid.Row="1"
Clicked="OnMyRecipeClicked"/> Clicked="OnMyRecipeClicked"/>
<Button Text="Ajouter Recette" <Button Text="Ajouter une recette"
ImageSource="settings_icon.png" ImageSource="add_icon.png"
Style="{StaticResource button1}" Style="{StaticResource button1}"
Grid.Row="2" Grid.Row="2"
Clicked="OnAddRecipeClicked"/> Clicked="OnAddRecipeClicked"/>

@ -12,7 +12,7 @@
<Grid RowDefinitions="Auto, *"> <Grid RowDefinitions="Auto, *">
<!-- Return --> <!-- Return -->
<local:ReturnButton NeedReturn="{Binding NeedReturn}" Grid.Row="0" <local:ReturnButton NeedReturn="{Binding NeedReturn, Source={x:Reference fl}}" Grid.Row="0"
HorizontalOptions="Start" Padding="10, 10, 0, 0"/> HorizontalOptions="Start" Padding="10, 10, 0, 0"/>
<!-- Header --> <!-- Header -->
<ImageButton Source="person_default.png" HorizontalOptions="Center" <ImageButton Source="person_default.png" HorizontalOptions="Center"

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M450-200v-250H200v-60h250v-250h60v250h250v60H510v250h-60Z"/></svg>

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

@ -0,0 +1,376 @@
[
{
"Key": "Recipe",
"Value": [
{
"__type": "recipe:#Model",
"authorMail": "admin@mctg.fr",
"id": 9806,
"image": "cookies1.jpg",
"ingredient": [
{
"id": "Patates",
"quantity": {
"digit": 23,
"unit": 0
}
},
{
"id": "Farine",
"quantity": {
"digit": 23,
"unit": 3
}
}
],
"preparation-steps": [
{
"description": "Faire cuire.",
"order": 1
},
{
"description": "Manger.",
"order": 2
}
],
"priority": 2,
"reviews": [
{
"authorMail": "admin@mctg.fr",
"content": "Bonne recette, je recommande !",
"id": 30967,
"stars": 4
},
{
"authorMail": "admin@mctg.fr",
"content": "Bof bof, mais mangeable...",
"id": 27370,
"stars": 3
}
],
"title": "Cookies classiques",
"type": 3
},
{
"__type": "recipe:#Model",
"authorMail": "admin@mctg.fr",
"id": 4678,
"image": "cookies2.jpg",
"ingredient": [
{
"id": "Farine",
"quantity": {
"digit": 200,
"unit": 3
}
}
],
"preparation-steps": [
{
"description": "Moulinez la pâte.",
"order": 1
},
{
"description": "Faire cuire pendant une bonne heure.",
"order": 2
},
{
"description": "Sortir du four et mettre dans un plat.",
"order": 3
}
],
"priority": 1,
"reviews": [],
"title": "Cookies au chocolat",
"type": 3
},
{
"__type": "recipe:#Model",
"authorMail": "admin@mctg.fr",
"id": 28213,
"image": "room_service_icon.png",
"ingredient": [
{
"id": "Farine",
"quantity": {
"digit": 200,
"unit": 3
}
},
{
"id": "Lait",
"quantity": {
"digit": 2,
"unit": 4
}
}
],
"preparation-steps": [
{
"description": "Achetez les ingrédients.",
"order": 1
},
{
"description": "Préparez le matériel. Ustensiles et tout.",
"order": 2
},
{
"description": "Pleurez.",
"order": 3
}
],
"priority": 4,
"reviews": [
{
"authorMail": "pedrosamigos@hotmail.com",
"content": "C'était vraiment IN-CROY-ABLE !!!",
"id": 5127,
"stars": 5
}
],
"title": "Gateau nature",
"type": 3
},
{
"__type": "recipe:#Model",
"authorMail": "admin@mctg.fr",
"id": 27448,
"image": "room_service_icon.png",
"ingredient": [ ],
"preparation-steps": [
{
"description": "Achetez les légumes.",
"order": 1
},
{
"description": "Préparez le plat. Ustensiles et préchauffez le four.",
"order": 2
},
{
"description": "Coupez les pommes en morceaux et disposez-les sur le plat.",
"order": 3
},
{
"description": "Mettez enfin le plat au four, puis une fois cuit, dégustez !",
"order": 4
}
],
"priority": 3,
"reviews": [ ],
"title": "Gateau au pommes",
"type": 3
},
{
"__type": "recipe:#Model",
"authorMail": "pedrosamigos@hotmail.com",
"id": 14217,
"image": "room_service_icon.png",
"ingredient": [
{
"id": "Mais",
"quantity": {
"digit": 2,
"unit": 1
}
},
{
"id": "Sachet pépites de chocolat",
"quantity": {
"digit": 1,
"unit": 0
}
},
{
"id": "Dinde",
"quantity": {
"digit": 2,
"unit": 3
}
}
],
"preparation-steps": [
{
"description": "Ajouter les oeufs.",
"order": 1
},
{
"description": "Ajouter la farine.",
"order": 2
},
{
"description": "Ajouter 100g de chocolat fondu.",
"order": 3
},
{
"description": "Mélanger le tout.",
"order": 4
},
{
"description": "Faire cuire 45h au four traditionnel.",
"order": 5
}
],
"priority": 0,
"reviews": [ ],
"title": "Gateau au chocolat",
"type": 3
},
{
"__type": "recipe:#Model",
"authorMail": "pedrosamigos@hotmail.com",
"id": 3856,
"image": "room_service_icon.png",
"ingredient": [
{
"id": "Morceaux de bois",
"quantity": {
"digit": 2,
"unit": 0
}
},
{
"id": "Sachet gélatine",
"quantity": {
"digit": 1,
"unit": 0
}
},
{
"id": "Jambon",
"quantity": {
"digit": 2,
"unit": 1
}
}
],
"preparation-steps": [
{
"description": "Faire une cuisson bien sec de la dinde à la poêle",
"order": 1
},
{
"description": "Mettre la dinde au frigo.",
"order": 2
},
{
"description": "Mettre le jambon dans le micro-onde.",
"order": 3
},
{
"description": "Faire chauffer 3min.",
"order": 4
},
{
"description": "Présentez sur un plat la dinde et le jambon : Miam !",
"order": 5
}
],
"priority": 2,
"reviews": [ ],
"title": "Dinde au jambon",
"type": 2
},
{
"__type": "recipe:#Model",
"authorMail": "pedrosamigos@hotmail.com",
"id": 29272,
"image": "room_service_icon.png",
"ingredient": [
{
"id": "Pissenlis",
"quantity": {
"digit": 200,
"unit": 0
}
},
{
"id": "Boule de pétanque",
"quantity": {
"digit": 10,
"unit": 0
}
},
{
"id": "Poivre",
"quantity": {
"digit": 4,
"unit": 2
}
}
],
"preparation-steps": [
{
"description": "Trouvez des épices de curry.",
"order": 1
},
{
"description": "Trouvez maintenant du poulet.",
"order": 2
},
{
"description": "Coupez la tête du poulet et posez-la dans un plat.",
"order": 3
},
{
"description": "Parsemez d'épices curry la tête de la poule.",
"order": 4
},
{
"description": "Mettre le tout au four traditionnel 30min.",
"order": 5
},
{
"description": "Dégustez en famille !",
"order": 6
}
],
"priority": 4,
"reviews": [
{
"authorMail": "admin@mctg.fr",
"content": "Meilleure recette que j'ai avalé de tout les temps !!!!!!!",
"id": 7846,
"stars": 5
}
],
"title": "Poulet au curry",
"type": 2
}
]
},
{
"Key": "User",
"Value": [
{
"__type": "user:#Model",
"hashedpass": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918",
"mail": "admin@mctg.fr",
"name": "Admin",
"priorities": [
4,
0,
1,
3,
2
],
"profilepic": "default_picture.png",
"surname": "Admin"
},
{
"__type": "user:#Model",
"hashedpass": "df7415f099b2e105822cb6052a0de0a4eb6a4c4060b5ea191bff1271e1c377fa",
"mail": "pedrosamigos@hotmail.com",
"name": "Pedros",
"priorities": [
4,
0,
1,
3,
2
],
"profilepic": "default_picture.png",
"surname": "Amigos"
}
]
}
]
Loading…
Cancel
Save