Merge pull request 'Added documentation in all the code and added a DisplayAlert when deleting an anime from a list' (#38) from Mathéo into master
continuous-integration/drone/push Build is failing Details

Reviewed-on: #38
pull/40/head
Matheo HERSAN 2 years ago
commit 565b5b16a9

@ -4,22 +4,41 @@ using MangaMap.Views;
namespace MangaMap; namespace MangaMap;
/// <summary>
/// Classe représentant l'application principale.
/// </summary>
public partial class App : Application public partial class App : Application
{ {
/// <summary>
/// Nom du fichier de sauvegarde des données.
/// </summary>
public string FileName { get; set; } = "SauvegardeDonnees.xml"; public string FileName { get; set; } = "SauvegardeDonnees.xml";
/// <summary>
/// Chemin du fichier de sauvegarde des données.
/// </summary>
public string FilePath { get; set; } = Path.Combine(AppDomain.CurrentDomain.BaseDirectory); public string FilePath { get; set; } = Path.Combine(AppDomain.CurrentDomain.BaseDirectory);
public Manager MyManager { get; private set; } = new Manager(new Stub.Stub()); //pour utiliser le stub comme moyen de persistance. /// <summary>
/// Gestionnaire principal de l'application.
/// </summary>
public Manager MyManager { get; private set; } = new Manager(new Stub.Stub()); // Utilise le Stub comme moyen de persistance.
/// <summary>
/// Administrateur principal de l'application.
/// </summary>
public Admin MyAdmin { get; private set; } = new Admin("test", "test@test.ts", "Pseudo_test"); public Admin MyAdmin { get; private set; } = new Admin("test", "test@test.ts", "Pseudo_test");
/// <summary>
/// Constructeur de l'application.
/// </summary>
public App() public App()
{ {
InitializeComponent(); InitializeComponent();
if (File.Exists(Path.Combine(FilePath, FileName))) if (File.Exists(Path.Combine(FilePath, FileName)))
{ {
MyManager = new Manager(new Stub.DataContract()); //pour utiliser le dataContract comme moyen de persistance. MyManager = new Manager(new Stub.DataContract()); // Utilise le DataContract comme moyen de persistance.
} }
MyManager.charger(); MyManager.charger();
@ -29,10 +48,10 @@ public partial class App : Application
if (!File.Exists(Path.Combine(FilePath, FileName))) if (!File.Exists(Path.Combine(FilePath, FileName)))
{ {
MyManager.Persistance = new DataContract(); //pour utiliser le stub comme moyen de persistance. MyManager.Persistance = new DataContract(); // Utilise le Stub comme moyen de persistance.
} }
MyManager.sauvegarder(); MyManager.sauvegarder();
Console.WriteLine("sauvegarde faite"); Console.WriteLine("Sauvegarde effectuée.");
} }
} }

@ -2,12 +2,19 @@
namespace MangaMap; namespace MangaMap;
/// <summary>
/// Classe représentant le Shell.
/// </summary>
public partial class AppShell : Shell public partial class AppShell : Shell
{ {
public AppShell() /// <summary>
{ /// Constructeur du shell de l'application.
InitializeComponent(); /// </summary>
public AppShell()
{
InitializeComponent();
// Enregistrement des routes pour les pages de l'application
Routing.RegisterRoute("homePagedetails", typeof(homePage)); Routing.RegisterRoute("homePagedetails", typeof(homePage));
Routing.RegisterRoute("inscriptionPagedetails", typeof(signUpPage)); Routing.RegisterRoute("inscriptionPagedetails", typeof(signUpPage));
Routing.RegisterRoute("connexionPagedetails", typeof(loginPage)); Routing.RegisterRoute("connexionPagedetails", typeof(loginPage));

@ -4,33 +4,58 @@ using MangaMap.Model;
using System.ComponentModel; using System.ComponentModel;
using INotifyPropertyChanged = System.ComponentModel.INotifyPropertyChanged; using INotifyPropertyChanged = System.ComponentModel.INotifyPropertyChanged;
/// <summary>
/// Classe représentant le contenu d'en-tête personnalisé (CustomHeader).
/// </summary>
public partial class NewContent1 : ContentView, INotifyPropertyChanged public partial class NewContent1 : ContentView, INotifyPropertyChanged
{ {
public Manager my_manager => (App.Current as App).MyManager; public Manager my_manager => (App.Current as App).MyManager;
/// <summary>
/// Constructeur du contenu d'en-tête personnalisé.
/// </summary>
public NewContent1() public NewContent1()
{ {
InitializeComponent(); InitializeComponent();
} }
/// <summary>
/// Gère l'événement de clic sur le bouton d'accueil.
/// </summary>
/// <param name="sender">L'objet déclencheur de l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
async void ImageButton_Clicked(System.Object sender, System.EventArgs e) async void ImageButton_Clicked(System.Object sender, System.EventArgs e)
{ {
//Navigation.PushAsync(new homePage());
await Shell.Current.GoToAsync("//page/homePage"); await Shell.Current.GoToAsync("//page/homePage");
} }
/// <summary>
/// Gère l'événement de clic sur le bouton de paramètres.
/// </summary>
/// <param name="sender">L'objet déclencheur de l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
async void SettingButton_Clicked(object sender, System.EventArgs e) async void SettingButton_Clicked(object sender, System.EventArgs e)
{ {
await Shell.Current.GoToAsync("//page/secondaire/settingsPage"); await Shell.Current.GoToAsync("//page/secondaire/settingsPage");
} }
/// <summary>
/// Gère l'événement de clic sur le bouton de compte.
/// </summary>
/// <param name="sender">L'objet déclencheur de l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
async void AccountButton_Clicked(object sender, System.EventArgs e) async void AccountButton_Clicked(object sender, System.EventArgs e)
{ {
await Shell.Current.GoToAsync("//page/secondaire/connexionPage"); await Shell.Current.GoToAsync("//page/secondaire/connexionPage");
} }
/// <summary>
/// Gère l'événement de clic sur le bouton de liste.
/// </summary>
/// <param name="sender">L'objet déclencheur de l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
async void ListButton_Clicked(object sender, System.EventArgs e) async void ListButton_Clicked(object sender, System.EventArgs e)
{ {
//await Shell.Current.GoToAsync("//page/secondaire/listPage");
await Navigation.PushAsync(new listPage()); await Navigation.PushAsync(new listPage());
} }
} }

@ -8,11 +8,19 @@ using System.Collections.ObjectModel;
namespace MangaMap.Stub namespace MangaMap.Stub
{ {
//Cette classe permet de définir ce qui doit être enregistrer par la persistance. /// <summary>
/// Classe de données pour la persistance contenant les listes des oeuvres et des utilisateurs.
/// </summary>
public class DataToPersist public class DataToPersist
{ {
/// <summary>
/// Obtient ou définit la liste des oeuvres à persister.
/// </summary>
public ObservableCollection<Oeuvre> Oeuvres { get; set; } = new ObservableCollection<Oeuvre>(); public ObservableCollection<Oeuvre> Oeuvres { get; set; } = new ObservableCollection<Oeuvre>();
/// <summary>
/// Obtient ou définit la liste des utilisateurs à persister.
/// </summary>
public List<Utilisateur> Utilisateurs { get; set; } = new List<Utilisateur>(); public List<Utilisateur> Utilisateurs { get; set; } = new List<Utilisateur>();
} }
} }

@ -8,10 +8,22 @@ using MangaMap.Model;
namespace MangaMap.Stub namespace MangaMap.Stub
{ {
/// <summary>
/// Interface pour la gestion de la persistance des données.
/// </summary>
public interface IPersistanceManager public interface IPersistanceManager
{ {
/// <summary>
/// Charge les données persistantes et renvoie les listes des oeuvres et des utilisateurs.
/// </summary>
/// <returns>Un tuple contenant la liste des oeuvres et la liste des utilisateurs.</returns>
(ObservableCollection<Oeuvre>, List<Utilisateur>) chargeDonne(); (ObservableCollection<Oeuvre>, List<Utilisateur>) chargeDonne();
/// <summary>
/// Sauvegarde les listes des oeuvres et des utilisateurs.
/// </summary>
/// <param name="o">La liste des oeuvres à sauvegarder.</param>
/// <param name="u">La liste des utilisateurs à sauvegarder.</param>
void sauvegarder(ObservableCollection<Oeuvre> o, List<Utilisateur> u); void sauvegarder(ObservableCollection<Oeuvre> o, List<Utilisateur> u);
} }
} }

@ -8,11 +8,15 @@ using System.Threading.Tasks;
namespace MangaMap.Stub namespace MangaMap.Stub
{ {
/// <summary>
/// Classe de stub pour la gestion de la persistance des données.
/// </summary>
public class Stub : IPersistanceManager public class Stub : IPersistanceManager
//Cette classe sert à faire charger un jeu de données qui n'est pas celui enregistrer dans le fichier sur l'ordinateur.
//Il permet de faire des transistion entre différent moyen de persister.
{ {
/// <summary>
/// Charge un jeu de données en mémoire.
/// </summary>
/// <returns>Un tuple contenant la liste des oeuvres et la liste des utilisateurs.</returns>
public (ObservableCollection<Oeuvre>, List<Utilisateur>) chargeDonne() public (ObservableCollection<Oeuvre>, List<Utilisateur>) chargeDonne()
{ {
ObservableCollection<Oeuvre> l1 = new ObservableCollection<Oeuvre>(); ObservableCollection<Oeuvre> l1 = new ObservableCollection<Oeuvre>();
@ -39,9 +43,14 @@ namespace MangaMap.Stub
return (l1, l2); return (l1, l2);
} }
/// <summary>
/// Méthode non implémentée pour la sauvegarde des données.
/// </summary>
/// <param name="o">La liste des oeuvres à sauvegarder.</param>
/// <param name="u">La liste des utilisateurs à sauvegarder.</param>
public void sauvegarder(ObservableCollection<Oeuvre> o, List<Utilisateur> u) public void sauvegarder(ObservableCollection<Oeuvre> o, List<Utilisateur> u)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
} }

@ -6,12 +6,20 @@ public partial class ListOeuvre : ContentView
{ {
public Manager my_manager => (App.Current as App).MyManager; public Manager my_manager => (App.Current as App).MyManager;
/// <summary>
/// Constructeur de la liste d'oeuvres.
/// </summary>
public ListOeuvre() public ListOeuvre()
{ {
InitializeComponent(); InitializeComponent();
BindingContext = this; BindingContext = this;
} }
/// <summary>
/// Gère l'événement de clic sur l'image de l'anime dans la liste.
/// </summary>
/// <param name="sender">L'objet déclencheur de l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
private async void AnimeImageClickedList(object sender, EventArgs e) private async void AnimeImageClickedList(object sender, EventArgs e)
{ {
var selectedAnime = (sender as ImageButton)?.BindingContext as Oeuvre; var selectedAnime = (sender as ImageButton)?.BindingContext as Oeuvre;
@ -21,4 +29,4 @@ public partial class ListOeuvre : ContentView
await Navigation.PushAsync(new ficheAnime(selectedAnime)); await Navigation.PushAsync(new ficheAnime(selectedAnime));
} }
} }
} }

@ -1,23 +1,39 @@
namespace MangaMap.Views.Composants;
using MangaMap.Model; using MangaMap.Model;
public partial class StyleBouton : ContentView namespace MangaMap.Views.Composants
{ {
public Manager my_manager => (App.Current as App).MyManager; /// <summary>
/// Code-behind pour le UserControl StyleBouton.xaml.
/// </summary>
public partial class StyleBouton : ContentView
{
/// <summary>
/// Instance du manager pour accéder aux données.
/// </summary>
public Manager my_manager => (App.Current as App).MyManager;
public StyleBouton() /// <summary>
{ /// Constructeur de la classe StyleBouton.
InitializeComponent(); /// </summary>
BindingContext = my_manager; public StyleBouton()
} {
InitializeComponent();
BindingContext = my_manager;
}
private async void AnimeImageClickedList(object sender, EventArgs e) /// <summary>
{ /// Gère l'événement lorsqu'une image d'anime est cliquée.
var selectedAnime = (sender as ImageButton)?.BindingContext as Oeuvre; /// </summary>
if (selectedAnime != null) /// <param name="sender">L'objet déclenchant l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
private async void AnimeImageClickedList(object sender, EventArgs e)
{ {
// Naviguez vers la page de la fiche d'anime en passant l'objet sélectionné var selectedAnime = (sender as ImageButton)?.BindingContext as Oeuvre;
await Navigation.PushAsync(new ficheAnime(selectedAnime)); if (selectedAnime != null)
{
// Naviguez vers la page de la fiche d'anime en passant l'objet sélectionné
await Navigation.PushAsync(new ficheAnime(selectedAnime));
}
} }
} }
} }

@ -11,13 +11,21 @@ public partial class createOeuvre : ContentPage
public Manager my_manager => (App.Current as App).MyManager; public Manager my_manager => (App.Current as App).MyManager;
private string imagePath; private string imagePath;
/// <summary>
/// Constructeur de la page de création d'oeuvre.
/// </summary>
public createOeuvre() public createOeuvre()
{ {
InitializeComponent(); InitializeComponent();
BindingContext = this; BindingContext = this;
} }
async void SelectImageClicked(object sender, EventArgs e) /// <summary>
/// Gère l'événement de clic sur le bouton de sélection d'image.
/// </summary>
/// <param name="sender">L'objet déclencheur de l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
private async void SelectImageClicked(object sender, EventArgs e)
{ {
var result = await FilePicker.PickAsync(new PickOptions var result = await FilePicker.PickAsync(new PickOptions
{ {
@ -33,7 +41,12 @@ public partial class createOeuvre : ContentPage
} }
} }
async void AddClicked(object sender, System.EventArgs e) /// <summary>
/// Gère l'événement de clic sur le bouton d'ajout d'oeuvre.
/// </summary>
/// <param name="sender">L'objet déclencheur de l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
private async void AddClicked(object sender, System.EventArgs e)
{ {
// Récupérer les valeurs des entrées // Récupérer les valeurs des entrées
string nom = nameEntry.Text; string nom = nameEntry.Text;
@ -51,7 +64,7 @@ public partial class createOeuvre : ContentPage
} }
if (string.IsNullOrWhiteSpace(nom) || if (string.IsNullOrWhiteSpace(nom) ||
string.IsNullOrWhiteSpace(description) || string.IsNullOrWhiteSpace(type)) string.IsNullOrWhiteSpace(description) || string.IsNullOrWhiteSpace(type))
{ {
await DisplayAlert("Erreur", "Veuillez remplir tous les champs.", "OK"); await DisplayAlert("Erreur", "Veuillez remplir tous les champs.", "OK");
return; return;
@ -59,7 +72,7 @@ public partial class createOeuvre : ContentPage
if (nbEp < 0) if (nbEp < 0)
{ {
await DisplayAlert("Erreur", "Il faut avoir au 1 épisode pour l'application.", "OK"); await DisplayAlert("Erreur", "Il faut avoir au moins 1 épisode pour l'application.", "OK");
return; return;
} }
@ -69,4 +82,4 @@ public partial class createOeuvre : ContentPage
await Navigation.PushAsync(new homePage()); await Navigation.PushAsync(new homePage());
return; return;
} }
} }

@ -1,265 +1,294 @@
namespace MangaMap.Views; namespace MangaMap.Views
using Model;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Input;
using System.Xml.Linq;
using Microsoft.Maui.Graphics;
public partial class ficheAnime : ContentPage, INotifyPropertyChanged
{ {
public Manager my_manager => (App.Current as App).MyManager; using Model;
public Oeuvre AnimeModel { get; set; } using System.ComponentModel;
using System.Diagnostics;
public ficheAnime() using System.Drawing;
using System.Windows.Input;
using System.Xml.Linq;
using Microsoft.Maui.Graphics;
public partial class ficheAnime : ContentPage, INotifyPropertyChanged
{ {
InitializeComponent(); public Manager my_manager => (App.Current as App).MyManager;
public Oeuvre AnimeModel { get; set; }
BindingContext = this; /// <summary>
/// Constructeur par défaut de la page ficheAnime.
/// </summary>
public ficheAnime()
{
InitializeComponent();
} BindingContext = this;
}
public ficheAnime(Oeuvre anime) /// <summary>
{ /// Constructeur de la page ficheAnime prenant en paramètre un objet Oeuvre.
AnimeModel = anime; /// </summary>
/// <param name="anime">L'objet Oeuvre à afficher dans la fiche.</param>
public ficheAnime(Oeuvre anime)
{
AnimeModel = anime;
InitializeComponent(); InitializeComponent();
BindingContext = this; BindingContext = this;
SetNote(); SetNote();
}
public async void AjouterListe(object sender, EventArgs e)
{
if (my_manager.UtilisateurActuel.Email == null)
{
await DisplayAlert("Erreur", "Vous n'êtes pas connecté.", "OK");
return;
} }
// Si la série est déjà dans la liste il faut bloquer l'ajout. /// <summary>
foreach (Oeuvre oeuvre in my_manager.UtilisateurActuel.ListeOeuvreEnVisionnage) /// Gère l'événement lorsqu'on clique sur le bouton d'ajout à la liste.
/// </summary>
/// <param name="sender">L'objet déclenchant l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
public async void AjouterListe(object sender, EventArgs e)
{ {
if (oeuvre.Nom == AnimeModel.Nom) if (my_manager.UtilisateurActuel.Email == null)
{ {
await DisplayAlert("Erreur", "Avez déjà cette série dans une la liste 'En visionnage'.", "OK"); await DisplayAlert("Erreur", "Vous n'êtes pas connecté.", "OK");
return; return;
} }
}
foreach (Oeuvre oeuvre in my_manager.UtilisateurActuel.ListeOeuvreDejaVu) // Si la série est déjà dans la liste il faut bloquer l'ajout.
{ foreach (Oeuvre oeuvre in my_manager.UtilisateurActuel.ListeOeuvreEnVisionnage)
if (oeuvre.Nom == AnimeModel.Nom)
{ {
await DisplayAlert("Erreur", "Avez déjà cette série dans une la liste 'Déjà vu'.", "OK"); if (oeuvre.Nom == AnimeModel.Nom)
return; {
await DisplayAlert("Erreur", "Avez déjà cette série dans une la liste 'En visionnage'.", "OK");
return;
}
} }
} foreach (Oeuvre oeuvre in my_manager.UtilisateurActuel.ListeOeuvreDejaVu)
foreach (Oeuvre oeuvre in my_manager.UtilisateurActuel.ListeOeuvreFavorites)
{
if (oeuvre.Nom == AnimeModel.Nom)
{ {
await DisplayAlert("Erreur", "Avez déjà cette série dans une la liste 'En favoris'.", "OK"); if (oeuvre.Nom == AnimeModel.Nom)
return; {
await DisplayAlert("Erreur", "Avez déjà cette série dans une la liste 'Déjà vu'.", "OK");
return;
}
} }
} foreach (Oeuvre oeuvre in my_manager.UtilisateurActuel.ListeOeuvreFavorites)
foreach (Oeuvre oeuvre in my_manager.UtilisateurActuel.ListeOeuvrePourPlusTard)
{
if (oeuvre.Nom == AnimeModel.Nom)
{ {
await DisplayAlert("Erreur", "Avez déjà cette série dans une la liste 'Pour plus tard'.", "OK"); if (oeuvre.Nom == AnimeModel.Nom)
return; {
await DisplayAlert("Erreur", "Avez déjà cette série dans une la liste 'En favoris'.", "OK");
return;
}
}
foreach (Oeuvre oeuvre in my_manager.UtilisateurActuel.ListeOeuvrePourPlusTard)
{
if (oeuvre.Nom == AnimeModel.Nom)
{
await DisplayAlert("Erreur", "Avez déjà cette série dans une la liste 'Pour plus tard'.", "OK");
return;
}
} }
}
string selectedOption = await DisplayActionSheet("Ajouter à quelle liste ?", "Annuler", null, "En Visionnage", "Déjà Vu", "Pour Plus Tard", "Favoris");
if (selectedOption == "Annuler" || selectedOption == null)
return;
Debug.WriteLine("Selected Option: " + selectedOption); string selectedOption = await DisplayActionSheet("Ajouter à quelle liste ?", "Annuler", null, "En Visionnage", "Déjà Vu", "Pour Plus Tard", "Favoris");
// Ajouter l'anime à la liste sélectionnée if (selectedOption == "Annuler" || selectedOption == null)
switch (selectedOption) return;
{
case "En Visionnage":
Debug.WriteLine("Ajout à la liste En Visionnage");
my_manager.UtilisateurActuel.ListeOeuvreEnVisionnage.Add(AnimeModel);
break;
case "Déjà Vu":
Debug.WriteLine("Ajout à la liste Déjà Vu");
my_manager.UtilisateurActuel.ListeOeuvreDejaVu.Add(AnimeModel);
break;
case "Pour Plus Tard":
Debug.WriteLine("Ajout à la liste Pour Plus Tard");
my_manager.UtilisateurActuel.ListeOeuvrePourPlusTard.Add(AnimeModel);
break;
case "Favoris":
Debug.WriteLine("Ajout à la liste Favoris");
my_manager.UtilisateurActuel.ListeOeuvreFavorites.Add(AnimeModel);
break;
}
my_manager.sauvegarder(); Debug.WriteLine("Selected Option: " + selectedOption);
await Navigation.PushAsync(new listPage()); // Ajouter l'anime à la liste sélectionnée
} switch (selectedOption)
{
case "En Visionnage":
Debug.WriteLine("Ajout à la liste En Visionnage");
my_manager.UtilisateurActuel.ListeOeuvreEnVisionnage.Add(AnimeModel);
break;
case "Déjà Vu":
Debug.WriteLine("Ajout à la liste Déjà Vu");
my_manager.UtilisateurActuel.ListeOeuvreDejaVu.Add(AnimeModel);
break;
case "Pour Plus Tard":
Debug.WriteLine("Ajout à la liste Pour Plus Tard");
my_manager.UtilisateurActuel.ListeOeuvrePourPlusTard.Add(AnimeModel);
break;
case "Favoris":
Debug.WriteLine("Ajout à la liste Favoris");
my_manager.UtilisateurActuel.ListeOeuvreFavorites.Add(AnimeModel);
break;
}
public async void SupprimerListe(object sender, EventArgs e)
{
if (my_manager.UtilisateurActuel.ListeOeuvreEnVisionnage.Remove(AnimeModel) ||
my_manager.UtilisateurActuel.ListeOeuvreDejaVu.Remove(AnimeModel) ||
my_manager.UtilisateurActuel.ListeOeuvreFavorites.Remove(AnimeModel) ||
my_manager.UtilisateurActuel.ListeOeuvrePourPlusTard.Remove(AnimeModel))
my_manager.sauvegarder(); my_manager.sauvegarder();
else await Navigation.PushAsync(new listPage());
{
await DisplayAlert("Erreur", "Avez n'avez pas cette série dans une liste.", "OK");
return;
} }
}
private void SetNote() /// <summary>
{ /// Gère l'événement lorsqu'on clique sur le bouton de suppression de la liste.
stars.Children.Clear(); /// </summary>
bool test = my_manager.UtilisateurActuel.notesNombres.ContainsKey(AnimeModel.Nom); /// <param name="sender">L'objet déclenchant l'événement.</param>
List<int> x; /// <param name="e">Les arguments de l'événement.</param>
public async void SupprimerListe(object sender, EventArgs e)
{
if (my_manager.UtilisateurActuel.ListeOeuvreEnVisionnage.Remove(AnimeModel) ||
my_manager.UtilisateurActuel.ListeOeuvreDejaVu.Remove(AnimeModel) ||
my_manager.UtilisateurActuel.ListeOeuvreFavorites.Remove(AnimeModel) ||
my_manager.UtilisateurActuel.ListeOeuvrePourPlusTard.Remove(AnimeModel))
my_manager.sauvegarder();
else
{
await DisplayAlert("Erreur", "Avez n'avez pas cette série dans une liste.", "OK");
return;
}
await DisplayAlert("Succès", "La série a bien été supprimée de la liste.", "OK");
}
for (int i = 0; i < 5; i++) /// <summary>
/// Affiche les étoiles de notation de l'anime.
/// </summary>
private void SetNote()
{ {
if (my_manager.UtilisateurActuel.notesNombres.TryGetValue(AnimeModel.Nom,out x) && i < x[0]) stars.Children.Clear();
bool test = my_manager.UtilisateurActuel.notesNombres.ContainsKey(AnimeModel.Nom);
List<int> x;
for (int i = 0; i < 5; i++)
{ {
ImageButton imageButton = new ImageButton if (my_manager.UtilisateurActuel.notesNombres.TryGetValue(AnimeModel.Nom, out x) && i < x[0])
{
ImageButton imageButton = new ImageButton
{
Source = "star_full.png",
BackgroundColor = Microsoft.Maui.Graphics.Color.FromHex("1E1E1E"),
WidthRequest = 50,
HeightRequest = 50,
AutomationId = i.ToString(),
Margin = 10,
};
imageButton.Clicked += StarClicked;
Grid.SetRow(imageButton, 0);
Grid.SetColumn(imageButton, i);
stars.Children.Add(imageButton);
}
else if (!test && i < AnimeModel.Note)
{ {
Source = "star_full.png", ImageButton imageButton = new ImageButton
BackgroundColor = Microsoft.Maui.Graphics.Color.FromHex("1E1E1E"), {
WidthRequest = 50, Source = "star_full.png",
HeightRequest = 50, BackgroundColor = Microsoft.Maui.Graphics.Color.FromHex("1E1E1E"),
AutomationId = i.ToString(), WidthRequest = 50,
Margin = 10, HeightRequest = 50,
}; AutomationId = i.ToString(),
Margin = 10,
imageButton.Clicked += StarClicked; };
Grid.SetRow(imageButton, 0); imageButton.Clicked += StarClicked;
Grid.SetColumn(imageButton, i);
stars.Children.Add(imageButton); Grid.SetRow(imageButton, 0);
Grid.SetColumn(imageButton, i);
stars.Children.Add(imageButton);
}
else
{
ImageButton imageButton = new ImageButton
{
Source = "star_empty.png",
BackgroundColor = Microsoft.Maui.Graphics.Color.FromHex("1E1E1E"),
WidthRequest = 50,
HeightRequest = 50,
AutomationId = i.ToString(),
Margin = 10,
};
imageButton.Clicked += StarClicked;
Grid.SetRow(imageButton, 0);
Grid.SetColumn(imageButton, i);
stars.Children.Add(imageButton);
}
} }
else if (!test && i < AnimeModel.Note) }
/// <summary>
/// Gère l'événement lorsqu'on clique sur une étoile.
/// </summary>
/// <param name="sender">L'objet déclenchant l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
private async void StarClicked(object sender, EventArgs e)
{
if (my_manager.UtilisateurActuel.Email == null)
{ {
ImageButton imageButton = new ImageButton await DisplayAlert("Erreur", "Vous n'êtes pas connecté.", "OK");
{ return;
Source = "star_full.png",
BackgroundColor = Microsoft.Maui.Graphics.Color.FromHex("1E1E1E"),
WidthRequest = 50,
HeightRequest = 50,
AutomationId = i.ToString(),
Margin = 10,
};
imageButton.Clicked += StarClicked;
Grid.SetRow(imageButton, 0);
Grid.SetColumn(imageButton, i);
stars.Children.Add(imageButton);
} }
else
var button = (ImageButton)sender;
var idAutomation = button.AutomationId;
List<int> x = new List<int>();
int somme = 0;
int compteur = 0;
if (int.TryParse(idAutomation, out int id))
{ {
ImageButton imageButton = new ImageButton if (my_manager.UtilisateurActuel.notesNombres.ContainsKey(AnimeModel.Nom))
{
my_manager.UtilisateurActuel.notesNombres.Remove(AnimeModel.Nom, out x);
x[0] = id + 1;
my_manager.UtilisateurActuel.notesNombres.Add(AnimeModel.Nom, x);
BackgroundColor = Microsoft.Maui.Graphics.Color.FromHex("1E1E1E");
}
else
{
x.Add(id + 1);
x.Add(0);
my_manager.UtilisateurActuel.notesNombres.Add(AnimeModel.Nom, x);
//BackgroundColor = Microsoft.Maui.Graphics.Color.FromHex("1E1E1E");
}
SetNote();
foreach (Utilisateur u in my_manager.Utilisateurs)
{ {
Source = "star_empty.png", if (u.notesNombres.TryGetValue(AnimeModel.Nom, out x) && x[0] != 0)
BackgroundColor = Microsoft.Maui.Graphics.Color.FromHex("1E1E1E"), {
WidthRequest = 50, compteur = compteur + 1;
HeightRequest = 50, somme = somme + x[0];
AutomationId = i.ToString(), }
Margin = 10, }
};
AnimeModel.Note = somme / compteur;
imageButton.Clicked += StarClicked; my_manager.sauvegarder();
Grid.SetRow(imageButton, 0);
Grid.SetColumn(imageButton, i);
stars.Children.Add(imageButton);
} }
} }
}
private async void StarClicked(object sender, EventArgs e) /// <summary>
{ /// Gère l'événement lorsqu'on clique sur le bouton de validation du nombre d'épisodes.
if (my_manager.UtilisateurActuel.Email == null) /// </summary>
/// <param name="sender">L'objet déclenchant l'événement.</param>
/// <param name="e">Les arguments de l'événement.</param>
private async void NbEpCheck(object sender, EventArgs e)
{ {
await DisplayAlert("Erreur", "Vous n'êtes pas connecté.", "OK"); if (my_manager.UtilisateurActuel.Email == null)
return; {
} await DisplayAlert("Erreur", "Vous n'êtes pas connecté.", "OK");
return;
}
var button = (ImageButton)sender; List<int> x = new List<int>();
var idAutomation = button.AutomationId; int nb = Convert.ToInt32(nombreEP.Text);
List<int> x = new List<int>();
int somme = 0;
int compteur = 0;
if (int.TryParse(idAutomation, out int id))
{
if (my_manager.UtilisateurActuel.notesNombres.ContainsKey(AnimeModel.Nom)) if (my_manager.UtilisateurActuel.notesNombres.ContainsKey(AnimeModel.Nom))
{ {
my_manager.UtilisateurActuel.notesNombres.Remove(AnimeModel.Nom, out x); my_manager.UtilisateurActuel.notesNombres.Remove(AnimeModel.Nom, out x);
x[0] = id + 1; x[1] = nb;
my_manager.UtilisateurActuel.notesNombres.Add(AnimeModel.Nom, x); my_manager.UtilisateurActuel.notesNombres.Add(AnimeModel.Nom, x);
BackgroundColor = Microsoft.Maui.Graphics.Color.FromHex("1E1E1E"); return;
} }
else else
{ {
x.Add(id + 1);
x.Add(0); x.Add(0);
x.Add(nb);
my_manager.UtilisateurActuel.notesNombres.Add(AnimeModel.Nom, x); my_manager.UtilisateurActuel.notesNombres.Add(AnimeModel.Nom, x);
//BackgroundColor = Microsoft.Maui.Graphics.Color.FromHex("1E1E1E");
}
SetNote();
foreach (Utilisateur u in my_manager.Utilisateurs)
{
if(u.notesNombres.TryGetValue(AnimeModel.Nom, out x) && x[0] != 0)
{
compteur = compteur + 1;
somme = somme + x[0];
}
} }
AnimeModel.Note = somme / compteur;
my_manager.sauvegarder(); my_manager.sauvegarder();
} }
} }
}
private async void NbEpCheck(object sender, EventArgs e)
{
if (my_manager.UtilisateurActuel.Email == null)
{
await DisplayAlert("Erreur", "Vous n'êtes pas connecté.", "OK");
return;
}
List<int> x = new List<int>();
int nb = Convert.ToInt32(nombreEP.Text);
if (my_manager.UtilisateurActuel.notesNombres.ContainsKey(AnimeModel.Nom))
{
my_manager.UtilisateurActuel.notesNombres.Remove(AnimeModel.Nom, out x);
x[1] = nb;
my_manager.UtilisateurActuel.notesNombres.Add(AnimeModel.Nom, x);
return;
}
else
{
x.Add(0);
x.Add(nb);
my_manager.UtilisateurActuel.notesNombres.Add(AnimeModel.Nom, x);
}
my_manager.sauvegarder();
}
}

@ -1,83 +1,104 @@
namespace MangaMap.Views; namespace MangaMap.Views
using MangaMap.Model;
using System.Collections.ObjectModel;
public partial class homePage : ContentPage
{ {
public Manager my_manager => (App.Current as App).MyManager; using MangaMap.Model;
using System.Collections.ObjectModel;
public homePage() /// <summary>
/// Classe représentant la page d'accueil de l'application.
/// </summary>
public partial class homePage : ContentPage
{ {
InitializeComponent(); /// <summary>
BindingContext = my_manager; /// Référence au gestionnaire de l'application.
searchResults.ItemsSource = my_manager.Oeuvres; /// </summary>
//chargerSerie(); public Manager my_manager => (App.Current as App).MyManager;
}
private async void AnimeImageClicked(object sender, EventArgs e) /// <summary>
{ /// Constructeur de la page d'accueil.
var selectedAnime = (sender as ImageButton)?.BindingContext as Oeuvre; /// </summary>
if (selectedAnime != null) public homePage()
{ {
// Naviguez vers la page de la fiche d'anime en passant l'objet sélectionné InitializeComponent();
await Navigation.PushAsync(new ficheAnime(selectedAnime)); BindingContext = my_manager;
searchResults.ItemsSource = my_manager.Oeuvres;
//chargerSerie();
} }
/*var button = (ImageButton)sender; /// <summary>
var idAutomation = button.AutomationId; /// Gestionnaire d'événement lorsqu'une image d'anime est cliquée.
/// </summary>
if (int.TryParse(idAutomation, out int id)) /// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement.</param>
private async void AnimeImageClicked(object sender, EventArgs e)
{ {
await Navigation.PushAsync(new ficheAnime(my_manager.Oeuvres[id])); var selectedAnime = (sender as ImageButton)?.BindingContext as Oeuvre;
}*/ if (selectedAnime != null)
} {
// Naviguer vers la page de la fiche d'anime en passant l'objet sélectionné
await Navigation.PushAsync(new ficheAnime(selectedAnime));
}
/*private void chargerSerie() /*var button = (ImageButton)sender;
{ var idAutomation = button.AutomationId;
int imagesParLigne = 4;
int indice = 0;
for (int i = 0; i < my_manager.Oeuvres.Count; i++) if (int.TryParse(idAutomation, out int id))
{
await Navigation.PushAsync(new ficheAnime(my_manager.Oeuvres[id]));
}*/
}
/*private void chargerSerie()
{ {
Oeuvre favoris = my_manager.Oeuvres[i]; int imagesParLigne = 4;
int indice = 0;
ImageButton imageButton = new ImageButton for (int i = 0; i < my_manager.Oeuvres.Count; i++)
{ {
Source = favoris.Affiche, Oeuvre favoris = my_manager.Oeuvres[i];
WidthRequest = 170,
MaximumHeightRequest = 190,
MinimumHeightRequest = 190,
HeightRequest = 190,
CornerRadius = 15,
Aspect = Aspect.Fill,
AutomationId = indice.ToString(),
Margin = 90
};
imageButton.Clicked += AnimeImageClicked; ImageButton imageButton = new ImageButton
{
Source = favoris.Affiche,
WidthRequest = 170,
MaximumHeightRequest = 190,
MinimumHeightRequest = 190,
HeightRequest = 190,
CornerRadius = 15,
Aspect = Aspect.Fill,
AutomationId = indice.ToString(),
Margin = 90
};
int ligne = 1 + (indice / imagesParLigne); imageButton.Clicked += AnimeImageClicked;
int colonne = indice % imagesParLigne;
Grid.SetRow(imageButton, ligne); int ligne = 1 + (indice / imagesParLigne);
Grid.SetColumn(imageButton, colonne); int colonne = indice % imagesParLigne;
grille.Children.Add(imageButton);
indice++; Grid.SetRow(imageButton, ligne);
} Grid.SetColumn(imageButton, colonne);
}*/ grille.Children.Add(imageButton);
private void OnTextChanged(object sender, TextChangedEventArgs e) indice++;
{ }
}*/
if(string.IsNullOrWhiteSpace(e.NewTextValue)) /// <summary>
/// Gestionnaire d'événement lorsqu'un texte est modifié dans la zone de recherche.
/// </summary>
/// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement contenant le nouveau texte.</param>
private void OnTextChanged(object sender, TextChangedEventArgs e)
{ {
searchResults.ItemsSource = my_manager.Oeuvres; if (string.IsNullOrWhiteSpace(e.NewTextValue))
} {
else // Afficher toutes les œuvres si le champ de recherche est vide
{ searchResults.ItemsSource = my_manager.Oeuvres;
searchResults.ItemsSource = my_manager.Oeuvres.Where(i => i.Nom.ToLower().Contains(e.NewTextValue.ToLower())); }
else
{
// Filtrer les œuvres en fonction du texte de recherche
searchResults.ItemsSource = my_manager.Oeuvres.Where(i => i.Nom.ToLower().Contains(e.NewTextValue.ToLower()));
}
} }
} }
} }

@ -2,11 +2,17 @@ namespace MangaMap.Views;
using MangaMap.Model; using MangaMap.Model;
using static System.Net.Mime.MediaTypeNames; using static System.Net.Mime.MediaTypeNames;
/// <summary>
/// Classe représentant la page de liste de l'application.
/// </summary>
public partial class listPage : ContentPage public partial class listPage : ContentPage
{ {
/// <summary>
/// Constructeur de la page de liste.
/// </summary>
public listPage() public listPage()
{ {
InitializeComponent(); InitializeComponent();
} }
} }

@ -4,20 +4,39 @@ using System.Threading.Tasks;
using MangaMap.Stub; using MangaMap.Stub;
using MangaMap.Model; using MangaMap.Model;
/// <summary>
/// Classe représentant la page de connexion administrateur de l'application.
/// </summary>
public partial class loginAdminPage : ContentPage public partial class loginAdminPage : ContentPage
{ {
/// <summary>
/// Référence au gestionnaire de l'application.
/// </summary>
public Manager my_manager => (App.Current as App).MyManager; public Manager my_manager => (App.Current as App).MyManager;
/// <summary>
/// Constructeur de la page de connexion administrateur.
/// </summary>
public loginAdminPage() public loginAdminPage()
{ {
InitializeComponent(); InitializeComponent();
} }
/// <summary>
/// Gestionnaire d'événement lorsqu'un utilisateur clique sur le bouton "Utilisateur".
/// </summary>
/// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement.</param>
async void userClicked(object sender, EventArgs e) async void userClicked(object sender, EventArgs e)
{ {
await Shell.Current.GoToAsync("//page/secondaire/inscriptionPage"); await Shell.Current.GoToAsync("//page/secondaire/inscriptionPage");
} }
/// <summary>
/// Gestionnaire d'événement lorsqu'un utilisateur clique sur le bouton "Connexion".
/// </summary>
/// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement.</param>
async void OnLoginClicked(object sender, EventArgs e) async void OnLoginClicked(object sender, EventArgs e)
{ {
// Récupération du pseudo et du mot de passe entrés // Récupération du pseudo et du mot de passe entrés
@ -39,7 +58,7 @@ public partial class loginAdminPage : ContentPage
return; return;
} }
// On garde la connection admin // On garde la connexion admin
my_manager.isAdmin = true; my_manager.isAdmin = true;
// Rediriger l'utilisateur vers la page principale // Rediriger l'utilisateur vers la page principale
await Shell.Current.GoToAsync("//page/homePage"); await Shell.Current.GoToAsync("//page/homePage");

@ -4,23 +4,42 @@ using System.Threading.Tasks;
using MangaMap.Stub; using MangaMap.Stub;
using MangaMap.Model; using MangaMap.Model;
/// <summary>
/// Classe représentant la page de connexion de l'application.
/// </summary>
public partial class loginPage : ContentPage public partial class loginPage : ContentPage
{ {
/// <summary>
/// Référence au gestionnaire de l'application.
/// </summary>
public Manager my_manager => (App.Current as App).MyManager; public Manager my_manager => (App.Current as App).MyManager;
/// <summary>
/// Constructeur de la page de connexion.
/// </summary>
public loginPage() public loginPage()
{ {
InitializeComponent(); InitializeComponent();
} }
/// <summary>
/// Gestionnaire d'événement lorsqu'un utilisateur clique sur le bouton "Inscription".
/// </summary>
/// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement.</param>
async void OnSignUpClicked(object sender, EventArgs e) async void OnSignUpClicked(object sender, EventArgs e)
{ {
await Shell.Current.GoToAsync("//page/secondaire/inscriptionPage"); await Shell.Current.GoToAsync("//page/secondaire/inscriptionPage");
} }
/// <summary>
/// Gestionnaire d'événement lorsqu'un utilisateur clique sur le bouton "Connexion".
/// </summary>
/// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement.</param>
async void OnLoginClicked(object sender, EventArgs e) async void OnLoginClicked(object sender, EventArgs e)
{ {
// Récupération de l'email et du mot de passe entrés // Récupération de l'e-mail et du mot de passe entrés
string email = emailEntry.Text; string email = emailEntry.Text;
string password = passwordEntry.Text; string password = passwordEntry.Text;
@ -34,7 +53,7 @@ public partial class loginPage : ContentPage
// Vérifier que l'e-mail a la bonne forme // Vérifier que l'e-mail a la bonne forme
//if (!Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$")) //if (!Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
//{ //{
// await DisplayAlert("Erreur", "L'email n'est pas valide.", "OK"); // await DisplayAlert("Erreur", "L'e-mail n'est pas valide.", "OK");
// return; // return;
//} //}
@ -50,7 +69,6 @@ public partial class loginPage : ContentPage
my_manager.UtilisateurActuel = utilisateur; my_manager.UtilisateurActuel = utilisateur;
// Rediriger l'utilisateur vers la page principale // Rediriger l'utilisateur vers la page principale
await Shell.Current.GoToAsync("//page/homePage"); await Shell.Current.GoToAsync("//page/homePage");
} }
} }

@ -1,15 +1,29 @@
namespace MangaMap.Views; namespace MangaMap.Views;
using Model; using Model;
/// <summary>
/// Classe représentant la page des paramètres de l'application.
/// </summary>
public partial class settingsPage : ContentPage public partial class settingsPage : ContentPage
{ {
/// <summary>
/// Référence au gestionnaire de l'application.
/// </summary>
public Manager my_manager => (App.Current as App).MyManager; public Manager my_manager => (App.Current as App).MyManager;
public settingsPage() /// <summary>
{ /// Constructeur de la page des paramètres.
InitializeComponent(); /// </summary>
} public settingsPage()
{
InitializeComponent();
}
/// <summary>
/// Gestionnaire d'événement lorsqu'un utilisateur clique sur le bouton "Déconnexion".
/// </summary>
/// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement.</param>
private async void OnDisconnectClicked(object sender, EventArgs e) private async void OnDisconnectClicked(object sender, EventArgs e)
{ {
my_manager.UtilisateurActuel = new Utilisateur(); my_manager.UtilisateurActuel = new Utilisateur();
@ -17,14 +31,24 @@ public partial class settingsPage : ContentPage
await Shell.Current.GoToAsync("//page/secondaire/connexionPage"); await Shell.Current.GoToAsync("//page/secondaire/connexionPage");
} }
/// <summary>
/// Gestionnaire d'événement lorsqu'un utilisateur clique sur le bouton "Connexion Administrateur".
/// </summary>
/// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement.</param>
private async void LoginAdminClicked(object sender, EventArgs e) private async void LoginAdminClicked(object sender, EventArgs e)
{ {
await Shell.Current.Navigation.PushAsync(new loginAdminPage()); await Shell.Current.Navigation.PushAsync(new loginAdminPage());
} }
/// <summary>
/// Gestionnaire d'événement lorsqu'un utilisateur clique sur le bouton "Ajouter".
/// </summary>
/// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement.</param>
private async void AddClicked(object sender, EventArgs e) private async void AddClicked(object sender, EventArgs e)
{ {
if(!my_manager.isAdmin) if (!my_manager.isAdmin)
{ {
await DisplayAlert("Erreur", "Vous n'êtes pas connecté en tant qu'Administrateur.", "OK"); await DisplayAlert("Erreur", "Vous n'êtes pas connecté en tant qu'Administrateur.", "OK");
return; return;

@ -4,21 +4,39 @@ using MangaMap.Model;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using static System.Runtime.InteropServices.JavaScript.JSType; using static System.Runtime.InteropServices.JavaScript.JSType;
/// <summary>
/// Classe représentant la page d'inscription de l'application.
/// </summary>
public partial class signUpPage : ContentPage public partial class signUpPage : ContentPage
{ {
/// <summary>
/// Référence au gestionnaire de l'application.
/// </summary>
public Manager my_manager => (App.Current as App).MyManager; public Manager my_manager => (App.Current as App).MyManager;
public signUpPage() /// <summary>
{ /// Constructeur de la page d'inscription.
/// </summary>
public signUpPage()
{
InitializeComponent(); InitializeComponent();
} }
/// <summary>
/// Gestionnaire d'événement lorsqu'un utilisateur clique sur le bouton "Connexion".
/// </summary>
/// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement.</param>
async void OnLoginClicked(object sender, EventArgs e) async void OnLoginClicked(object sender, EventArgs e)
{ {
await Shell.Current.GoToAsync("//page/secondaire/connexionPage"); await Shell.Current.GoToAsync("//page/secondaire/connexionPage");
} }
/// <summary>
/// Gestionnaire d'événement lorsqu'un utilisateur clique sur le bouton "Inscription".
/// </summary>
/// <param name="sender">L'objet qui a déclenché l'événement.</param>
/// <param name="e">Arguments de l'événement.</param>
async void OnSignUpClicked(object sender, System.EventArgs e) async void OnSignUpClicked(object sender, System.EventArgs e)
{ {
// Récupérer les valeurs des entrées // Récupérer les valeurs des entrées
@ -32,7 +50,7 @@ public partial class signUpPage : ContentPage
foreach (Utilisateur u in my_manager.Utilisateurs) foreach (Utilisateur u in my_manager.Utilisateurs)
{ {
if (u.Email == email ||u.Pseudo==pseudo) if (u.Email == email || u.Pseudo == pseudo)
{ {
await DisplayAlert("Erreur", "L'utilisateur existe déjà.", "OK"); await DisplayAlert("Erreur", "L'utilisateur existe déjà.", "OK");
return; return;
@ -40,7 +58,7 @@ public partial class signUpPage : ContentPage
} }
if (string.IsNullOrWhiteSpace(email) || if (string.IsNullOrWhiteSpace(email) ||
string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(confirmPassword)) string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(confirmPassword))
{ {
await DisplayAlert("Erreur", "Veuillez remplir tous les champs.", "OK"); await DisplayAlert("Erreur", "Veuillez remplir tous les champs.", "OK");
return; return;
@ -83,6 +101,11 @@ public partial class signUpPage : ContentPage
} }
} }
/// <summary>
/// Vérifie si un mot de passe est suffisamment fort.
/// </summary>
/// <param name="password">Le mot de passe à vérifier.</param>
/// <returns>True si le mot de passe est suffisamment fort, sinon False.</returns>
bool IsPasswordStrong(string password) bool IsPasswordStrong(string password)
{ {
// Vérifier si le mot de passe est assez long // Vérifier si le mot de passe est assez long

Loading…
Cancel
Save