Add architecture description
continuous-integration/drone/push Build is failing Details

pull/30/head
Corentin LEMAIRE 2 years ago
parent d6ba2acaaa
commit 23430738d6

@ -200,17 +200,16 @@ Manager o-- "+ CurrentPlaying" CustomTitle
#### Album
Cette classe sert à modéliser des albums de musique. Ils ne sont pas jouables et sont uniquement implantés à titre informatif. Dans ce but, elle comporte plusieurs attributs comme un nom, une description, des informations complémentaires
ou encore une URL pour son image (la pochette).
Cette classe sert à modéliser des **albums** de musique. Ils ne sont pas jouables et sont uniquement implantés à titre **informatif**. Dans ce but, elle comporte plusieurs attributs comme un *nom*, une *description*, des *informations complémentaires* ou encore une *URL* pour son image (la pochette).
#### Artist
Cette classe sert à modéliser les artistes qui réalise les albums. Il ne possède qu'un nom en atrribut.
Cette classe sert à modéliser les ***artistes*** qui réalise les albums. Il ne possède qu'un *nom* en atrribut.
#### Title
Cette classe sert à modé+liser différents titres. Il possède plusieurs attributs comme un nom, une URL pour son image (cover) ainsi que des informations complémentaires.
Cette classe sert à modéliser différents **titres**. Il possède plusieurs attributs comme un *nom*, une *URL* pour son image (cover) ainsi que des *informations complémentaires*.
#### InfoTitle
@ -231,7 +230,7 @@ Cette classe possède une structure similaire à la classe **Album**. Elle conti
#### Manager
Notre classe manager est une interface entre le code-behind et les vues. Il permet de gérer les données grâce au DataManager et de gérer les fonctionnalités de l'application comme les playlists. Cette interface fonctionne avec le parton de conception de façade, étant une interface, un point d'entrée de l'application vers le modèle. De plus, nous utilisons un deuxième patron de conception, qui est la stratégie, avec une injection de dépendance par le constructeur de la classe. En effet, afin de créer un Manager, il faut joindre la méthode de sérialisation (stubs ou LINQ dans notre cas).
Notre classe **Manager** est une interface entre le code-behind et les vues. Il permet de gérer les données grâce au *DataManager* et de **gérer** les fonctionnalités de l'application comme les playlists. Cette classe fonctionne avec le *parton de conception de façade*, étant une interface, un point d'entrée de l'application vers le modèle. De plus, nous utilisons un deuxième patron de conception, qui est la *stratégie*, avec une *injection de dépendance* par le constructeur de la classe. En effet, afin de créer un Manager, il faut joindre la méthode de sérialisation (stubs ou LINQ dans notre cas).
---
@ -502,43 +501,48 @@ StubAlbum *-- "+ StubArtist" StubArtist
LinqXmlSerialization o-- "+ StubInfoTitle" StubInfoTitle
@enduml
````
```
### Explications
#### IDataManager
Cette **interface** nous permet de passer d'un sérialisation à une autre. En effet, chacune des méthodes qui implémenteront IDataManager auront les méthodes telles que les *CRUD* et celles de la gestion de la *sérialisation*.
#### LinqXmlSerialization
Notre sérialisation fonctionne avec lecture/écriture dans des fichiers XML. Pour cela, nous utilisons la bibliothèque *LINQ_XML* qui nous permet de créer et de modifier les différents fichiers. Pour la sérialisation des CustomTitle et des Playlists nous utilisons le LINQ. Cependant, pour les classes InfoTitle, Artist et Albums, ces données sont récupérées des Stubs, étant statiques et à but informatif. Cette sérialisation a été testée et approuvée sur Windows et Android.
Notre **sérialisation** fonctionne avec lecture/écriture dans des fichiers **XML**. Pour cela, nous utilisons la bibliothèque *LINQ_XML* qui nous permet de créer et de modifier les différents fichiers. Pour la sérialisation des *CustomTitle* et des *Playlists* nous utilisons le LINQ. Cependant, pour les classes *InfoTitle*, *Artist* et *Album*, ces données sont récupérées des Stubs, étant statiques et à but *informatif*. Cette sérialisation a été testée et approuvée sur **Windows** et **Android**.
#### StubManager
Le StubManager est un point d'entrée vers les Stubs, permettant de les gérer avec des requêtes CRUD. Ces Stubs vont permettre de tester l'application, le modèle et l'application en générant des données directement dans le code. Cette classe permet de séparer le code de chaque Stub afin de le gérer avec plus de facilité.
Le StubManager est un **point d'entrée** vers les Stubs, permettant de les **gérer** avec des *requêtes CRUD*. Ces Stubs vont permettre de *tester* l'application, le modèle et l'application en générant des données directement dans le code. Cette classe permet de séparer le code de chaque Stub afin de le gérer avec plus de facilité.
#### StubAlbum
Le StubAlbum permet de générer des albums avec des informations écrites dans le code. Il permet également de les gérer. Cette classe utiliser les stubs StubArtist afin de lier l'album avec l'artiste. Ce stub est aussi utilisé afin d'afficher ces albums à titre informatif.
Le StubAlbum permet de **générer des albums** avec des informations écrites dans le code. Il permet également de les **gérer**. Cette classe utilise *StubArtist* afin de lier l'album avec l'artiste. Ce stub est aussi utilisé afin d'afficher ces albums à titre *informatif*.
#### StubArtist
Le StubArtist permet de créer des informations sur des artistes. Il permet de plus de créer les artistes pour les InfoTitle à afficher dans les titres informatifs présents dans les albums.
Le StubArtist permet de **créer des informations sur des artistes**S. Il permet de plus de créer les artistes pour les *InfoTitle* à afficher dans les titres informatifs présents dans les albums.
#### StubInfoTitle
Le StubInfoTitle est un stub avec des valeurs pour les titres informatifs affichés dans les albums déjà rédigées. Il est donc utilisé dans la sérialisation LINQ aussi.
Le StubInfoTitle est un stub avec des valeurs pour les **titres informatifs** affichés dans les albums déjà rédigées. Il est donc utilisé dans la *sérialisation LINQ* aussi.
#### StubCustomTitle
Le StubCustomTitle permet de tester la gestion des titres personnalisés avec des valeurs prédéfinies. Ce stub n'est utilisé que pour les tests et le debug.
Le StubCustomTitle permet de **tester la gestion des titres personnalisés** avec des valeurs prédéfinies. Ce stub n'est utilisé que pour les *tests* et le *debug*.
#### StubPlaylist
Le StubPlaylist créé des informations écrites dans le code afin de tester les méthodes et la classe des playlists. Elle n'est pas utilisée par la sérialisation LINQ.
Le StubPlaylist **créé des informations** écrites dans le code afin de **tester** les méthodes et la classe des playlists. Elle n'est pas utilisée par la sérialisation LINQ.
---
@ -587,7 +591,7 @@ Manager *-- "+ DataManager" IDataManager
### Explications
Ce diagramme représente les liens entre les deux diagrammes ci-dessus.
Ce diagramme représente les **liens** entre les deux diagrammes ci-dessus.
---
@ -656,5 +660,30 @@ Notre sérialisation permet de sauvegarder nos données dans des fichiers *XML*
Cette méthode est appelée lorsque l'utilisateur démarre l'application *Linaris* (MAUI). Celle-ci appelle ensuite les différentes fonctions de chargement codées en *C#* présentes dans la classe *LinqXmlSerialization*. Grâce à la bibliothèque **LINQ_XML**, la sérialisation peut récupérer les données présentes dans les différents fichiers pour les classes **Artist**, **CustomTitle** et **Playlist** et les mettre dans les différentes *ObservableCollection*. Pour les classes **InfoTitle** et **Album**, les données sont récupérées dans les collections des stubs correspondant et les mettre dans les différentes *ObservableCollection*. Les données sont ensuite utilisables par les vues via le **manager**.
---
#### Description de l'architecture
Notre programme se compose de deux parties distinctes : les **vues** et le **modèle**.
Pour les **vues**, cela correspond aux visuels de l'application. Pour le modèle, cela correspond à la logique de l'application, son fonctionnement.
Ces deux parties sont liées par le **DataBinding** liant les données et le modèle avec les vues.
<br/>
Pour permettre cela, nous avons mis en oeuvre le patron de conception de **"façade"** grâce à la classe *Manager*. En effet, cette classe est un *point d'entrée* vers le modèle, où le fonctionnement est caché par des méthodes et propriétés comme c'est le cas pour les listes d'objets. Les vues ne savent pas comment ces listes sont créées, elles ne font que l'utiliser. Cette classe **gère le modèle** mais est aussi l'interlocuteur des vues. C'est un **lien** entre ces deux parties. Cela nous permettait de bien **séparer les vues et le modèle**, mais aussi de **réduire** et **clarifier** le code présent dans le code-behind des vues.
<br/>
De plus, nous avons intégré le patron de conception de **"stratégie"** grâce à l'interface *IDataManager* et l'*injection de dépendance par le contructeur* du Manager. En effet, cela nous permet de *changer de méthode* de sérialisation très facilement. Nous pouvons donc utiliser les stubs pour tester le programme et repasser sur la sérialisation XML pour utiliser l'application en un simple changement. Cela permet une **adaptabilité** de code non négligeable. Il suffit de changer le paramètre donné au constructeur du Manager.
En outre, pour que cela fonctionne, l'interface *IDataManager* est indispensable, afin de s'assurer que toutes les méthodes de sérialisation aient bien **toutes les méthodes** nécessaires au bon fonctionnement de l'application. Grâce à cela, nous n'avons pas à nous demander quelle est la méthode de sérialisation pusique l'appel de la méthode de l'interface appellera la méthode de la technique de sérialisation demandée. Nous obtenons un résultat différent selon la méthode **sans changer le code**. Par exemple, dans la classe *App*, nous pouvons simplement appeler la méthode de sérialisation du manager, nous n'avons pas à changer ce code à chaque changement de méthode de sérialisation. Cela nous fait **gagner en temps** et en **adaptabilité de code**.
Grâce à ces patrons de conception, nous pouvons passer des stubs à la sérialisation XML bien plus facilement et **sans changer le code** des vues.
<br/>
Afin de lier nos différents projets, nous avons ajouté des **dépendances**. En effet, pour que l'*application console*, les *vues* et les *tests unitaires* fonctionnent, nous avons ajouté la dépendance vers le *modèle*.
Par ailleurs, nous avons aussi ajouté la dépendance vers **XUnit** pour le projet des *tests unitaires* et la dépendance vers **ToolKit** pour les vues afin de pouvoir utiliser *MediaElement* pour la lecture de médias.

@ -19,9 +19,9 @@
<StackLayout Grid.Row="0" Grid.Column="1" Orientation="Horizontal" HorizontalOptions="CenterAndExpand" x:Name="ButtonStack">
<ImageButton x:Name="Random" Source="rdm.png" Margin="0,10,8,10" WidthRequest="25" HeightRequest="25" Aspect="AspectFit" MinimumWidthRequest="1" MinimumHeightRequest="1" BackgroundColor="Transparent"/>
<ImageButton x:Name="Back" Clicked="RewindButton_Clicked" Source="back.png" Margin="8,10,8,10" WidthRequest="20" HeightRequest="20" Aspect="AspectFit" MinimumWidthRequest="1" MinimumHeightRequest="1" BackgroundColor="Transparent"/>
<ImageButton x:Name="Play" Clicked="PlayButton_Clicked" Source="play.png" Margin="8,0,8,0" WidthRequest="38" HeightRequest="38" Aspect="AspectFit" MinimumWidthRequest="5" MinimumHeightRequest="5" BackgroundColor="Transparent"/>
<ImageButton x:Name="Next" Clicked="NextButton_Clicked" Source="next.png" Margin="8,10,8,10" WidthRequest="20" HeightRequest="20" Aspect="AspectFit" MinimumWidthRequest="1" MinimumHeightRequest="1" BackgroundColor="Transparent"/>
<ImageButton x:Name="Back" Source="back.png" Margin="8,10,8,10" WidthRequest="20" HeightRequest="20" Aspect="AspectFit" MinimumWidthRequest="1" MinimumHeightRequest="1" BackgroundColor="Transparent"/>
<ImageButton x:Name="Play" Source="play.png" Margin="8,0,8,0" WidthRequest="38" HeightRequest="38" Aspect="AspectFit" MinimumWidthRequest="5" MinimumHeightRequest="5" BackgroundColor="Transparent"/>
<ImageButton x:Name="Next" Source="next.png" Margin="8,10,8,10" WidthRequest="20" HeightRequest="20" Aspect="AspectFit" MinimumWidthRequest="1" MinimumHeightRequest="1" BackgroundColor="Transparent"/>
<ImageButton x:Name="Loop" Source="loop.png" Margin="8,10,0,10" WidthRequest="25" HeightRequest="25" Aspect="AspectFit" MinimumWidthRequest="1" MinimumHeightRequest="1" BackgroundColor="Transparent"/>
</StackLayout>

@ -1,126 +1,11 @@
using NAudio.Wave;
namespace Linaris;
public partial class FooterPage : ContentView
{
WaveOutEvent outputDevice;
AudioFileReader audioFile;
System.Timers.Timer timer;
bool changementManuel = true;
bool closing = false;
string morceauEnCours;
public FooterPage()
{
InitializeComponent();
outputDevice = new WaveOutEvent();
// (s,a) = convention, s = sender, a = arguments, si appli fermée, on free tout
outputDevice.PlaybackStopped += PlaybackStoppedHandler;
morceauEnCours = Path.Combine(AppContext.BaseDirectory, "Resources", "Musics", "winter.mp3");
audioFile = new AudioFileReader(morceauEnCours);
outputDevice.Init(audioFile);
}
public void PlayButton_Clicked(object sender, EventArgs e)
{
string url = ((FileImageSource)Play.Source).File;
if (url == "play.png")
{
outputDevice?.Play();
Play.Source = "pause.png";
Timer_Elapsed(sender, e);
timer = new System.Timers.Timer(1000);
timer.Elapsed += Timer_Elapsed;
timer.Enabled = true;
}
else
{
outputDevice?.Pause();
Play.Source = "play.png";
}
}
public void RewindButton_Clicked(Object sender, EventArgs e)
{
audioFile.Position = 0;
outputDevice?.Play();
Play.Source = "pause.png";
Timer_Elapsed(sender, e);
timer = new System.Timers.Timer(1000);
timer.Elapsed += Timer_Elapsed;
timer.Enabled = true;
}
public void NextButton_Clicked(Object sender, EventArgs e)
{
audioFile.Position = audioFile.Length;
}
public void StopButton_Clicked(object sender, EventArgs e)
{
outputDevice.Stop();
audioFile.Position = 0;
}
public void Slider_ValueChanged(object sender, ValueChangedEventArgs e)
{
outputDevice.Volume = (float)e.NewValue;
}
public void Bar_ValueChanged(object sender, ValueChangedEventArgs e)
{
if (changementManuel)
{
double totalSeconds = audioFile.TotalTime.TotalSeconds;
double newPosition = e.NewValue * totalSeconds;
audioFile.CurrentTime = TimeSpan.FromSeconds(newPosition);
}
else
{
changementManuel = true;
}
}
private void PlaybackStoppedHandler(object sender, StoppedEventArgs e)
{
if (closing)
{
outputDevice.Dispose();
audioFile.Dispose();
}
else
{
Play.Dispatcher.Dispatch(() => Play.Source = "play.png");
}
}
private void Timer_Elapsed(object sender, EventArgs e)
{
TimeSpan totalTimeSpan = audioFile.TotalTime;
TimeSpan currentTimeSpan = audioFile.CurrentTime;
string totalTimeFormatted = totalTimeSpan.ToString(@"hh\:mm\:ss");
string currentTimeFormatted = currentTimeSpan.ToString(@"hh\:mm\:ss");
changementManuel = false;
bar.Dispatcher.Dispatch(() => bar.Value = audioFile.CurrentTime.TotalSeconds / audioFile.TotalTime.TotalSeconds);
currentTime.Dispatcher.Dispatch(() => currentTime.Text = currentTimeFormatted);
endTime.Dispatcher.Dispatch(() => endTime.Text = totalTimeFormatted);
}
private void LoadNewAudioFile(string filePath)
{
if (outputDevice.PlaybackState == PlaybackState.Playing)
{
outputDevice.Stop();
}
audioFile = new AudioFileReader(filePath);
outputDevice.Init(audioFile);
}
}
Loading…
Cancel
Save