diff --git a/.drone.yml b/.drone.yml index 7c67229..ac474fb 100644 --- a/.drone.yml +++ b/.drone.yml @@ -49,11 +49,10 @@ steps: path: /docs commands: - /entrypoint.sh - environment: - NODOXYGEN: true when: branch: - master + - dev-doxygen event: - push - pull_request diff --git a/Documentation/doxygen/Doxyfile b/Documentation/doxygen/Doxyfile index 319cdef..06b6b8e 100644 --- a/Documentation/doxygen/Doxyfile +++ b/Documentation/doxygen/Doxyfile @@ -5,7 +5,7 @@ DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "Linaris" PROJECT_NUMBER = 1.0.0 PROJECT_BRIEF = "Music player and manager" -PROJECT_LOGO = appicon.svg +PROJECT_LOGO = appicon.png OUTPUT_DIRECTORY = /docs/doxygen CREATE_SUBDIRS = NO ALLOW_UNICODE_NAMES = NO @@ -226,7 +226,7 @@ HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = HTML_EXTRA_STYLESHEET = -HTML_EXTRA_FILES = images/CodeFirst.png images/clubinfo.png +HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 215 HTML_COLORSTYLE_SAT = 45 HTML_COLORSTYLE_GAMMA = 240 diff --git a/Documentation/doxygen/appicon.png b/Documentation/doxygen/appicon.png new file mode 100644 index 0000000..6d0b4f4 Binary files /dev/null and b/Documentation/doxygen/appicon.png differ diff --git a/Images/logo.png b/Images/logo.png new file mode 100644 index 0000000..32d4d00 Binary files /dev/null and b/Images/logo.png differ diff --git a/README.md b/README.md index 0199dc7..ab49c91 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Linaris_MAUI_SAE_201 +# Linaris [![Build Status](https://codefirst.iut.uca.fr/api/badges/corentin.lemaire/Linaris_MAUI_SAE_201/status.svg)](https://codefirst.iut.uca.fr/corentin.lemaire/Linaris_MAUI_SAE_201) @@ -9,9 +9,15 @@ Linaris is a music and playlist management application, as well as a source of information about various popular albums and tracks. Our app is available on Windows and Android platforms through Visual Studio. +## Trailer + + + +[![Trailer](Images/logo.png)](https://opencast.dsi.uca.fr/paella/ui/watch.html?id=41e87f72-a922-44a3-ad2c-1432540bace0) + ## Documentation -You can find all the documentation [here](https://codefirst.iut.uca.fr/git/corentin.lemaire/Linaris_MAUI_SAE_201/src/branch/dev-doc#user-content-documentation-1) +You can find all the documentation [here](https://codefirst.iut.uca.fr/git/corentin.lemaire/Linaris_MAUI_SAE_201#documentation-1) ### Prerequisites diff --git a/Sources/Console/Console.csproj b/Sources/Console/Console.csproj index a816ead..b491ec4 100644 --- a/Sources/Console/Console.csproj +++ b/Sources/Console/Console.csproj @@ -13,10 +13,6 @@ - - - - diff --git a/Sources/Linaris/AlbumPage.xaml.cs b/Sources/Linaris/AlbumPage.xaml.cs index 66b931f..9b0952f 100644 --- a/Sources/Linaris/AlbumPage.xaml.cs +++ b/Sources/Linaris/AlbumPage.xaml.cs @@ -1,3 +1,4 @@ +using CommunityToolkit.Maui.Core.Primitives; using CommunityToolkit.Maui.Views; using Model; @@ -20,7 +21,7 @@ public partial class AlbumPage : ContentPage { InitializeComponent(); album = (Application.Current as App).Manager.CurrentAlbum; - BindingContext = album; + BindingContext = Album; } @@ -33,9 +34,16 @@ public partial class AlbumPage : ContentPage } } + protected override void OnAppearing() + { + base.OnAppearing(); + GetFooterData(); + } + protected override void OnDisappearing() { base.OnDisappearing(); + SetFooterData(); ContentView footer = this.FindByName("Footer"); var musicElement = footer?.FindByName("music"); if (musicElement != null) @@ -43,4 +51,50 @@ public partial class AlbumPage : ContentPage musicElement.Stop(); } } + + private void GetFooterData() + { + FooterPage FooterPage = (Application.Current as App).FooterPage; + Footer.CurrentPlaying = FooterPage.CurrentPlaying; + Footer.PlayImage = FooterPage.PlayImage; + Footer.LoopImage = FooterPage.LoopImage; + Footer.ShuffleImage = FooterPage.ShuffleImage; + Footer.Volume = FooterPage.Volume; + Footer.Position = FooterPage.Position; + Footer.Duration = FooterPage.Duration; + Footer.SliderPosition = FooterPage.SliderPosition; + + // Place l'index de lecture de la musique à la position du slider + var musicElement = Footer?.FindByName("music"); + musicElement.Dispatcher.StartTimer(TimeSpan.FromMilliseconds(100), () => + { + musicElement.SeekTo((Application.Current as App).MusicPosition); + if ((Application.Current as App).MediaState == MediaElementState.Playing) + { + musicElement.Play(); + } + else + { + musicElement.Pause(); + } + return false; + }); + } + + public void SetFooterData() + { + FooterPage FooterPage = (Application.Current as App).FooterPage; + FooterPage.CurrentPlaying = (Application.Current as App).Manager.CurrentPlaying; + FooterPage.PlayImage = Footer.PlayImage; + FooterPage.LoopImage = Footer.LoopImage; + FooterPage.ShuffleImage = Footer.ShuffleImage; + FooterPage.Volume = Footer.Volume; + FooterPage.Position = Footer.Position; + FooterPage.Duration = Footer.Duration; + FooterPage.SliderPosition = Footer.SliderPosition; + + var musicElement = Footer?.FindByName("music"); + (Application.Current as App).MusicPosition = musicElement.Position; + (Application.Current as App).MediaState = musicElement.CurrentState; + } } \ No newline at end of file diff --git a/Sources/Linaris/App.xaml b/Sources/Linaris/App.xaml index ae057eb..d56b28b 100644 --- a/Sources/Linaris/App.xaml +++ b/Sources/Linaris/App.xaml @@ -12,3 +12,4 @@ + \ No newline at end of file diff --git a/Sources/Linaris/App.xaml.cs b/Sources/Linaris/App.xaml.cs index e506791..008f26e 100644 --- a/Sources/Linaris/App.xaml.cs +++ b/Sources/Linaris/App.xaml.cs @@ -1,15 +1,27 @@ -using Model.Serialization; +using CommunityToolkit.Maui.Core.Primitives; +using Model.Serialization; using Model.Stub; +using System.Diagnostics; namespace Linaris; public partial class App : Application { - public Manager Manager = new(new LinqXmlSerialization(Path.Combine(FileSystem.Current.AppDataDirectory, "Data"))); + public Manager Manager { get; set; } + + public FooterPage FooterPage { get; set; } + + public TimeSpan MusicPosition { get; set; } + + public MediaElementState MediaState { get; set; } public App() { InitializeComponent(); + MusicPosition = TimeSpan.FromSeconds(0); + MediaState = MediaElementState.Paused; + Manager = new Manager(new LinqXmlSerialization(FileSystem.Current.AppDataDirectory)); + FooterPage = new FooterPage(); MainPage = new AppShell(); } diff --git a/Sources/Linaris/Converters.cs b/Sources/Linaris/Converters.cs index f55ecc2..2189d2c 100644 --- a/Sources/Linaris/Converters.cs +++ b/Sources/Linaris/Converters.cs @@ -1,22 +1,21 @@ using System; -namespace Linaris.Converters +namespace Linaris.Converters; + +public class InverseBooleanConverter : IValueConverter { - public class InverseBooleanConverter : IValueConverter + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + if (value is bool boolValue) { - if (value is bool boolValue) - { - return !boolValue; - } - - return value; + return !boolValue; } - public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) - { - throw new NotImplementedException(); - } + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return Convert(value, targetType, parameter, culture); } } \ No newline at end of file diff --git a/Sources/Linaris/EditPlaylistPage.xaml.cs b/Sources/Linaris/EditPlaylistPage.xaml.cs index 2b52ba4..2a456af 100644 --- a/Sources/Linaris/EditPlaylistPage.xaml.cs +++ b/Sources/Linaris/EditPlaylistPage.xaml.cs @@ -38,12 +38,9 @@ public partial class EditPlaylistPage : ContentPage return; } - if (sender is Image image) + if (sender is Image image && image.BindingContext is Playlist p) { - if (image.BindingContext is Playlist playlist) - { - playlist.ImageURL = result.FullPath; - } + p.ImageURL = result.FullPath; } } diff --git a/Sources/Linaris/FooterPage.xaml b/Sources/Linaris/FooterPage.xaml index 38bd297..997f179 100644 --- a/Sources/Linaris/FooterPage.xaml +++ b/Sources/Linaris/FooterPage.xaml @@ -1,20 +1,40 @@ - + - + - - - - - - + + + \ No newline at end of file diff --git a/Sources/Linaris/FooterPage.xaml.cs b/Sources/Linaris/FooterPage.xaml.cs index dd59eca..78d2d9d 100644 --- a/Sources/Linaris/FooterPage.xaml.cs +++ b/Sources/Linaris/FooterPage.xaml.cs @@ -1,29 +1,178 @@ +using CommunityToolkit.Maui.Core.Primitives; using Model; using Model.Stub; using System.ComponentModel; +using System.Configuration; using System.Runtime.CompilerServices; namespace Linaris; public partial class FooterPage : ContentView, INotifyPropertyChanged { + public new event PropertyChangedEventHandler PropertyChanged; - private readonly Manager Manager = (Application.Current as App).Manager; + private CustomTitle currentPlaying; + + public CustomTitle CurrentPlaying + { + get => currentPlaying; + set + { + currentPlaying = value; + OnPropertyChanged(); + } + } + + private string playImage = "play.png"; + + public string PlayImage + { + get => playImage; + set + { + playImage = value; + OnPropertyChanged(); + } + } + + private string loopImage = "loop.png"; + + public string LoopImage + { + get => loopImage; + set + { + loopImage = value; + OnPropertyChanged(); + } + } + + private string shuffleImage = "rdm.png"; + + public string ShuffleImage + { + get => shuffleImage; + set + { + shuffleImage = value; + OnPropertyChanged(); + } + } + + private double volume = 1; + + public double Volume + { + get => volume; + set + { + volume = value; + OnPropertyChanged(); + } + } + + private string position = "00:00:00"; + + public string Position + { + get => position; + set + { + position = value; + OnPropertyChanged(); + } + } + + private string duration = "00:00:00"; + + public string Duration + { + get => duration; + set + { + duration = value; + OnPropertyChanged(); + } + } + + private double sliderPosition = 0; + + public double SliderPosition + { + get => sliderPosition; + set + { + sliderPosition = value; + OnPropertyChanged(); + } + } + + private readonly Manager manager = (Application.Current as App).Manager; + + public Manager Manager + { + get => manager; + } public FooterPage() { InitializeComponent(); - BindingContext = Manager.CurrentPlaying; + + music.StateChanged += Music_StateChanged; + TimeSlider.ValueChanged += TimeSlider_ValueChanged; + VolumeSlider.ValueChanged += VolumeSlider_ValueChanged; + music.PositionChanged += Music_PositionChanged; + CurrentPlaying = Manager.CurrentPlaying; + BindingContext = this; } + private void Music_PositionChanged(object sender, EventArgs e) + { + SliderPosition = music.Position.TotalSeconds / music.Duration.TotalSeconds; + Position = music.Position.ToString(@"hh\:mm\:ss"); + Duration = music.Duration.ToString(@"hh\:mm\:ss"); + } + + private void VolumeSlider_ValueChanged(object sender, ValueChangedEventArgs e) + { + Volume = VolumeSlider.Value; + music.Volume = Volume; + } + + private void TimeSlider_ValueChanged(object sender, ValueChangedEventArgs e) + { + if ((TimeSlider.Value * music.Duration.TotalSeconds) - music.Position.TotalSeconds <= 1 && (TimeSlider.Value * music.Duration.TotalSeconds) - music.Position.TotalSeconds >= -1) + { + return; + } + TimeSpan PositionTimeSpan = TimeSpan.FromSeconds(TimeSlider.Value * music.Duration.TotalSeconds); + music.SeekTo(PositionTimeSpan); + Position = music.Position.ToString(@"hh\:mm\:ss"); + Duration = music.Duration.ToString(@"hh\:mm\:ss"); + } + + private void Music_StateChanged(object sender, MediaStateChangedEventArgs e) + { + if (music.CurrentState == MediaElementState.Paused || music.CurrentState == MediaElementState.Stopped) + { + PlayImage = "play.png"; + } + else + { + PlayImage = "pause.png"; + } + } public void RewindButton_Clicked(object sender, EventArgs e) { if (Manager.CurrentPlaylist == null || Manager.CurrentPlaying == null) return; Manager.PreviousTitle(); + CurrentPlaying = Manager.CurrentPlaying; Dispatcher.DispatchAsync(() => { music.Source = Manager.CurrentPlaying.Path; + music.SeekTo(TimeSpan.FromSeconds(0)); + Duration = music.Duration.ToString(@"hh\:mm\:ss"); }); } @@ -31,29 +180,56 @@ public partial class FooterPage : ContentView, INotifyPropertyChanged { if (Manager.CurrentPlaylist == null || Manager.CurrentPlaying == null) return; Manager.NextTitle(); + CurrentPlaying = Manager.CurrentPlaying; Dispatcher.DispatchAsync(() => { music.Source = Manager.CurrentPlaying.Path; + music.SeekTo(TimeSpan.FromSeconds(0)); + Duration = music.Duration.ToString(@"hh\:mm\:ss"); }); } public void ShuffleButton_Clicked(object sender, EventArgs e) { Manager.Shuffle(); + if (ShuffleImage == "rdm.png") + { + ShuffleImage = "rdm2.png"; + } else { + ShuffleImage = "rdm.png"; + } } public void LoopButton_Clicked(object sender, EventArgs e) { Manager.Loop(); + if (LoopImage == "loop.png") + { + LoopImage = "loop2.png"; + } + else + { + LoopImage = "loop.png"; + } } public void OnCompleted(object sender, EventArgs e) { - if (Manager.CurrentPlaying == null) return; - Manager.NextTitle(); - Dispatcher.DispatchAsync(() => + NextButton_Clicked(sender, e); + } + + public void PlayButton_Clicked(object sender, EventArgs e) + { + if (music.CurrentState == MediaElementState.Paused || music.CurrentState == MediaElementState.Stopped) { - music.Source = Manager.CurrentPlaying.Path; - }); + music.Play(); + } + else + { + music.Pause(); + } } + + protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } \ No newline at end of file diff --git a/Sources/Linaris/InfoTitlePage.xaml.cs b/Sources/Linaris/InfoTitlePage.xaml.cs index 1eb4768..679cb58 100644 --- a/Sources/Linaris/InfoTitlePage.xaml.cs +++ b/Sources/Linaris/InfoTitlePage.xaml.cs @@ -1,3 +1,4 @@ +using CommunityToolkit.Maui.Core.Primitives; using CommunityToolkit.Maui.Views; using Model; @@ -27,9 +28,16 @@ public partial class InfoTitlePage : ContentPage BindingContext = this; } + protected override void OnAppearing() + { + base.OnAppearing(); + GetFooterData(); + } + protected override void OnDisappearing() { base.OnDisappearing(); + SetFooterData(); ContentView footer = this.FindByName("Footer"); var musicElement = footer?.FindByName("music"); if (musicElement != null) @@ -37,4 +45,50 @@ public partial class InfoTitlePage : ContentPage musicElement.Stop(); } } + + private void GetFooterData() + { + FooterPage FooterPage = (Application.Current as App).FooterPage; + Footer.CurrentPlaying = FooterPage.CurrentPlaying; + Footer.PlayImage = FooterPage.PlayImage; + Footer.LoopImage = FooterPage.LoopImage; + Footer.ShuffleImage = FooterPage.ShuffleImage; + Footer.Volume = FooterPage.Volume; + Footer.Position = FooterPage.Position; + Footer.Duration = FooterPage.Duration; + Footer.SliderPosition = FooterPage.SliderPosition; + + // Place l'index de lecture de la musique à la position du slider + var musicElement = Footer?.FindByName("music"); + musicElement.Dispatcher.StartTimer(TimeSpan.FromMilliseconds(300), () => + { + musicElement.SeekTo((Application.Current as App).MusicPosition); + if ((Application.Current as App).MediaState == MediaElementState.Playing) + { + musicElement.Play(); + } + else + { + musicElement.Pause(); + } + return false; + }); + } + + public void SetFooterData() + { + FooterPage FooterPage = (Application.Current as App).FooterPage; + FooterPage.CurrentPlaying = (Application.Current as App).Manager.CurrentPlaying; + FooterPage.PlayImage = Footer.PlayImage; + FooterPage.LoopImage = Footer.LoopImage; + FooterPage.ShuffleImage = Footer.ShuffleImage; + FooterPage.Volume = Footer.Volume; + FooterPage.Position = Footer.Position; + FooterPage.Duration = Footer.Duration; + FooterPage.SliderPosition = Footer.SliderPosition; + + var musicElement = Footer?.FindByName("music"); + (Application.Current as App).MusicPosition = musicElement.Position; + (Application.Current as App).MediaState = musicElement.CurrentState; + } } \ No newline at end of file diff --git a/Sources/Linaris/Layout.xaml.cs b/Sources/Linaris/Layout.xaml.cs index c116bb0..8cac82a 100644 --- a/Sources/Linaris/Layout.xaml.cs +++ b/Sources/Linaris/Layout.xaml.cs @@ -11,50 +11,21 @@ public partial class Layout : ContentView private async void Go_Home(object sender, EventArgs e) { - StopMusic(); await Navigation.PopToRootAsync(); } private async void Go_Playlists(object sender, EventArgs e) { - StopMusic(); await Navigation.PushAsync(new PlaylistsPage()); } private async void Go_Back(object sender, EventArgs e) { - StopMusic(); await Navigation.PopAsync(); } private async void Go_Files(object sender, EventArgs e) { - StopMusic(); await Navigation.PushAsync(new LocalFilesPage()); } - private void StopMusic() - { - var parentPage = GetParentPage(this); - if (parentPage == null) return; - ContentView footer = parentPage.FindByName("Footer"); - var musicElement = footer?.FindByName("music"); - if (musicElement != null) - { - musicElement.Stop(); - } - } - - private ContentPage GetParentPage(Element element) - { - Element parent = element?.Parent; - while (parent != null) - { - if (parent is ContentPage contentPage) - { - return contentPage; - } - parent = parent.Parent; - } - return null; - } } \ No newline at end of file diff --git a/Sources/Linaris/Linaris.csproj b/Sources/Linaris/Linaris.csproj index f954d66..bdee2a0 100644 --- a/Sources/Linaris/Linaris.csproj +++ b/Sources/Linaris/Linaris.csproj @@ -1,5 +1,4 @@  - net7.0-android $(TargetFrameworks);net7.0-windows10.0.19041.0 @@ -30,6 +29,16 @@ 10.0.17763.0 10.0.17763.0 6.5 + True + False + D9EF0E98B2CABE92C87A46ACC4954059B92B6346 + SHA256 + True + False + True + D:\Bureau + 24 + fr @@ -52,10 +61,8 @@ - + - - @@ -81,6 +88,9 @@ MSBuild:Compile + + MSBuild:Compile + MSBuild:Compile @@ -96,6 +106,9 @@ MSBuild:Compile + + MSBuild:Compile + MSBuild:Compile @@ -123,4 +136,6 @@ + + diff --git a/Sources/Linaris/LocalFilesPage.xaml b/Sources/Linaris/LocalFilesPage.xaml index ca1ffb2..4f29c03 100644 --- a/Sources/Linaris/LocalFilesPage.xaml +++ b/Sources/Linaris/LocalFilesPage.xaml @@ -14,7 +14,7 @@ - + @@ -33,19 +33,19 @@ -