Compare commits

..

16 Commits

@ -28,5 +28,7 @@
</array> </array>
<key>XSAppIconAssets</key> <key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string> <string>Assets.xcassets/appicon.appiconset</string>
<key>NSCameraUsageDescription</key>
<string>This app uses camera for scanning barcodes</string>
</dict> </dict>
</plist> </plist>

@ -102,6 +102,9 @@
<toolkit:IconTintColorBehavior TintColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}"/> <toolkit:IconTintColorBehavior TintColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}"/>
</Image.Behaviors> </Image.Behaviors>
</Image> </Image>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding DetailsLivreVM.OpenInfoCommand}"/>
</Grid.GestureRecognizers>
</Grid> </Grid>
<contentView:SeparatorCutStartView/> <contentView:SeparatorCutStartView/>
@ -178,7 +181,7 @@
<Label Text="Résumé" <Label Text="Résumé"
Style="{StaticResource DetailsLivreTitle}" Style="{StaticResource DetailsLivreTitle}"
Grid.Row="0"/> Grid.Row="0"/>
<Label Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Et malesuada fames ac turpis egestas integer eget aliquet. Nunc sed id semper risus. Nisl purus in mollis nunc sed id semper risus. Egestas congue quisque egestas diam in arcu cursus euismod. Elementum integer enim neque volutpat ac tincidunt vitae. Amet luctus venenatis lectus magna fringilla urna porttitor rhoncus dolor. Sollicitudin tempor id eu nisl nunc. Eget mauris pharetra et ultrices neque. In vitae turpis massa sed elementum tempus. Posuere ac ut consequat semper viverra nam. Quisque non tellus orci ac auctor augue mauris augue. Cursus in hac habitasse platea dictumst. Pellentesque diam volutpat commodo sed egestas egestas fringilla phasellus faucibus. Vel fringilla est ullamcorper eget nulla facilisi etiam." <Label Text="{Binding DetailsLivreVM.Book.WorkDescription}"
Style="{StaticResource DetailsLivreBody}" Style="{StaticResource DetailsLivreBody}"
Grid.Row="1"/> Grid.Row="1"/>
</Grid> </Grid>

@ -42,8 +42,10 @@
Grid.Column="1"/> Grid.Column="1"/>
</Grid> </Grid>
<CollectionView ItemsSource="{Binding FavorisVM.Manager.AllFavoriteBooks}" <CollectionView ItemsSource="{Binding FavorisVM.Manager.AllFavoriteBooks}"
SelectionMode="Single" SelectedItem="{Binding FavorisVM.Manager.SelectedBook}"
SelectionChanged="OnSelectionChanged" SelectionMode="Single"
SelectionChangedCommand="{Binding FavorisVM.OnSelectionChangedCommand}"
SelectionChangedCommandParameter="{Binding FavorisVM.Manager.SelectedBook}"
Grid.Row="2"> Grid.Row="2">
<CollectionView.ItemTemplate> <CollectionView.ItemTemplate>
<DataTemplate x:DataType="viewModel:BookVM"> <DataTemplate x:DataType="viewModel:BookVM">

@ -24,10 +24,5 @@ public partial class FavorisView : ContentPage
#region Methods #region Methods
void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
//App.Current.MainPage.Navigation.PushAsync(new DetailsLivreView());
}
#endregion #endregion
} }

@ -77,6 +77,8 @@ namespace LivreLand.ViewModel
public ICommand RemoveBookCommand { get; private set; } public ICommand RemoveBookCommand { get; private set; }
public ICommand OpenInfoCommand { get; private set; }
#endregion #endregion
#region Constructor #region Constructor
@ -92,6 +94,7 @@ namespace LivreLand.ViewModel
AddBookToReadListCommand = new RelayCommand<BookVM>((bookVM) => AddBookToReadList(bookVM)); AddBookToReadListCommand = new RelayCommand<BookVM>((bookVM) => AddBookToReadList(bookVM));
LoanBookCommand = new RelayCommand<BookVM>((bookVM) => LoanBook(bookVM)); LoanBookCommand = new RelayCommand<BookVM>((bookVM) => LoanBook(bookVM));
RemoveBookCommand = new RelayCommand<BookVM>((bookVM) => RemoveBook(bookVM)); RemoveBookCommand = new RelayCommand<BookVM>((bookVM) => RemoveBook(bookVM));
OpenInfoCommand = new RelayCommand(() => OpenInfo());
} }
#endregion #endregion
@ -106,7 +109,7 @@ namespace LivreLand.ViewModel
private void ShowPicker() private void ShowPicker()
{ {
Manager.GetAllStatusCommand.Execute(null); Manager.GetAllStatusCommand.Execute(null);
Manager.SelectedStatus = this.Book.Status; Manager.SelectedStatus = Book.Status;
IsPickerVisible = true; IsPickerVisible = true;
} }
@ -167,6 +170,13 @@ namespace LivreLand.ViewModel
Navigator.PopupBackButtonNavigationCommand.Execute(null); Navigator.PopupBackButtonNavigationCommand.Execute(null);
} }
private async Task OpenInfo()
{
var isbn = Manager.SelectedBook.ISBN13;
string url = "https://openlibrary.org/isbn/" + isbn;
await Launcher.OpenAsync(url);
}
#endregion #endregion
} }
} }

@ -1,9 +1,11 @@
using PersonalMVVMToolkit; using LivreLand.View;
using PersonalMVVMToolkit;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input;
using ViewModels; using ViewModels;
namespace LivreLand.ViewModel namespace LivreLand.ViewModel
@ -16,6 +18,8 @@ namespace LivreLand.ViewModel
public ManagerVM Manager { get; private set; } public ManagerVM Manager { get; private set; }
public ICommand OnSelectionChangedCommand { get; private set; }
#endregion #endregion
#region Constructor #region Constructor
@ -24,6 +28,20 @@ namespace LivreLand.ViewModel
{ {
Navigator = navigatorVM; Navigator = navigatorVM;
Manager = managerVM; Manager = managerVM;
OnSelectionChangedCommand = new RelayCommand<BookVM>((bookVM) => OnSelectionChanged(bookVM));
}
#endregion
#region Methods
private void OnSelectionChanged(BookVM bookVM)
{
if (bookVM != null)
{
var result = new DetailsLivreVM(Manager, Navigator, bookVM);
App.Current.MainPage.Navigation.PushAsync(new DetailsLivreView(result));
}
} }
#endregion #endregion

@ -4,32 +4,37 @@
******* *******
Sommaire ### Sommaire
1. [Accessibilité](#acces) 1. [Accessibilité](#acces)
2. [Progression](#progression) 2. [Progression](#progression)
3. [Présentation du projet](#presentation) 3. [Présentation du projet](#presentation)
4. [Contenu](#contenu) 4. [Architecture](#architecture)
5. [Auteurs](#auteurs) 5. [Contenu](#contenu)
6. [Auteurs](#auteurs)
******* *******
<div id='acces'/> <div id='acces'/>
Pour accéder au code de l'application, vous pouvez cloner la branche `master` du dépôt Code#0 et ouvrir celle-ci dans `Microsoft Visual Studio` par exemple. Pour accéder au code de l'application, vous pouvez cloner la branche `master` du dépôt Code#0 et ouvrir celle-ci dans `Microsoft Visual Studio` par exemple.
Disponible sur :
![](https://img.shields.io/badge/Android-3DDC84?style=for-the-badge&logo=android&logoColor=white)
![](https://img.shields.io/badge/iOS-000000?style=for-the-badge&logo=ios&logoColor=white)
> **Warning**: L'application est fonctionnelle sous Windows et Android mais n'a pas été testée sous IOS.
> **Warning**: Le déploiement n'a pas encore été fait. - Dans la branche **Master**, vous retrouverez l'intégralité de l'application avec les fonctionnalités du `TP2` ainsi que le scan de code-barres.
Disponible sur : - Dans la branche **TP3**, vous retrouverez l'implémentation du `MVVM Community Toolkit` dans notre application.
![](https://img.shields.io/badge/Android-3DDC84?style=for-the-badge&logo=android&logoColor=white)
![](https://img.shields.io/badge/iOS-000000?style=for-the-badge&logo=ios&logoColor=white)
******* *******
<div id='progression'/> <div id='progression'/>
🚧 __EN PROGRESSION__ ## 🚧 __EN PROGRESSION__
Étape 1 : Développement des vues en XAML ### Étape 1 : Développement des vues en XAML
- Intégralité des pages proposées sur la page d'accueil développées - Intégralité des pages proposées sur la page d'accueil développées
- Mode clair & Mode sombre disponibles (pas très esthétique) - Mode clair & Mode sombre disponibles (pas très esthétique)
- Utilisable en mode portrait ou mode paysage - Utilisable en mode portrait ou mode paysage
@ -40,7 +45,11 @@ Disponible sur :
--- ---
Étape 2 : Personnal MVVM Toolkit ### Étape 2 : Personnal MVVM Toolkit
La création de ce `Toolkit Personnel` a pour but de faciliter le développement de l'application en fournissant un ensemble de fonctionnalités et de composants réutilisables. De plus, à l'aide d'une classe comme `RelayCommand`, notre objectif est de ne pas inclure dans nos ViewModels une dépendance avec les Commands de .NET MAUI.
Nous pouvons représenter la structure de notre toolkit avec le diagramme suivant :
```mermaid ```mermaid
classDiagram classDiagram
@ -77,14 +86,120 @@ class RelayCommand{
ObservableObject ..|> INotifyPropertyChanged ObservableObject ..|> INotifyPropertyChanged
BaseViewModel --|> ObservableObject BaseViewModel --|> ObservableObject
RelayCommand ..|> ICommand RelayCommand ..|> ICommand
```
Cette structure est une version remplaçant pour le moment le `Community Toolkit` mis en place par Microsoft qui permet aussi de supprimer beaucoup de code inutile en remplaçant celui-ci par des annotations et des classes partielles.
---
### Étape 3 : MVVM
Nous utilisons au sein de notre projet le **patron d'architecture MVVM** avec les ViewModels Wrapping et Applicatives.
Nous retrouvons donc les 3 grandes parties du patron :
- **Model** :
Le `Model` représente la `logique métier`. Il est écrit en `C#` et est adpaté pour diifférentes applications.
- **View** :
Les `Vues` sont écrites en `XAML` et représentent l'interface utilisateur avec les vues de l'application. Le `Data Binding` est utilisé entre les propriétés du XAML et celles des ViewModels. Enfin, des évènements sont déclenchés à partir de certains composants des vues.
- **ViewModel** :
Les `ViewModels` sont écrits en `C#` et sont divisables en deux grandes catégories :
* Les **Wrapping ViewModel** encapsulent les données du modèle et exposent des propriétés et des commandes nécessaires à la vue pour interagir avec le modèle.
* Les **Applicative ViewModel** peuvent inclure une logique métier spécifique et des propriétés calculées, elles peuvent également exposer des commandes pour effectuer des actions spécifiques liées à la vue.
Le schéma suivant montre bien les relations entre les grandes parties du `patron MVVM` :
![Schema_MVVM](documentation/schema_mvvm.png)
Le **diagramme de classes** pouvant être extrèmement grand à cause des multiples classes au sein de notre projet, j'ai décidé de représenter une partie de celui-ci qui pourrait se répéter pour toutes les autres parties. L'objectif principal étant de comprendre comment fonctionne le **modèle MVVM** et comment les classes intéragissent entre elles, j'ai choisi de faire mon exemple avec la partie des livres qui est la plus générale du sujet.
```mermaid
classDiagram
class Book {
Id: string
Title: string
Publishers: List<string>
PublishDate: DateTime
ISBN13: string
Series: List<string>
NbPages: int
Format: string
Language: Languages
Contributors: List<Contributor>
ImageSmall: string
ImageMedium: string
ImageLarge: string
Works: List<Work>
Authors: List<Author>
Status: Status
UserTags: List<string>
UserRating: float?
UserNote: string
Equals(other: Book): bool
GetHashCode(): int
}
class DetailsLivreView {
DetailsLivreVM: DetailsLivreVM
}
class DetailsLivreVM {
isPickerVisible: bool
addFavorisButtonText: string
Manager: ManagerVM
Navigator: NavigatorVM
Book: BookVM
IsPickerVisible: bool
AddFavorisButtonText: string
BackButtonCommand: ICommand
ShowPickerCommand: ICommand
AddRemoveBookToFavoritesCommand: ICommand
AddBookToReadListCommand: ICommand
LoanBookCommand: ICommand
RemoveBookCommand: ICommand
OpenInfoCommand: ICommand
BackButton()
ShowPicker()
AddRemoveBookToFavorites(bookVM: BookVM)
AddBookToReadList(bookVM: BookVM)
LoanBook(bookVM: BookVM)
RemoveBook(bookVM: BookVM)
OpenInfo()
}
class BookVM {
Id: string
ISBN13: string
Title: string
Publishers: List<string>
PublishDate: DateTime
Works: List<WorkVM>
WorkDescription: string
Authors: List<AuthorVM>
Author: string
Status: Status
NbPages: int
Language: Languages
ImageSmall: string
UserRating: float?
}
DetailsLivreView --> DetailsLivreVM
DetailsLivreVM --> BookVM
DetailsLivreVM --> ManagerVM
DetailsLivreVM --> NavigatorVM
BookVM --> Book
BookVM --> WorkVM
BookVM --> AuthorVM
``` ```
******* *******
<div id='presentation'/> <div id='presentation'/>
### **Présentation** ## **Présentation**
LivreLand : votre bibliothèque connectée ! LivreLand : votre bibliothèque connectée !
Retrouver tous vos livres préférés en un clic. Retrouver tous vos livres préférés en un clic.
@ -95,17 +210,200 @@ Retrouver tous vos livres préférés en un clic.
## Fonctionnalités ## Fonctionnalités
- Livres triés par auteur, date, notes, statut de lecture... **TP2 - Base** :
- Livres à lire plus tard - [x] Page d'accueil
- Livres prêtés - [x] Affichage des livres de l'utilisateur : afficher tous les livres de l'utilisateur dans la vue BooksPage et permettre la sélection d'un livre et la navigation vers la page BookPage
- Livres favoris * seule la note n'est pas encore affichée sous la forme d'étoiles (commencée dans `star-notation-22-10`)
- [x] Filtrage par auteur et par date de publication : afficher dans la vue de filtrage (FilterPage)
**TP2 - Ajouts** :
- [x] Changer le statut de lecture d'un livre
- [x] Ajouter un livre aux favoris
- [x] Filtrer les livres par Auteur, Date de publication, Note
* le filtrage fonctionne, au deuxième clique sur une date par exemple une fois une première date visitée, je remarque des soucis avec de temps à autre une exception
- [x] Ajouter un livre à sa collection en saisissant l'ISBN
- [x] Supprimer un livre
- [x] Prêter un livre (et ajouter un contact si besoin)
* la page avec les contacts n'est pas esthétiquement très réussie
- [x] Consulter la liste des livres prêtés
* j'ai fait le choix de n'afficher que les livres _actuellement_ prêtés ou empruntés
_Erreurs rencontrées_ :
* L'ajout en favoris fonctionne, cependant il m'ait arrivé lorque je choisissais à partir de la page BooksPage d'ajouter un livre qui ne se trouve pas sur la première page, que celui-ci supprime souvent tous les livres déjà en favoris.
* J'ai également eu à certains moments des problèmes avec l'accession à la page de détails et une double page qui s'ouvrait; dans ce cas je relance généralement l'application.
**TP3** :
- [X] Modifier l'intégralité du code pour que l'application utilise désormais le MVVM Community Toolkit à la place du toolkit personnel
* lecture de la documentation et implémentation dans la branche `TP3` (à affiner pour respecter parfaitement le Toolkit)
**TP 4** :
Ajouter les vues et les VM nécessaires pour permettre :
- [x] Le scan de code-barres afin d'ajouter de nouveaux livres
* le scan de code-barres fonctionne mais le livre n'est pas encore directement ajouté dans la liste (affichage de l'isbn dans en bas de page & du bouton d'ajout du livre)
- [X] La recherche en ligne (via le web service)
* il est possible d'accéder à la page d'un livre en ligne en cliquant dans la partie "Infos en ligne" de la page Détails
*******
<div id='architecture'/>
## Architectures du modèle et des services fournises
Dans cette partie, vous retrouverez dans un premier temps deux diagrammes mis à disposition dans le sujet représentant d'abord le `Modèle` puis les `Services et Interfaces` :
### Modèle
```mermaid
classDiagram
direction LR
class Book {
+Id : string
+Title : string
+Publishers : List~string~
+PublishDate : DateTime
+ISBN13 : string
+Series : List~string~
+NbPages : int
+Format : string
+ImageSmall : string
+ImageMedium : string
+ImageLarge : string
}
class Languages {
<<enum>>
Unknown,
French,
}
class Work {
+Id : string
+Description : string
+Title : string
+Subjects : List~string~
}
class Contributor {
+Name : string
+Role : string
}
class Author {
+Id : string
+Name : string
+ImageSmall : string
+ImageMedium : string
+ImageLarge : string
+Bio : string
+AlternateNames : List~string~
+BirthDate : DateTime?
+DeathDate : DateTime?
}
class Link {
+Title : string
+Url : string
}
class Ratings {
+Average : float
+Count : int
}
Book --> "1" Languages : Language
Book --> "*" Contributor : Contributors
Author --> "*" Link : Links
Work --> "*" Author : Authors
Work --> "1" Ratings : Ratings
Book --> "*" Author : Authors
Book --> "*" Work : Works
class Status {
<<enum>>
Unknown,
Finished,
Reading,
NotRead,
ToBeRead
}
class Contact{
+string Id;
+string FirstName;
+string LastName;
}
```
---
### Services et Interfaces
```mermaid
classDiagram
direction LR
Book --> "1" Languages : Language
Book --> "*" Contributor : Contributors
Author --> "*" Link : Links
Work --> "1" Ratings : Ratings
Work --> "*" Author : Authors
Book --> "*" Work : Works
Book --> "*" Author : Authors
class IUserLibraryManager {
<<interface>>
+AddBook(Book book) : Task<Book>
+AddBook(string id) : Task<Book>
+AddBookByIsbn(string isbn) : Task<Book>
+RemoveBook(Book book) : Task<bool>
+RemoveBook(string id) : Task<bool>
+RemoveBook(string id) : Task<bool>
+AddToFavorites(Book book) : Task<bool>
+AddToFavorites(string bookId) : Task<bool>
+RemoveFromFavorites(Book book) : Task<bool>
+RemoveFromFavorites(string bookId) : Task<bool>
+UpdateBook(Book updatedBook) : Task<Book>
+AddContact(Contact contact) : Task<Contact>
+RemoveContact(Contact contact) : Task<bool>
+LendBook(Book book, Contact contact, DateTime? loanDate) : Task<bool>
+GetBackBook(Book book, DateTime? returnedDate) : Task<bool>
+BorrowBook(Book book, Contact owner, DateTime? borrowedDate) : Task<bool>
+GiveBackBook(Book book, DateTime? returnedDate) : Task<bool>
+GetCurrentLoans(int index, int count)
+GetPastLoans(int index, int count)
+GetCurrentBorrowings(int index, int count)
+GetPastBorrowings(int index, int count)
+GetContacts(int index, int count)
}
class ILibraryManager {
<<interface>>
+GetBookById(string id)
+GetBookByIsbn(string isbn)
+GetBooksByTitle(string title, int index, int count, string sort)
+GetBooksByAuthorId(string authorId, int index, int count, string sort)
+GetBooksByAuthor(string author, int index, int count, string sort)
+GetAuthorById(string id)
+GetAuthorsByName(string substring, int index, int count, string sort)
}
class Status {
<<enum>>
}
IUserLibraryManager ..|> ILibraryManager
IUserLibraryManager ..> Status
IUserLibraryManager ..> Contact
IUserLibraryManager ..> Book
ILibraryManager ..> Book
ILibraryManager ..> Author
```
******* *******
## Ressources ## Ressources
- Temps - Temps
- 4 Septembre au - 4 Septembre au 22 Octobre 2023
- Matériel - Matériel
- Ordinateurs portables sous Windows - Ordinateurs portables sous Windows
- Émulateur sous Visual Studio 2022 - Émulateur sous Visual Studio 2022

@ -42,6 +42,13 @@ namespace ViewModels
get => Model.PublishDate; get => Model.PublishDate;
} }
public List<WorkVM> Works
{
get => Model.Works.Select(w => new WorkVM(w)).ToList();
}
public string WorkDescription => Model.Works.Count > 0 ? Model.Works.First().Description : " ";
public List<AuthorVM> Authors public List<AuthorVM> Authors
{ {
get => Model.Authors.Select(a => new AuthorVM(a)).ToList(); get => Model.Authors.Select(a => new AuthorVM(a)).ToList();
@ -52,6 +59,7 @@ namespace ViewModels
public Status Status public Status Status
{ {
get => Model.Status; get => Model.Status;
set => SetProperty(Model.Status, value, status => Model.Status = status);
} }
public int NbPages public int NbPages

@ -487,8 +487,8 @@ namespace ViewModels
book.Status = SelectedStatus; book.Status = SelectedStatus;
await Model.UpdateBook(book); await Model.UpdateBook(book);
var updatedBook = new BookVM(book); var updatedBook = new BookVM(book);
bookVM = updatedBook; SelectedBook.Status = updatedBook.Status;
OnPropertyChanged(nameof(bookVM)); OnPropertyChanged(nameof(SelectedBook));
} }
private async Task UpdateToBeReadBook(BookVM bookVM) private async Task UpdateToBeReadBook(BookVM bookVM)

@ -0,0 +1,42 @@
using Model;
using PersonalMVVMToolkit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ViewModels
{
public class WorkVM : BaseViewModel<Work>
{
#region Fields
#endregion
#region Properties
public string Id
{
get => Model.Id;
set => SetProperty(Model.Id, value, v => Model.Id = value);
}
public string Description
{
get => Model.Description;
set => SetProperty(Model.Description, value, v => Model.Description = value);
}
#endregion
#region Constructor
public WorkVM(Work w) : base(w)
{
}
#endregion
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Loading…
Cancel
Save