Compare commits

..

54 Commits
EF2 ... master

Author SHA1 Message Date
Nathan BOILEAU 7e37acd37f Fix: error drone
continuous-integration/drone/push Build is passing Details
2 years ago
Nathan BOILEAU dd0d2a661a Merge branch 'linkDB_API'
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU 11fe1c243f Petite modif
continuous-integration/drone/push Build is passing Details
2 years ago
Nathan BOILEAU 149a74ffa1 Correction orthographe
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU 39193ed5ac 🎨 READ ME + Remove files
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU 1a9d159d64 Merge branch 'master' of https://codefirst.iut.uca.fr/git/bastien.ollier/LOL
continuous-integration/drone/push Build is passing Details
2 years ago
Nathan BOILEAU b7e657d883 📝 Ajoute diagramme d'architecture
2 years ago
Nathan BOILEAU ed9293609f Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
2 years ago
Nathan BOILEAU b1752c3461 Merge branch 'linkDB_API'
continuous-integration/drone/push Build encountered an error Details
2 years ago
Nathan BOILEAU 76de6eccbb Finish link between API and DB
continuous-integration/drone/push Build is passing Details
2 years ago
Nathan BOILEAU 3eabcfa96e Fix: GetChampions, issue on filtrage
continuous-integration/drone/push Build is passing Details
2 years ago
Nathan BOILEAU 5aabac6125 🚧 Beginning of link between DB and API
continuous-integration/drone/push Build is passing Details
2 years ago
Nathan BOILEAU 0ad967d963 Fix ♻️
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU 481201a7e3 Mise à jour de 'README.md'
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU 048001fdb7 ♻️
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU b733e64a3d image README 🎉
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU 27324c0dbb Mise à jour de 'README.md'
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU f48746ef86 rename folder
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU 4aecbf62f5 add: Image for readme :tada
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU 9e7a8eb789 Mise à jour de 'README.md'
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU ba1ad0493d Merge branch 'API2' of https://codefirst.iut.uca.fr/git/bastien.ollier/LOL into API2
continuous-integration/drone/push Build is failing Details
2 years ago
Nathan BOILEAU fa755f9b5e Début liaison avec BD, implementation EFDataManager
2 years ago
Bastien OLLIER 59fa7ac935 add interface graphique au projet
continuous-integration/drone/push Build is failing Details
2 years ago
Bastien OLLIER 6f8004813c fusion
continuous-integration/drone/push Build is passing Details
2 years ago
Bastien OLLIER 640e90d92d add get dans skin controller
2 years ago
Nathan BOILEAU 6f8ec9fa20 Détails REST FULL
continuous-integration/drone/push Build is passing Details
2 years ago
Nathan BOILEAU d476aff59f add : V2 of ControllerChampions
continuous-integration/drone/push Build is passing Details
2 years ago
bastien.ollier 428501e7fa deso
continuous-integration/drone/push Build is passing Details
2 years ago
bastien.ollier df40c8ca20 Merge branch 'API2' of https://codefirst.iut.uca.fr/git/bastien.ollier/LOL into API2
continuous-integration/drone/push Build is failing Details
2 years ago
bastien.ollier 4f9dced86f add debut skinManager avec les differents DTO
2 years ago
Nathan BOILEAU 28a9be2e19 merge
continuous-integration/drone/push Build is passing Details
2 years ago
Nathan BOILEAU 8dcb73d17c Unit Test sur PUT + DELETE
2 years ago
bastien.ollier 20717978fc add get avec selection du nom
continuous-integration/drone/push Build is failing Details
2 years ago
Bastien OLLIER e9bde48eae regler erreur build 🎉
continuous-integration/drone/push Build is failing Details
2 years ago
Bastien OLLIER c405b64fa2 log
continuous-integration/drone/push Build is failing Details
2 years ago
nathan boileau fb75fbb6be Début liaison API -> EF :magic_wand:
continuous-integration/drone/push Build is failing Details
2 years ago
nathan boileau f3bf722d61 Merge branch 'API2' of https://codefirst.iut.uca.fr/git/bastien.ollier/LOL into API2
continuous-integration/drone/push Build is passing Details
2 years ago
nathan boileau b659748ca5 Commentaire utile
2 years ago
Nathan BOILEAU d41e6ada17 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
2 years ago
Bastien OLLIER 60379e0a75 add log to delete champion
continuous-integration/drone/push Build is passing Details
2 years ago
Bastien OLLIER aef5c416af Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
2 years ago
Bastien OLLIER 89bb35ff90 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
2 years ago
Bastien OLLIER 7971852f0b Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
2 years ago
Bastien OLLIER ca07957521 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
2 years ago
Bastien OLLIER fd29eb4609 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
2 years ago
Bastien OLLIER 7d6bee6a89 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
2 years ago
nathan boileau 50c9a6b30c correct using
continuous-integration/drone/push Build is passing Details
2 years ago
Bastien OLLIER 873322d140 Ajouter 'Sources/apiLOL/Dockerfile'
continuous-integration/drone/push Build is failing Details
2 years ago
Bastien OLLIER 783ac8944c Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
2 years ago
nathan boileau d83d2e9d9f Merge branch 'API2' of https://codefirst.iut.uca.fr/git/bastien.ollier/LOL into API2
continuous-integration/drone/push Build is failing Details
2 years ago
nathan boileau 2dbe282b7b organisation projet
2 years ago
Bastien OLLIER ea94827f83 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
2 years ago
Bastien OLLIER ced544f2d5 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
2 years ago
Bastien OLLIER 5e4d455b28 Ajouter 'Sources/apiLOL/Dockerfile'
continuous-integration/drone/push Build is passing Details
2 years ago

@ -17,6 +17,32 @@ steps:
- dotnet restore LeagueOfLegends.sln
- dotnet build LeagueOfLegends.sln -c Release --no-restore
- dotnet publish LeagueOfLegends.sln -c Release --no-restore -o CI_PROJECT_DIR/build/release
- name: docker-build-and-push
image: plugins/docker
settings:
dockerfile: Sources/apiLOL/Dockerfile
context: Sources/
registry: hub.codefirst.iut.uca.fr
repo: hub.codefirst.iut.uca.fr/bastien.ollier/lol
username:
from_secret: SECRET_REGISTRY_USERNAME
password:
from_secret: SECRET_REGISTRY_PASSWORD
- name: deploy-container
image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest
environment:
IMAGENAME: hub.codefirst.iut.uca.fr/bastien.ollier/lol:latest
CONTAINERNAME: container_lol
COMMAND: create
OVERWRITE: true
username:
from_secret: SECRET_REGISTRY_USERNAME
password:
from_secret: SECRET_REGISTRY_PASSWORD
- name: tests
image: mcr.microsoft.com/dotnet/sdk:6.0
@ -39,7 +65,7 @@ steps:
- dotnet sonarscanner end /d:sonar.login=$${PLUGIN_SONAR_TOKEN}
secrets: [ SECRET_SONAR_LOGIN ]
settings:
# accessible en ligne de commande par ${PLUGIN_SONAR_HOST}
# accessible en ligne de commande par ${PLUGIN_SONAR_HOST}
sonar_host: https://codefirst.iut.uca.fr/sonar/
# accessible en ligne de commande par ${PLUGIN_SONAR_TOKEN}
sonar_token:

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

@ -1,168 +1,85 @@
# prepaLoL
## Diagramme de classes du modèle
```mermaid
classDiagram
class LargeImage{
+/Base64 : string
}
class Champion{
+/Name : string
+/Bio : string
+/Icon : string
+/Characteristics : Dictionary~string, int~
~ AddSkin(skin : Skin) bool
~ RemoveSkin(skin: Skin) bool
+ AddSkill(skill: Skill) bool
+ RemoveSkill(skill: Skill) bool
+ AddCharacteristics(someCharacteristics : params Tuple~string, int~[])
+ RemoveCharacteristics(label : string) bool
+ this~label : string~ : int?
}
Champion --> "1" LargeImage : Image
class ChampionClass{
<<enumeration>>
Unknown,
Assassin,
Fighter,
Mage,
Marksman,
Support,
Tank,
}
Champion --> "1" ChampionClass : Class
class Skin{
+/Name : string
+/Description : string
+/Icon : string
+/Price : float
}
Skin --> "1" LargeImage : Image
Champion "1" -- "*" Skin
class Skill{
+/Name : string
+/Description : string
}
class SkillType{
<<enumeration>>
Unknown,
Basic,
Passive,
Ultimate,
}
Skill --> "1" SkillType : Type
Champion --> "*" Skill
class Rune{
+/Name : string
+/Description : string
}
Rune --> "1" LargeImage : Image
class RuneFamily{
<<enumeration>>
Unknown,
Precision,
Domination
}
Rune --> "1" RuneFamily : Family
class Category{
<<enumeration>>
Major,
Minor1,
Minor2,
Minor3,
OtherMinor1,
OtherMinor2
}
class RunePage{
+/Name : string
+/this[category : Category] : Rune?
- CheckRunes(newRuneCategory : Category)
- CheckFamilies(cat1 : Category, cat2 : Category) bool?
- UpdateMajorFamily(minor : Category, expectedValue : bool)
}
RunePage --> "*" Rune : Dictionary~Category,Rune~
```
## Diagramme de classes des interfaces de gestion de l'accès aux données
```mermaid
classDiagram
direction LR;
class IGenericDataManager~T~{
<<interface>>
GetNbItems() Task~int~
GetItems(index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~T~~
GetNbItemsByName(substring : string)
GetItemsByName(substring : string, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~T~~
UpdateItem(oldItem : T, newItem : T) Task~T~~
AddItem(item : T) Task~T~
DeleteItem(item : T) Task~bool~
}
class IChampionsManager{
<<interface>>
GetNbItemsByCharacteristic(charName : string)
GetItemsByCharacteristic(charName : string, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Champion?~~
GetNbItemsByClass(championClass : ChampionClass)
GetItemsByClass(championClass : ChampionClass, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Champion?~~
GetNbItemsBySkill(skill : Skill?)
GetItemsBySkill(skill : Skill?, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Champion?~~
GetNbItemsBySkill(skill : string)
GetItemsBySkill(skill : string, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Champion?~~
GetNbItemsByRunePage(runePage : RunePage?)
GetItemsByRunePage(runePage : RunePage?, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Champion?~~
}
class ISkinsManager{
<<interface>>
GetNbItemsByChampion(champion : Champion?)
GetItemsByChampion(champion : Champion?, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Skin?~~
}
class IRunesManager{
<<interface>>
GetNbItemsByFamily(family : RuneFamily)
GetItemsByFamily(family : RuneFamily, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Rune?~~
}
class IRunePagesManager{
<<interface>>
GetNbItemsByRune(rune : Rune?)
GetItemsByRune(rune : Rune?, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~RunePage?~~
GetNbItemsByChampion(champion : Champion?)
GetItemsByChampion(champion : Champion?, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~RunePage?~~
}
IGenericDataManager~Champion?~ <|.. IChampionsManager : T--Champion?
IGenericDataManager~Skin?~ <|.. ISkinsManager : T--Skin?
IGenericDataManager~Rune?~ <|.. IRunesManager : T--Rune?
IGenericDataManager~RunePage?~ <|.. IRunePagesManager : T--RunePage?
class IDataManager{
<<interface>>
}
IChampionsManager <-- IDataManager : ChampionsMgr
ISkinsManager <-- IDataManager : SkinsMgr
IRunesManager <-- IDataManager : RunesMgr
IRunePagesManager <-- IDataManager : RunePagesMgr
```
## Diagramme de classes simplifié du Stub
```mermaid
classDiagram
direction TB;
IDataManager <|.. StubData
ChampionsManager ..|> IChampionsManager
StubData --> ChampionsManager
RunesManager ..|> IRunesManager
StubData --> RunesManager
RunePagesManager ..|> IRunePagesManager
StubData --> RunePagesManager
SkinsManager ..|> ISkinsManager
StubData --> SkinsManager
StubData --> RunesManager
StubData --> "*" Champion
StubData --> "*" Rune
StubData --> "*" RunePages
StubData --> "*" Skins
```
# Projet d'Entity FrameWork et Consomation et Développement de services sur League Of Legends
Ce projet est un travail universitaire réalisé durant la deuxième année de BUT à Clermont-Ferrand. Il correspond au travail demandé dans le cadre du cours regroupant Entity Framework et Consommation et Developpement de Services.
![League Of Legends](./Docs/imageCodeFirst/frontImageReadMe.jpg)
Explique ce qu'on a fait et ce qu'on a pas fait et pourquoi on a priorisé ca plutot que d'autre
# :zap: Consommation et Développement de services
Voici l'état des différentes tâches liées à la consommation et au développement de services :
> * :white_check_mark: La mise en place de toutes les opérations CRUD est terminée.
> * :white_check_mark: Une API RESTful a été mise en place en respectant les règles de routage et en utilisant les bons codes de statut.
> * :white_check_mark: La version de l'API a été gérée de manière appropriée.
> * :white_check_mark: Les logs ont été implémentés.
> * :construction: Les tests unitaires sont en cours de réalisation.
> * :construction: La création du client MAUI et sa liaison avec l'API sont en cours de réalisation.
> * :white_check_mark: La liaison avec la base de données est opérationnelle.
> * :white_check_mark: Le filtrage et la pagination des données ont été implémentés.
> * :white_check_mark: Le code est de qualité grâce à l'utilisation de SonarQube.
> * :white_check_mark: L'API a été dockerisée et hébergée sur CodeFirst.
> * :construction: Sécurité
`Note : Le client MAUI n'a pas été réalisé par manque de temps. Les tests unitaires et la sécurité sont réalisés au fur et à mesure de l'avancement du projet.`
---
# :zap: Entity Framework :
Voici l'état des différentes tâches liées à Entity Framework :
> * :white_check_mark: **Exercice 1** : Une base de données a été créée avec une table pour les champions, et des requêtes CRUD ont été implémentées, ainsi que du filtrage et de la pagination. Le client console n'a pas été réalisé pour cet exercice par manque de temps.
> * :white_check_mark: **Exercice 2** : Des tests unitaires ont été écrits et une base de données a été simulée à l'aide de SQLiteInMemory.
> * :white_check_mark: **Exercice 3** : Entity Framework a été déployé et les tests ont été effectués via Code#0.
> * :white_check_mark: **Exercice 4**: Les tables pour les runes et les skins ont été implémentées (sans les relations).
> * :white_check_mark: **Exercice 5** : Une relation OneToMany a été établie entre les champions et les skins.
> * :white_check_mark: **Exercice 6** : Une relation ManyToMany a été établie entre les champions, les rune pages et les runes.
> * :construction: **Exercice 7** : Le mapping entre le modèle et l'entité a été réalisé pour améliorer la qualité du code.
> * :construction: **Exercice 8** : La mise en place du pattern UnitOfWork n'a pas pu être implémentée par manque de temps.
---
### Diagramme d'architechture :
![Diagramme d'architechture](./Docs/imageCodeFirst/DiagrammeArchitecture.png)
#### Partie Client :
La partie client qui n'a pas pu être réalisé dans notre cas, est sensée être constituée du client MAUI et du client Console, qui affichent les ressources et testent l'architecture en utilisant le HTTPDataManager pour effectuer des requêtes à l'API et récupérer des données.
#### DataManager :
Le DataManager utilise l'une des extensions mapper pour convertir les objets DTO en Model, et peut être remplacé par EFDataManager ou StubLib.
#### Partie API :
La partie API reçoit les requêtes et renvoie les objets en conséquence, en utilisant l'EFDataManager pour accéder aux données stockées en base de données. La fluent API permet de définir précisément les attributs de la base de données.
#### Entity Framework :
L'EntityFramework est implémenté avec toutes les classes Entity dérivant du modèle, en utilisant OneToMany et ManyToMany de manière dérivée de celle prévue. Les méthodes CRUD sont implémentées grâce à l'utilisation de l'EFDataManager et le Mapper entre entity et model est requis.
#### Déploiement :
Le projet est déployé avec le projet en conteneur, mais le pattern UnitOfWork n'a pas été abordé.
# :tada: Comment lancer le projet
## 1 - Cloner le dépot
Cloner le dépôt Git en utilisant la commande suivante :
git clone https://codefirst.iut.uca.fr/git/bastien.ollier/LOL.git
## 2 - Configurer le démarrage du projet
> Configurer le projet de démarrage en cliquant sur "Projet de démarrage" à gauche de la flèche verte, puis en sélectionnant l'option "apiLOL".
## 3 - Lancement du projet
Le projet est maintenant prêt à être lancé. Vous pouvez commencer à faire des requêtes sur la base de données via l'API.

@ -1,132 +1,165 @@
using Model;
using Microsoft.EntityFrameworkCore;
using Model;
namespace EFLol.DBDataManager
{
public class EFDataManager : IDataManager
{
public class EFDataManager : IDataManager
{
public IChampionsManager ChampionsMgr => new EFChampionManager();
public ISkinsManager SkinsMgr => throw new NotImplementedException();
public IRunesManager RunesMgr => throw new NotImplementedException();
public IRunePagesManager RunePagesMgr => throw new NotImplementedException();
}
public class EFChampionManager : IChampionsManager
{
private MyDbContext _context;
public async Task<Champion?> AddItem(Champion? item)
{
if (item == null)
{
return null;
}
var addItem = await _context.AddAsync(item);
await _context.SaveChangesAsync();
return addItem.Entity;
// Va chercher les info du context (Context.champions.get)(DbSet) et les map en champion model
// Dans Program.cs de API rajouter un scope (AddScope <IDataManager, EFDataManager>) -> Ca va dire que a chaque fois que j'utilise un IDataManager, il va créer un EFDataManager
}
public async Task<bool> DeleteItem(Champion? item)
{
if (item == null)
{
return false;
}
var deletedItem = _context.Remove(item);
await _context.SaveChangesAsync();
return true;
}
private Func<Champion, string, bool> filterByName = (champion, substring) =>
champion.Name.Contains(substring, StringComparison.InvariantCultureIgnoreCase);
public Task<Champion?> UpdateItem(Champion? oldItem, Champion? newItem)
{
throw new NotImplementedException();
}
public Task<int> GetNbItems()
{
throw new NotImplementedException();
}
public async Task<IEnumerable<Champion?>> GetItems(int index, int count, string? orderingPropertyName = null, bool @descending = false)
{
IEnumerable<Champion> champions = _context.Champions.Skip(index * count).Take(count).OrderBy(champion => orderingPropertyName).Select(champion => champion.ChampionToPoco());
public IRunesManager RunesMgr => throw new NotImplementedException();
public IRunePagesManager RunePagesMgr => throw new NotImplementedException();
}
public class EFChampionManager : IChampionsManager
{
private MyDbContext _context;
public EFChampionManager()
{
_context = new MyDbContext();
}
public async Task<Champion?> AddItem(Champion? item)
{
if (item == null)
{
throw new Exception("Item is null");
}
_context.Add(item.ChampionToEntity());
_context.SaveChanges();
return item;
}
public async Task<bool> DeleteItem(Champion? item)
{
if (item == null)
{
return false;
}
var champ = _context.Champions.Select(c => c == item.ChampionToEntity());
if (champ.Count() < 1)
{
return false;
}
_context.Champions.Remove(item.ChampionToEntity());
_context.SaveChanges();
return true;
}
public async Task<Champion?> UpdateItem(Champion? oldItem, Champion? newItem)
{
if (oldItem != null && newItem != null)
{
var champEntity = await _context.Champions.FirstOrDefaultAsync(c => c.Name == oldItem.Name);
if (champEntity == null)
{
throw new Exception("Champion not found in database");
}
champEntity.Bio = newItem.Bio;
_context.SaveChanges();
return champEntity.ChampionToPoco();
}
throw new Exception("Invalid input parameters");
}
public async Task<int> GetNbItems() => _context.Champions.Count();
public async Task<IEnumerable<Champion?>> GetItems(int index, int count, string? orderingPropertyName = null,
bool descending = false)
{
IEnumerable<Champion> champions = _context.Champions.Skip(index * count)
.Take(count)
.OrderBy(champions => orderingPropertyName)
.Select(champions => champions.ChampionToPoco());
return champions;
}
public Task<int> GetNbItemsByName(string substring)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsByName(string substring, int index, int count, string? orderingPropertyName = null,
bool @descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbItemsByCharacteristic(string charName)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsByCharacteristic(string charName, int index, int count, string? orderingPropertyName = null,
bool @descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbItemsByClass(ChampionClass championClass)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsByClass(ChampionClass championClass, int index, int count, string? orderingPropertyName = null,
bool @descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbItemsBySkill(Skill? skill)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsBySkill(Skill? skill, int index, int count, string? orderingPropertyName = null, bool @descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbItemsByRunePage(RunePage? runePage)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsByRunePage(RunePage? runePage, int index, int count, string? orderingPropertyName = null,
bool @descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbItemsBySkill(string skill)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsBySkill(string skill, int index, int count, string? orderingPropertyName = null, bool @descending = false)
{
throw new NotImplementedException();
}
}
}
private Func<Champion, string, bool> filterByName = (champion, substring) =>
champion.Name.IndexOf(substring, StringComparison.InvariantCultureIgnoreCase) >= 0;
public Task<IEnumerable<Champion?>> GetItemsByName(string substring, int index, int count,
string? orderingPropertyName = null, bool descending = false)
=> _context.Champions.Select(champion => champion.ChampionToPoco())
.GetItemsWithFilterAndOrdering(champ => filterByName(champ, substring), index, count,
orderingPropertyName, descending);
public Task<int> GetNbItemsByName(string substring)
{
throw new NotImplementedException();
}
public Task<int> GetNbItemsByCharacteristic(string charName)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsByCharacteristic(string charName, int index, int count,
string? orderingPropertyName = null,
bool @descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbItemsByClass(ChampionClass championClass)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsByClass(ChampionClass championClass, int index, int count,
string? orderingPropertyName = null,
bool @descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbItemsBySkill(Skill? skill)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsBySkill(Skill? skill, int index, int count,
string? orderingPropertyName = null, bool @descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbItemsByRunePage(RunePage? runePage)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsByRunePage(RunePage? runePage, int index, int count,
string? orderingPropertyName = null,
bool @descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbItemsBySkill(string skill)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Champion?>> GetItemsBySkill(string skill, int index, int count,
string? orderingPropertyName = null, bool @descending = false)
{
throw new NotImplementedException();
}
}
}

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFLol.DBDataManager
{
static class ExtensionsDataManager
{
internal static Task<IEnumerable<T?>> GetItemsWithFilterAndOrdering<T>(this IEnumerable<T> collection,
Func<T, bool> filter, int index, int count, string? orderingPropertyName = null, bool descending = false)
{
IEnumerable<T> temp = collection;
temp = temp.Where(item => filter(item));
if (orderingPropertyName != null)
{
var prop = typeof(T).GetProperty(orderingPropertyName!);
if (prop != null)
{
temp = descending
? temp.OrderByDescending(item => prop.GetValue(item))
: temp.OrderBy(item => prop.GetValue(item));
}
}
return Task.FromResult<IEnumerable<T?>>(temp.Skip(index * count).Take(count));
}
internal static Task<int> GetNbItemsWithFilter<T>(this IEnumerable<T> collection, Func<T, bool> filter)
{
return Task.FromResult(collection.Count(item => filter(item)));
}
internal static Task<T?> AddItem<T>(this IList<T> collection, T? item)
{
if (item == null || collection.Contains(item))
{
return Task.FromResult<T?>(default(T));
}
collection.Add(item);
return Task.FromResult<T?>(item);
}
internal static Task<bool> DeleteItem<T>(this IList<T> collection, T? item)
{
if (item == null)
{
return Task.FromResult(false);
}
bool result = collection.Remove(item!);
return Task.FromResult(result);
}
internal static Task<T?> UpdateItem<T>(this IList<T> collection, T? oldItem, T? newItem)
{
if (oldItem == null || newItem == null) return Task.FromResult<T?>(default(T));
if (!collection.Contains(oldItem))
{
return Task.FromResult<T?>(default(T));
}
collection.Remove(oldItem!);
collection.Add(newItem!);
return Task.FromResult<T?>(newItem);
}
}
}

@ -26,7 +26,7 @@ namespace EFLol
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlite("Data Source=loldb.db");
optionsBuilder.UseSqlite("Data Source=../EFLol/loldb.db");
}
}

@ -1,83 +1,83 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C76D0C23-1FFA-4963-93CD-E12BD643F030}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleTests", "Tests\ConsoleTests\ConsoleTests.csproj", "{1889FA6E-B7C6-416E-8628-9449FB9070B9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{3B720C0C-53FE-4642-A2DB-87FD8634CD74}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stub", "Stub", "{2C607793-B163-4731-A4D1-AFE8A7C4C170}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StubLib", "StubLib\StubLib.csproj", "{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}"
ProjectSection(ProjectDependencies) = postProject
{3B720C0C-53FE-4642-A2DB-87FD8634CD74} = {3B720C0C-53FE-4642-A2DB-87FD8634CD74}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "apiLOL", "apiLOL\apiLOL.csproj", "{65B824B5-FADB-4C89-8B4B-D541B62B7DCA}"
ProjectSection(ProjectDependencies) = postProject
{1889FA6E-B7C6-416E-8628-9449FB9070B9} = {1889FA6E-B7C6-416E-8628-9449FB9070B9}
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9} = {2960F9BA-49DE-494D-92E3-CE5A794BA1A9}
{3B720C0C-53FE-4642-A2DB-87FD8634CD74} = {3B720C0C-53FE-4642-A2DB-87FD8634CD74}
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB} = {B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFLol", "EFLol\EFLol.csproj", "{7AEE66D2-490B-4049-B9D3-C629D7F78DA7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUnitaire", "TestUnitaire\TestUnitaire.csproj", "{5702F240-97BD-4757-918C-6E485C57D0EC}"
ProjectSection(ProjectDependencies) = postProject
{65B824B5-FADB-4C89-8B4B-D541B62B7DCA} = {65B824B5-FADB-4C89-8B4B-D541B62B7DCA}
{7AEE66D2-490B-4049-B9D3-C629D7F78DA7} = {7AEE66D2-490B-4049-B9D3-C629D7F78DA7}
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB} = {B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Release|Any CPU.Build.0 = Release|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Release|Any CPU.Build.0 = Release|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Release|Any CPU.Build.0 = Release|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Release|Any CPU.Build.0 = Release|Any CPU
{65B824B5-FADB-4C89-8B4B-D541B62B7DCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65B824B5-FADB-4C89-8B4B-D541B62B7DCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65B824B5-FADB-4C89-8B4B-D541B62B7DCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65B824B5-FADB-4C89-8B4B-D541B62B7DCA}.Release|Any CPU.Build.0 = Release|Any CPU
{7AEE66D2-490B-4049-B9D3-C629D7F78DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7AEE66D2-490B-4049-B9D3-C629D7F78DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7AEE66D2-490B-4049-B9D3-C629D7F78DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7AEE66D2-490B-4049-B9D3-C629D7F78DA7}.Release|Any CPU.Build.0 = Release|Any CPU
{5702F240-97BD-4757-918C-6E485C57D0EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5702F240-97BD-4757-918C-6E485C57D0EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5702F240-97BD-4757-918C-6E485C57D0EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5702F240-97BD-4757-918C-6E485C57D0EC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{1889FA6E-B7C6-416E-8628-9449FB9070B9} = {C76D0C23-1FFA-4963-93CD-E12BD643F030}
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB} = {2C607793-B163-4731-A4D1-AFE8A7C4C170}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {92F3083D-793F-4552-8A9A-0AD6534159C9}
EndGlobalSection
EndGlobal

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C76D0C23-1FFA-4963-93CD-E12BD643F030}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleTests", "Tests\ConsoleTests\ConsoleTests.csproj", "{1889FA6E-B7C6-416E-8628-9449FB9070B9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{3B720C0C-53FE-4642-A2DB-87FD8634CD74}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stub", "Stub", "{2C607793-B163-4731-A4D1-AFE8A7C4C170}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StubLib", "StubLib\StubLib.csproj", "{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}"
ProjectSection(ProjectDependencies) = postProject
{3B720C0C-53FE-4642-A2DB-87FD8634CD74} = {3B720C0C-53FE-4642-A2DB-87FD8634CD74}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "apiLOL", "apiLOL\apiLOL.csproj", "{65B824B5-FADB-4C89-8B4B-D541B62B7DCA}"
ProjectSection(ProjectDependencies) = postProject
{1889FA6E-B7C6-416E-8628-9449FB9070B9} = {1889FA6E-B7C6-416E-8628-9449FB9070B9}
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9} = {2960F9BA-49DE-494D-92E3-CE5A794BA1A9}
{3B720C0C-53FE-4642-A2DB-87FD8634CD74} = {3B720C0C-53FE-4642-A2DB-87FD8634CD74}
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB} = {B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFLol", "EFLol\EFLol.csproj", "{7AEE66D2-490B-4049-B9D3-C629D7F78DA7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUnitaire", "TestUnitaire\TestUnitaire.csproj", "{5702F240-97BD-4757-918C-6E485C57D0EC}"
ProjectSection(ProjectDependencies) = postProject
{65B824B5-FADB-4C89-8B4B-D541B62B7DCA} = {65B824B5-FADB-4C89-8B4B-D541B62B7DCA}
{7AEE66D2-490B-4049-B9D3-C629D7F78DA7} = {7AEE66D2-490B-4049-B9D3-C629D7F78DA7}
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB} = {B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Release|Any CPU.Build.0 = Release|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Release|Any CPU.Build.0 = Release|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Release|Any CPU.Build.0 = Release|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Release|Any CPU.Build.0 = Release|Any CPU
{65B824B5-FADB-4C89-8B4B-D541B62B7DCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65B824B5-FADB-4C89-8B4B-D541B62B7DCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65B824B5-FADB-4C89-8B4B-D541B62B7DCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65B824B5-FADB-4C89-8B4B-D541B62B7DCA}.Release|Any CPU.Build.0 = Release|Any CPU
{7AEE66D2-490B-4049-B9D3-C629D7F78DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7AEE66D2-490B-4049-B9D3-C629D7F78DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7AEE66D2-490B-4049-B9D3-C629D7F78DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7AEE66D2-490B-4049-B9D3-C629D7F78DA7}.Release|Any CPU.Build.0 = Release|Any CPU
{5702F240-97BD-4757-918C-6E485C57D0EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5702F240-97BD-4757-918C-6E485C57D0EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5702F240-97BD-4757-918C-6E485C57D0EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5702F240-97BD-4757-918C-6E485C57D0EC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{1889FA6E-B7C6-416E-8628-9449FB9070B9} = {C76D0C23-1FFA-4963-93CD-E12BD643F030}
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB} = {2C607793-B163-4731-A4D1-AFE8A7C4C170}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {92F3083D-793F-4552-8A9A-0AD6534159C9}
EndGlobalSection
EndGlobal

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:LolApp.ViewModels"
xmlns:myviews="clr-namespace:LolApp.ContentViews"
xmlns:appvm="clr-namespace:LolApp.ViewModels"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="LolApp.AddChampionPage"
Title="AddChampionPage"
x:Name="root">
<Grid RowDefinitions="Auto,*, Auto" BackgroundColor="{StaticResource Black}">
<VerticalStackLayout>
<Label Text="Nouveau Champion" IsVisible="{Binding IsNew}"
Style="{StaticResource title}"/>
<Label Text="Modifier le Champion" IsVisible="{Binding IsNew, Converter={StaticResource invertedBoolConverter}}"
Style="{StaticResource title}"/>
<Grid><Line Stroke="{StaticResource Primary}"
X1="0" Y1="0" X2="200" Y2="0"
HorizontalOptions="Center"/>
</Grid>
</VerticalStackLayout>
<ScrollView Grid.Row="1">
<Grid ColumnDefinitions="*, 3*" RowDefinitions="Auto, Auto, Auto, 162, 162, Auto, Auto, Auto, Auto">
<Label Text="Nom :"
Style="{StaticResource labelForEntry}"/>
<Entry Grid.Column="1" Placeholder="Nom du champion" Text="{Binding Champion.Name}"
Style="{StaticResource defaultEntry}"
IsEnabled="{Binding IsNew}"/>
<Label Text="Icone :" Grid.Row="1" Style="{StaticResource labelForEntry}"/>
<ImageButton Grid.Row="1" Grid.Column="1" HeightRequest="42" WidthRequest="42"
Source="{Binding Champion.IconBase64, TargetNullValue='lol.png',
Converter={StaticResource base64ToImageSourceConverter}}"
BackgroundColor="{StaticResource Secondary}"
HorizontalOptions="Start"
Margin="6"
Command="{Binding PickIconCommand}"/>
<Label Text="Image :" Grid.Row="2" Style="{StaticResource labelForEntry}"/>
<Grid Grid.Row="2" Grid.Column="1" x:Name="largeImageGrid" Margin="0, 0, 12, 0">
<ImageButton WidthRequest="{Binding Width, Source={x:Reference largeImageGrid}}"
HeightRequest="150"
Source="{Binding Champion.LargeImageBase64, TargetNullValue='lollogo.jpg',
Converter={StaticResource base64ToImageSourceConverter}}"
BackgroundColor="{StaticResource Secondary}"
HorizontalOptions="Start"
Margin="6"
Command="{Binding PickLargeImageCommand}"/>
</Grid>
<Label Text="Bio :" Grid.Row="3"
Style="{StaticResource labelForEntry}"/>
<Editor Grid.Column="1" Grid.Row="3"
Text="{Binding Champion.Bio}" Style="{StaticResource defaultEditor}"/>
<Label Text="Classe :" Grid.Row="4"
Style="{StaticResource labelForEntry}"/>
<myviews:ChampionClassSelector Grid.Row="4" Grid.Column="1" MaximumWidthRequest="{OnPlatform WinUI=400}"
CheckedColor="{StaticResource Primary}"
UncheckedColor="{StaticResource Secondary}"
SelectedValue="{Binding Champion.ChampionClass, Mode=TwoWay}"/>
<Label Text="Caractéristiques :" Grid.Row="5" Grid.RowSpan="2"
Style="{StaticResource labelForEntry}" VerticalOptions="Start"/>
<Border Stroke="{StaticResource Secondary}" Grid.Row="5" Grid.Column="1" VerticalOptions="FillAndExpand"> <ListView ItemsSource="{Binding Champion.Characteristics}"
Margin="6" HeightRequest="100" HorizontalOptions="Fill" VerticalOptions="Fill"
BackgroundColor="{StaticResource Black}" SeparatorColor="{StaticResource Secondary}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Command="{Binding Source={x:Reference root}, Path=BindingContext.RemoveCharacteristicCommand}"
CommandParameter="{Binding .}"
IsDestructive="True" Text="Delete"/>
</ViewCell.ContextActions>
<Border Margin="0,4" BackgroundColor="{StaticResource Secondary}">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10, 10, 0, 10"/>
</Border.StrokeShape>
<Grid ColumnDefinitions="*, Auto">
<Label Text="{Binding Key}" TextColor="{StaticResource Black}"
HorizontalOptions="Start" VerticalOptions="Center" Margin="4, 0, 0, 0"/>
<Label Text="{Binding Value}" Grid.Column="1" TextColor="{StaticResource Black}"
HorizontalOptions="End" VerticalOptions="Center" Margin="0, 0, 4, 0"/>
</Grid>
</Border>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
<Grid Grid.Column="1" Grid.Row="6" ColumnDefinitions="*, 58, Auto">
<Entry Style="{StaticResource defaultEntry}" Placeholder="Caractéristique" Text="{Binding NewCharacteristicDescription}"/>
<Entry Style="{StaticResource defaultEntry}" Placeholder="Valeur" Grid.Column="1" Text="{Binding NewCharacteristicValue}" Keyboard="Numeric">
<Entry.Behaviors>
<toolkit:NumericValidationBehavior Flags="ValidateOnValueChanged"
MinimumValue="0"
MaximumValue="9999999"
MaximumDecimalPlaces="0"
InvalidStyle="{StaticResource InvalidEntryStyle}"
ValidStyle="{StaticResource defaultEntry}"/>
</Entry.Behaviors>
</Entry>
<Button Grid.Column="2" Margin="4,8" CornerRadius="22"
Text="{StaticResource plus}"
Command="{Binding AddCharacteristicCommand}"/>
</Grid>
<Label Style="{StaticResource labelForEntry}" Text="Compétences :" Grid.Row="7" VerticalOptions="Start"/>
<Grid Grid.Row="7" Grid.Column="1" ColumnDefinitions="*, Auto">
<ListView ItemsSource="{Binding Champion.Skills}" Margin="6"
HeightRequest="100" HorizontalOptions="Fill" VerticalOptions="Fill" HasUnevenRows="True"
BackgroundColor="{StaticResource Black}" SeparatorColor="{StaticResource Secondary}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid RowDefinitions="Auto, Auto, *">
<Grid.Resources>
<Style TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource Primary}"/>
</Style>
</Grid.Resources>
<Label Text="{Binding Name}" FontSize="Medium" FontAttributes="Bold" VerticalOptions="Center"/>
<Label Text="{Binding Type}" Grid.Row="1" VerticalOptions="Center" FontAttributes="Italic" FontSize="Micro"/>
<Label Text="{Binding Description}" FontSize="Micro" FontAttributes="Italic"
Grid.Row="2"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Column="1" Margin="4,8" CornerRadius="22"
Text="{StaticResource plus}" VerticalOptions="Start"
Command="{Binding AddSkillCommand}"/>
</Grid>
</Grid>
</ScrollView>
<HorizontalStackLayout Grid.Row="2" HorizontalOptions="Center" Spacing="40" Margin="0, 10, 0, 20">
<Button Text="Ajouter" Command="{Binding AddChampionCommand}" IsVisible="{Binding IsNew}"/>
<Button Text="Modifier" Command="{Binding EditChampionCommand}" IsVisible="{Binding IsNew, Converter={StaticResource invertedBoolConverter}}"/>
<Button Text="Annuler" Command="{Binding CancelCommand}"/>
</HorizontalStackLayout>
</Grid>
</ContentPage>

@ -0,0 +1,13 @@
using LolApp.ViewModels;
using ViewModels;
namespace LolApp;
public partial class AddChampionPage : ContentPage
{
public AddChampionPage(ChampionsMgrVM championsMgrVM, ChampionVM champion = null)
{
InitializeComponent();
BindingContext = new AddChampionPageVM(championsMgrVM, champion);
}
}

@ -0,0 +1,73 @@
<?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="LolApp.AddOrEditSkinPage"
Title="AddOrEditSkinPage"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit">
<Grid RowDefinitions="Auto,*, Auto" BackgroundColor="{StaticResource Black}">
<VerticalStackLayout>
<Label Text="Nouveau Skin" IsVisible="{Binding IsNew}"
Style="{StaticResource title}"/>
<Label Text="Modifier le Skin" IsVisible="{Binding IsNew, Converter={StaticResource invertedBoolConverter}}"
Style="{StaticResource title}"/>
<Grid><Line Stroke="{StaticResource Primary}"
X1="0" Y1="0" X2="200" Y2="0"
HorizontalOptions="Center"/>
</Grid>
</VerticalStackLayout>
<ScrollView Grid.Row="1">
<Grid ColumnDefinitions="*, 3*" RowDefinitions="Auto, Auto, Auto, Auto, 162">
<Label Text="Nom :"
Style="{StaticResource labelForEntry}"/>
<Entry Grid.Column="1" Placeholder="Nom du skin" Text="{Binding Skin.Name}"
Style="{StaticResource defaultEntry}"
IsEnabled="{Binding IsNew}"/>
<Label Text="Icone :" Grid.Row="1" Style="{StaticResource labelForEntry}"/>
<ImageButton Grid.Row="1" Grid.Column="1" HeightRequest="42" WidthRequest="42"
Source="{Binding Skin.IconBase64, TargetNullValue='lol.png',
Converter={StaticResource base64ToImageSourceConverter}}"
BackgroundColor="{StaticResource Secondary}"
HorizontalOptions="Start"
Margin="6"
Command="{Binding PickIconCommand}"/>
<Label Text="Image :" Grid.Row="2" Style="{StaticResource labelForEntry}"/>
<Grid Grid.Row="2" Grid.Column="1" x:Name="largeImageGrid" Margin="0, 0, 12, 0">
<ImageButton WidthRequest="{Binding Width, Source={x:Reference largeImageGrid}}"
HeightRequest="150"
Source="{Binding Skin.LargeImageBase64, TargetNullValue='lollogo.jpg',
Converter={StaticResource base64ToImageSourceConverter}}"
BackgroundColor="{StaticResource Secondary}"
HorizontalOptions="Start"
Margin="6"
Command="{Binding PickLargeImageCommand}"/>
</Grid>
<Label Text="Prix :" Grid.Row="3"
Style="{StaticResource labelForEntry}"/>
<HorizontalStackLayout Grid.Column="1" Grid.Row="3" Margin="6">
<Image Source="rp.png" HeightRequest="16" WidthRequest="16"/>
<Entry Grid.Column="1" Placeholder="Nom du skin" Text="{Binding Skin.Price}"
Style="{StaticResource defaultEntry}" Margin="4, 0, 0, 0" HorizontalTextAlignment="Start">
<Entry.Behaviors>
<toolkit:NumericValidationBehavior Flags="ValidateOnValueChanged"
MinimumValue="0"
MaximumValue="9999999"
MaximumDecimalPlaces="0"
InvalidStyle="{StaticResource InvalidEntryStyle}"
ValidStyle="{StaticResource defaultEntry}"/>
</Entry.Behaviors>
</Entry>
</HorizontalStackLayout>
<Label Text="Description :" Grid.Row="4"
Style="{StaticResource labelForEntry}"/>
<Editor Grid.Column="1" Grid.Row="4"
Text="{Binding Skin.Description}" Style="{StaticResource defaultEditor}"/>
</Grid>
</ScrollView>
<HorizontalStackLayout Grid.Row="2" HorizontalOptions="Center" Spacing="40" Margin="0, 10, 0, 20">
<Button Text="Ajouter" Command="{Binding AddSkinCommand}" IsVisible="{Binding IsNew}"/>
<Button Text="Modifier" Command="{Binding EditSkinCommand}" IsVisible="{Binding IsNew, Converter={StaticResource invertedBoolConverter}}"/>
<Button Text="Annuler" Command="{Binding CancelCommand}"/>
</HorizontalStackLayout>
</Grid>
</ContentPage>

@ -0,0 +1,24 @@
using LolApp.ViewModels;
using ViewModels;
namespace LolApp;
public partial class AddOrEditSkinPage : ContentPage
{
AddOrEditSkinPage()
{
InitializeComponent();
}
public AddOrEditSkinPage(SkinsMgrVM skinsMgrVM, SkinVM skin)
:this()
{
BindingContext = new AddOrEditSkinPageVM(skinsMgrVM, skin);
}
public AddOrEditSkinPage(SkinsMgrVM skinsMgrVM, ChampionVM champion)
:this()
{
BindingContext = new AddOrEditSkinPageVM(skinsMgrVM, champion);
}
}

@ -0,0 +1,36 @@
<?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="LolApp.AddSkill"
xmlns:appvm="clr-namespace:LolApp.ViewModels"
Title="AddSkill">
<Grid RowDefinitions="Auto,*, Auto" BackgroundColor="{StaticResource Gray900}">
<VerticalStackLayout>
<Label Text="Nouvelle Compétence"
Style="{StaticResource title}"/>
<Grid>
<Line Stroke="{StaticResource Primary}"
X1="0" Y1="0" X2="200" Y2="0"
HorizontalOptions="Center"/>
</Grid>
</VerticalStackLayout>
<ScrollView Grid.Row="1">
<Grid ColumnDefinitions="*, 3*" RowDefinitions="Auto, Auto, *">
<Label Text="Nom :" Style="{StaticResource labelForEntry}"/>
<Entry Text="{Binding Name}" Style="{StaticResource defaultEntry}"
Grid.Column="1"/>
<Label Text="Type :" Style="{StaticResource labelForEntry}" Grid.Row="1"/>
<Picker ItemsSource="{Binding AllSkills}" SelectedItem="{Binding SkillType}"
Grid.Row="1" Grid.Column="1"
Style="{StaticResource defaultPicker}"/>
<Label Text="Description :" Style="{StaticResource labelForEntry}" Grid.Row="2"/>
<Editor Grid.Row="2" Grid.Column="1" Text="{Binding Description}"
Style="{StaticResource defaultEditor}"/>
</Grid>
</ScrollView>
<HorizontalStackLayout Grid.Row="2" HorizontalOptions="Center" Spacing="40" Margin="0, 10, 0, 20">
<Button Text="Ajouter" Command="{Binding AddSkillToChampionCommand}"/>
<Button Text="Annuler" Command="{Binding CancelCommand}"/>
</HorizontalStackLayout>
</Grid>
</ContentPage>

@ -0,0 +1,13 @@
using LolApp.ViewModels;
using ViewModels;
namespace LolApp;
public partial class AddSkill : ContentPage
{
public AddSkill(EditableChampionVM champion)
{
InitializeComponent();
BindingContext = new AddSkillVM(champion);
}
}

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

@ -0,0 +1,12 @@
namespace LolApp;
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
}

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="LolApp.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:LolApp"
Shell.FlyoutBehavior="Disabled">
<TabBar>
<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage"
Icon="{OnPlatform 'lol.png'}" />
<ShellContent
Title="Champions"
ContentTemplate="{DataTemplate local:ChampionsPage}"
Route="Championspage"
Icon="{OnPlatform 'sword.png'}" />
</TabBar>
</Shell>

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

@ -0,0 +1,185 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="LolApp.ChampionPage"
Title="ChampionPage"
x:Name="root"
BackgroundColor="Black">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Modifier" Command="{Binding AppVM.NavigateToEditChampionPageCommand, Source={x:Reference root}}"
CommandParameter="{Binding}"/>
</ContentPage.ToolbarItems>
<ScrollView>
<VerticalStackLayout>
<AbsoluteLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
MaximumHeightRequest="{OnPlatform WinUI=300}"
HeightRequest="{Binding Width,
Source={RelativeSource AncestorType={x:Type ContentPage}},
Converter={StaticResource imageRatioConverter},
ConverterParameter={StaticResource imageRatio}}">
<Image Source="{Binding Image, Converter={StaticResource base64ToImageSourceConverter}}"
Aspect="AspectFit"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
MaximumHeightRequest="{OnPlatform WinUI=300}"/>
</AbsoluteLayout>
<Grid Padding="10" BackgroundColor="{StaticResource Black}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" FontAttributes="Bold" TextColor="{StaticResource Primary}"
FontSize="Title"/>
<VerticalStackLayout Grid.Column="1" HorizontalOptions="Center">
<Image Source="{Binding Class, Converter={StaticResource championClassToIconConverter}}"
HeightRequest="26" WidthRequest="26" x:Name="imgClass" PropertyChanged="imgClass_PropertyChanged">
<Image.Behaviors>
<toolkit:IconTintColorBehavior TintColor="{StaticResource Primary}"
x:Name="tintColor"/>
</Image.Behaviors>
</Image>
<Label Text="{Binding Class}" TextColor="{StaticResource Primary}"
FontSize="Micro"/>
</VerticalStackLayout>
</Grid>
<ScrollView VerticalScrollBarVisibility="Always" BackgroundColor="Black" >
<Label Text="{Binding Bio}" TextColor="{StaticResource Primary}" Padding="10" FontAttributes="Italic"/>
</ScrollView>
<Label Padding="10" Text="Caractéristiques" FontSize="Title" TextColor="{StaticResource Primary}"
BackgroundColor="Black"/>
<Grid MaximumHeightRequest="240">
<Grid.Resources>
<x:Double x:Key="gridHeight">120</x:Double>
<x:Int32 x:Key="nbCellsPerLine">3</x:Int32>
</Grid.Resources>
<Grid.HeightRequest>
<MultiBinding Converter="{StaticResource multiMathExpressionConverter}"
ConverterParameter="ceiling(x1/x2)*x0">
<Binding Source="{StaticResource gridHeight}"/>
<Binding Path="Characteristics.Count"/>
<Binding Source="{StaticResource nbCellsPerLine}"/>
</MultiBinding>
</Grid.HeightRequest>
<CollectionView ItemsSource="{Binding Characteristics}"
ItemsLayout="VerticalGrid, 3"
VerticalScrollBarVisibility="Always">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10" HeightRequest="120">
<Border Stroke="{StaticResource PrimaryBrush}" StrokeThickness="2" BackgroundColor="{StaticResource Black}">
<Grid RowDefinitions="4*,3*">
<Label Text="{Binding Key}" HorizontalOptions="Center" TextColor="{StaticResource Primary}"
FontSize="Small" FontAttributes="Bold" VerticalOptions="End" HorizontalTextAlignment="Center"
Margin="0, 0, 0, 5"/>
<Label Grid.Row="1" Text="{Binding Value}" HorizontalOptions="Center" TextColor="{StaticResource Primary}"
VerticalOptions="Start" HorizontalTextAlignment="Center"
FontSize="Small"/>
</Grid>
</Border>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
<Label Padding="10" Text="Compétences" FontSize="Title" TextColor="{StaticResource Primary}"
BackgroundColor="Black"/>
<ListView ItemsSource="{Binding Skills}" Margin="10"
BackgroundColor="Black" HasUnevenRows="True" VerticalScrollBarVisibility="Always"
MaximumHeightRequest="400">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.Resources>
<Style TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource Primary}"/>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Text="{Binding Name}" FontSize="Medium" FontAttributes="Bold" VerticalOptions="Center"/>
<Label Text="{Binding Type}" Grid.Column="1" VerticalOptions="Center" FontAttributes="Italic" FontSize="Micro"/>
<Label Text="{Binding Description}" FontSize="Micro" FontAttributes="Italic"
Grid.ColumnSpan="2" Grid.Row="1"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid ColumnDefinitions="*, Auto">
<Label Padding="10" Text="Skins" FontSize="Title" TextColor="{StaticResource Primary}"
BackgroundColor="Black"/>
<Button Grid.Column="1" Text="{StaticResource plus}" CornerRadius="22" BackgroundColor="{StaticResource Primary}"
TextColor="{StaticResource Black}" FontSize="Header"
Command="{Binding AppVM.NavigateToAddNewSkinPageCommand, Source={x:Reference root}}"
CommandParameter="{Binding}"
VerticalOptions="Center" HorizontalOptions="Center"
Margin="6"/>
</Grid>
<ListView BindingContext="{Binding AppVM, Source={x:Reference root}}"
ItemsSource="{Binding SkinsMgrVM.Skins}" HasUnevenRows="True"
BackgroundColor="{StaticResource Black}"
x:Name="listSkins">
<ListView.Behaviors>
<toolkit:EventToCommandBehavior
EventName="ItemSelected"
Command="{Binding NavigateToSkinDetailsPageCommand}"
EventArgsConverter="{StaticResource SelectedItemEventArgsConverter}"
/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem IsDestructive="True"
Text="Supprimer"
Command="{Binding BindingContext.SkinsMgrVM.DeleteSkinCommand, Source={x:Reference listSkins}}"
CommandParameter="{Binding .}"/>
<MenuItem Text="Modifier"
Command="{Binding Source={x:Reference listSkins}, Path=BindingContext.NavigateToEditSkinPageCommand}"
CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>
<Border Stroke="{StaticResource Primary}" Padding="8,4" HeightRequest="60" Margin="4"
StrokeThickness="3" BackgroundColor="{StaticResource Black}">
<Border.StrokeShape>
<RoundRectangle CornerRadius="0, 10, 10, 10"/>
</Border.StrokeShape>
<HorizontalStackLayout VerticalOptions="Center">
<Image Source="{Binding Icon, Converter={StaticResource base64ToImageSourceConverter}}"
HeightRequest="46" WidthRequest="46"/>
<Label Text="{Binding Name}" TextColor="{StaticResource Primary}" FontSize="Small"
VerticalOptions="Center" Margin="10,4"/>
</HorizontalStackLayout>
</Border>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

@ -0,0 +1,31 @@
using CommunityToolkit.Maui.Behaviors;
using LolApp.ViewModels;
using ViewModels;
namespace LolApp;
public partial class ChampionPage : ContentPage
{
public ApplicationVM AppVM { get; set; }
public ChampionVM Champion { get; }
public ChampionPage(ChampionVM cvm, ApplicationVM appVM)
{
AppVM = appVM;
BindingContext = Champion = cvm;
InitializeComponent();
}
void imgClass_PropertyChanged(System.Object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Image img = sender as Image;
if(e.PropertyName == "Source" && img != null && img.Behaviors.Any(b => b is IconTintColorBehavior))
{
var beh = (img.Behaviors.First(b => b is IconTintColorBehavior) as IconTintColorBehavior);
var color = beh.TintColor;
img.Behaviors.Remove(beh);
img.Behaviors.Add(new IconTintColorBehavior() { TintColor = color});
}
}
}

@ -0,0 +1,196 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:myviews="clr-namespace:LolApp.ContentViews"
xmlns:vm="clr-namespace:ViewModels;assembly=ViewModels"
xmlns:appvm="clr-namespace:LolApp.ViewModels"
x:Class="LolApp.ChampionsPage"
Title="Champions"
x:Name="root">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Ajouter" Command="{Binding AppVM.NavigateToAddNewChampionPageCommand}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ControlTemplate x:Key="searchByStringControl">
<Grid Margin="20,4" HeightRequest="{OnPlatform 30, Android=40}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Entry Placeholder="{TemplateBinding PlaceHolder}"
Text="{TemplateBinding Text, Mode=TwoWay}"/>
<Button Text="{StaticResource magnifying-glass}"
FontFamily="FASolid"
Grid.Column="1" Margin="4, 0, 0, 0"
Command="{TemplateBinding Command}"
CommandParameter="{TemplateBinding CommandParameter}"/>
</Grid>
</ControlTemplate>
</ContentPage.Resources>
<ContentPage.Behaviors>
<toolkit:EventToCommandBehavior
EventName = "Loaded"
Command="{Binding AppVM.ChampionsMgrVM.LoadChampionsCommand}"/>
</ContentPage.Behaviors>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<toolkit:Expander Grid.Row="1">
<toolkit:Expander.Header>
<HorizontalStackLayout>
<Label Text="Chercher par "
HorizontalOptions="Center"/>
<Label Text="nom" IsVisible="{Binding VM.SearchedName, Converter={StaticResource isStringNotNullOrWhiteSpaceConverter}}"/>
<Label Text="compétence" IsVisible="{Binding VM.SearchedSkill, Converter={StaticResource isStringNotNullOrWhiteSpaceConverter}}"/>
<Label Text=" "/>
<Label Text="{Binding IsExpanded,
Source={RelativeSource AncestorType={x:Type toolkit:Expander}},
Converter={StaticResource isExpandedToCaretConverter}}"
FontFamily="FASolid"
VerticalOptions="Center"/>
</HorizontalStackLayout>
</toolkit:Expander.Header>
<VerticalStackLayout HorizontalOptions="Fill" BackgroundColor="WhiteSmoke">
<myviews:SearchByStringView ControlTemplate="{StaticResource searchByStringControl}"
PlaceHolder="Entrez un nom"
Text="{Binding VM.SearchedName, Mode=TwoWay}"
Command="{Binding AppVM.ChampionsMgrVM.LoadChampionsByNameCommand}"
CommandParameter="{Binding VM.SearchedName}"/>
<myviews:SearchByStringView ControlTemplate="{StaticResource searchByStringControl}"
PlaceHolder="Entrez une compétence"
Text="{Binding VM.SearchedSkill, Mode=TwoWay}"
Command="{Binding AppVM.ChampionsMgrVM.LoadChampionsBySkillCommand}"
CommandParameter="{Binding VM.SearchedSkill}"/>
<myviews:SearchByStringView ControlTemplate="{StaticResource searchByStringControl}"
PlaceHolder="Entrez une caractéristique"
Text="{Binding VM.SearchedCharacteristic, Mode=TwoWay}"
Command="{Binding AppVM.ChampionsMgrVM.LoadChampionsByCharacteristicCommand}"
CommandParameter="{Binding VM.SearchedCharacteristic}"/>
<Label Text="Filtrer par classe :" Margin="20, 4, 0, 0"
FontSize="Micro"/>
<CollectionView ItemsSource="{x:Static appvm:ChampionClassVM.Classes}" ItemsLayout="VerticalGrid, 3"
SelectionMode="Single" HeightRequest="110" x:Name="classesView"
SelectionChangedCommand="{Binding AppVM.ChampionsMgrVM.LoadChampionsByClassCommand}"
SelectionChangedCommandParameter="{Binding VM.SelectedItem, Source={RelativeSource Self}}"
SelectedItem="{Binding VM.SelectedClass, Source={x:Reference root}, Mode=TwoWay}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid HorizontalOptions="Center" RowDefinitions="*, *" WidthRequest="100" Padding="10, 10, 10, 0"
BackgroundColor="{Binding IsSelected, Converter={StaticResource isSelectedToColorConverter}}">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference root}, Path=BindingContext.VM.SelectedChampionClassChangedCommand}"
CommandParameter="{Binding}" />
</Grid.GestureRecognizers>
<Image Source="{Binding Model, Converter={StaticResource championClassToIconConverter}}" HeightRequest="26" WidthRequest="26"
/>
<Label Text="{Binding Model}" TextColor="{StaticResource Black}"
HorizontalOptions="Center" Grid.Row="1"
FontSize="Micro">
</Label>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</toolkit:Expander>
<ListView Grid.Row="2" CachingStrategy="RecycleElementAndDataTemplate"
ItemsSource="{Binding AppVM.ChampionsMgrVM.Champions}"
RowHeight="50"
SelectedItem="{Binding AppVM.ChampionsMgrVM.SelectedChampion, Mode=TwoWay}">
<ListView.Behaviors>
<toolkit:EventToCommandBehavior
EventName="ItemSelected"
Command="{Binding AppVM.NavigateToChampionDetailsPageCommand}"
EventArgsConverter="{StaticResource SelectedItemEventArgsConverter}"
/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Command="{Binding Source={x:Reference root}, Path=BindingContext.AppVM.ChampionsMgrVM.DeleteChampionCommand}"
CommandParameter="{Binding .}"
IsDestructive="True" Text="Supprimer"/>
<MenuItem Command="{Binding Source={x:Reference root}, Path=BindingContext.AppVM.NavigateToEditChampionPageCommand}"
CommandParameter="{Binding .}"
IsDestructive="False" Text="Modifier"/>
</ViewCell.ContextActions>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Source="{Binding Icon, Converter={StaticResource base64ToImageSourceConverter}}"
HeightRequest="40"
WidthRequest="40"
Grid.RowSpan="2"
VerticalOptions="Center"
Margin="0, 0, 10, 0"/>
<Label Text="{Binding Name}" Grid.Column="1"
FontAttributes="Bold"
FontSize="{OnPlatform Header, WinUI=Small}"
VerticalOptions="Center"/>
<Label Text="{Binding Class}" Grid.Row="1" Grid.Column="1"
FontAttributes="Italic"
FontSize="Caption"
VerticalOptions="Center"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Row="3" HorizontalOptions="Center" HeightRequest="45"
IsVisible="{Binding AppVM.ChampionsMgrVM.NbChampions, Converter={StaticResource intToBoolConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Style="{StaticResource iconButton}"
Command="{Binding AppVM.ChampionsMgrVM.PreviousPageCommand}">
<Button.ImageSource>
<FontImageSource Glyph="{StaticResource angle-left}"
FontFamily="FASolid"
Size="Title"/>
</Button.ImageSource>
</Button>
<StackLayout Orientation="Horizontal" Grid.Column="1"
HorizontalOptions="Center" VerticalOptions="Center">
<StackLayout.Resources>
<Style BasedOn="{StaticResource defaultLabel}" TargetType="Label">
<Setter Property="Margin" Value="2"/>
</Style>
</StackLayout.Resources>
<Label Text="{Binding AppVM.ChampionsMgrVM.Index, Converter={StaticResource plusOneConverter}}" HorizontalOptions="End"/>
<Label Text="/"/>
<Label Text="{Binding AppVM.ChampionsMgrVM.NbPages}" HorizontalOptions="Start"/>
</StackLayout>
<Button Grid.Column="2" Style="{StaticResource iconButton}"
Command="{Binding AppVM.ChampionsMgrVM.NextPageCommand}">
<Button.ImageSource>
<FontImageSource Glyph="{StaticResource angle-right}" FontFamily="FASolid"
Size="Title"/>
</Button.ImageSource>
</Button>
</Grid>
</Grid>
</ContentPage>

@ -0,0 +1,17 @@
using LolApp.ViewModels;
using ViewModels;
namespace LolApp;
public partial class ChampionsPage : ContentPage
{
public ApplicationVM AppVM { get; }
public ChampionsPageVM VM { get; }
public ChampionsPage(ApplicationVM appVM)
{
InitializeComponent();
AppVM = appVM;
VM = new ChampionsPageVM(AppVM.ChampionsMgrVM);
BindingContext = this;
}
}

@ -0,0 +1,131 @@
<?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:model="clr-namespace:Model;assembly=Model"
x:Class="LolApp.ContentViews.ChampionClassSelector"
x:Name="root">
<ContentView.Resources>
<model:ChampionClass x:Key="assassin">Assassin</model:ChampionClass>
<model:ChampionClass x:Key="fighter">Fighter</model:ChampionClass>
<model:ChampionClass x:Key="mage">Mage</model:ChampionClass>
<model:ChampionClass x:Key="marksman">Marksman</model:ChampionClass>
<model:ChampionClass x:Key="support">Support</model:ChampionClass>
<model:ChampionClass x:Key="tank">Tank</model:ChampionClass>
<ControlTemplate x:Key="RadioButtonTemplate">
<Border Stroke="{StaticResource Transparent}"
BackgroundColor="{StaticResource Transparent}"
HorizontalOptions="Fill"
VerticalOptions="Fill"
Padding="0">
<Border.StrokeShape>
<RoundRectangle CornerRadius="40, 40, 0, 40"/>
</Border.StrokeShape>
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CheckedStates">
<VisualState x:Name="Checked">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="{Binding CheckedColor, Source={x:Reference root}}" />
<Setter Property="Stroke"
Value="{Binding CheckedColor, Source={x:Reference root}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="{Binding UncheckedColor, Source={x:Reference root}}" />
<Setter Property="Stroke"
Value="{Binding UncheckedColor, Source={x:Reference root}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
<Grid>
<ContentPresenter VerticalOptions="Center" HorizontalOptions="Center" />
</Grid>
</Border>
</ControlTemplate>
<Style TargetType="RadioButton">
<Setter Property="ControlTemplate"
Value="{StaticResource RadioButtonTemplate}" />
</Style>
</ContentView.Resources>
<Grid ColumnDefinitions="*, *, *" RowDefinitions="*, *"
Margin="6" ColumnSpacing="6" RowSpacing="6"
RadioButtonGroup.GroupName="championClasses"
RadioButtonGroup.SelectedValue="{Binding SelectedValue, Source={x:Reference root}, Mode=TwoWay}">
<Grid.Resources>
<Style TargetType="Label">
<Setter Property="FontSize" Value="{OnPlatform Micro, WinUI=12}"/>
</Style>
</Grid.Resources>
<RadioButton Value="{Binding Source={StaticResource assassin}}">
<RadioButton.Content>
<VerticalStackLayout>
<Image Source="{Binding Source={StaticResource assassin}, Converter={StaticResource championClassToIconConverter}}"
WidthRequest="26" HeightRequest="26" HorizontalOptions="Center"/>
<Label Text="Assassin" HorizontalOptions="Center"/>
</VerticalStackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton Grid.Column="1"
Value="{Binding Source={StaticResource fighter}}">
<RadioButton.Content>
<VerticalStackLayout>
<Image Source="{Binding Source={StaticResource fighter}, Converter={StaticResource championClassToIconConverter}}"
WidthRequest="26" HeightRequest="26" HorizontalOptions="Center"/>
<Label Text="Fighter" HorizontalOptions="Center"/>
</VerticalStackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton Grid.Column="2"
Value="{Binding Source={StaticResource mage}}">
<RadioButton.Content>
<VerticalStackLayout>
<Image Source="{Binding Source={StaticResource mage}, Converter={StaticResource championClassToIconConverter}}"
WidthRequest="26" HeightRequest="26" HorizontalOptions="Center"/>
<Label Text="Mage" HorizontalOptions="Center"/>
</VerticalStackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton Grid.Row="1" Grid.Column="0"
Value="{Binding Source={StaticResource marksman}}">
<RadioButton.Content>
<VerticalStackLayout>
<Image Source="{Binding Source={StaticResource marksman}, Converter={StaticResource championClassToIconConverter}}"
WidthRequest="26" HeightRequest="26" HorizontalOptions="Center"/>
<Label Text="Marksman" HorizontalOptions="Center"/>
</VerticalStackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton Grid.Row="1" Grid.Column="1"
Value="{Binding Source={StaticResource support}}">
<RadioButton.Content>
<VerticalStackLayout>
<Image Source="{Binding Source={StaticResource support}, Converter={StaticResource championClassToIconConverter}}"
WidthRequest="26" HeightRequest="26" HorizontalOptions="Center"/>
<Label Text="Support" HorizontalOptions="Center"/>
</VerticalStackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton Grid.Row="1" Grid.Column="2"
Value="{Binding Source={StaticResource tank}}">
<RadioButton.Content>
<VerticalStackLayout>
<Image Source="{Binding Source={StaticResource tank}, Converter={StaticResource championClassToIconConverter}}"
WidthRequest="26" HeightRequest="26" HorizontalOptions="Center"/>
<Label Text="Tank" HorizontalOptions="Center"/>
</VerticalStackLayout>
</RadioButton.Content>
</RadioButton>
</Grid>
</ContentView>

@ -0,0 +1,34 @@
using Model;
namespace LolApp.ContentViews;
public partial class ChampionClassSelector : ContentView
{
public ChampionClassSelector()
{
InitializeComponent();
}
public static readonly BindableProperty SelectedValueProperty = BindableProperty.Create(nameof(SelectedValue), typeof(ChampionClass), typeof(ChampionClassSelector), ChampionClass.Unknown, BindingMode.TwoWay);
public ChampionClass SelectedValue
{
get => (ChampionClass)GetValue(SelectedValueProperty);
set => SetValue(SelectedValueProperty, value);
}
public static readonly BindableProperty CheckedColorProperty = BindableProperty.Create(nameof(CheckedColor), typeof(Color), typeof(ChampionClassSelector), Colors.DarkSalmon);
public Color CheckedColor
{
get => (Color)GetValue(CheckedColorProperty);
set => SetValue(CheckedColorProperty, value);
}
public static readonly BindableProperty UncheckedColorProperty = BindableProperty.Create(nameof(UncheckedColor), typeof(Color), typeof(ChampionClassSelector), Colors.DarkSalmon);
public Color UncheckedColor
{
get => (Color)GetValue(UncheckedColorProperty);
set => SetValue(UncheckedColorProperty, value);
}
}

@ -0,0 +1,38 @@
using System.Windows.Input;
namespace LolApp.ContentViews;
public class SearchByStringView : ContentView
{
public static readonly BindableProperty PlaceHolderProperty = BindableProperty.Create(nameof(PlaceHolder), typeof(string), typeof(SearchByStringView), string.Empty);
public string PlaceHolder
{
get => (string)GetValue(PlaceHolderProperty);
set => SetValue(PlaceHolderProperty, value);
}
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(SearchByStringView), string.Empty);
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(SearchByStringView), null);
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(SearchByStringView), null);
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
}

@ -0,0 +1,99 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks>
<OutputType>Exe</OutputType>
<RootNamespace>LolApp</RootNamespace>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Display name -->
<ApplicationTitle>LolApp</ApplicationTitle>
<!-- App Identifier -->
<ApplicationId>fr.uca.iut.lolapp</ApplicationId>
<ApplicationIdGuid>d3cd18a9-c614-4933-bd36-3008e72004d5</ApplicationIdGuid>
<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
<ProjectGuid>{0C898A04-092A-49AA-BE65-8AE818A2AF50}</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net7.0-ios|AnyCPU'">
<CreatePackage>false</CreatePackage>
<CodesignProvision>appleIUT_TP2022</CodesignProvision>
<CodesignKey>iPhone Developer: Cedric BOUHOURS (M2E3ZQNZ3K)</CodesignKey>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net7.0-maccatalyst|AnyCPU'">
<CreatePackage>false</CreatePackage>
<CodesignKey>Developer ID Application</CodesignKey>
<PackageSigningKey>3rd Party Mac Developer Installer</PackageSigningKey>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.0-ios|AnyCPU'">
<CreatePackage>false</CreatePackage>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net7.0-windows10.0.22621.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<!-- App Icon -->
<MauiFont Include="Resources\Fonts\*" />
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
<MauiIcon Include="Resources\AppIcon\appicon.png" />
</ItemGroup>
<ItemGroup>
<None Remove="Resources\Images\fighter.svg" />
<None Remove="Resources\Images\lollogo.jpg" />
<None Remove="Resources\Images\sword.png" />
<None Remove="Resources\Images\lol.png" />
<None Remove="Resources\Fonts\Font Awesome 6 Free-Solid-900.otf" />
<None Remove="Resources\Images\support.svg" />
<None Remove="Resources\Images\tank.svg" />
<None Remove="Resources\Images\marksman.svg" />
<None Remove="Resources\Images\assassin.svg" />
<None Remove="Resources\Images\mage.svg" />
<None Remove="Resources\AppIcon\appicon.png" />
<None Remove="Resources\Splash\tank.svg" />
<None Remove="Resources\Splash\splash.png" />
<None Remove="Resources\Images\rp.png" />
</ItemGroup>
<ItemGroup>
<MauiImage Include="Resources\Images\lollogo.jpg" />
<MauiImage Include="Resources\Images\sword.png" />
<MauiImage Include="Resources\Images\lol.png" />
<MauiImage Include="Resources\Images\tank.svg" />
<MauiImage Include="Resources\Images\support.svg" />
<MauiImage Include="Resources\Images\marksman.svg" />
<MauiImage Include="Resources\Images\mage.svg" />
<MauiImage Include="Resources\Images\fighter.svg" />
<MauiImage Include="Resources\Images\assassin.svg" />
<MauiImage Include="Resources\Images\rp.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
<ProjectReference Include="..\StubLib\StubLib.csproj" />
<ProjectReference Include="..\ViewModels\ViewModels.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Maui" Version="5.0.0" />
<PackageReference Include="CommunityToolkit.Maui.Markup" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
<PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="7.0.59" />
</ItemGroup>
<ItemGroup>
<MauiSplashScreen Include="Resources\Splash\splash.png" />
</ItemGroup>
</Project>

@ -0,0 +1,37 @@
<?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="LolApp.MainPage">
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<Image
Source="lollogo.jpg"
SemanticProperties.Description="Cute dot net bot waving hi to you!"
HeightRequest="200"
HorizontalOptions="Center" />
<Label
Text="League of Legends Data"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"/>
<Label
Text="Find information about champions, skins and runes"
SemanticProperties.HeadingLevel="Level2"
SemanticProperties.Description="Welcome to dot net Multi platform App U I"
FontSize="18"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"/>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

@ -0,0 +1,11 @@
namespace LolApp;
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}

@ -0,0 +1,39 @@
using CommunityToolkit.Maui;
using LolApp.ViewModels;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Model;
using StubLib;
using ViewModels;
namespace LolApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkit()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
fonts.AddFont("Font Awesome 6 Free-Solid-900.otf", "FASolid");
});
builder.Services.AddSingleton<IDataManager, StubData>()
.AddSingleton<ChampionsMgrVM>()
.AddSingleton<SkinsMgrVM>()
.AddSingleton<ApplicationVM>()
.AddSingleton<ChampionsPage>();
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

@ -0,0 +1,34 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using CommunityToolkit.Maui.Converters;
namespace LolApp.Resources.Converters
{
public class Base64ToImageSourceConverter : ByteArrayToImageSourceConverter, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string base64 = value as string;
if (string.IsNullOrWhiteSpace(base64)) return null;
try
{
byte[] bytes = System.Convert.FromBase64String(base64);
return base.ConvertFrom(bytes, culture);
}
catch
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
ImageSource source = value as ImageSource;
if (source == null) return null;
byte[] bytes = base.ConvertBackTo(source, culture) as byte[];
return System.Convert.ToBase64String(bytes);
}
}
}

@ -0,0 +1,42 @@
using System;
using System.Globalization;
using Model;
namespace LolApp.Resources.Converters
{
public class ChampionClassToIconConverter : IValueConverter
{
private static Dictionary<ChampionClass, string> icons = new()
{
[ChampionClass.Assassin] = "assassin.png",
[ChampionClass.Fighter] = "fighter.png",
[ChampionClass.Mage] = "mage.png",
[ChampionClass.Marksman] = "marksman.png",
[ChampionClass.Support] = "support.png",
[ChampionClass.Tank] = "tank.png"
};
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
ChampionClass champClass = (ChampionClass)value;
if(!icons.TryGetValue(champClass, out string icon))
{
return "";
}
return ImageSource.FromFile($"{icon}");
}
catch
{
return "";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

@ -0,0 +1,28 @@
using System;
using System.Globalization;
namespace LolApp.Resources.Converters
{
public class ImageRatioConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
double parentWidth = (double)value;
double ratio = (double)parameter;
return parentWidth*ratio;
}
catch
{
return 0.0;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

@ -0,0 +1,28 @@
using System;
using System.Globalization;
namespace LolApp.Resources.Converters
{
public class PlusOneConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int i = -1;
try
{
i = (int)value;
}
catch (InvalidCastException e)
{
throw new InvalidCastException("PlusOneConverter : the value must be an int");
}
return i + 1;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" data-testid="overview:roleicon" class="style__StyledSpecsIcon-sc-8gkpub-17-SvgAssassin ckFmqk"><path d="M56.59 73.71l1.67-2.88c5.75-9.34 5.51-16 3.83-20.59a39.78 39.78 0 01-9.1 16 2 2 0 01-1.43.48H48.2a2.17 2.17 0 01-1.67-.72 39.78 39.78 0 01-9.1-16c-1.68 4.55-1.68 11.26 3.83 20.59l1.68 2.88-3.36 5.75 10.06 17.72L59.7 79.22z"></path><path d="M73.11 38.74c-3.35-4.31-6-10-6-18.91 0-4.07-3.59-8.15-7.66-12-4.79-4.31-5.75-5.74-9.58-5.74s-4.79 1.43-9.34 5.74c-4.07 3.83-7.66 7.91-7.66 12 0 8.86-2.88 14.6-6 18.68L12.76 52.87 2.23 45.69v12.93S2.47 84 39.58 97.89c0 0-14.13-7.18-16.28-31.13-.24-1.67-.24-9.1-.24-10.29A119.77 119.77 0 0036.71 74c-.72-1.2-1.44-2.64-2.16-3.83-5-10.54-4.07-18.2-1.67-23.47a22.77 22.77 0 017.42-8.86l9.58 9.58 9.58-9.58a22.77 22.77 0 017.42 8.86c2.4 5.27 3.59 12.93-1.43 23.23-.72 1.38-1.45 2.58-2.16 4.07a119.77 119.77 0 0013.65-17.53c0 1.19 0 8.62-.24 10.29-2.39 23.95-16.28 31.13-16.28 31.13C97.53 84 97.77 58.62 97.77 58.62V45.69l-10.53 7.18z"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" data-testid="overview:roleicon" class="style__StyledSpecsIcon-sc-8gkpub-17-SvgFighter ggYKyo"><path d="M67.84 56.35v5.5c8.62-8.62 14.37 0 14.37 0C112.14 40.78 90.35 2 90.35 2s-.72 17.24-15.08 27.77v16.52c-.24 4.79-3.84 7.9-7.43 10.06M17.79 62.09s4.07-6.46 10.78-2.63L20.91 48.2l6.7-16c-17.24-10.54-18-29.93-18-29.93S-12.14 41 17.79 62.09M26.89 83.89l5.51-18.68-.24-.48L19.23 77.9A17.78 17.78 0 017.5 83.17H3l-1 2.39 12 11.5zM92.27 83.89a16.24 16.24 0 01-11.74-5.27L68.8 66.88l3.83 17.72L85.8 98l12-11.49-1-2.4zM55.87 42.7c0 .24-.24.48-.24.71h.72c5.75.48 7.66 2.64 9.1 7.67a9.35 9.35 0 002.39-1.92c1-1 1.68-1.67 1.68-2.63V28.09a2 2 0 00-1.68-1.92l-31.37-5.74H36a2.39 2.39 0 00-2.39 2.39v6.71l24.9 3.35z"></path><path d="M60.18 54c-1.2-5.27-1.44-4.55-5.75-4.79L40.78 48v-3.87h5.51A4.09 4.09 0 0050.36 41l1-3.35L32.4 35l-5 12.22 11.74 17-5.54 18.47L49.88 98l16.53-15.07s-6.23-28.5-6.23-29M49.88 2.23l-4.79 10.29 4.79 3.83 4.79-3.83zM62.1 9.41l1.43 6h6l2.87-11zM30.25 15.4h6l.24-.72 1.2-5.27-10.3-5z"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" data-testid="overview:roleicon" class="style__StyledSpecsIcon-sc-8gkpub-17-SvgMage VdIXV"><path d="M84.48 77.3h13.41l-3.83-12.93h-9.58a36.73 36.73 0 00-27.54 12.45L50 85l-6.94-8.14a36.73 36.73 0 00-27.54-12.49H5.94L2.11 77.3h13.41a36.73 36.73 0 0127.54 12.45l.71.72h-9.1v7.42h30.9v-7.42h-9.1l.71-.72a35.85 35.85 0 0127.3-12.45"></path><path d="M56.23 54.31L50 62.21l-6.23-7.9a5.42 5.42 0 01-.24-6.47L50 37.31l6.47 10.53a5.42 5.42 0 01-.24 6.47M42.58 28.93l-7.91 12.69a13.37 13.37 0 00.72 15.09L50 75.14l14.61-18.43a13 13 0 00.72-15.09L50 17l-.48.72a5.58 5.58 0 01-4.31 1.68c-4.07 0-7.18-8.62 4.55-17.24 0 0-28.74 5.5-14.85 30.41z"></path></svg>

After

Width:  |  Height:  |  Size: 706 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" data-testid="overview:roleicon" class="style__StyledSpecsIcon-sc-8gkpub-17-SvgMarksman hdOPMH"><path d="M28.69 27.25h6.94l1.92-6.94-13.41-7.91zM71.31 27.25l4.55-14.85-13.41 7.91 1.92 6.94zM71.31 35.39c-1.43 0-12.21-3.83-12.21-3.83L50 42.34l-9.1-10.78s-10.54 3.83-12.21 3.83c-7.67 0-4.79-7.18-4.79-7.18S4.26 48.32 2.11 64.13c0 0 5.74-8.86 24.42-13.17a26.22 26.22 0 0013.89 12.93c-.72-3.11-1.44-6.71-2.15-10.06a22.36 22.36 0 01-3.84-4.31c.72 0 7.19-.72 8.15-.72.71 2.64 4.55 28.74 4.55 28.74l-7 10.3v10L50 93.82l9.82 4.07V87.6l-7-10.3s3.84-26.1 4.55-28.74c.72 0 7.19.72 8.15.72a16.52 16.52 0 01-3.84 4.31 98.08 98.08 0 00-2.15 10.06 25.33 25.33 0 0013.94-12.93c18.68 4.55 24.42 13.17 24.42 13.17C95.74 48.32 76.1 28 76.1 28s2.88 7.42-4.79 7.42"></path><path d="M50 2.11l-7.66 21.31h.24L50 33.24l7.42-9.82h.24z"></path></svg>

After

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" data-testid="overview:roleicon" class="style__StyledSpecsIcon-sc-8gkpub-17-SvgSupport cHVWzU"><path d="M90.4 2.11c0 27.3-25.4 36.63-25.4 36.63L60.94 61a8.39 8.39 0 00-.48 2.39 6.95 6.95 0 0013.89 0 6.7 6.7 0 00-5.75-6.7c6.71-11.5 16.29-6 16.29-6 1.43-1.44 2.63-2.88 3.83-4.07l-7.19-2.88h9.34a38.5 38.5 0 005.75-11.25L87 28.69h10.3a33 33 0 00-6.9-26.58M35.32 38.74S9.93 29.41 9.93 2.11c0 0-9.82 10.77-7.42 26.1h10.3L3.23 32a41.09 41.09 0 004.07 8.9h11l-8.61 3.59a39.83 39.83 0 005.27 6s9.58-5.51 16.29 6a6.7 6.7 0 00-5.75 6.7 6.95 6.95 0 1013.41-2.39zM45.14 22.7l2.63-6.7h4.79l2.63 6.94-5 13.89zm-1-16l-7 16 10.15 25.38v23.71l-5 16 5 10H53l5-10-5-16V48.08L63.1 22.7l-7-16z"></path></svg>

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" data-testid="overview:roleicon" class="style__StyledSpecsIcon-sc-8gkpub-17-SvgTank hRKkgf"><path d="M85.92 63.89L55 90V67.48h7.42v-9.1H37.55v9.1H45v22.75L14.08 63.89 8.33 21l27.54-10.51a39.13 39.13 0 0128.26 0L91.67 21zM66.28 5a47.61 47.61 0 00-32.56 0L2.11 17.19l6.7 49.57L41.86 95A13 13 0 0050 97.89 12.5 12.5 0 0058.14 95l33.05-28.24 6.7-49.57z"></path><path d="M78.74 32.28L62 21.26v5.27H38v-5.27l-16.26 7.19a2.9 2.9 0 00-1.67 3.11l4.31 19.16a3.22 3.22 0 002.15 2.15l11.26 2.4V50h23.94v5.27l11.5-2.4a2.52 2.52 0 002.15-2.15l4.31-15.57a2.39 2.39 0 00-1-2.87M57.42 20.07H42.58L50 11.68z"></path></svg>

After

Width:  |  Height:  |  Size: 665 B

@ -0,0 +1,17 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories). Deployment of the asset to your application
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
These files will be deployed with you package and will be accessible using Essentials:
async Task LoadMauiAsset()
{
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
using var reader = new StreamReader(stream);
var contents = reader.ReadToEnd();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Color x:Key="Primary">#D2B977</Color>
<Color x:Key="Secondary">#F0E7B7</Color>
<Color x:Key="Tertiary">#2B0B98</Color>
<Color x:Key="White">White</Color>
<Color x:Key="Black">Black</Color>
<Color x:Key="Gray100">#E1E1E1</Color>
<Color x:Key="Gray200">#C8C8C8</Color>
<Color x:Key="Gray300">#ACACAC</Color>
<Color x:Key="Gray400">#919191</Color>
<Color x:Key="Gray500">#6E6E6E</Color>
<Color x:Key="Gray600">#404040</Color>
<Color x:Key="Gray900">#212121</Color>
<Color x:Key="Gray950">#141414</Color>
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
<Color x:Key="Yellow100Accent">#F7B548</Color>
<Color x:Key="Yellow200Accent">#FFD590</Color>
<Color x:Key="Yellow300Accent">#FFE5B9</Color>
<Color x:Key="Cyan100Accent">#28C2D1</Color>
<Color x:Key="Cyan200Accent">#7BDDEF</Color>
<Color x:Key="Cyan300Accent">#C3F2F4</Color>
<Color x:Key="Blue100Accent">#3E8EED</Color>
<Color x:Key="Blue200Accent">#72ACF1</Color>
<Color x:Key="Blue300Accent">#A7CBF6</Color>
<Color x:Key="Transparent">Transparent</Color>
</ResourceDictionary>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<x:String x:Key="angle-left">&#xf104;</x:String>
<x:String x:Key="angle-right">&#xf105;</x:String>
<x:String x:Key="caret-left">&#xf0d9;</x:String>
<x:String x:Key="caret-down">&#xf0d7;</x:String>
<x:String x:Key="magnifying-glass">&#xf002;</x:String>
<x:String x:Key="plus">&#x2b;</x:String>
</ResourceDictionary>

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:conv="clr-namespace:LolApp.Resources.Converters"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit">
<x:Double x:Key="imageRatio">0.59</x:Double>
<Style TargetType="Button" x:Key="iconButton">
<Setter Property="Margin" Value="4"/>
<Setter Property="Padding" Value="10"/>
</Style>
<Style TargetType="Label" x:Key="defaultLabel">
<Setter Property="HorizontalOptions" Value="Center"/>
<Setter Property="HorizontalTextAlignment" Value="Center"/>
<Setter Property="FontSize" Value="{OnPlatform Medium}"/>
<Setter Property="Margin" Value="10"/>
</Style>
<Style TargetType="Label" x:Key="title">
<Setter Property="HorizontalOptions" Value="Center"/>
<Setter Property="HorizontalTextAlignment" Value="Center"/>
<Setter Property="FontSize" Value="{OnPlatform Title}"/>
<Setter Property="TextColor" Value="{StaticResource Primary}"/>
<Setter Property="Margin" Value="0, 10, 0, 4"/>
<Setter Property="FontAttributes" Value="Bold"/>
</Style>
<Style TargetType="Label" x:Key="labelForEntry">
<Setter Property="HorizontalOptions" Value="End"/>
<Setter Property="VerticalOptions" Value="Center"/>
<Setter Property="HorizontalTextAlignment" Value="End"/>
<Setter Property="FontSize" Value="{OnPlatform Small}"/>
<Setter Property="Margin" Value="4"/>
<Setter Property="TextColor" Value="{StaticResource Primary}"/>
</Style>
<Style TargetType="Entry" x:Key="defaultEntry">
<Setter Property="BackgroundColor" Value="{StaticResource Secondary}"/>
<Setter Property="VerticalOptions" Value="Center"/>
<Setter Property="HorizontalOptions" Value="FillAndExpand"/>
<Setter Property="PlaceholderColor" Value="{StaticResource Gray300}"/>
<Setter Property="Margin" Value="6"/>
<Setter Property="FontSize" Value="{OnPlatform Small}"/>
</Style>
<Style TargetType="Editor" x:Key="defaultEditor">
<Setter Property="BackgroundColor" Value="{StaticResource Secondary}"/>
<Setter Property="VerticalOptions" Value="FillAndExpand"/>
<Setter Property="HorizontalOptions" Value="FillAndExpand"/>
<Setter Property="PlaceholderColor" Value="{StaticResource Gray300}"/>
<Setter Property="Margin" Value="6"/>
<Setter Property="FontSize" Value="{OnPlatform Small}"/>
</Style>
<Style TargetType="Picker" x:Key="defaultPicker">
<Setter Property="BackgroundColor" Value="{StaticResource Secondary}"/>
<Setter Property="VerticalOptions" Value="Center"/>
<Setter Property="HorizontalOptions" Value="FillAndExpand"/>
<Setter Property="Margin" Value="6"/>
<Setter Property="FontSize" Value="{OnPlatform Small}"/>
</Style>
<Style x:Key="InvalidEntryStyle" TargetType="Entry" BasedOn="{StaticResource defaultEntry}">
<Setter Property="TextColor" Value="Red" />
</Style>
<conv:PlusOneConverter x:Key="plusOneConverter"/>
<conv:Base64ToImageSourceConverter x:Key="base64ToImageSourceConverter"/>
<conv:ImageRatioConverter x:Key="imageRatioConverter"/>
<conv:ChampionClassToIconConverter x:Key="championClassToIconConverter"/>
<toolkit:SelectedItemEventArgsConverter x:Key="SelectedItemEventArgsConverter" />
<toolkit:BoolToObjectConverter TrueObject="{StaticResource caret-down}"
FalseObject="{StaticResource caret-left}"
DefaultConvertReturnValue="{StaticResource caret-left}"
x:Key="isExpandedToCaretConverter"/>
<toolkit:IntToBoolConverter x:Key="intToBoolConverter"/>
<toolkit:IsStringNotNullOrWhiteSpaceConverter x:Key="isStringNotNullOrWhiteSpaceConverter"
DefaultConvertReturnValue="False"/>
<toolkit:IsStringNullOrWhiteSpaceConverter x:Key="isStringNullOrWhiteSpaceConverter"/>
<toolkit:MultiMathExpressionConverter x:Key="multiMathExpressionConverter" />
<toolkit:IsEqualConverter x:Key="isEqualConverter" />
<toolkit:MathExpressionConverter x:Key="mathExpressionConverter"/>
<toolkit:BoolToObjectConverter TrueObject="{StaticResource Primary}"
FalseObject="{StaticResource Transparent}"
x:Key="isSelectedToColorConverter"/>
<toolkit:MultiConverter x:Key="selectedIconToColorConverter">
<toolkit:IsEqualConverter />
<toolkit:BoolToObjectConverter FalseObject="{StaticResource Primary}"
TrueObject="{StaticResource Black}"/>
</toolkit:MultiConverter>
<toolkit:IsListNotNullOrEmptyConverter x:Key="isListNotNullOrEmptyConverter"/>
<toolkit:IsListNullOrEmptyConverter x:Key="isListNullOrEmptyConverter"/>
<toolkit:InvertedBoolConverter x:Key="invertedBoolConverter"/>
</ResourceDictionary>

@ -0,0 +1,406 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Style TargetType="ActivityIndicator">
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
</Style>
<Style TargetType="IndicatorView">
<Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
<Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
</Style>
<Style TargetType="Border">
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="StrokeShape" Value="Rectangle"/>
<Setter Property="StrokeThickness" Value="1"/>
</Style>
<Style TargetType="BoxView">
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="Button">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Primary}}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="14,10"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="CheckBox">
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="DatePicker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Editor">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Entry">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Frame">
<Setter Property="HasShadow" Value="False" />
<Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="CornerRadius" Value="8" />
</Style>
<Style TargetType="ImageButton">
<Setter Property="Opacity" Value="1" />
<Setter Property="BorderColor" Value="Transparent"/>
<Setter Property="BorderWidth" Value="0"/>
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Opacity" Value="0.5" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Label">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="ListView">
<Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="Picker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="ProgressBar">
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="RadioButton">
<Setter Property="BackgroundColor" Value="Transparent"/>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="RefreshView">
<Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="SearchBar">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="SearchHandler">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Shadow">
<Setter Property="Radius" Value="15" />
<Setter Property="Opacity" Value="0.5" />
<Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
<Setter Property="Offset" Value="10,10" />
</Style>
<Style TargetType="Slider">
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="SwipeItem">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
</Style>
<Style TargetType="Switch">
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="ThumbColor" Value="{StaticResource White}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="On">
<VisualState.Setters>
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Off">
<VisualState.Setters>
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="TimePicker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent"/>
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Page" ApplyToDerivedTypes="True">
<Setter Property="Padding" Value="0"/>
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
</Style>
<Style TargetType="Shell" ApplyToDerivedTypes="True">
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray950}}" />
<Setter Property="Shell.ForegroundColor" Value="{OnPlatform WinUI={StaticResource Primary}, Default={StaticResource White}}" />
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
<Setter Property="Shell.NavBarHasShadow" Value="False" />
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray950}}" />
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
</Style>
<Style TargetType="TabbedPage">
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
</Style>
</ResourceDictionary>

@ -0,0 +1,46 @@
<?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="LolApp.SkinPage"
Title="SkinPage"
x:Name="root"
BackgroundColor="{StaticResource Black}">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Modifier" Command="{Binding AppVM.NavigateToEditSkinPageCommand, Source={x:Reference root}}"
CommandParameter="{Binding}"/>
</ContentPage.ToolbarItems>
<ScrollView>
<VerticalStackLayout>
<AbsoluteLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
MaximumHeightRequest="{OnPlatform WinUI=300}"
HeightRequest="{Binding Width,
Source={RelativeSource AncestorType={x:Type ContentPage}},
Converter={StaticResource imageRatioConverter},
ConverterParameter={StaticResource imageRatio}}">
<Image Source="{Binding Image, Converter={StaticResource base64ToImageSourceConverter}}"
Aspect="AspectFit"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
MaximumHeightRequest="{OnPlatform WinUI=300}"/>
</AbsoluteLayout>
<Grid Padding="10" BackgroundColor="{StaticResource Black}"
ColumnDefinitions="*, Auto">
<Label Text="{Binding Name}" FontAttributes="Bold" TextColor="{StaticResource Primary}"
FontSize="Title"/>
<HorizontalStackLayout Grid.Column="1">
<Image Source="rp.png" HeightRequest="16" WidthRequest="16"/>
<Label Text="{Binding Price}" TextColor="{StaticResource Primary}"
FontSize="Small" VerticalOptions="Center" Margin="4, 0, 0, 0"/>
</HorizontalStackLayout>
</Grid>
<ScrollView VerticalScrollBarVisibility="Always" BackgroundColor="Black" >
<Label Text="{Binding Description}" TextColor="{StaticResource Primary}"
Margin="10" FontAttributes="Italic"/>
</ScrollView>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

@ -0,0 +1,18 @@
using LolApp.ViewModels;
using ViewModels;
namespace LolApp;
public partial class SkinPage : ContentPage
{
public ApplicationVM AppVM { get; set; }
public SkinVM SkinVM { get; }
public SkinPage(SkinVM svm, ApplicationVM appVM)
{
BindingContext = SkinVM = svm;
AppVM = appVM;
InitializeComponent();
}
}

@ -0,0 +1,80 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
//using Microsoft.Maui.Graphics.Platform;
using ViewModels;
namespace LolApp.ViewModels
{
[ObservableObject]
public partial class AddChampionPageVM
{
ChampionsMgrVM ChampionsMgrVM { get; }
public AddChampionPageVM(ChampionsMgrVM championsMgrVM, ChampionVM champion = null)
{
ChampionsMgrVM = championsMgrVM;
if(champion == null) return;
oldChampion = champion;
IsNew = false;
this.champion = new EditableChampionVM(oldChampion);
}
private ChampionVM oldChampion;
[ObservableProperty]
bool isNew = true;
[ObservableProperty]
EditableChampionVM champion = new ();
[RelayCommand]
public async void PickIcon() => Champion.IconBase64 = await PickIconsAndImagesUtils.PickPhoto(42);
[RelayCommand]
public async void PickLargeImage() => Champion.LargeImageBase64 = await PickIconsAndImagesUtils.PickPhoto(1000);
[RelayCommand]
async Task Cancel()
=> await App.Current.MainPage.Navigation.PopAsync();
[RelayCommand]
async Task AddChampion()
{
ChampionVM champVM = Champion.ToChampionVM();
await ChampionsMgrVM.AddChampion(champVM);
await App.Current.MainPage.Navigation.PopAsync();
}
[RelayCommand]
async Task EditChampion()
{
ChampionVM newChampion = Champion.ToChampionVM();
await ChampionsMgrVM.EditChampion(oldChampion, newChampion);
await App.Current.MainPage.Navigation.PopAsync();
}
[ObservableProperty]
string newCharacteristicDescription;
[ObservableProperty]
int newCharacteristicValue;
[RelayCommand]
void AddCharacteristic()
{
Champion?.AddCharacteristic(newCharacteristicDescription, newCharacteristicValue);
}
[RelayCommand]
void RemoveCharacteristic(KeyValuePair<string, int> characteristic)
=> Champion?.RemoveCharacteristic(characteristic);
[RelayCommand]
async Task AddSkill()
=> await App.Current.MainPage.Navigation.PushModalAsync(new AddSkill(Champion));
}
}

@ -0,0 +1,64 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Model;
using ViewModels;
namespace LolApp.ViewModels
{
[ObservableObject]
public partial class AddOrEditSkinPageVM
{
SkinsMgrVM SkinsMgrVM { get; }
private SkinVM oldSkin;
[ObservableProperty]
bool isNew = true;
[ObservableProperty]
EditableSkinVM skin;
public AddOrEditSkinPageVM(SkinsMgrVM skinsMgrVM, SkinVM oldSkin)
{
SkinsMgrVM = skinsMgrVM;
this.oldSkin = oldSkin;
IsNew = false;
this.skin = new EditableSkinVM(oldSkin);
}
public AddOrEditSkinPageVM(SkinsMgrVM skinsMgrVM, ChampionVM champion)
{
SkinsMgrVM = skinsMgrVM;
skin = new EditableSkinVM(champion);
}
[RelayCommand]
public async void PickIcon() => Skin.IconBase64 = await PickIconsAndImagesUtils.PickPhoto(42);
[RelayCommand]
public async void PickLargeImage() => Skin.LargeImageBase64 = await PickIconsAndImagesUtils.PickPhoto(1000);
[RelayCommand]
async Task Cancel()
=> await App.Current.MainPage.Navigation.PopAsync();
[RelayCommand]
async Task AddSkin()
{
SkinVM skinVM = Skin.ToSkinVM();
await SkinsMgrVM.AddSkin(skinVM);
await App.Current.MainPage.Navigation.PopAsync();
}
[RelayCommand]
async Task EditSkin()
{
SkinVM newSkin = Skin.ToSkinVM();
await SkinsMgrVM.EditSkin(oldSkin, newSkin);
await App.Current.MainPage.Navigation.PopAsync();
}
}
}

@ -0,0 +1,46 @@
using System;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Model;
using ViewModels;
namespace LolApp.ViewModels
{
public partial class AddSkillVM : ObservableObject
{
public AddSkillVM(EditableChampionVM champion)
{
Champion = champion;
}
[ObservableProperty]
SkillType skillType;
[ObservableProperty]
string name;
[ObservableProperty]
string description;
[ObservableProperty]
EditableChampionVM champion;
[RelayCommand]
async void AddSkillToChampion()
{
champion.Skills.Add(new SkillVM(new Skill(name, skillType, description)));
await App.Current.MainPage.Navigation.PopModalAsync();
}
[RelayCommand]
async void Cancel()
{
await App.Current.MainPage.Navigation.PopModalAsync();
}
public IEnumerable<SkillType> AllSkills { get; }
= Enum.GetValues(typeof(SkillType)).Cast<SkillType>().Except(new SkillType[] {SkillType.Unknown}).ToList();
}
}

@ -0,0 +1,58 @@
using System;
using CommunityToolkit.Mvvm.Input;
using Model;
using ViewModels;
namespace LolApp.ViewModels
{
public partial class ApplicationVM
{
public ChampionsMgrVM ChampionsMgrVM { get; set; }
public SkinsMgrVM SkinsMgrVM { get; set; }
public ApplicationVM(ChampionsMgrVM championsMgrVM, SkinsMgrVM skinsMgrVM)
{
ChampionsMgrVM = championsMgrVM;
SkinsMgrVM = skinsMgrVM;
}
[RelayCommand]
async Task NavigateToChampionDetailsPage(ChampionVM cvm)
{
SkinsMgrVM.Champion = cvm;
SkinsMgrVM.Index = 0;
SkinsMgrVM.Count = 5;
await SkinsMgrVM.LoadSkinsCommand.ExecuteAsync(cvm);
await App.Current.MainPage.Navigation.PushAsync(new ChampionPage(cvm, this));
}
[RelayCommand]
async Task NavigateToAddNewChampionPage()
=> await App.Current.MainPage.Navigation.PushAsync(new AddChampionPage(ChampionsMgrVM));
[RelayCommand(CanExecute = nameof(CanNavigateToEditChampionPage))]
async Task NavigateToEditChampionPage(object champ)
=> await App.Current.MainPage.Navigation.PushAsync(new AddChampionPage(ChampionsMgrVM, champ as ChampionVM));
bool CanNavigateToEditChampionPage(object champ) => champ != null && champ is ChampionVM;
[RelayCommand]
async Task NavigateToSkinDetailsPage(object svm)
{
if (svm == null || svm is not SkinVM) return;
await App.Current.MainPage.Navigation.PushAsync(new SkinPage(svm as SkinVM, this));
}
[RelayCommand]
async Task NavigateToAddNewSkinPage(ChampionVM champion)
=> await App.Current.MainPage.Navigation.PushAsync(new AddOrEditSkinPage(SkinsMgrVM, champion));
[RelayCommand(CanExecute = nameof(CanNavigateToEditSkinPage))]
async Task NavigateToEditSkinPage(object skin)
=> await App.Current.MainPage.Navigation.PushAsync(new AddOrEditSkinPage(SkinsMgrVM, skin as SkinVM));
bool CanNavigateToEditSkinPage(object skin) => skin != null && skin is SkinVM;
}
}

@ -0,0 +1,27 @@
using System;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using Model;
namespace LolApp.ViewModels
{
[ObservableObject]
public partial class ChampionClassVM
{
[ObservableProperty]
private ChampionClass model;
[ObservableProperty]
private bool isSelected;
public ChampionClassVM(ChampionClass model)
{
Model = model;
}
public static IEnumerable<ChampionClassVM> Classes { get; }
= Enum.GetValues(typeof(ChampionClass)).Cast<ChampionClass>().Except(new ChampionClass[] {ChampionClass.Unknown})
.Select(cc => new ChampionClassVM(cc));
}
}

@ -0,0 +1,111 @@
using System;
using System.Reflection;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Model;
using ViewModels;
namespace LolApp.ViewModels
{
[ObservableObject]
public partial class ChampionsPageVM
{
public ChampionsMgrVM ChampionsMgrVM { get; set; }
public ChampionsPageVM(ChampionsMgrVM championsMgrVM)
{
ChampionsMgrVM = championsMgrVM;
PropertyChanged += ChampionsMgrVM_PropertyChanged;
}
[ObservableProperty]
private ChampionClassVM selectedClass;
[RelayCommand]
public async Task SelectedChampionClassChanged(ChampionClassVM champClass)
{
if(SelectedClass != null) SelectedClass.IsSelected = false;
if(champClass.Model == ChampionClass.Unknown
|| champClass.Model == SelectedClass?.Model)
{
SelectedClass = null;
return;
}
SelectedClass = champClass;
SelectedClass.IsSelected = true;
await ChampionsMgrVM.LoadChampionsByClass(SelectedClass.Model);//ChampionsMgrVM.SelectedClass);
}
[ObservableProperty]
private ChampionVM selectedChampion;
[ObservableProperty]
private string searchedName;
[ObservableProperty]
private string searchedSkill;
[ObservableProperty]
private string searchedCharacteristic;
private static string[] searchedStrings = { nameof(SearchedName), nameof(SearchedSkill), nameof(SearchedCharacteristic), nameof(SelectedClass) };
private async void ChampionsMgrVM_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(searchedStrings.Any(s => e.PropertyName == s))
{
if(GetProperty(e.PropertyName).GetValue(this) != GetProperty(e.PropertyName).GetDefaultValue())
{
foreach(string s in searchedStrings.Except(new string[]{e.PropertyName }))
{
var prop = GetProperty(s);
prop.ResetPropertyValue(this);
}
return;
}
ChampionsMgrVM.Index=0;
if(searchedStrings.All(s => GetProperty(s).GetValue(this) == GetProperty(s).GetDefaultValue()))
{
await ChampionsMgrVM.LoadChampions();
}
}
}
private PropertyInfo? GetProperty(string propName)
=> typeof(ChampionsPageVM).GetProperty(propName);
}
public static class Extensions
{
public static void ResetPropertyValue(this PropertyInfo pi, ChampionsPageVM instance)
{
if(pi.PropertyType == typeof(ChampionClassVM))
{
var temp = pi.GetValue(instance);
if(temp != null)
(temp as ChampionClassVM).IsSelected = false;
return;
}
pi.SetValue(instance, pi.GetDefaultValue());
}
public static object GetDefaultValue(this Type t)
{
if (t.IsValueType)
return Activator.CreateInstance(t);
return null;
}
public static object GetDefaultValue(this PropertyInfo pi)
=> pi.PropertyType.GetDefaultValue();
}
}

@ -0,0 +1,32 @@
using System;
//using Microsoft.Maui.Graphics.Platform;
using Microsoft.Maui.Graphics.Skia;
namespace LolApp.ViewModels
{
public static class PickIconsAndImagesUtils
{
public async static Task<string> PickPhoto(float maxWidthAndHeight)
{
FileResult photo = await MediaPicker.Default.PickPhotoAsync();
return photo != null ? await ToBase64(photo, maxWidthAndHeight) : null;
}
public async static Task<string> ToBase64(FileResult photo, float maxWidthAndHeight)
{
using (var stream = await photo.OpenReadAsync())
using (var memoryStream = new MemoryStream())
{
var image = SkiaImage.FromStream(memoryStream);
//var image = PlatformImage.FromStream(stream);
if(image != null)
{
var newImage = image.Downsize(maxWidthAndHeight, true);
return newImage.AsBase64();
}
}
return null;
}
}
}

@ -1,5 +1,5 @@
using apiLOL;
using apiLOL.Controllers;
using apiLOL.DTO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@ -10,50 +10,103 @@ namespace TestUnitaire
public class TestAPILol
{
[Theory]
[InlineData("Beatrice", "sdfsdfd")]
[InlineData("Maurice", "Ahri est un champion de League of Legends")]
[InlineData("Loupiotte", "Akali est un champion de League of Legends")]
public async Task TestPostChampion(string name, string bio)
[InlineData("Beatrice", "sdfsdfd", "icon.png")]
[InlineData("Maurice", "Ahri est un champion de League of Legends", "icon.png")]
[InlineData("Loupiotte", "Akali est un champion de League of Legends", "icon.png")]
public async Task TestPostChampion(string name, string bio, string icon)
{
// Arrange
var data = new StubData();
var logger = new NullLogger<ControllerChampions>();
var controller = new ControllerChampions(data, logger);
var champDTO = new ChampionDTO(name, bio);
var champDTO = new ChampionDTO(name, bio, icon);
// Act
var nbInListBefore = data.ChampionsMgr.GetNbItems().Result;
var result = await controller.Post(champDTO);
var result = await controller.AddChampion(champDTO);
var nbInListAfter = data.ChampionsMgr.GetNbItems().Result;
// Assert
// IS the champion added to the list, number of champions in the list + 1
Assert.Equal(nbInListBefore + 1, nbInListAfter);
// Test le code de retour
}
[Theory]
[InlineData("Beatrice", "Aatrox est un champion de League of Legends")]
[InlineData("Maurice", "Ahri est un champion de League of Legends")]
[InlineData("Loupiotte", "Akali est un champion de League of Legends")]
public async Task TestGetChampion(string name, string bio)
[InlineData("Beatrice", "Aatrox est un champion de League of Legends", "icon.png")]
[InlineData("Maurice", "Ahri est un champion de League of Legends", "icon.png")]
[InlineData("Loupiotte", "Akali est un champion de League of Legends", "icon.png")]
public async Task TestGetChampion(string name, string bio, string icon)
{
// Arrange
var data = new StubData();
var logger = new NullLogger<ControllerChampions>();
var controller = new ControllerChampions(data, logger);
var champDTO = new ChampionDTO(name, bio);
var champDTO = new ChampionDTO(name, bio, icon);
// Act
// Call method POST to add a champion
var result = await controller.Post(champDTO);
var result = await controller.AddChampion(champDTO);
// Call method GET to get the champion
var resultGet = await controller.GetChampion(name);
var resultGet = await controller.GetChampionByName(name);
// Assert
// IS the champion added to the list, number of champions in the list + 1
Assert.Equal(name, champDTO.Name);
Assert.Equal(bio, champDTO.Bio);
}
[Theory]
[InlineData("Beatrice", "Nouvelle bio")]
[InlineData("Maurice", "Nouvelle bio")]
[InlineData("Loupiotte", "Nouvelle bio")]
public async Task TestPutChampion(string name, string bio)
{
// Method that change the bio of a champion. Make a test to check if the bio is changed
// Arrange
var data = new StubData();
var logger = new NullLogger<ControllerChampions>();
var controller = new ControllerChampions(data, logger);
// Act
// Add a champion
var champDTO = new ChampionDTO(name, "Ancienne bio", "icon.png");
var resultPost = await controller.AddChampion(champDTO);
// Call method PUT to change the bio of a champion
var resultPut = await controller.UpdateChampion(name, bio);
var champion = (await data.ChampionsMgr.GetItemsByName(name, 0, 1)).First();
var bioOfChampion = champion.Bio;
// Assert
// Does the bio of the champion is changed
Assert.Equal(bio, bioOfChampion);
}
[Theory]
[InlineData("Beatrice")]
[InlineData("Maurice")]
[InlineData("Loupiotte")]
public async Task TestDeleteChampion(string name)
{
// Method that delete a champion. Make a test to check if the champion is deleted
// Arrange
var data = new StubData();
var logger = new NullLogger<ControllerChampions>();
var controller = new ControllerChampions(data, logger);
// Act
// Add a champion
var champDTO = new ChampionDTO(name, "Ancienne bio", "icon.png");
var resultPost = await controller.AddChampion(champDTO);
// Call method DELETE to delete the champion
var resultDelete = await controller.DeleteChampion(name);
// Assert
// Does the type of the result is a OkObjectResult
Assert.IsType<OkObjectResult>(resultDelete);
}
}
}

@ -1,37 +0,0 @@
using apiLOL;
using apiLOL.Controllers;
using Microsoft.AspNetCore.Mvc;
using StubLib;
namespace TestUnitaire
{
public class TestAPILol
{
[Fact]
public void Test1()
{
}
[Fact]
public void TestPostChampion()
{
// Arrange
var data = new StubData();
var controller = new ControllerChampions(new StubData());
var champDTO = new ChampionDTO("Charles", "Charles est un champion de League of Legends");
// Act
var result = controller.Post(champDTO);
data.ChampionsMgr.AddItem(champDTO.ToModel());
var nbItem = data.ChampionsMgr.GetNbItems();
Task<int> nbItemTask = nbItem;
// Assert
Assert.IsType<OkResult>(result);
// Verify that the champions is added to the stub
Assert.Equal(7, nbItemTask.Result);
}
}
}

@ -1,49 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestUnitaire
{
public class TestEFLol
{
public void TestAddIntoDB()
{
// Arrange
ChampionEntity Zeus = new ChampionEntity
{
Name = "Zeus",
Bio = "Zeus is the king of the gods."
};
// Act
using (var context = new ChampionContext())
{
Console.WriteLine("Adding Zeus to the database...");
context.Champions.Add(Zeus);
context.SaveChanges();
}
// Assert
using (var context = new ChampionContext())
{
var champion = context.Champions.FirstOrDefault(c => c.Name == "Zeus");
if (champion == null)
{
Assert.True(false, "Champion not found in database.");
}
Assert.NotNull(champion);
Assert.Equal("Zeus", champion.Name);
Assert.Equal("Zeus is the king of the gods.", champion.Bio);
}
}
public void TestDeleteFromDB()
{
// Act
}
}
}

@ -1,34 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\apiLOL\apiLOL.csproj" />
</ItemGroup>
</Project>

@ -14,14 +14,17 @@
<ItemGroup>
<None Remove="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<None Include="ConsoleTests.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
</ItemGroup>

@ -0,0 +1,101 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using Model;
using Microsoft.Maui.Controls;
using System.Collections.ObjectModel;
namespace ViewModels
{
public partial class ChampionVM : ObservableObject
{
public Champion Model
{
get => model;
set
{
model = value;
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(Bio));
OnPropertyChanged(nameof(Class));
OnPropertyChanged(nameof(Icon));
OnPropertyChanged(nameof(Image));
OnPropertyChanged(nameof(Skills));
OnPropertyChanged(nameof(Characteristics));
}
}
private Champion model;
public ChampionVM(Champion model)
{
Model = model;
foreach(var skill in Model.Skills)
{
Skills.Add(new SkillVM(skill));
}
Skills.CollectionChanged += Skills_CollectionChanged;
}
private void Skills_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
SkillVM vm = e.NewItems?[0] as SkillVM;
switch(e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
Model.Skills.Add(new Skill(vm.Name, vm.Type, vm.Description));
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
Model.Skills.Remove(vm.Model);
break;
}
}
public string Name => Model.Name;
public ChampionClass Class
{
get => Model.Class;
set => SetProperty(Model.Class, value, newValue => Model.Class = newValue);
}
public string Bio
{
get => Model.Bio;
set => SetProperty(Model.Bio, value, newBio => Model.Bio = newBio);
}
public string Icon
{
get => Model.Icon;
set
{
SetProperty(Model.Icon, value, newIcon =>
{
Model.Icon = newIcon;
});
}
}
public string Image
{
get => Model.Image.Base64;
set
{
SetProperty(Model.Image.Base64, value, newImage =>
{
Model.Image.Base64 = newImage;
});
}
}
[ObservableProperty]
private ObservableCollection<SkillVM> skills = new ObservableCollection<SkillVM>();
public ReadOnlyDictionary<string, int> Characteristics
=> Model.Characteristics;
[ObservableProperty]
private ObservableCollection<SkinVM> skins = new ObservableCollection<SkinVM>();
}
}

@ -0,0 +1,227 @@
using System.Threading.Tasks;
using Model;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Input;
using System.Data.SqlTypes;
using System.Reflection;
namespace ViewModels;
public partial class ChampionsMgrVM : ObservableObject
{
internal IDataManager DataMgr { get; set; }
public ChampionsMgrVM(IDataManager dataManager)
{
DataMgr = dataManager;
loadingMethods = new Dictionary<LoadingCriterium, Func<object, Task>>()
{
[LoadingCriterium.None] = async (o) => await LoadChampions(),
[LoadingCriterium.ByName] = async (o) =>
{
string substring = o as string;
if(substring == null) return;
await LoadChampionsByName(substring);
},
[LoadingCriterium.BySkill] = async (o) =>
{
string skillString = o as string;
if(skillString == null) return;
await LoadChampionsBySkill(skillString);
},
[LoadingCriterium.ByCharacteristic] = async (o) =>
{
string characString = o as string;
if(characString == null) return;
await LoadChampionsByCharacteristic(characString);
},
[LoadingCriterium.ByClass] = async (o) =>
{
if(!Enum.IsDefined(typeof(ChampionClass), o)) return;
ChampionClass champClass = (ChampionClass)o;
await LoadChampionsByClass(champClass);
},
};
}
private async Task LoadChampionsFunc(Func<Task<IEnumerable<Champion?>>> loader,
Func<Task<int>> nbReader,
LoadingCriterium criterium,
object parameter = null)
{
Champions.Clear();
var someChampions = (await loader()).Select(c => new ChampionVM(c)).ToList();
foreach (var cvm in someChampions)
{
Champions.Add(cvm);
}
NbChampions = await nbReader();
currentLoadingCriterium = criterium;
currentLoadingParameter = parameter;
}
[RelayCommand]
public async Task LoadChampions()
{
await LoadChampionsFunc(async () => await DataMgr.ChampionsMgr.GetItems(index, count, "Name"),
async () => await DataMgr.ChampionsMgr.GetNbItems(),
LoadingCriterium.None);
}
[RelayCommand(CanExecute =nameof(CanLoadChampionsByName))]
public async Task LoadChampionsByName(string substring)
{
await LoadChampionsFunc(async () => await DataMgr.ChampionsMgr.GetItemsByName(substring, index, count, "Name"),
async () => await DataMgr.ChampionsMgr.GetNbItemsByName(substring),
LoadingCriterium.ByName,
substring);
}
private bool CanLoadChampionsByName(string substring)
=> !string.IsNullOrWhiteSpace(substring);
[RelayCommand(CanExecute =nameof(CanLoadChampionsBySkill))]
public async Task LoadChampionsBySkill(string skill)
{
await LoadChampionsFunc(
async () => await DataMgr.ChampionsMgr.GetItemsBySkill(skill, index, count, "Name"),
async () => await DataMgr.ChampionsMgr.GetNbItemsBySkill(skill),
LoadingCriterium.BySkill,
skill);
}
private bool CanLoadChampionsBySkill(string substring) => !string.IsNullOrWhiteSpace(substring);
[RelayCommand(CanExecute = nameof(CanLoadChampionsByCharacteristic))]
public async Task LoadChampionsByCharacteristic(string characteristic)
{
await LoadChampionsFunc(
async () => await DataMgr.ChampionsMgr.GetItemsByCharacteristic(characteristic, index, count, "Name"),
async () => await DataMgr.ChampionsMgr.GetNbItemsByCharacteristic(characteristic),
LoadingCriterium.ByCharacteristic,
characteristic);
}
private bool CanLoadChampionsByCharacteristic(string characteristic)
=> !string.IsNullOrWhiteSpace(characteristic);
[RelayCommand]
public async Task LoadChampionsByClass(ChampionClass champClass)
{
if(champClass == ChampionClass.Unknown)
{
return;
}
await LoadChampionsFunc(
async () => await DataMgr.ChampionsMgr.GetItemsByClass(champClass, index, count, "Name"),
async () => await DataMgr.ChampionsMgr.GetNbItemsByClass(champClass),
LoadingCriterium.ByClass,
champClass);
}
[RelayCommand(CanExecute =nameof(CanDeleteChampion))]
public async Task<bool> DeleteChampion(object champVM)
{
ChampionVM cvm = champVM as ChampionVM;
if(cvm == null || !Champions.Contains(cvm)) return false;
bool result = await DataMgr.ChampionsMgr.DeleteItem(cvm.Model);
if(result)
{
Champions.Remove(cvm);
await LoadChampions();
}
return result;
}
bool CanDeleteChampion(object cvm)
=> cvm!= null && cvm is ChampionVM && Champions.Contains(cvm);
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(NextPageCommand))]
[NotifyCanExecuteChangedFor(nameof(PreviousPageCommand))]
private int index = 0;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(NbPages))]
[NotifyCanExecuteChangedFor(nameof(NextPageCommand))]
[NotifyCanExecuteChangedFor(nameof(PreviousPageCommand))]
private int count = 5;
public int NbPages
{
get
{
if(Count == 0 || NbChampions == 0)
{
return 0;
}
return (NbChampions-1) / Count + 1;
}
}
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(NbPages))]
[NotifyCanExecuteChangedFor(nameof(NextPageCommand))]
[NotifyCanExecuteChangedFor(nameof(PreviousPageCommand))]
private int nbChampions = 0;
[ObservableProperty]
private ObservableCollection<ChampionVM> champions = new ObservableCollection<ChampionVM>();
[RelayCommand(CanExecute =nameof(CanPreviousPage))]
async Task PreviousPage()
{
if(Index > 0)
{
Index--;
await loadingMethods[currentLoadingCriterium](currentLoadingParameter);
}
}
bool CanPreviousPage() => Index > 0;
[RelayCommand(CanExecute =nameof(CanNextPage))]
async Task NextPage()
{
if(Index < NbPages-1)
{
Index++;
await loadingMethods[currentLoadingCriterium](currentLoadingParameter);
}
}
bool CanNextPage() => Index < NbPages-1;
enum LoadingCriterium
{
None,
ByName,
BySkill,
ByCharacteristic,
ByClass
}
private LoadingCriterium currentLoadingCriterium = LoadingCriterium.None;
private object currentLoadingParameter = null;
private Dictionary<LoadingCriterium, Func<object, Task>> loadingMethods;
public async Task AddChampion(ChampionVM champVM)
{
var added = await DataMgr.ChampionsMgr.AddItem(champVM.Model);
if(added != null)
{
Champions.Add(champVM);
await LoadChampions();
}
}
public async Task EditChampion(ChampionVM oldChampion, ChampionVM newChampion)
{
var edited = await DataMgr.ChampionsMgr.UpdateItem(oldChampion.Model, newChampion.Model);
oldChampion.Model = newChampion.Model;
if(edited != null)
{
await LoadChampions();
}
}
}

@ -0,0 +1,73 @@
using System;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Model;
namespace ViewModels
{
[ObservableObject]
public partial class EditableChampionVM
{
public EditableChampionVM()
{ }
public EditableChampionVM(ChampionVM championVM)
{
Name = championVM.Name;
IconBase64 = championVM.Icon;
LargeImageBase64 = championVM.Image;
Bio = championVM.Bio;
ChampionClass = championVM.Class;
foreach(var ch in championVM.Characteristics)
{
AddCharacteristic(ch.Key, ch.Value);
}
foreach(var skill in championVM.Skills)
{
Skills.Add(skill);
}
}
[ObservableProperty]
string name;
[ObservableProperty]
string iconBase64;
[ObservableProperty]
string largeImageBase64;
[ObservableProperty]
string bio;
[ObservableProperty]
ChampionClass championClass;
[ObservableProperty]
ObservableCollection<KeyValuePair<string, int>> characteristics = new ();
public void AddCharacteristic(string description, int value)
=> Characteristics.Add(new KeyValuePair<string, int>(description, value));
public void RemoveCharacteristic(KeyValuePair<string, int> characteristic)
=> Characteristics.Remove(characteristic);
[ObservableProperty]
ObservableCollection<SkillVM> skills = new ObservableCollection<SkillVM>();
public ChampionVM ToChampionVM()
{
var champion = new Champion(name, championClass, iconBase64, largeImageBase64, bio);
champion.AddCharacteristics(characteristics.Select(kvp => Tuple.Create(kvp.Key, kvp.Value)).ToArray());
foreach(var skillVM in Skills)
{
champion.AddSkill(skillVM.Model);
}
return new ChampionVM(champion);
}
}
}

@ -0,0 +1,51 @@
using System;
using System.Reflection.PortableExecutable;
using System.Xml.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using Model;
namespace ViewModels
{
[ObservableObject]
public partial class EditableSkinVM
{
public EditableSkinVM(ChampionVM championVM)
{
champion = championVM.Model;
}
public EditableSkinVM(SkinVM skinVM)
{
Name = skinVM.Name;
IconBase64 = skinVM.Icon;
LargeImageBase64 = skinVM.Image;
Description = skinVM.Description;
Price = skinVM.Price;
champion = skinVM.Champion;
}
[ObservableProperty]
private string name;
[ObservableProperty]
private string iconBase64;
[ObservableProperty]
private string largeImageBase64;
[ObservableProperty]
private string description;
[ObservableProperty]
private float price;
private Champion champion;
public SkinVM ToSkinVM()
{
var skin = new Skin(name, champion, price, iconBase64, largeImageBase64, description);
return new SkinVM(skin);
}
}
}

@ -0,0 +1,32 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using Model;
namespace ViewModels
{
[ObservableObject]
public partial class SkillVM
{
[ObservableProperty]
private Skill model;
public SkillVM(Skill model)
{
Model = model;
}
public string Name => Model.Name;
public SkillType Type => Model.Type;
public string Description
{
get => Model.Description;
set
{
SetProperty(Model.Description, value, newValue => Model.Description = newValue);
}
}
}
}

@ -0,0 +1,65 @@
using System;
using System.Reflection.PortableExecutable;
using System.Security.Claims;
using CommunityToolkit.Mvvm.ComponentModel;
using Model;
namespace ViewModels
{
[ObservableObject]
public partial class SkinVM
{
public Skin Model
{
get => model;
set
{
model = value;
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(Description));
OnPropertyChanged(nameof(Price));
OnPropertyChanged(nameof(Icon));
OnPropertyChanged(nameof(Image));
}
}
private Skin model;
public SkinVM(Skin model)
=> Model = model;
public string Name => Model.Name;
public string Description
{
get => Model.Description;
set => SetProperty(Model.Description, value, newValue => Model.Description = newValue);
}
public float Price
{
get => Model.Price;
set => SetProperty(Model.Price, value, newValue => Model.Price = newValue);
}
public string Icon
{
get => Model.Icon;
set
{
SetProperty(Model.Icon, value, newIcon => Model.Icon = newIcon);
}
}
public string Image
{
get => Model.Image.Base64;
set
{
SetProperty(Model.Image.Base64, value, newImage => Model.Image.Base64 = newImage);
}
}
public Champion Champion => Model.Champion;
}
}

@ -0,0 +1,135 @@
using System;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Model;
namespace ViewModels
{
[ObservableObject]
public partial class SkinsMgrVM
{
internal IDataManager DataMgr { get; set; }
[ObservableProperty]
private ChampionVM champion;
public SkinsMgrVM(IDataManager dataManager)
{
DataMgr = dataManager;
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(NextPageCommand))]
[NotifyCanExecuteChangedFor(nameof(PreviousPageCommand))]
private int index = 0;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(NbPages))]
[NotifyCanExecuteChangedFor(nameof(NextPageCommand))]
[NotifyCanExecuteChangedFor(nameof(PreviousPageCommand))]
private int count = 5;
public int NbPages
{
get
{
if(Count == 0 || NbSkins == 0)
{
return 0;
}
return (NbSkins-1) / Count + 1;
}
}
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(NbPages))]
[NotifyCanExecuteChangedFor(nameof(NextPageCommand))]
[NotifyCanExecuteChangedFor(nameof(PreviousPageCommand))]
private int nbSkins = 0;
[ObservableProperty]
private ObservableCollection<SkinVM> skins = new ObservableCollection<SkinVM>();
[RelayCommand]
async Task LoadSkins()
{
Skins.Clear();
IEnumerable<Skin?> skins;
if(Champion != null)
{
skins = await DataMgr.SkinsMgr.GetItemsByChampion(Champion.Model, Index, Count,"Name");
}
else
{
skins = await DataMgr.SkinsMgr.GetItems(Index, Count, "Name");
}
foreach(var skin in skins)
{
if(skin != null)
Skins.Add(new SkinVM(skin));
}
}
[RelayCommand(CanExecute =nameof(CanPreviousPage))]
async Task PreviousPage()
{
if(Index > 0)
{
Index--;
await LoadSkins();
}
}
bool CanPreviousPage() => Index > 0;
[RelayCommand(CanExecute =nameof(CanNextPage))]
async Task NextPage()
{
if(Index < NbPages-1)
{
Index++;
await LoadSkins();
}
}
bool CanNextPage() => Index < NbPages-1;
[RelayCommand(CanExecute =nameof(CanDeleteSkin))]
public async Task<bool> DeleteSkin(object skinVM)
{
SkinVM svm = skinVM as SkinVM;
if(svm == null || !Skins.Contains(svm)) return false;
bool result = await DataMgr.SkinsMgr.DeleteItem(svm.Model);
if(result)
{
Skins.Remove(svm);
await LoadSkins();
}
return result;
}
bool CanDeleteSkin(object svm)
=> svm!= null && svm is SkinVM && Skins.Contains(svm);
public async Task AddSkin(SkinVM skinVM)
{
var added = await DataMgr.SkinsMgr.AddItem(skinVM.Model);
if(added != null)
{
Skins.Add(skinVM);
await LoadSkins();
}
}
public async Task EditSkin(SkinVM oldSkin, SkinVM newSkin)
{
var edited = await DataMgr.SkinsMgr.UpdateItem(oldSkin.Model, newSkin.Model);
oldSkin.Model = newSkin.Model;
if(edited != null)
{
await LoadSkins();
}
}
}
}

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseMaui>true</UseMaui>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
</ItemGroup>
</Project>

@ -1,16 +0,0 @@
namespace apiLOL
{
public class ChampionDTO
{
public ChampionDTO(string name, string bio)
{
Name = name;
Bio = bio;
}
public string Name { get; set; }
public string Bio { get; set; }
}
}

@ -1,13 +0,0 @@
namespace apiLOL
{
public class ChampionPageDTO
{
public IEnumerable<ChampionDTO> Data { get; set; }
public int Index { get; set; }
public int Count { get; set; }
public int TotalCount { get; set; }
}
}

@ -1,7 +1,7 @@
using System.Security.Cryptography;
using apiLOL.DTO;
using Microsoft.AspNetCore.Mvc;
using Model;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace apiLOL.Controllers
{
@ -22,72 +22,87 @@ namespace apiLOL.Controllers
}
// GET: api/<ControllerLol>
[HttpGet]
public async Task<IActionResult> Get([FromQuery]int index = 0, int count = 10)
[ProducesResponseType(typeof(ChampionPageDTO), 200)]
public async Task<IActionResult> GetChampions([FromQuery] int index = 0, int count = 10, string? name = "")
{
//FromQuery permet de filtrer dans la collection de champions en fonction du nom
// Possible de faire une classe PageRequest pour gérer les paramètres index et count
_logger.LogInformation($"methode Get de ControllerChampions appelée");
int nbChampions = await _dataManager.GetNbItems();
_logger.LogInformation($"Nombre de champions : {nbChampions}");
var champs = (await _dataManager.GetItems(index, count)).Select(Model => Model.ToDTO());
var champs = (await _dataManager.GetItemsByName(name, index, int.MaxValue))
.Where(champ => string.IsNullOrEmpty(name) || champ.Name.Contains(name, StringComparison.InvariantCultureIgnoreCase))
.Take(count)
.Select(Model => Model.ToDTO());
var page = new ChampionPageDTO
{
Data = champs,
Index = index,
Count = count,
Count = champs.Count(),
TotalCount = nbChampions
};
return Ok(page);
}
// GET api/<ControllerLol>/Charle
[HttpGet]
[Route("{name}")]
public async Task<IActionResult> GetChampion(string name)
[ProducesResponseType(typeof(ChampionDTO), 200)]
public async Task<IActionResult> GetChampionByName(string name)
{
_logger.LogInformation($"methode GetChampion de ControllerChampions appelée avec le paramètre {name}");
try
{
var champs = (await _dataManager.GetItemsByName(name, 0, 1));
return Ok(champs.First().ToDTO());
var champs = await _dataManager.GetItemsByName(name, 0, 1);
if (champs.Any())
{
return Ok(champs.First().ToDTO());
}
else
{
return NotFound();
}
}
catch
{
catch (Exception ex)
{
_logger.LogError($"erreur methode Get de ControllerChampions: {ex}");
return BadRequest("erreur de nom de champion");
}
}
// POST api/<ControllerLol>
[HttpPost]
public async Task<IActionResult> Post(ChampionDTO champDTO)
public async Task<IActionResult> AddChampion(ChampionDTO champDTO)
{
_logger.LogInformation($"methode Post de ControllerChampions appelée avec le paramètre {champDTO.Name}");
try
{
// Check if the champion already exists in the database
var champs = await _dataManager.GetItemsByName(champDTO.Name, 0, 1);
if (champs.Any())
{
return BadRequest("le champion existe deja");
}
Champion tmp = champDTO.ToModel();
Champion champion = await _dataManager.AddItem(tmp);
ChampionDTO dtoChamp = champion.ToDTO();
return CreatedAtAction(nameof(GetChampion), new { name = dtoChamp.Name }, dtoChamp);
return CreatedAtAction(nameof(GetChampionByName), new { name = dtoChamp.Name }, dtoChamp);
}
catch
catch (Exception ex)
{
return BadRequest("le champion existe deja");
_logger.LogError($"erreur methode Post de ControllerChampions: {ex}");
return BadRequest("erreur lors de l'ajout du champion");
}
}
// PUT api/<ControllerLol>/5
[HttpPut("{name}")]
public async Task<IActionResult> Put(string name, string bio)
public async Task<IActionResult> UpdateChampion(string name, string bio)
{
_logger.LogInformation($"methode Put de ControllerChampions appelée avec le paramètre name: {name} et bio: {bio}");
_logger.LogInformation(
$"methode Put de ControllerChampions appelée avec le paramètre name: {name} et bio: {bio}");
try
{
@ -95,27 +110,67 @@ namespace apiLOL.Controllers
champs.Bio = bio;
return Ok(champs.ToDTO());
}
catch
catch (Exception ex)
{
_logger.LogError($"erreur methode Put de ControllerChampions: {ex}");
return BadRequest("erreur de nom de champion");
}
}
// DELETE api/<ControllerLol>/5
[HttpDelete("{name}")]
public async Task<IActionResult> Delete(String name)
public async Task<IActionResult> DeleteChampion(string name)
{
_logger.LogInformation($"methode Delete de ControllerChampions appelée avec le paramètre name: {name}");
try
{
var champ = (await _dataManager.GetItemsByName(name, 0, 1)).First();
_dataManager.DeleteItem(champ);
return Ok(champ.ToDTO());
}
catch
catch (Exception ex)
{
_logger.LogError($"erreur methode Delete de ControllerChampions: {ex}");
return BadRequest("erreur de nom de champion");
}
}
}
[ApiController]
[Route("api/v2/[controller]")]
[ApiVersion("2.0")]
public class ControllerChampionsSecondVersion : Controller
{
private readonly ILogger _logger;
public ControllerChampionsSecondVersion(ILogger<ControllerChampions> log)
{
_logger = log;
}
[HttpGet()]
public string Get(int id)
{
return "Version 2 of GET";
}
[HttpPost]
public void Post([FromBody] string value)
{
}
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}

@ -0,0 +1,64 @@
using apiLOL.DTO;
using Microsoft.AspNetCore.Mvc;
using Model;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace apiLOL.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiVersion("1.0")]
public class ControllerSkins : ControllerBase
{
private readonly IDataManager data;
// EFdata manager qui implémente l'interface IDataManager
// coté client : Refaire un APIdata manager qui implémente l'interface IDataManager
private readonly ILogger _logger;
public ControllerSkins(IDataManager manager, ILogger<ControllerChampions> log)
{
data = manager;
_logger = log;
}
// GET api/<ControllerSkins>/5
[HttpGet]
public async Task<IActionResult> Get([FromQuery] int index = 0, int count = 10, string? name = "")
{
_logger.LogInformation($"methode Get de ControllerSkins appelée index:{index}, count: {count} et name:{name}");
int nbSkins = await data.SkinsMgr.GetNbItems();
_logger.LogInformation($"Nombre de skins : {nbSkins}");
var skin = (await data.SkinsMgr.GetItems(index, await data.SkinsMgr.GetNbItems())).Select(Model => Model.ToDTO());//.Where(Model => Model.Name.Contains(name)).Skip(index * count).Take(count).Select(Model => Model.ToDTO());
var page = new SkinPageDTO
{
Data = (IEnumerable<SkinDTO>)skin,
Index = index,
Count = count,
TotalCount = nbSkins
};
return Ok(page);
}
// POST api/<ControllerSkins>
[HttpPost]
public void Post([FromBody] string value)
{
}
// PUT api/<ControllerSkins>/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
// DELETE api/<ControllerSkins>/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}

@ -0,0 +1,21 @@
namespace apiLOL.DTO
{
public class ChampionDTO
{
public ChampionDTO(string name, string bio, string icon)
{
Name = name;
Bio = bio;
Icon = icon;
}
public string Name { get; set; }
public string Bio { get; set; }
public string Icon { get; set; }
public bool Equals(ChampionDTO other) => other.Name == this.Name && other.Bio == this.Bio && other.Icon == this.Icon;
public string toString() => $"ChampionDTO: {Name} {Bio} {Icon}";
}
}

@ -1,18 +1,20 @@
using Model;
namespace apiLOL
{
public static class ChampionMapper
{
public static ChampionDTO ToDTO(this Champion champion) => new ChampionDTO(champion.Name, champion.Bio);
public static Champion ToModel(this ChampionDTO championDTO)
{
Champion champ = new Champion(championDTO.Name);
champ.Bio = championDTO.Bio;
return champ;
}
}
}
using Model;
namespace apiLOL.DTO
{
public static class ChampionMapper
{
public static ChampionDTO ToDTO(this Champion champion)
=> new ChampionDTO(champion.Name, champion.Bio, champion.Icon);
public static Champion ToModel(this ChampionDTO championDTO)
{
Champion champ = new Champion(championDTO.Name);
champ.Bio = championDTO.Bio;
champ.Icon = championDTO.Icon;
return champ;
}
}
}

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

Loading…
Cancel
Save