diff --git a/BOXVR Playlist Manager/App.config b/BOXVR Playlist Manager/App.config index e3ad060..ea90831 100644 --- a/BOXVR Playlist Manager/App.config +++ b/BOXVR Playlist Manager/App.config @@ -7,7 +7,7 @@ - + diff --git a/BOXVR Playlist Manager/BOXVR Playlist Manager.csproj b/BOXVR Playlist Manager/BOXVR Playlist Manager.csproj index 2939980..280cd3a 100644 --- a/BOXVR Playlist Manager/BOXVR Playlist Manager.csproj +++ b/BOXVR Playlist Manager/BOXVR Playlist Manager.csproj @@ -52,6 +52,9 @@ ..\packages\z440.atl.core.2.5.0\lib\net30\ATL.dll + + ..\..\SpotiSharp\bin\Debug\netcoreapp3.1\HtmlAgilityPack.dll + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll @@ -141,7 +144,6 @@ - MSBuild:Compile diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegQueue.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegQueue.cs index 529aa75..e687949 100644 --- a/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegQueue.cs +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegQueue.cs @@ -19,7 +19,8 @@ private void RunFFMPEG(FFmpegJob job) { Directory.CreateDirectory(Paths.TrackDataFolder(LocationMode.PlayerData)); App.logger.Debug(("FFMPEG start: " + FFmpegQueue.binaryPath + " " + job.GetCommand())); - bool done = false; + CancellationTokenSource doneCts = new CancellationTokenSource(); + var done = doneCts.Token; string exepath = FFmpegQueue.binaryPath; string command = job.GetCommand(); new Thread((ThreadStart)(() => @@ -38,9 +39,9 @@ private void RunFFMPEG(FFmpegJob job) process.WaitForExit(); process.Close(); job._message = output; - done = true; + doneCts.Cancel(); })).Start(); - while(!done) { } + while(!done.IsCancellationRequested) { } job._onFinished.Invoke(job); App.logger.Debug("FFMEG done"); } diff --git a/BOXVR Playlist Manager/Helpers/NotifyingObject.cs b/BOXVR Playlist Manager/Helpers/NotifyingObject.cs index 9b4b893..ada419b 100644 --- a/BOXVR Playlist Manager/Helpers/NotifyingObject.cs +++ b/BOXVR Playlist Manager/Helpers/NotifyingObject.cs @@ -6,7 +6,7 @@ namespace BoxVR_Playlist_Manager.Helpers { public class NotifyingObject : INotifyPropertyChanged { - Dispatcher _dispatcher; + protected Dispatcher _dispatcher; public NotifyingObject(Dispatcher dispatcher) { _dispatcher = dispatcher; diff --git a/BOXVR Playlist Manager/MainWindow.xaml b/BOXVR Playlist Manager/MainWindow.xaml index 293d7e5..78bdca6 100644 --- a/BOXVR Playlist Manager/MainWindow.xaml +++ b/BOXVR Playlist Manager/MainWindow.xaml @@ -25,7 +25,6 @@ - M444.788 291.1l42.616 24.599c4.867 2.809 7.126 8.618 5.459 13.985-11.07 35.642-29.97 67.842-54.689 94.586a12.016 12.016 0 0 1-14.832 2.254l-42.584-24.595a191.577 191.577 0 0 1-60.759 35.13v49.182a12.01 12.01 0 0 1-9.377 11.718c-34.956 7.85-72.499 8.256-109.219.007-5.49-1.233-9.403-6.096-9.403-11.723v-49.184a191.555 191.555 0 0 1-60.759-35.13l-42.584 24.595a12.016 12.016 0 0 1-14.832-2.254c-24.718-26.744-43.619-58.944-54.689-94.586-1.667-5.366.592-11.175 5.459-13.985L67.212 291.1a193.48 193.48 0 0 1 0-70.199l-42.616-24.599c-4.867-2.809-7.126-8.618-5.459-13.985 11.07-35.642 29.97-67.842 54.689-94.586a12.016 12.016 0 0 1 14.832-2.254l42.584 24.595a191.577 191.577 0 0 1 60.759-35.13V25.759a12.01 12.01 0 0 1 9.377-11.718c34.956-7.85 72.499-8.256 109.219-.007 5.49 1.233 9.403 6.096 9.403 11.723v49.184a191.555 191.555 0 0 1 60.759 35.13l42.584-24.595a12.016 12.016 0 0 1 14.832 2.254c24.718 26.744 43.619 58.944 54.689 94.586 1.667 5.366-.592 11.175-5.459 13.985L444.788 220.9a193.485 193.485 0 0 1 0 70.2zM336 256c0-44.112-35.888-80-80-80s-80 35.888-80 80 35.888 80 80 80 80-35.888 80-80z @@ -69,7 +68,7 @@ --> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Please wait, generating beatmaps... + + + + Please setup paths in the settings + + + + + + + + + + + + + + + diff --git a/BoxVRPlaylistManagerNETCore/UI/MainWindow.xaml.cs b/BoxVRPlaylistManagerNETCore/UI/MainWindow.xaml.cs new file mode 100644 index 0000000..0140af7 --- /dev/null +++ b/BoxVRPlaylistManagerNETCore/UI/MainWindow.xaml.cs @@ -0,0 +1,20 @@ +using System.Windows; +using log4net; + +namespace BoxVRPlaylistManagerNETCore.UI +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + private ILog _log = LogManager.GetLogger(nameof(MainWindow)); + public MainWindow() + { + _log.Debug("MainWindow initializing"); + InitializeComponent(); + var viewModel = new MainWindowViewModel(Dispatcher); + DataContext = viewModel; + } + } +} diff --git a/BoxVRPlaylistManagerNETCore/UI/MainWindowViewModel.cs b/BoxVRPlaylistManagerNETCore/UI/MainWindowViewModel.cs new file mode 100644 index 0000000..79a89a4 --- /dev/null +++ b/BoxVRPlaylistManagerNETCore/UI/MainWindowViewModel.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Threading; +using BoxVRPlaylistManagerNETCore.FitXr.BeatStructure; +using BoxVRPlaylistManagerNETCore.Helpers; +using log4net; +using SpotiSharp; +using YoutubeExplode; + +namespace BoxVRPlaylistManagerNETCore.UI +{ + public class MainWindowViewModel : NotifyingObject + { + private ILog _log = LogManager.GetLogger(typeof(MainWindowViewModel)); + private Dispatcher _dispatcher; + public ObservableCollection Playlists { get; set; } + + private PlaylistViewModel _selectedPlaylist; + public PlaylistViewModel SelectedPlaylist + { + get => _selectedPlaylist; + set => SetProperty(ref _selectedPlaylist, value); + } + + public ICommand AddLocalPlaylistCommand { get; set; } + public ICommand AddSpotifyPlaylistCommand { get; set; } + public ICommand SettingsCommand { get; set; } + public ICommand RemovePlaylistCommand { get; set; } + public ICommand ExpandPlaylistSubmenuCommand { get; set; } + + private bool _pathsNotSetup; + public bool PathsNotSetup { get => _pathsNotSetup; set => SetProperty(ref _pathsNotSetup, value); } + + private bool _displayAddPlaylistSubmenu; + public bool DisplayAddPlayListSubmenu { get => _displayAddPlaylistSubmenu; set => SetProperty(ref _displayAddPlaylistSubmenu, value); } + + + public MainWindowViewModel(Dispatcher dispatcher):base(dispatcher) + { + _dispatcher = dispatcher; + AddLocalPlaylistCommand = new RelayCommand(NewLocalPlaylistCommandExecute); + AddSpotifyPlaylistCommand = new RelayCommand(NewSpotifyPlaylistCommandExecute); + RemovePlaylistCommand = new RelayCommand(RemovePlaylistCommandExecute); + SettingsCommand = new RelayCommand(SettingsCommandExecute); + ExpandPlaylistSubmenuCommand = new RelayCommand(ExpandPlaylistSubmenuCommandExecute); + if(string.IsNullOrEmpty(Paths.PersistentDataPath) || string.IsNullOrEmpty(Paths.ApplicationPath)) + { + PathsNotSetup = true; + return; + } + Task.Run(LoadPlaylists); + } + + private void LoadPlaylists() + { + Playlists = new ObservableCollection(); + PlaylistManager.instance.LoadWorkoutPlaylists(); + var playlists = PlaylistManager.instance.GetAllWorkoutPlaylists(); + + _dispatcher.Invoke(() => + { + foreach(var playlist in playlists) + { + Playlists.Add(new PlaylistViewModel(playlist, _dispatcher)); + } + SelectedPlaylist = Playlists.First(); + }); + + _log.Debug($"{Playlists.Count} playlists loaded from"); + } + + public void ExpandPlaylistSubmenuCommandExecute(object arg) + { + DisplayAddPlayListSubmenu = !DisplayAddPlayListSubmenu; + } + + public void NewLocalPlaylistCommandExecute(object arg) + { + DisplayAddPlayListSubmenu = false; + var workoutPlaylist = PlaylistManager.instance.AddNewPlaylist(); + Playlists.Add(new PlaylistViewModel(workoutPlaylist, _dispatcher)); + } + + public void NewSpotifyPlaylistCommandExecute(object arg) + { + List spotifyPlaylistTracks = new List(); + var client = SpotifyHelpers.ConnectToSpotify(); + var trackQueue = new ConcurrentQueue(); + var youTube = new YoutubeClient(); + //if(input.IsSpotifyUrl()) + //{ + // var (type, url) = input.GetSpotifyUrlId(); + // switch(type) + // { + // case UrlType.Playlist: + // var taskPlaylist = client.QueueSpotifyTracksFromPlaylist(url, trackQueue); + // while(!taskPlaylist.IsCompleted) + // { + // while(trackQueue.TryDequeue(out var info)) + // { + // _log.Debug($"Downloading ::::: {info.Artist} - {info.Title}".Truncate()); + // _log.Debug($"[Queue: {trackQueue.Count}]".MoveToRight()); + // youTube.DownloadAndConvertTrack(info); + // _log.Debug($"Done ::::: {info.Artist} - {info.Title}".Truncate()); + // } + // Thread.Sleep(200); + // } + // while(trackQueue.TryDequeue(out var info)) + // { + // _log.Debug($"Downloading ::::: {info.Artist} - {info.Title}".Truncate()); + // _log.Debug($"[Queue: {trackQueue.Count}]".MoveToRight()); + // youTube.DownloadAndConvertTrack(info); + // _log.Debug($"Done ::::: {info.Artist} - {info.Title}".Truncate()); + // } + // break; + // case UrlType.Album: + // var taskAlbum = client.QueueSpotifyTracksFromAlbum(url, trackQueue); + // while(!taskAlbum.IsCompleted) + // { + // while(trackQueue.TryDequeue(out var info)) + // { + // _log.Debug($"Downloading ::::: {info.Artist} - {info.Title}".Truncate()); + // _log.Debug($"[Queue: {trackQueue.Count}]".MoveToRight()); + // youTube.DownloadAndConvertTrack(info); + // _log.Debug($"Done ::::: {info.Artist} - {info.Title}".Truncate()); + // } + // Thread.Sleep(200); + // } + // while(trackQueue.TryDequeue(out var info)) + // { + // _log.Debug($"Downloading ::::: {info.Artist} - {info.Title}".Truncate()); + // _log.Debug($"[Queue: {trackQueue.Count}]".MoveToRight()); + // youTube.DownloadAndConvertTrack(info); + // _log.Debug($"Done ::::: {info.Artist} - {info.Title}".Truncate()); + // } + // break; + // case UrlType.Track: + // var track = client.GetSpotifyTrack(input).GetAwaiter().GetResult(); + // if(track == null) + // { + // Environment.Exit(1); + // } + // _log.Debug($"Downloading ::::: {track.Artist} - {track.Title}".Truncate()); + // youTube.DownloadAndConvertTrack(track); + // _log.Debug($"Done ::::: {track.Artist} - {track.Title}".Truncate()); + // break; + // } + //} + } + + public void RemovePlaylistCommandExecute(object arg) + { + var result = MessageBox.Show($"Are you sure you want to remove playlist {SelectedPlaylist.Title}?", "Removal confirmation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No); + if(result == MessageBoxResult.Yes) + { + PlaylistManager.instance.DeletePlaylist(SelectedPlaylist.Title); + } + Playlists.Remove(SelectedPlaylist); + SelectedPlaylist = Playlists.First(); + } + + private void SettingsCommandExecute(object arg) + { + _log.Debug("Settings window opened"); + var settingsWindow = new SettingsWindow(); + var settingsChanged = settingsWindow.ShowDialog(); + if(string.IsNullOrEmpty(Paths.PersistentDataPath) || string.IsNullOrEmpty(Paths.ApplicationPath)) + { + PathsNotSetup = true; + return; + } + PathsNotSetup = false; + if(settingsChanged.HasValue && settingsChanged.Value) + { + _log.Debug("Settings were changed"); + LoadPlaylists(); + } + else + { + _log.Debug("No settings changed"); + } + } + } +} diff --git a/BoxVRPlaylistManagerNETCore/UI/NotifyingObject.cs b/BoxVRPlaylistManagerNETCore/UI/NotifyingObject.cs new file mode 100644 index 0000000..444b5ae --- /dev/null +++ b/BoxVRPlaylistManagerNETCore/UI/NotifyingObject.cs @@ -0,0 +1,65 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Threading; + +namespace BoxVRPlaylistManagerNETCore.UI +{ + public class NotifyingObject : INotifyPropertyChanged + { + protected Dispatcher _dispatcher; + public NotifyingObject(Dispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + /// + /// Multicast event for property change notifications. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Checks if a property already matches a desired value. Sets the property and + /// notifies listeners only when necessary. + /// + /// Type of the property. + /// Reference to a property with both getter and setter. + /// Desired value for the property. + /// + /// Name of the property used to notify listeners. This + /// value is optional and can be provided automatically when invoked from compilers that + /// support CallerMemberName. + /// + /// + /// True if the value was changed, false if the existing value matched the + /// desired value. + /// + protected bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) + { + if(Equals(storage, value)) + { + return false; + } + + storage = value; + _dispatcher.Invoke(() => this.OnPropertyChanged(propertyName)); + return true; + } + + /// + /// Notifies listeners that a property value has changed. + /// + /// + /// Name of the property used to notify listeners. This + /// value is optional and can be provided automatically when invoked from compilers + /// that support . + /// + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChangedEventHandler eventHandler = this.PropertyChanged; + if(eventHandler != null) + { + eventHandler(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/BoxVRPlaylistManagerNETCore/UI/PlaylistViewModel.cs b/BoxVRPlaylistManagerNETCore/UI/PlaylistViewModel.cs new file mode 100644 index 0000000..76e7d77 --- /dev/null +++ b/BoxVRPlaylistManagerNETCore/UI/PlaylistViewModel.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Windows.Threading; +using BoxVRPlaylistManagerNETCore.FitXr.BeatStructure; +using BoxVRPlaylistManagerNETCore.FitXr.Models; +using BoxVRPlaylistManagerNETCore.UI.Enums; +using Microsoft.Win32; + +namespace BoxVRPlaylistManagerNETCore.UI +{ + public class PlaylistViewModel : NotifyingObject + { + public WorkoutPlaylist _workoutPlaylist; + + public WorkoutPlaylist _originalWorkoutPlaylist; + + public ObservableCollection Tracks { get; private set; } + + private string _title; + + public string Title + { + get => _title; + set + { + if(SetProperty(ref _title, value)) + { + _workoutPlaylist.definition.workoutName = value; + IsModified = true; + } + } + } + + + public float Duration + { + get => _workoutPlaylist.definition.duration; + } + + public TimeSpan DurationTimeSpan => TimeSpan.FromSeconds(Duration); + + + private bool _isModified; + public bool IsModified + { + get => _isModified; + private set + { + if(!IsLoading) + { + SetProperty(ref _isModified, value); + } + } + } + + private bool _isLoading; + public bool IsLoading + { + get => _isLoading; + private set => SetProperty(ref _isLoading, value); + } + + private bool _isGeneratingBeatmaps; + public bool IsGeneratingBeatmaps + { + get => _isGeneratingBeatmaps; + private set => SetProperty(ref _isGeneratingBeatmaps, value); + } + + private SongViewModel _selectedTrack; + public SongViewModel SelectedTrack + { + get => _selectedTrack; + set => SetProperty(ref _selectedTrack, value); + } + + + // ------------------ Commands --------------------- + + public ICommand AddSongCommand { get; set; } + public ICommand SongMoveUpCommand { get; set; } + public ICommand SongMoveDownCommand { get; set; } + public ICommand RemoveSongCommand { get; set; } + public ICommand SavePlaylistCommand { get; set; } + + public PlaylistViewModel(WorkoutPlaylist workoutPlaylist, Dispatcher dispatcher) : base(dispatcher) + { + IsLoading = true; + AddSongCommand = new RelayCommand(AddSongCommandExecute); + RemoveSongCommand = new RelayCommand(RemoveSongCommandExecute); + SavePlaylistCommand = new RelayCommand(SaveCommandExecute); + SongMoveUpCommand = new RelayCommand(SongMoveUpCommandExecute); + SongMoveDownCommand = new RelayCommand(SongMoveDownCommandExecute); + + _originalWorkoutPlaylist = workoutPlaylist; + _workoutPlaylist = new WorkoutPlaylist(workoutPlaylist); + Title = workoutPlaylist.definition.workoutName; + Tracks = new ObservableCollection(); + foreach(var song in workoutPlaylist.songs) + { + Tracks.Add(new SongViewModel(song, workoutPlaylist.definition.PlaylistType, _dispatcher)); + } + Tracks.CollectionChanged += Tracks_CollectionChanged; + IsLoading = false; + } + + private void Tracks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + IsModified = true; + _workoutPlaylist.CalcTotalLength(); + OnPropertyChanged(nameof(DurationTimeSpan)); + } + + public void AddSongCommandExecute(object arg) + { + var dlg = new OpenFileDialog(); + + dlg.CheckFileExists = true; + dlg.CheckPathExists = true; + dlg.Multiselect = true; + dlg.Title = "Add Track"; + + //Taken from BOXVR source code. + dlg.Filter = "MP3 Files (*.mp3)|*.mp3|M4A Files (*.m4a)|*.m4a|OGG Files (*.ogg)|*.ogg"; + var result = dlg.ShowDialog(); + + if(result ?? false) + { + foreach(var file in dlg.FileNames) + { + AddSong(file); + } + } + + } + + public void AddSong(string fileName) + { + //Create a dummy track object with all required properties for display and for later import + var track = TagLib.File.Create(fileName); + var songDefinition = new SongDefinition() + { + trackDefinition = new TrackDefinition() + { + tagLibArtist = track.Tag.JoinedPerformers, + tagLibTitle = track.Tag.Title, + duration = (float)track.Properties.Duration.TotalSeconds, + trackData = new TrackData() + { + originalFilePath = fileName + } + } + }; + + _workoutPlaylist.AddSong(songDefinition); + + Tracks.Add(new SongViewModel(songDefinition, PlaylistType.Local, _dispatcher)); + } + + public void RemoveSongCommandExecute(object arg) + { + if(arg is SongViewModel songViewModel) + { + _workoutPlaylist.RemoveSong(songViewModel.SongDefinition); + Tracks.Remove(songViewModel); + } + } + + public void SongMoveUpCommandExecute(object arg) + { + if(arg is SongViewModel songViewModel) + { + var index = Tracks.IndexOf(songViewModel); + if(index > 0) + { + var prev = Tracks[index - 1]; + Tracks[index - 1] = songViewModel; + Tracks[index] = prev; + } + SelectedTrack = songViewModel; + } + } + + public void SongMoveDownCommandExecute(object arg) + { + if(arg is SongViewModel songViewModel) + { + var index = Tracks.IndexOf(songViewModel); + if(index < Tracks.Count) + { + var next = Tracks[index + 1]; + Tracks[index + 1] = songViewModel; + Tracks[index] = next; + } + SelectedTrack = songViewModel; + } + } + + public void SaveCommandExecute(object arg) + { + IsGeneratingBeatmaps = true; + Task.Run(() => + { + List list = new List(); + foreach(var songViewModel in Tracks) + { + if(!_originalWorkoutPlaylist.songs.Contains(songViewModel.SongDefinition)) + { + var addedSong = PlaylistManager.instance.PlaylistAddEntry(_workoutPlaylist, songViewModel.SongDefinition.trackDefinition.trackData.originalFilePath, FitXr.Enums.LocationMode.PlayerData); + list.Add(addedSong); + } + else + { + list.Add(songViewModel.SongDefinition); + } + } + _workoutPlaylist.songs = list; + PlaylistManager.instance.ExportPlaylistJson(_workoutPlaylist); + IsGeneratingBeatmaps = false; + IsModified = false; + }); + } + } +} diff --git a/BoxVRPlaylistManagerNETCore/UI/RelayCommand.cs b/BoxVRPlaylistManagerNETCore/UI/RelayCommand.cs new file mode 100644 index 0000000..b2f4b05 --- /dev/null +++ b/BoxVRPlaylistManagerNETCore/UI/RelayCommand.cs @@ -0,0 +1,33 @@ +using System; +using System.Windows.Input; + +namespace BoxVRPlaylistManagerNETCore.UI +{ + public class RelayCommand : ICommand + { + private Action execute; + private Func canExecute; + + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + public RelayCommand(Action execute, Func canExecute = null) + { + this.execute = execute; + this.canExecute = canExecute; + } + + public bool CanExecute(object parameter) + { + return this.canExecute == null || this.canExecute(parameter); + } + + public void Execute(object parameter) + { + this.execute(parameter); + } + } +} diff --git a/BoxVRPlaylistManagerNETCore/UI/SettingsWindow.xaml b/BoxVRPlaylistManagerNETCore/UI/SettingsWindow.xaml new file mode 100644 index 0000000..c70017c --- /dev/null +++ b/BoxVRPlaylistManagerNETCore/UI/SettingsWindow.xaml @@ -0,0 +1,60 @@ + + + M572.694 292.093L500.27 416.248A63.997 63.997 0 0 1 444.989 448H45.025c-18.523 0-30.064-20.093-20.731-36.093l72.424-124.155A64 64 0 0 1 152 256h399.964c18.523 0 30.064 20.093 20.73 36.093zM152 224h328v-48c0-26.51-21.49-48-48-48H272l-64-64H48C21.49 64 0 85.49 0 112v278.046l69.077-118.418C86.214 242.25 117.989 224 152 224z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BoxVRPlaylistManagerNETCore/UI/SettingsWindow.xaml.cs b/BoxVRPlaylistManagerNETCore/UI/SettingsWindow.xaml.cs new file mode 100644 index 0000000..7eb76ed --- /dev/null +++ b/BoxVRPlaylistManagerNETCore/UI/SettingsWindow.xaml.cs @@ -0,0 +1,24 @@ +using System.Windows; + +namespace BoxVRPlaylistManagerNETCore.UI +{ + /// + /// Interaction logic for SettingsWindow.xaml + /// + public partial class SettingsWindow : Window + { + public SettingsWindow() + { + InitializeComponent(); + var viewModel = new SettingsWindowViewModel(Dispatcher); + viewModel.RequestClose += ViewModel_RequestClose; + DataContext = viewModel; + } + + private void ViewModel_RequestClose(object sender, bool e) + { + DialogResult = e; + Close(); + } + } +} diff --git a/BoxVRPlaylistManagerNETCore/UI/SettingsWindowViewModel.cs b/BoxVRPlaylistManagerNETCore/UI/SettingsWindowViewModel.cs new file mode 100644 index 0000000..d223a11 --- /dev/null +++ b/BoxVRPlaylistManagerNETCore/UI/SettingsWindowViewModel.cs @@ -0,0 +1,97 @@ +using System; +using System.Diagnostics; +using System.Windows.Forms; +using System.Windows.Input; +using System.Windows.Threading; + +namespace BoxVRPlaylistManagerNETCore.UI +{ + public class SettingsWindowViewModel : NotifyingObject + { + public ICommand OkCommand { get; set; } + public ICommand CancelCommand { get; set; } + public ICommand DonateCommand { get; set; } + public ICommand BrowseExeCommand { get; set; } + public ICommand BrowseAppDataCommand { get; set; } + + public event EventHandler RequestClose; + public SettingsWindowViewModel(Dispatcher dispatcher) : base(dispatcher) + { + OkCommand = new RelayCommand(OkCommandExecute); + CancelCommand = new RelayCommand(CancelCommandExecute); + DonateCommand = new RelayCommand(DonateCommandExecute); + BrowseExeCommand = new RelayCommand(BrowseExeCommandExecute); + BrowseAppDataCommand = new RelayCommand(BrowseAppDataCommandExecute); + } + + private void OkCommandExecute(object arg) + { + RequestClose?.Invoke(this, true); + } + + private void CancelCommandExecute(object arge) + { + RequestClose?.Invoke(this, false); + } + + private void DonateCommandExecute(object arg) + { + var process = new Process(); + process.StartInfo = new ProcessStartInfo() + { + UseShellExecute = true, + FileName = @"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2MGZP7J29CP9W" + }; + process.Start(); + } + + private void BrowseExeCommandExecute(object arg) + { + App.Configuration.BoxVRExePath = GetFolder(App.Configuration.BoxVRExePath); + } + + private void BrowseAppDataCommandExecute(object arg) + { + App.Configuration.BoxVRAppDataPath = GetFolder(App.Configuration.BoxVRAppDataPath); + } + + public string BoxVRExePath + { + get => App.Configuration.BoxVRExePath; + set + { + if(App.Configuration.BoxVRExePath != value) + { + App.Configuration.BoxVRExePath = value; + OnPropertyChanged(nameof(BoxVRExePath)); + } + } + } + public string BoxVRAppDataPath + { + get => App.Configuration.BoxVRAppDataPath; + set + { + if(App.Configuration.BoxVRAppDataPath != value) + { + App.Configuration.BoxVRAppDataPath = value; + OnPropertyChanged(nameof(BoxVRAppDataPath)); + } + } + } + + private string GetFolder(string startFolder) + { + using(var dlg = new FolderBrowserDialog()) + { + dlg.ShowNewFolderButton = false; + + if(!string.IsNullOrWhiteSpace(startFolder)) + dlg.SelectedPath = Environment.ExpandEnvironmentVariables(startFolder); + + var result = dlg.ShowDialog(); + return result == System.Windows.Forms.DialogResult.OK ? dlg.SelectedPath : startFolder; + } + } + } +} diff --git a/BoxVRPlaylistManagerNETCore/UI/SongViewModel.cs b/BoxVRPlaylistManagerNETCore/UI/SongViewModel.cs new file mode 100644 index 0000000..41da3aa --- /dev/null +++ b/BoxVRPlaylistManagerNETCore/UI/SongViewModel.cs @@ -0,0 +1,56 @@ +using System; +using System.Windows.Threading; +using BoxVRPlaylistManagerNETCore.FitXr.Models; +using BoxVRPlaylistManagerNETCore.UI.Enums; + +namespace BoxVRPlaylistManagerNETCore.UI +{ + public class SongViewModel : NotifyingObject + { + public SongDefinition SongDefinition { get; set; } + + public string Title => SongDefinition.trackDefinition.tagLibTitle; + public string Artist => SongDefinition.trackDefinition.tagLibArtist; + + public TimeSpan Duration => SongDefinition.trackDefinition.DurationTimeSpan; + + public float Bpm => SongDefinition.trackDefinition.bpm; + + + private PlaylistType _songType; + public PlaylistType SongType + { + get => _songType; + set + { + if(SetProperty(ref _songType, value)) + { + OnPropertyChanged(nameof(DisplayProgressBar)); + } + } + } + + private bool _downloaded; + public bool Downloaded + { + get => _downloaded; + set + { + if(SetProperty(ref _downloaded, value)) + { + OnPropertyChanged(nameof(DisplayProgressBar)); + } + } + } + + private int _downloadProgress; + public int DownloadProgress { get => _downloadProgress; set => SetProperty(ref _downloadProgress, value); } + + public bool DisplayProgressBar => SongType == PlaylistType.Spotify && !Downloaded; + public SongViewModel(SongDefinition songDefinition, PlaylistType songType, Dispatcher dispatcher) : base(dispatcher) + { + SongDefinition = songDefinition; + SongType = songType; + } + } +} diff --git a/BoxVRPlaylistManagerNETCore/appsettings.json b/BoxVRPlaylistManagerNETCore/appsettings.json new file mode 100644 index 0000000..8cddefe --- /dev/null +++ b/BoxVRPlaylistManagerNETCore/appsettings.json @@ -0,0 +1,4 @@ +{ + "BoxVRExePath": "", + "BoxVRAppDataPath": "" +} \ No newline at end of file