diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..834e311e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,7 @@ +# These are supported funding model platforms + +github: difegue +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: lanraragi +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 794332e2..8ff558de 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -51,9 +51,9 @@ jobs: # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on env: - Solution_Name: MpcNET - Uap_Project_Path: FluentMPC\FluentMPC.csproj - Uap_Project_Directory: FluentMPC + Solution_Name: Stylophone + Uap_Project_Path: Stylophone\Stylophone.csproj + Uap_Project_Directory: Stylophone steps: - name: Checkout @@ -76,9 +76,9 @@ jobs: # Execute all unit tests in the solution # TODO: This only runs the tests for MpcNET right now, since it's .NET Standard-based. Running the UWP tests would be nice too. - - name: Execute unit tests - working-directory: ./Sources - run: dotnet test + #- name: Execute unit tests + # working-directory: ./Sources + # run: dotnet test # Decode the base 64 encoded pfx and save the Signing_Certificate - name: Decode the pfx diff --git a/.gitignore b/.gitignore index 4fb28247..588c486d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,36 @@ -################################################################################ -# This .gitignore file was automatically created by Microsoft(R) Visual Studio. -################################################################################ - -/src/.vs - -/src/MpcNET/bin -/src/MpcNET/obj - -/src/MpcNET.Test/bin -/src/MpcNET.Test/obj - - -/src/MpcNET.sln.DotSettings.user -/src/MpcNET.Test/MpcNET.Test.csproj.DotSettings -/src/MpcNET/MpcNET.csproj.DotSettings -/.vs -*.user -/Sources/.vs/MpcNET/v15 -/Sources/MpcNET/bin/Debug/netstandard2.0 +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +/Sources/.vs +/Sources/MpcNET.sln.DotSettings.user + +/Sources/MpcNET/bin /Sources/MpcNET/obj + +/Sources/MpcNET/MpcNET.csproj.DotSettings + +/Sources/MpcNET.Test/bin /Sources/MpcNET.Test/obj -/Sources/MpcNET/bin -/Sources/MpcNET.Test/bin/Debug/netcoreapp2.0 -/Sources/.vs -/Sources/FluentMPC.Core/bin/Debug/netstandard2.0 -/Sources/FluentMPC/bin/x86/Debug -/Sources/FluentMPC/obj -/Sources/FluentMPC.Core/obj -/Sources/FluentMPC.Tests.MSTest/obj +/Sources/MpcNET.Test/MpcNET.Test.csproj.DotSettings + +/Sources/Stylophone/bin +/Sources/Stylophone/obj +/Sources/Stylophone/Package.StoreAssociation.xml +/Sources/Stylophone/AppPackages +/Sources/Stylophone/BundleArtifacts + +/Sources/Stylophone.Common/bin +/Sources/Stylophone.Common/obj + +/Sources/Stylophone.Localization/bin +/Sources/Stylophone.Localization/obj + + +/.vs +*.user *.pfx -/Sources/FluentMPC/bin/x64/Debug -/Sources/FluentMPC/Package.StoreAssociation.xml -/Sources/FluentMPC/AppPackages -/Sources/FluentMPC/bin -/Sources/FluentMPC/BundleArtifacts +Sources/.DS_Store +Sources/Stylophone.iOS/bin +Sources/Stylophone.iOS/obj +.DS_Store diff --git a/README.md b/README.md index 5ac9c1d8..2bfc1ae9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + Stylophone =========== @@ -8,6 +8,8 @@ Based on [MpcNET](https://github.com/petrkr/MpcNET), the original .NET Client Li English badge +[Buy a sticker if you want!](https://ko-fi.com/s/9fcf421b6e) + ## Features * Full playback control @@ -37,16 +39,15 @@ You can easily contribute translations to Stylophone! To help translate, follow - If an issue already exists, then don't do this step. - Fork and clone this repo - Open in VS 2019 -- In the `FluentMPC` project, find the `Strings` folder. -- Create a new folder inside `Strings` that looks like this: `en-US` but using the language you're translating into. -- Add a new `Resources.resw` item in that new folder -- Copy all the existing data from `Strings > en-US` into your new `Resources.resw` +- In the `Stylophone.Localization` project, find the `Strings` folder. +- Create a new file inside `Strings` that looks like this: `Resources.en-US.resx` but using the language you're translating into. +- Copy all the existing data from `Resources.en-US.resx` into your new `Resources.[language].resx` - Translate the strings from english to your language - Once done, then commit > push > create pull request! ### Improving an existing language (can be done with any text editor) - Fork and clone this repo -- Open the the `.resw` file (e.g. `en-US > Resources.resw`) you want to edit. Choose any text editor +- Open the `.resx` file (e.g. `Resources.en-US.resx`) you want to edit. Choose any text editor - Translate - Commit > push > create pull request! @@ -61,5 +62,8 @@ You can easily contribute translations to Stylophone! To help translate, follow ## Privacy Policy -Stylophone collects no data from your computer. +Stylophone collects no data from your computer by default. The Windows Store version can send anonymized error reports related to crashes of the application back to me. + +If you enable Telemetry in the app's settings, the application will send detailed crash reports using App Center. +Those reports can contain information about your hardware. (Motherboard type, etc) diff --git a/Screenshots/Screen1.jpg b/Screenshots/Screen1.jpg index 92966365..99d6c21f 100644 Binary files a/Screenshots/Screen1.jpg and b/Screenshots/Screen1.jpg differ diff --git a/Screenshots/Screen2.jpg b/Screenshots/Screen2.jpg index 1317537f..536e063c 100644 Binary files a/Screenshots/Screen2.jpg and b/Screenshots/Screen2.jpg differ diff --git a/Screenshots/Screen3.jpg b/Screenshots/Screen3.jpg index 50049855..3a1e5bad 100644 Binary files a/Screenshots/Screen3.jpg and b/Screenshots/Screen3.jpg differ diff --git a/Screenshots/Screen4.jpg b/Screenshots/Screen4.jpg index ba2216df..e62a5d67 100644 Binary files a/Screenshots/Screen4.jpg and b/Screenshots/Screen4.jpg differ diff --git a/Screenshots/Screen5.jpg b/Screenshots/Screen5.jpg index 771e85a1..07ebf063 100644 Binary files a/Screenshots/Screen5.jpg and b/Screenshots/Screen5.jpg differ diff --git a/Screenshots/Screen6.jpg b/Screenshots/Screen6.jpg index c32db99d..5641063a 100644 Binary files a/Screenshots/Screen6.jpg and b/Screenshots/Screen6.jpg differ diff --git a/Screenshots/ScreenXbox.jpg b/Screenshots/ScreenXbox.jpg index a0b29793..0360c8cb 100644 Binary files a/Screenshots/ScreenXbox.jpg and b/Screenshots/ScreenXbox.jpg differ diff --git a/Sources/.vs/MpcNET/DesignTimeBuild/.dtbcache b/Sources/.vs/MpcNET/DesignTimeBuild/.dtbcache deleted file mode 100644 index 17d54e23..00000000 Binary files a/Sources/.vs/MpcNET/DesignTimeBuild/.dtbcache and /dev/null differ diff --git a/Sources/FluentMPC.Tests.MSTest/Assets/LockScreenLogo.scale-200.png b/Sources/FluentMPC.Tests.MSTest/Assets/LockScreenLogo.scale-200.png deleted file mode 100644 index 735f57ad..00000000 Binary files a/Sources/FluentMPC.Tests.MSTest/Assets/LockScreenLogo.scale-200.png and /dev/null differ diff --git a/Sources/FluentMPC.Tests.MSTest/Assets/SplashScreen.scale-200.png b/Sources/FluentMPC.Tests.MSTest/Assets/SplashScreen.scale-200.png deleted file mode 100644 index 023e7f1f..00000000 Binary files a/Sources/FluentMPC.Tests.MSTest/Assets/SplashScreen.scale-200.png and /dev/null differ diff --git a/Sources/FluentMPC.Tests.MSTest/Assets/Square150x150Logo.scale-200.png b/Sources/FluentMPC.Tests.MSTest/Assets/Square150x150Logo.scale-200.png deleted file mode 100644 index af49fec1..00000000 Binary files a/Sources/FluentMPC.Tests.MSTest/Assets/Square150x150Logo.scale-200.png and /dev/null differ diff --git a/Sources/FluentMPC.Tests.MSTest/Assets/Square44x44Logo.scale-200.png b/Sources/FluentMPC.Tests.MSTest/Assets/Square44x44Logo.scale-200.png deleted file mode 100644 index ce342a2e..00000000 Binary files a/Sources/FluentMPC.Tests.MSTest/Assets/Square44x44Logo.scale-200.png and /dev/null differ diff --git a/Sources/FluentMPC.Tests.MSTest/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Sources/FluentMPC.Tests.MSTest/Assets/Square44x44Logo.targetsize-24_altform-unplated.png deleted file mode 100644 index f6c02ce9..00000000 Binary files a/Sources/FluentMPC.Tests.MSTest/Assets/Square44x44Logo.targetsize-24_altform-unplated.png and /dev/null differ diff --git a/Sources/FluentMPC.Tests.MSTest/Assets/StoreLogo.png b/Sources/FluentMPC.Tests.MSTest/Assets/StoreLogo.png deleted file mode 100644 index 7385b56c..00000000 Binary files a/Sources/FluentMPC.Tests.MSTest/Assets/StoreLogo.png and /dev/null differ diff --git a/Sources/FluentMPC.Tests.MSTest/Assets/Wide310x150Logo.scale-200.png b/Sources/FluentMPC.Tests.MSTest/Assets/Wide310x150Logo.scale-200.png deleted file mode 100644 index 288995b3..00000000 Binary files a/Sources/FluentMPC.Tests.MSTest/Assets/Wide310x150Logo.scale-200.png and /dev/null differ diff --git a/Sources/FluentMPC.Tests.MSTest/FluentMPC.Tests.MSTest.csproj b/Sources/FluentMPC.Tests.MSTest/FluentMPC.Tests.MSTest.csproj deleted file mode 100644 index c03f4734..00000000 --- a/Sources/FluentMPC.Tests.MSTest/FluentMPC.Tests.MSTest.csproj +++ /dev/null @@ -1,162 +0,0 @@ - - - - - Debug - x86 - {0C4B7830-1CAE-4EDC-BF92-526A50424157} - AppContainerExe - Properties - FluentMPC.Tests.MSTest - FluentMPC.Tests.MSTest - en-US - UAP - 10.0.19041.0 - 10.0.18362.0 - 14 - 512 - {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - FluentMPC.Tests.MSTest_TemporaryKey.pfx - $(VisualStudioVersion) - - - true - bin\x86\Debug\ - DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP - ;2008 - full - x86 - false - prompt - true - - - bin\x86\Release\ - TRACE;NETFX_CORE;WINDOWS_UWP - true - ;2008 - pdbonly - x86 - false - prompt - true - true - - - true - bin\ARM\Debug\ - DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP - ;2008 - full - ARM - false - prompt - true - - - bin\ARM\Release\ - TRACE;NETFX_CORE;WINDOWS_UWP - true - ;2008 - pdbonly - ARM - false - prompt - true - true - - - true - bin\x64\Debug\ - DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP - ;2008 - full - x64 - false - prompt - true - - - bin\x64\Release\ - TRACE;NETFX_CORE;WINDOWS_UWP - true - ;2008 - pdbonly - x64 - false - prompt - true - true - - - PackageReference - - - - 6.2.9 - - - 2.4.2 - - - 2.0.1 - - - 2.1.2 - - - 2.1.2 - - - - - - - - - UnitTestApp.xaml - - - - - - MSBuild:Compile - Designer - - - - - Designer - - - - - - - - - - - - - - - - - - {5A5CE693-A67D-4B28-8DCE-4B0EA3A47533} - FluentMPC - - - - 14.0 - - - - \ No newline at end of file diff --git a/Sources/FluentMPC.Tests.MSTest/Package.appxmanifest b/Sources/FluentMPC.Tests.MSTest/Package.appxmanifest deleted file mode 100644 index 5ee97965..00000000 --- a/Sources/FluentMPC.Tests.MSTest/Package.appxmanifest +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - FluentMPC.Tests.MSTest - Vincent - Assets\StoreLogo.png - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Sources/FluentMPC.Tests.MSTest/Properties/AssemblyInfo.cs b/Sources/FluentMPC.Tests.MSTest/Properties/AssemblyInfo.cs deleted file mode 100644 index 5ccae4ca..00000000 --- a/Sources/FluentMPC.Tests.MSTest/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("FluentMPC.Tests.MSTest")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FluentMPC.Tests.MSTest")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: AssemblyMetadata("TargetPlatform", "UAP")] - -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: ComVisible(false)] diff --git a/Sources/FluentMPC.Tests.MSTest/Properties/UnitTestApp.rd.xml b/Sources/FluentMPC.Tests.MSTest/Properties/UnitTestApp.rd.xml deleted file mode 100644 index 8f1b21b4..00000000 --- a/Sources/FluentMPC.Tests.MSTest/Properties/UnitTestApp.rd.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - diff --git a/Sources/FluentMPC.Tests.MSTest/Tests.cs b/Sources/FluentMPC.Tests.MSTest/Tests.cs deleted file mode 100644 index f979f309..00000000 --- a/Sources/FluentMPC.Tests.MSTest/Tests.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; - -using FluentMPC.ViewModels; -using FluentMPC.ViewModels.Playback; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace FluentMPC.Tests.MSTest -{ - // TODO WTS: Add appropriate tests - [TestClass] - public class Tests - { - [TestMethod] - public void TestMethod1() - { - } - - // TODO WTS: Add tests for functionality you add to FoldersViewModel. - [TestMethod] - public void TestFoldersViewModelCreation() - { - // This test is trivial. Add your own tests for the logic you add to the ViewModel. - var vm = new FoldersViewModel(); - Assert.IsNotNull(vm); - } - - // TODO WTS: Add tests for functionality you add to LibraryViewModel. - [TestMethod] - public void TestLibraryViewModelCreation() - { - // This test is trivial. Add your own tests for the logic you add to the ViewModel. - var vm = new LibraryViewModel(); - Assert.IsNotNull(vm); - } - - // TODO WTS: Add tests for functionality you add to NowPlayingViewModel. - [TestMethod] - public void TestNowPlayingViewModelCreation() - { - // This test is trivial. Add your own tests for the logic you add to the ViewModel. - var vm = new PlaybackViewModel(); - Assert.IsNotNull(vm); - } - - // TODO WTS: Add tests for functionality you add to PlaylistsViewModel. - [TestMethod] - public void TestPlaylistsViewModelCreation() - { - // This test is trivial. Add your own tests for the logic you add to the ViewModel. - var vm = new PlaylistViewModel(); - Assert.IsNotNull(vm); - } - - // TODO WTS: Add tests for functionality you add to SettingsViewModel. - [TestMethod] - public void TestSettingsViewModelCreation() - { - // This test is trivial. Add your own tests for the logic you add to the ViewModel. - var vm = new SettingsViewModel(); - Assert.IsNotNull(vm); - } - } -} diff --git a/Sources/FluentMPC.Tests.MSTest/UnitTestApp.xaml b/Sources/FluentMPC.Tests.MSTest/UnitTestApp.xaml deleted file mode 100644 index b95cf77d..00000000 --- a/Sources/FluentMPC.Tests.MSTest/UnitTestApp.xaml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Styles/TextBlock.xaml b/Sources/FluentMPC/Styles/TextBlock.xaml deleted file mode 100644 index 54ffd6eb..00000000 --- a/Sources/FluentMPC/Styles/TextBlock.xaml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Styles/_Colors.xaml b/Sources/FluentMPC/Styles/_Colors.xaml deleted file mode 100644 index 40d61b94..00000000 --- a/Sources/FluentMPC/Styles/_Colors.xaml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Styles/_FontSizes.xaml b/Sources/FluentMPC/Styles/_FontSizes.xaml deleted file mode 100644 index a19f4a3c..00000000 --- a/Sources/FluentMPC/Styles/_FontSizes.xaml +++ /dev/null @@ -1,15 +0,0 @@ - - - 24 - 16 - - - - - diff --git a/Sources/FluentMPC/ViewModels/FoldersViewModel.cs b/Sources/FluentMPC/ViewModels/FoldersViewModel.cs deleted file mode 100644 index 47ef960d..00000000 --- a/Sources/FluentMPC/ViewModels/FoldersViewModel.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Input; - -using FluentMPC.Helpers; -using FluentMPC.Services; -using FluentMPC.ViewModels.Items; -using MpcNET.Commands.Database; -using MpcNET.Types; -using WinUI = Microsoft.UI.Xaml.Controls; - -namespace FluentMPC.ViewModels -{ - public class FoldersViewModel : Observable - { - public ObservableCollection SourceData { get; } = new ObservableCollection(); - public bool IsSourceEmpty => SourceData.Count == 0; - - public FoldersViewModel() - { - SourceData.CollectionChanged += (s, e) => OnPropertyChanged(nameof(IsSourceEmpty)); - } - - public async Task LoadDataAsync() - { - SourceData.Clear(); - - var response = await MPDConnectionService.SafelySendCommandAsync(new LsInfoCommand("/")); - - if (response != null) - foreach (var item in response) - { - SourceData.Add(new FilePathViewModel(item)); - } - - OnPropertyChanged(nameof(SourceData)); - } - - } -} diff --git a/Sources/FluentMPC/ViewModels/Items/AlbumViewModel.cs b/Sources/FluentMPC/ViewModels/Items/AlbumViewModel.cs deleted file mode 100644 index 85674a7f..00000000 --- a/Sources/FluentMPC/ViewModels/Items/AlbumViewModel.cs +++ /dev/null @@ -1,234 +0,0 @@ -using FluentMPC.Helpers; -using FluentMPC.Services; -using Microsoft.Toolkit.Uwp.Helpers; -using MpcNET; -using MpcNET.Commands.Database; -using MpcNET.Commands.Playback; -using MpcNET.Commands.Playlist; -using MpcNET.Commands.Queue; -using MpcNET.Commands.Reflection; -using MpcNET.Tags; -using MpcNET.Types; -using Sundew.Base.Collections; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Input; -using Windows.System.Threading; -using Windows.UI; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media.Imaging; - -namespace FluentMPC.ViewModels.Items -{ - public class AlbumViewModel : Observable - { - public string Name - { - get => _name; - set => Set(ref _name, value); - } - private string _name; - - public string Artist - { - get => _artist; - private set - { - DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _artist, value)); - } - } - private string _artist; - - public IList Files - { - get => _files; - set => Set(ref _files, value); - } - private IList _files; - - private bool _detailLoading; - public bool IsDetailLoading - { - get => _detailLoading; - internal set - { - DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _detailLoading, value)); - } - } - - private bool _artLoaded; - public bool AlbumArtLoaded - { - get => _artLoaded; - private set - { - DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _artLoaded, value)); - } - } - - public BitmapImage AlbumArt - { - get => _albumArt; - private set - { - DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _albumArt, value)); - } - } - - internal void SetAlbumArt(AlbumArt art) - { - if (art != null) - { - AlbumArt = art.ArtBitmap; - IsLight = !art.DominantColor.IsDark; - DominantColor = art.DominantColor.ToWindowsColor(); - } - - AlbumArtLoaded = true; - } - - private BitmapImage _albumArt; - - public Color DominantColor - { - get => _albumColor; - private set - { - DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _albumColor, value)); - } - } - - private Color _albumColor; - - private bool _isLight; - /// - /// If the dominant color of the album is too light to show white text on top of, this boolean will be true. - /// - public bool IsLight - { - get => _isLight; - private set - { - DispatcherService.ExecuteOnUIThreadAsync(() => Set(ref _isLight, value)); - } - } - - private ICommand _addToPlaylistCommand; - public ICommand AddToPlaylistCommand => _addToPlaylistCommand ?? (_addToPlaylistCommand = new RelayCommand(AddToPlaylist)); - private async void AddToPlaylist() - { - var playlistName = await DialogService.ShowAddToPlaylistDialog(); - if (playlistName == null || Files.Count == 0) return; - - var commandList = new CommandList(); - - foreach (var f in Files) - { - commandList.Add(new PlaylistAddCommand(playlistName, f.Path)); - } - - if (await MPDConnectionService.SafelySendCommandAsync(commandList) != null) - { - NotificationService.ShowInAppNotification(string.Format("AddedToPlaylistText".GetLocalized(), playlistName)); - } - } - - private ICommand _addToQueueCommand; - public ICommand AddAlbumCommand => _addToQueueCommand ?? (_addToQueueCommand = new RelayCommand(AddToQueue)); - private async void AddToQueue() - { - var commandList = new CommandList(); - - if (Files.Count == 0) - { - NotificationService.ShowInAppNotification(string.Format("ErrorAddingAlbum".GetLocalized(), "NoTracksLoaded".GetLocalized()), 0); - return; - } - - foreach (var f in Files) - { - commandList.Add(new AddCommand(f.Path)); - } - - if (await MPDConnectionService.SafelySendCommandAsync(commandList) != null) - NotificationService.ShowInAppNotification("AddedToQueueText".GetLocalized()); - } - - private ICommand _playCommand; - public ICommand PlayAlbumCommand => _playCommand ?? (_playCommand = new RelayCommand(PlayAlbum)); - private async void PlayAlbum() - { - if (Files.Count == 0) - { - NotificationService.ShowInAppNotification(string.Format("ErrorPlayingText".GetLocalized(), "NoTracksLoaded".GetLocalized()), 0); - return; - } - - var commandList = new CommandList(); - - // Clear queue, add album and play - commandList.Add(new ClearCommand()); - - foreach (var f in Files) - { - commandList.Add(new AddCommand(f.Path)); - } - - commandList.Add(new PlayCommand(0)); - - if (await MPDConnectionService.SafelySendCommandAsync(commandList) != null) - { - // Auto-navigate to the queue - NavigationService.Navigate(typeof(Views.ServerQueuePage)); - NotificationService.ShowInAppNotification(string.Format("NowPlayingText".GetLocalized(), Name)); - } - } - - public AlbumViewModel(string albumName) - { - Name = albumName; - DominantColor = (Color)Application.Current.Resources["SystemAccentColor"]; - Files = new List(); - IsDetailLoading = false; - - AlbumArt = new BitmapImage(new Uri("ms-appx:///Assets/AlbumPlaceholder.png")); - } - - public async Task LoadAlbumDataAsync(MpcConnection c) - { - IsDetailLoading = true; - try - { - var findReq = await c.SendAsync(new FindCommand(MpdTags.Album, Name)); - if (!findReq.IsResponseValid) - return; - - // If files were already added, don't re-add them. - // This can occasionally happen if the server is a bit overloaded when we look at an album, since AlbumDetailViewModel can call this method a second time. - if (Files.Count == 0) - Files.AddRange(findReq.Response.Content); - - Artist = Files.Select(f => f.Artist).Distinct().Aggregate((f1, f2) => $"{f1}, {f2}"); - - // If we've already generated album art, don't use the queue and directly grab it - if (await AlbumArtService.IsAlbumArtCachedAsync(Files[0])) - { - var art = await AlbumArtService.GetAlbumArtAsync(Files[0], true, 180); - SetAlbumArt(art); - } - else - { - // Queue this VM in the AlbumArtService so its album art is eventually recovered from the server - AlbumArtService.QueueAlbumArt(this); - } - } - finally - { - IsDetailLoading = false; - } - } - } -} diff --git a/Sources/FluentMPC/ViewModels/Items/FilePathViewModel.cs b/Sources/FluentMPC/ViewModels/Items/FilePathViewModel.cs deleted file mode 100644 index 9eb58de5..00000000 --- a/Sources/FluentMPC/ViewModels/Items/FilePathViewModel.cs +++ /dev/null @@ -1,144 +0,0 @@ -using FluentMPC.Helpers; -using FluentMPC.Services; -using MpcNET.Commands.Database; -using MpcNET.Commands.Playback; -using MpcNET.Commands.Playlist; -using MpcNET.Types; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows.Input; -using System.Collections.ObjectModel; -using Sundew.Base.Collections; -using System.Linq; -using MpcNET.Commands.Queue; -using MpcNET.Commands.Reflection; -using MpcNET; - -namespace FluentMPC.ViewModels.Items -{ - public class FilePathViewModel : Observable - { - private string _path; - - public string Path - { - get => _path; - private set => Set(ref _path, value); - } - - private string _name; - - public string Name - { - get => _name; - private set => Set(ref _name, value); - } - - public bool IsDirectory { get; set; } - public bool IsLoaded { get; set; } - - private ObservableCollection _childPaths; - public ObservableCollection Children - { - get => _childPaths; - set => Set(ref _childPaths, value); - } - - private bool _isLoadingChildren; - public async Task LoadChildrenAsync() - { - if (IsLoaded || _isLoadingChildren || IsDirectory == false || _childPaths == null || Path == null) return; - - _isLoadingChildren = true; - try - { - var newChildren = new List(); - - var response = await MPDConnectionService.SafelySendCommandAsync(new LsInfoCommand(Path)); - - if (response != null) - foreach (var item in response) - { - newChildren.Add(new FilePathViewModel(item)); - } - else - newChildren.Add(new FilePathViewModel("💥 Failed")); - - await DispatcherService.ExecuteOnUIThreadAsync(() => - { - _childPaths.AddRange(newChildren); - _childPaths.RemoveAt(0); // Remove the placeholder after adding the new items, otherwise the treeitem can close back up - IsLoaded = true; - }); - } - finally - { - _isLoadingChildren = false; - } - } - - private ICommand _playCommand; - public ICommand PlayCommand => _playCommand ?? (_playCommand = new RelayCommand(PlayPath)); - - private async void PlayPath() - { - // Clear queue, add path and play - var commandList = new CommandList(new IMpcCommand[] { new ClearCommand(), new AddCommand(Path), new PlayCommand(0) }); - - if (await MPDConnectionService.SafelySendCommandAsync(commandList) != null) - { - NotificationService.ShowInAppNotification(string.Format("NowPlayingText".GetLocalized(), Path)); - } - } - - private ICommand _addToQueueCommand; - public ICommand AddToQueueCommand => _addToQueueCommand ?? (_addToQueueCommand = new RelayCommand(AddToQueue)); - - private async void AddToQueue() - { - // AddCommand adds either the full directory or the song, depending on the path given. - var response = await MPDConnectionService.SafelySendCommandAsync(new AddCommand(Path)); - - if (response != null) - NotificationService.ShowInAppNotification("AddedToQueueText".GetLocalized()); - } - - private ICommand _addToPlaylistCommand; - public ICommand AddToPlaylistCommand => _addToPlaylistCommand ?? (_addToPlaylistCommand = new RelayCommand(AddToPlaylist)); - private async void AddToPlaylist() - { - var playlistName = await DialogService.ShowAddToPlaylistDialog(); - if (playlistName == null) return; - - var response = await MPDConnectionService.SafelySendCommandAsync(new PlaylistAddCommand(playlistName, Path)); - - if (response != null) - NotificationService.ShowInAppNotification(string.Format("AddedToPlaylistText".GetLocalized(), playlistName)); - } - - public FilePathViewModel(IMpdFilePath file) - { - Path = file.Path; - Name = file.Name ?? Path.Split('/').Last(); - - // If it's a directory, add children - if (file is MpdDirectory) - { - IsDirectory = true; - _childPaths = new ObservableCollection(); - - // Add a bogus child that'll be replaced when the list is loaded - _childPaths.Add(new FilePathViewModel("LoadingTreeItem".GetLocalized())); - } - - } - - public FilePathViewModel(string name) - { - Name = name; - _childPaths = new ObservableCollection(); - } - - } -} diff --git a/Sources/FluentMPC/ViewModels/Items/TrackViewModel.cs b/Sources/FluentMPC/ViewModels/Items/TrackViewModel.cs deleted file mode 100644 index 6b71c2b8..00000000 --- a/Sources/FluentMPC/ViewModels/Items/TrackViewModel.cs +++ /dev/null @@ -1,173 +0,0 @@ -using ColorThiefDotNet; -using FluentMPC.Helpers; -using FluentMPC.Services; -using Microsoft.Toolkit.Uwp.Helpers; -using MpcNET.Commands.Playback; -using MpcNET.Commands.Playlist; -using MpcNET.Types; -using System; -using System.Threading.Tasks; -using System.Windows.Input; -using Color = Windows.UI.Color; -using Windows.UI.Xaml.Media.Imaging; -using System.Linq; -using Windows.UI.Core; -using FluentMPC.Views; -using Windows.UI; -using MpcNET.Commands.Queue; -using System.Threading; -using FluentMPC.ViewModels.Playback; -using Windows.System; -using Microsoft.Toolkit.Uwp; - -namespace FluentMPC.ViewModels.Items -{ - public class TrackViewModel : Observable - { - private readonly DispatcherQueue _currentDispatcherQueue; - - public IMpdFile File { get; } - - public string Name => File.HasTitle ? File.Title : File.Path.Split('/').Last(); - - public bool IsPlaying => MPDConnectionService.CurrentStatus.SongId == File.Id; - - internal void UpdatePlayingStatus() => DispatcherService.ExecuteOnUIThreadAsync(() => OnPropertyChanged(nameof(IsPlaying))); - - public BitmapImage AlbumArt - { - get => _albumArt; - private set - { - _currentDispatcherQueue.EnqueueAsync(() => Set(ref _albumArt, value)); - } - } - - private BitmapImage _albumArt; - - public Color DominantColor - { - get => _albumColor; - private set - { - _currentDispatcherQueue.EnqueueAsync(() => Set(ref _albumColor, value)); - } - } - - private Color _albumColor; - - private bool _isLight; - public bool IsLight - { - get => _isLight; - private set - { - _currentDispatcherQueue.EnqueueAsync(() => Set(ref _isLight, value)); - } - } - - - private ICommand _playCommand; - public ICommand PlayTrackCommand => _playCommand ?? (_playCommand = new RelayCommand(PlayTrack)); - - private async void PlayTrack(IMpdFile file) => await MPDConnectionService.SafelySendCommandAsync(new PlayIdCommand(file.Id)); - - private ICommand _removeCommand; - public ICommand RemoveFromQueueCommand => _removeCommand ?? (_removeCommand = new RelayCommand(RemoveTrack)); - - private async void RemoveTrack(IMpdFile file) => await MPDConnectionService.SafelySendCommandAsync(new DeleteIdCommand(file.Id)); - - private ICommand _addToQueueCommand; - public ICommand AddToQueueCommand => _addToQueueCommand ?? (_addToQueueCommand = new RelayCommand(AddToQueue)); - - private async void AddToQueue(IMpdFile file) - { - var response = await MPDConnectionService.SafelySendCommandAsync(new AddIdCommand(file.Path)); - - if (response != null) - NotificationService.ShowInAppNotification("AddedToQueueText".GetLocalized()); - } - - private ICommand _addToPlaylistCommand; - public ICommand AddToPlayListCommand => _addToPlaylistCommand ?? (_addToPlaylistCommand = new RelayCommand(AddToPlaylist)); - - private async void AddToPlaylist(IMpdFile file) - { - var playlistName = await DialogService.ShowAddToPlaylistDialog(); - if (playlistName == null) return; - - var req = await MPDConnectionService.SafelySendCommandAsync(new PlaylistAddCommand(playlistName, file.Path)); - - if (req != null) - NotificationService.ShowInAppNotification(string.Format("AddedToPlaylistText".GetLocalized(), playlistName)); - } - - private ICommand _viewAlbumCommand; - public ICommand ViewAlbumCommand => _viewAlbumCommand ?? (_viewAlbumCommand = new RelayCommand(GoToMatchingAlbum)); - - private void GoToMatchingAlbum(IMpdFile file) - { - try - { - if (!file.HasAlbum) - { - NotificationService.ShowInAppNotification("NoAlbumErrorText".GetLocalized(), 0); - return; - } - - // Build an AlbumViewModel from the album name and navigate to it - var album = new AlbumViewModel(file.Album); - NavigationService.Navigate(album); - } - catch (Exception e) - { - NotificationService.ShowInAppNotification(string.Format("GenericErrorText".GetLocalized(), e), 0); - } - } - - public TrackViewModel(IMpdFile file, bool getAlbumArt = false, VisualizationType hostType = VisualizationType.None, DispatcherQueue dispatcherQueue = null, CancellationToken albumArtCancellationToken = default) - { - MPDConnectionService.SongChanged += (s, e) => UpdatePlayingStatus(); - - // Use specific UI dispatcher if given - // (Used for the compact view scenario, which rolls its own dispatcher..) - _currentDispatcherQueue = dispatcherQueue ?? DispatcherService.DispatcherQueue; - - File = file; - DominantColor = Colors.Black; - - // Fire off an async request to get the album art from MPD. - if (getAlbumArt) - { - - Task.Run(async () => - { - await _currentDispatcherQueue.EnqueueAsync(() => - { - var placeholder = new BitmapImage(new Uri("ms-appx:///Assets/AlbumPlaceholder.png")); - placeholder.DecodePixelWidth = (int)hostType; - AlbumArt = placeholder; - }); - - // This is RAM-intensive as it has to convert the image, so we only do it if needed (aka now playing bar and full playback only) - var calculateDominantColor = hostType.IsOneOf(VisualizationType.NowPlayingBar, VisualizationType.FullScreenPlayback); - - // Use the int value of the VisualizationType to know how large the decoded bitmap has to be. - var art = await AlbumArtService.GetAlbumArtAsync(File, calculateDominantColor, (int)hostType, _currentDispatcherQueue, albumArtCancellationToken); - - if (art != null) - { - if (calculateDominantColor) - { - DominantColor = art.DominantColor.ToWindowsColor(); - IsLight = !(art.DominantColor.IsDark); - } - - AlbumArt = art.ArtBitmap; - } - }); - } - } - - } -} diff --git a/Sources/FluentMPC/ViewModels/QueueViewModel.cs b/Sources/FluentMPC/ViewModels/QueueViewModel.cs deleted file mode 100644 index abc9407a..00000000 --- a/Sources/FluentMPC/ViewModels/QueueViewModel.cs +++ /dev/null @@ -1,230 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using System.Windows.Input; -using FluentMPC.Helpers; -using FluentMPC.Services; -using FluentMPC.ViewModels.Items; -using Microsoft.Toolkit.Uwp.Helpers; -using MpcNET; -using MpcNET.Commands.Playback; -using MpcNET.Commands.Playlist; -using MpcNET.Commands.Queue; -using MpcNET.Commands.Reflection; -using MpcNET.Commands.Status; -using MpcNET.Types; -using Sundew.Base.Collections; - -namespace FluentMPC.ViewModels -{ - public class QueueViewModel : Observable - { - private NotifyCollectionChangedAction _previousAction; - private int _oldId; - private int _playlistVersion; - - private bool IsSingleTrackSelected(object list) - { - // Cast the received __ComObject - var selectedTracks = (IList)list; - - return (selectedTracks?.Count == 1); - } - - private ICommand _playCommand; - public ICommand PlayTrackCommand => _playCommand ?? (_playCommand = new RelayCommand>(PlayTrack, IsSingleTrackSelected)); - - private async void PlayTrack(object list) - { - // Cast the received __ComObject - var selectedTracks = (IList)list; - - if (selectedTracks?.Count > 0) - { - var trackVM = selectedTracks.First() as TrackViewModel; - await MPDConnectionService.SafelySendCommandAsync(new PlayIdCommand(trackVM.File.Id)); - } - } - - private ICommand _viewAlbumCommand; - public ICommand ViewAlbumCommand => _viewAlbumCommand ?? (_viewAlbumCommand = new RelayCommand>(ViewAlbum, IsSingleTrackSelected)); - - private void ViewAlbum(object list) - { - // Cast the received __ComObject - var selectedTracks = (IList)list; - - if (selectedTracks?.Count > 0) - { - var trackVM = selectedTracks.First() as TrackViewModel; - trackVM.ViewAlbumCommand.Execute(trackVM.File); - } - } - - private ICommand _removeCommand; - public ICommand RemoveFromQueueCommand => _removeCommand ?? (_removeCommand = new RelayCommand>(RemoveTrack)); - - private async void RemoveTrack(object list) - { - var selectedTracks = (IList)list; - - if (selectedTracks?.Count > 0) - { - var commandList = new CommandList(); - - foreach (var f in selectedTracks) - { - var trackVM = f as TrackViewModel; - commandList.Add(new DeleteIdCommand(trackVM.File.Id)); - } - // The delete command is fired twice -- Make sure the deleted tracks are unselected to avoid sending a second DeleteIdCommand. - selectedTracks.Clear(); - - await MPDConnectionService.SafelySendCommandAsync(commandList); - } - } - - private ICommand _addToPlaylistCommand; - public ICommand AddToPlayListCommand => _addToPlaylistCommand ?? (_addToPlaylistCommand = new RelayCommand>(AddToPlaylist)); - - private async void AddToPlaylist(object list) - { - var playlistName = await DialogService.ShowAddToPlaylistDialog(); - if (playlistName == null) return; - - var selectedTracks = (IList)list; - - if (selectedTracks?.Count > 0) - { - var commandList = new CommandList(); - - foreach (var f in selectedTracks) - { - var trackVM = f as TrackViewModel; - commandList.Add(new PlaylistAddCommand(playlistName, trackVM.File.Path)); - } - - var req = await MPDConnectionService.SafelySendCommandAsync(commandList); - - if (req != null) - NotificationService.ShowInAppNotification(string.Format("AddedToPlaylistText".GetLocalized(), playlistName)); - } - } - - public QueueViewModel() - { - MPDConnectionService.QueueChanged += MPDConnectionService_QueueChanged; - MPDConnectionService.ConnectionChanged += MPDConnectionService_ConnectionChanged; - - Source.CollectionChanged += (s, e) => OnPropertyChanged(nameof(IsSourceEmpty)); - } - - /// - /// This method is only fired if the source is changed outside of MPD Events. - /// So basically, only for reordering right now! - /// When reordering multiple items, the ListView sends events in a remove->add->remove->add fashion. - /// - private async void Source_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Add && _previousAction == NotifyCollectionChangedAction.Remove) - { - // One Remove->Add Pair means one MoveIdCommand - await MPDConnectionService.SafelySendCommandAsync(new MoveIdCommand(_oldId, e.NewStartingIndex)); - _previousAction = NotifyCollectionChangedAction.Move; - } - if (e.Action == NotifyCollectionChangedAction.Remove) - { - _previousAction = e.Action; - _oldId = e.OldItems.Count > 0 ? (e.OldItems[0] as TrackViewModel).File.Id : -1; - } - } - - private void MPDConnectionService_ConnectionChanged(object sender, EventArgs e) - { - if (MPDConnectionService.IsConnected) - Task.Run(async () => await LoadInitialDataAsync()); - } - - private async void MPDConnectionService_QueueChanged(object sender, EventArgs e) - { - // Ask for a new status ourselves as the shared ConnectionService one might not be up to date yet - var status = await MPDConnectionService.SafelySendCommandAsync(new StatusCommand()); - var newVersion = status.Playlist; - - // Update the queue only if playlist versions differ - if (newVersion != _playlistVersion) - { - _ = Task.Run(async () => { - - var response = await MPDConnectionService.SafelySendCommandAsync(new PlChangesCommand(_playlistVersion)); - - if (response != null) - { - Source.CollectionChanged -= Source_CollectionChanged; - await DispatcherService.ExecuteOnUIThreadAsync(() => { - - // If the queue was cleared, PlaylistLength is 0. - if (status.PlaylistLength == 0) - { - Source.Clear(); - } - else - { - // PlChanges gives the full list of files starting from the change, so we delete all existing tracks from the source after that change, and swap the new ones in. - // If the response is empty, that means the last file in the source was removed. - var initialPosition = response.Count() == 0 ? Source.Count - 1 : response.First().Position; - - - while (Source.Count != initialPosition) - { - Source.RemoveAt(initialPosition); - } - - foreach (var item in response) - { - Source.Add(new TrackViewModel(item, false)); - } - } - - }); - Source.CollectionChanged += Source_CollectionChanged; - - // Update internal playlist version - _playlistVersion = newVersion; - } - }); - } - } - - public ObservableCollection Source { get; } = new ObservableCollection(); - - public bool IsSourceEmpty => Source.Count == 0; - - public async Task LoadInitialDataAsync() - { - var tracks = new List(); - var response = await MPDConnectionService.SafelySendCommandAsync(new PlaylistInfoCommand()); - - if (response != null) - foreach (var item in response) - { - tracks.Add(new TrackViewModel(item, false)); - } - - await DispatcherService.ExecuteOnUIThreadAsync(() => { - Source.Clear(); - Source.AddRange(tracks); - }); - - // Set our internal playlist version - var status = await MPDConnectionService.SafelySendCommandAsync(new StatusCommand()); - _playlistVersion = status.Playlist; - - Source.CollectionChanged += Source_CollectionChanged; - } - } -} diff --git a/Sources/FluentMPC/ViewModels/SettingsViewModel.cs b/Sources/FluentMPC/ViewModels/SettingsViewModel.cs deleted file mode 100644 index bd2106d5..00000000 --- a/Sources/FluentMPC/ViewModels/SettingsViewModel.cs +++ /dev/null @@ -1,324 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Input; - -using FluentMPC.Helpers; -using FluentMPC.Services; -using Microsoft.Toolkit.Uwp.Helpers; -using MpcNET.Commands.Status; -using Windows.ApplicationModel; -using Windows.Storage; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace FluentMPC.ViewModels -{ - - public class SettingsViewModel : Observable - { - private ElementTheme _elementTheme = ThemeSelectorService.Theme; - - public ElementTheme ElementTheme - { - get { return _elementTheme; } - - set { Set(ref _elementTheme, value); } - } - - private string _versionDescription; - - public string VersionDescription - { - get { return _versionDescription; } - - set { Set(ref _versionDescription, value); } - } - - private string _serverInfo; - - public string ServerInfo - { - get { return _serverInfo; } - - set { Set(ref _serverInfo, value); } - } - - private string _serverHost; - - public string ServerHost - { - get { return _serverHost; } - - set - { - if (value != _serverHost) - { - Task.Run(async () => - { - await ApplicationData.Current.LocalSettings.SaveAsync(nameof(ServerHost), value ?? "localhost"); - await CheckServerAddressAsync(); - }); - } - Set(ref _serverHost, value); - } - } - - private int _serverPort; - - public int ServerPort - { - get { return _serverPort; } - - set - { - if (value != _serverPort) - { - Task.Run(async () => - { - await ApplicationData.Current.LocalSettings.SaveAsync(nameof(ServerPort), value); - await CheckServerAddressAsync(); - }); - } - Set(ref _serverPort, value); - } - } - - private bool _compactEnabled; - - public bool IsCompactSizing - { - get { return _compactEnabled; } - - set - { - if (value != _compactEnabled) - { - Task.Run(async () => - { - await ApplicationData.Current.LocalSettings.SaveAsync(nameof(IsCompactSizing), value); - - }); - } - Set(ref _compactEnabled, value); - } - } - - private bool _isCheckingServer; - - public bool IsCheckingServer - { - get { return _isCheckingServer; } - - set { Set(ref _isCheckingServer, value); } - } - - public bool IsServerValid => MPDConnectionService.IsConnected; - - private ICommand _switchThemeCommand; - - public ICommand SwitchThemeCommand - { - get - { - if (_switchThemeCommand == null) - { - _switchThemeCommand = new RelayCommand( - async (param) => - { - if (_hasInstanceBeenInitialized) - { - ElementTheme = param; - await ThemeSelectorService.SetThemeAsync(param); - } - }); - } - - return _switchThemeCommand; - } - } - - private ICommand _switchSizingCommand; - - public ICommand SwitchSizingCommand - { - get - { - if (_switchSizingCommand == null) - { - _switchSizingCommand = new RelayCommand( - (param) => - { - if (_hasInstanceBeenInitialized) - { - IsCompactSizing = bool.Parse(param); - } - }); - } - - return _switchSizingCommand; - } - } - - private ICommand _clearCacheCommand; - - public ICommand ClearCacheCommand - { - get - { - if (_clearCacheCommand == null) - { - _clearCacheCommand = new RelayCommand( - async () => - { - try - { - var cacheFolder = await ApplicationData.Current.LocalFolder.GetFolderAsync("AlbumArt"); - await cacheFolder.DeleteAsync(); - NotificationService.ShowInAppNotification("CacheDeletedText".GetLocalized()); - } - catch (Exception e) - { - NotificationService.ShowInAppNotification(string.Format("GenericErrorText".GetLocalized(), e), 0); - } - - }); - } - - return _clearCacheCommand; - } - } - - private ICommand _rescanDbCommand; - - public ICommand RescanDbCommand - { - get - { - if (_rescanDbCommand == null) - { - _rescanDbCommand = new RelayCommand( - async () => - { - if (MPDConnectionService.CurrentStatus.UpdatingDb > 0) - { - NotificationService.ShowInAppNotification("DatabaseAlreadyUpdatingText".GetLocalized()); - return; - } - - ContentDialog confirmDialog = new ContentDialog - { - Title = "UpdateDbDialogTitle".GetLocalized(), - Content = "UpdateDbDialogText".GetLocalized(), - PrimaryButtonText = "OKButtonText".GetLocalized(), - CloseButtonText = "CancelButtonText".GetLocalized() - }; - confirmDialog.RequestedTheme = ThemeSelectorService.Theme; - - ContentDialogResult result = await confirmDialog.ShowAsync(); - if (result != ContentDialogResult.Primary) - return; - - var res = await MPDConnectionService.SafelySendCommandAsync(new MpcNET.Commands.Database.UpdateCommand()); - - if (res != null) - NotificationService.ShowInAppNotification("DatabaseUpdateStartedText".GetLocalized()); - }); - } - - return _rescanDbCommand; - } - } - - public SettingsViewModel() - { - } - - private bool _hasInstanceBeenInitialized = false; - private int _previousUpdatingDb = 0; - - public async Task EnsureInstanceInitializedAsync() - { - if (!_hasInstanceBeenInitialized) - { - MPDConnectionService.ConnectionChanged += async (s, e) => await UpdateServerVersionAsync(); - MPDConnectionService.StatusChanged += async (s, e) => await CheckUpdatingDbAsync(); - - // Initialize values directly to avoid calling CheckServerAddressAsync twice - - _compactEnabled = - await ApplicationData.Current.LocalSettings.ReadAsync(nameof(IsCompactSizing)); - - _serverHost = - await ApplicationData.Current.LocalSettings.ReadAsync(nameof(ServerHost)); - - _serverPort = - await ApplicationData.Current.LocalSettings.ReadAsync(nameof(ServerPort)); - - await CheckServerAddressAsync(); - VersionDescription = GetVersionDescription(); - - _hasInstanceBeenInitialized = true; - } - } - - private async Task CheckUpdatingDbAsync() - { - var updatingDb = MPDConnectionService.CurrentStatus.UpdatingDb; - if (_previousUpdatingDb > 0 && _previousUpdatingDb != updatingDb && updatingDb == 0) - { - // A DB update job has concluded, refresh library - await CheckServerAddressAsync(); - await UpdateServerVersionAsync(); - } - _previousUpdatingDb = updatingDb; - } - - private string GetVersionDescription() - { - var appName = "AppDisplayName".GetLocalized(); - var package = Package.Current; - var packageId = package.Id; - var version = packageId.Version; - - return $"{appName} - {version.Major}.{version.Minor}.{version.Build}.{version.Revision}"; - } - - private async Task CheckServerAddressAsync() - { - if (IsCheckingServer) return; - - await DispatcherService.ExecuteOnUIThreadAsync(() => IsCheckingServer = true); - - await MPDConnectionService.InitializeAsync(); - await DispatcherService.ExecuteOnUIThreadAsync(() => - { - OnPropertyChanged(nameof(IsServerValid)); - IsCheckingServer = false; - }); - } - private async Task UpdateServerVersionAsync() - { - if (!MPDConnectionService.IsConnected) return; - - var response = await MPDConnectionService.SafelySendCommandAsync(new StatsCommand()); - - if (response != null) - { - var lastUpdatedDb = DateTime.MinValue; - - if (response.ContainsKey("db_update")) - { - var db_update = int.Parse(response["db_update"]); - lastUpdatedDb = DateTimeOffset.FromUnixTimeSeconds(db_update).UtcDateTime; - } - - // Build info string - await DispatcherService.ExecuteOnUIThreadAsync(() => - ServerInfo = $"MPD Protocol {MPDConnectionService.Version}\n" + - $"{response["songs"]} Songs, {response["albums"]} Albums\n" + - $"Database last updated {lastUpdatedDb}"); - } - - } - } -} diff --git a/Sources/FluentMPC/ViewModels/ShellViewModel.cs b/Sources/FluentMPC/ViewModels/ShellViewModel.cs deleted file mode 100644 index 2ec138ad..00000000 --- a/Sources/FluentMPC/ViewModels/ShellViewModel.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Input; - -using FluentMPC.Helpers; -using FluentMPC.Services; -using FluentMPC.Views; -using Microsoft.Toolkit.Uwp.Helpers; -using Microsoft.Toolkit.Uwp.UI.Controls; -using MpcNET.Commands.Database; -using MpcNET.Commands.Queue; -using MpcNET.Tags; -using MpcNET.Types; -using Windows.System; -using Windows.UI.Notifications; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Navigation; - -using WinUI = Microsoft.UI.Xaml.Controls; - -namespace FluentMPC.ViewModels -{ - public class ShellViewModel : Observable - { - private readonly KeyboardAccelerator _altLeftKeyboardAccelerator = BuildKeyboardAccelerator(VirtualKey.Left, VirtualKeyModifiers.Menu); - private readonly KeyboardAccelerator _backKeyboardAccelerator = BuildKeyboardAccelerator(VirtualKey.GoBack); - - private bool _isBackEnabled; - private Thickness _frameMargin; - private IList _keyboardAccelerators; - private WinUI.NavigationView _navigationView; - private WinUI.NavigationViewItem _selected; - private WinUI.NavigationViewItem _playlistContainer; - private InAppNotification _notificationHolder; - private ICommand _loadedCommand; - private ICommand _itemInvokedCommand; - - public bool IsBackEnabled - { - get { return _isBackEnabled; } - set { Set(ref _isBackEnabled, value); } - } - - public WinUI.NavigationViewItem Selected - { - get { return _selected; } - set { Set(ref _selected, value); } - } - - public Thickness FrameMargin - { - get { return _frameMargin; } - set { Set(ref _frameMargin, value); } - } - - public ICommand LoadedCommand => _loadedCommand ?? (_loadedCommand = new RelayCommand(OnLoaded)); - - public ICommand ItemInvokedCommand => _itemInvokedCommand ?? (_itemInvokedCommand = new RelayCommand(OnItemInvoked)); - - public ShellViewModel() - { - } - - public void Initialize(Frame frame, WinUI.NavigationView navigationView, WinUI.NavigationViewItem playlistContainer, InAppNotification notificationHolder, IList keyboardAccelerators) - { - _navigationView = navigationView; - _playlistContainer = playlistContainer; - _notificationHolder = notificationHolder; - _keyboardAccelerators = keyboardAccelerators; - NavigationService.Frame = frame; - NavigationService.NavigationFailed += Frame_NavigationFailed; - NavigationService.Navigated += Frame_Navigated; - _navigationView.BackRequested += OnBackRequested; - - _navigationView.AutoSuggestBox.TextChanged += UpdateSearchSuggestions; - _navigationView.AutoSuggestBox.QuerySubmitted += HandleSearchRequest; - - NotificationService.InAppNotificationRequested += Show_InAppNotification; - - DispatcherService.ExecuteOnUIThreadAsync(() => UpdatePlaylistNavigation()); - MPDConnectionService.PlaylistsChanged += (s,e) => - DispatcherService.ExecuteOnUIThreadAsync(() => UpdatePlaylistNavigation()); - } - - private void Show_InAppNotification(object sender, InAppNotificationRequestedEventArgs e) - { - DispatcherService.ExecuteOnUIThreadAsync(() => _notificationHolder.Show(e.NotificationText, e.NotificationTime)); - } - - private async void OnLoaded() - { - // Keyboard accelerators are added here to avoid showing 'Alt + left' tooltip on the page. - // More info on tracking issue https://github.com/Microsoft/microsoft-ui-xaml/issues/8 - _keyboardAccelerators.Add(_altLeftKeyboardAccelerator); - _keyboardAccelerators.Add(_backKeyboardAccelerator); - await Task.CompletedTask; - } - - private void OnItemInvoked(WinUI.NavigationViewItemInvokedEventArgs args) - { - if (args.IsSettingsInvoked) - { - NavigationService.Navigate(typeof(SettingsPage)); - return; - } - - // Slight trick of hand to avoid triggering the navigationService on items that don't have a matching page. - // This is done through setting SelectsOnInvoked in XAML. - if (args.InvokedItemContainer is WinUI.NavigationViewItem i && !i.SelectsOnInvoked) - return; - - var item = _navigationView.MenuItems.Union(_playlistContainer.MenuItems) - .OfType() - .FirstOrDefault(menuItem => (string)menuItem.Content == (string)args.InvokedItem); - - if (item == null) - return; - - var pageType = item.GetValue(NavHelper.NavigateToProperty) as Type; - - // Playlist items navigate with their name as parameter - if (_playlistContainer.MenuItems.Contains(item)) - NavigationService.Navigate(pageType, item.Content); - else - NavigationService.Navigate(pageType); - } - - private void OnBackRequested(WinUI.NavigationView sender, WinUI.NavigationViewBackRequestedEventArgs args) - { - NavigationService.GoBack(); - } - - private void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e) - { - throw e.Exception; - } - - private void Frame_Navigated(object sender, NavigationEventArgs e) - { - IsBackEnabled = NavigationService.CanGoBack; - - if (e.SourcePageType == typeof(LibraryDetailPage) || e.SourcePageType == typeof(PlaylistPage) || e.SourcePageType == typeof(PlaybackView)) - { - // Special margin to extend frame to the titlebar - FrameMargin = new Thickness(0, -32, 0, 0); - } - else - { - FrameMargin = new Thickness(0, 0, 0, 0); - } - - if (e.SourcePageType == typeof(SettingsPage)) - { - Selected = _navigationView.SettingsItem as WinUI.NavigationViewItem; - return; - } - - // Create a transient navigationviewitem to show a custom header value. - if (e.SourcePageType == typeof(SearchResultsPage)) - { - var item = new WinUI.NavigationViewItem(); - item.Content = string.Format("SearchResultsFor".GetLocalized(), e.Parameter as string); - Selected = item; - return; - } - - Selected = _navigationView.MenuItems - .OfType() - .FirstOrDefault(menuItem => IsMenuItemForPageType(menuItem, e.SourcePageType)); - } - - private bool IsMenuItemForPageType(WinUI.NavigationViewItem menuItem, Type sourcePageType) - { - var pageType = menuItem.GetValue(NavHelper.NavigateToProperty) as Type; - return pageType == sourcePageType; - } - - private void UpdatePlaylistNavigation() - { - // Update the navigationview by hand - It ain't clean but partial databinding would be an even bigger mess... - var playlists = MPDConnectionService.Playlists; - - // Remove all menuitems in the "Playlists" menu - _playlistContainer.MenuItems.Clear(); - - try - { - foreach (var playlist in playlists) - { - var navigationViewItem = new WinUI.NavigationViewItem(); - navigationViewItem.Icon = new SymbolIcon(Symbol.MusicInfo); - navigationViewItem.Content = playlist.Name; - NavHelper.SetNavigateTo(navigationViewItem, typeof(PlaylistPage)); - _playlistContainer.MenuItems.Add(navigationViewItem); - } - } catch (Exception e) - { - NotificationService.ShowInAppNotification($"Updating Playlist Navigation failed: {e.Message}", 0); - } - } - - private async void UpdateSearchSuggestions(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) - { - if (args.Reason != AutoSuggestionBoxTextChangeReason.UserInput) - return; - - var suitableItems = new List(); - - if (sender.Text.Trim().Length > 0) - suitableItems.Add(string.Format("GoToDetailSearch".GetLocalized(), sender.Text)); - - if (sender.Text.Length > 2) - { - // Clear out suggestions before filling them up again, as it takes a bit of time. - sender.ItemsSource = suitableItems; - var response = await MPDConnectionService.SafelySendCommandAsync(new SearchCommand(FindTags.Title, sender.Text)); - - if (response != null) - { - foreach (var f in response) - { - suitableItems.Add(f); - } - } - } - - sender.ItemsSource = suitableItems; - } - - private async void HandleSearchRequest(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) - { - if (args.ChosenSuggestion != null && args.ChosenSuggestion is IMpdFile) - { - var response = await MPDConnectionService.SafelySendCommandAsync(new AddCommand((args.ChosenSuggestion as IMpdFile).Path)); - - if (response != null) - NotificationService.ShowInAppNotification("AddedToQueueText".GetLocalized()); - } - else - { - // Navigate to detailed search page - NavigationService.Navigate(typeof(SearchResultsPage), args.QueryText); - } - } - - - private static KeyboardAccelerator BuildKeyboardAccelerator(VirtualKey key, VirtualKeyModifiers? modifiers = null) - { - var keyboardAccelerator = new KeyboardAccelerator() { Key = key }; - if (modifiers.HasValue) - { - keyboardAccelerator.Modifiers = modifiers.Value; - } - - keyboardAccelerator.Invoked += OnKeyboardAcceleratorInvoked; - return keyboardAccelerator; - } - - private static void OnKeyboardAcceleratorInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - var result = NavigationService.GoBack(); - args.Handled = result; - } - - } -} diff --git a/Sources/FluentMPC/Views/Dialogs/AddToPlaylistDialog.xaml b/Sources/FluentMPC/Views/Dialogs/AddToPlaylistDialog.xaml deleted file mode 100644 index e5b3fe9e..00000000 --- a/Sources/FluentMPC/Views/Dialogs/AddToPlaylistDialog.xaml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Views/Dialogs/FirstRunDialog.xaml b/Sources/FluentMPC/Views/Dialogs/FirstRunDialog.xaml deleted file mode 100644 index 42e1b2ac..00000000 --- a/Sources/FluentMPC/Views/Dialogs/FirstRunDialog.xaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Views/FoldersPage.xaml b/Sources/FluentMPC/Views/FoldersPage.xaml deleted file mode 100644 index 66d81085..00000000 --- a/Sources/FluentMPC/Views/FoldersPage.xaml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Views/LibraryPage.xaml b/Sources/FluentMPC/Views/LibraryPage.xaml deleted file mode 100644 index d3e32a55..00000000 --- a/Sources/FluentMPC/Views/LibraryPage.xaml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml b/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml deleted file mode 100644 index e188b43b..00000000 --- a/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml +++ /dev/null @@ -1,541 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml.cs b/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml.cs deleted file mode 100644 index b07eca5b..00000000 --- a/Sources/FluentMPC/Views/Playback/NowPlayingBar.xaml.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Microsoft.Toolkit.Uwp.Helpers; -using Microsoft.Toolkit.Uwp.UI.Animations; -using Microsoft.Toolkit.Uwp.UI.Controls; -using FluentMPC.ViewModels.Playback; -using System; -using Windows.UI.Core; -using Windows.UI.ViewManagement; -using Windows.UI.Xaml.Navigation; -using System.Threading.Tasks; -using Windows.Media.Playback; -using Windows.UI.Popups; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Animation; -using FluentMPC.Services; -using Microsoft.Toolkit.Uwp; -using Windows.System; - -namespace FluentMPC.Views -{ - public sealed partial class NowPlayingBar - { - public PlaybackViewModel PlaybackViewModel { get; } = new PlaybackViewModel(DispatcherQueue.GetForCurrentThread(), VisualizationType.NowPlayingBar); - - public NowPlayingBar() => InitializeComponent(); - - private void OnLoaded(object sender, RoutedEventArgs e) - { - MPDConnectionService.StatusChanged += PlaybackSession_PlaybackStateChanged; - MPDConnectionService.ConnectionChanged += MPDConnectionService_ConnectionLost; - - UpdateBars(); - } - - private void OnUnloaded(object sender, RoutedEventArgs e) - { - MPDConnectionService.StatusChanged -= PlaybackSession_PlaybackStateChanged; - MPDConnectionService.ConnectionChanged -= MPDConnectionService_ConnectionLost; - } - - /// - /// Show a teachingtip when connection to the server is lost. - /// - /// - /// - private void MPDConnectionService_ConnectionLost(object sender, EventArgs e) => UpdateBars(); - - private void UpdateBars() - { - DispatcherService.DispatcherQueue.EnqueueAsync(() => - { - if (!MPDConnectionService.IsConnected) - { - LoadingBar.Visibility = Visibility.Visible; - ProgressBar.Visibility = Visibility.Collapsed; - } - else - { - LoadingBar.Visibility = Visibility.Collapsed; - ProgressBar.Visibility = Visibility.Visible; - } - }); - } - - /// - /// Show the loading UI when a track is loading - /// - private void PlaybackSession_PlaybackStateChanged(object sender, EventArgs eventArgs) - { - Task.Run(() => PlaybackSession_PlaybackStateChangedAsync(sender, eventArgs)); - } - - private async Task PlaybackSession_PlaybackStateChangedAsync(object sender, EventArgs eventArgs) - { - await DispatcherService.DispatcherQueue.EnqueueAsync(() => - { - switch (MPDConnectionService.CurrentStatus.State) - { - case MpcNET.MpdState.Stop: - case MpcNET.MpdState.Play: - case MpcNET.MpdState.Pause: - LoadingBar.Visibility = Visibility.Collapsed; - ProgressBar.Visibility = Visibility.Visible; - break; - - case MpcNET.MpdState.Unknown: - LoadingBar.Visibility = Visibility.Visible; - ProgressBar.Visibility = Visibility.Collapsed; - break; - } - }); - } - - private void Volume_PointerWheelChanged(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) - { - var delta = e.GetCurrentPoint(this).Properties.MouseWheelDelta; - PlaybackViewModel.MediaVolume += 5 * delta / 120; - } - - } -} diff --git a/Sources/FluentMPC/Views/Playback/OverlayView.xaml b/Sources/FluentMPC/Views/Playback/OverlayView.xaml deleted file mode 100644 index 2bd0d835..00000000 --- a/Sources/FluentMPC/Views/Playback/OverlayView.xaml +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Views/Playback/OverlayView.xaml.cs b/Sources/FluentMPC/Views/Playback/OverlayView.xaml.cs deleted file mode 100644 index 520a93ef..00000000 --- a/Sources/FluentMPC/Views/Playback/OverlayView.xaml.cs +++ /dev/null @@ -1,67 +0,0 @@ -using FluentMPC.Services; -using FluentMPC.ViewModels.Playback; -using Microsoft.Toolkit.Uwp; -using Microsoft.Toolkit.Uwp.Helpers; -using System; -using System.Threading.Tasks; -using Windows.ApplicationModel.Core; -using Windows.System; -using Windows.UI; -using Windows.UI.Core; -using Windows.UI.ViewManagement; -using Windows.UI.Xaml.Navigation; - -namespace FluentMPC.Views -{ - /// - /// Compact Overlay view. Mostly borrowed from SoundByte. - /// - public sealed partial class OverlayView - { - public PlaybackViewModel PlaybackViewModel { get; private set; } - private int _mainAppViewId; - - public OverlayView() - { - InitializeComponent(); - } - - protected override void OnNavigatedTo(NavigationEventArgs e) - { - PlaybackViewModel = new PlaybackViewModel(DispatcherQueue.GetForCurrentThread(), VisualizationType.OverlayPlayback); - _mainAppViewId = (int)e.Parameter; - - CoreApplicationViewTitleBar coreTitleBar = CoreApplication.GetCurrentView().TitleBar; - coreTitleBar.ExtendViewIntoTitleBar = true; - - } - - private async void NavigateToMainView() - { - var currentViewId = -1; - - await DispatcherQueue.GetForCurrentThread().EnqueueAsync(() => - { - currentViewId = ApplicationView.GetForCurrentView().Id; - }); - - await ApplicationViewSwitcher.TryShowAsViewModeAsync(_mainAppViewId, ApplicationViewMode.Default); - - // Switch to this window - await ApplicationViewSwitcher.SwitchAsync(_mainAppViewId, currentViewId, - ApplicationViewSwitchingOptions.ConsolidateViews); - } - - protected override void OnNavigatedFrom(NavigationEventArgs e) - { - PlaybackViewModel?.Dispose(); - PlaybackViewModel = null; - } - - private void Page_Unloaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) - { - PlaybackViewModel?.Dispose(); - PlaybackViewModel = null; - } - } -} diff --git a/Sources/FluentMPC/Views/Playback/PlaybackView.xaml b/Sources/FluentMPC/Views/Playback/PlaybackView.xaml deleted file mode 100644 index bc5715d0..00000000 --- a/Sources/FluentMPC/Views/Playback/PlaybackView.xaml +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Views/PlaylistPage.xaml b/Sources/FluentMPC/Views/PlaylistPage.xaml deleted file mode 100644 index 2ec15b82..00000000 --- a/Sources/FluentMPC/Views/PlaylistPage.xaml +++ /dev/null @@ -1,284 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Views/SearchResultsPage.xaml b/Sources/FluentMPC/Views/SearchResultsPage.xaml deleted file mode 100644 index 5e975ebf..00000000 --- a/Sources/FluentMPC/Views/SearchResultsPage.xaml +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Views/ServerQueuePage.xaml b/Sources/FluentMPC/Views/ServerQueuePage.xaml deleted file mode 100644 index 9b8e9ef6..00000000 --- a/Sources/FluentMPC/Views/ServerQueuePage.xaml +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/FluentMPC/Views/SettingsPage.xaml b/Sources/FluentMPC/Views/SettingsPage.xaml deleted file mode 100644 index a9940895..00000000 --- a/Sources/FluentMPC/Views/SettingsPage.xaml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -