// Copyright (c) Arlo Godfrey. All Rights Reserved. // Licensed under the GNU Lesser General Public License, Version 3.0 with additional terms. // See the LICENSE, LICENSE.LESSER and LICENSE.ADDITIONAL files in the project root for more information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Diagnostics; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using OwlCore; using OwlCore.Events; using OwlCore.Extensions; using StrixMusic.Sdk.AdapterModels; using StrixMusic.Sdk.AppModels; using StrixMusic.Sdk.CoreModels; using StrixMusic.Sdk.Extensions; using StrixMusic.Sdk.MediaPlayback; using StrixMusic.Sdk.ViewModels.Helpers; namespace StrixMusic.Sdk.ViewModels { /// /// A wrapper for that contains props and methods for a ViewModel. /// public class ArtistCollectionViewModel : ObservableObject, ISdkViewModel, IArtistCollectionViewModel, IImageCollectionViewModel { private readonly IArtistCollection _collection; private readonly SemaphoreSlim _populateArtistsMutex = new(1, 1); private readonly SemaphoreSlim _populateImagesMutex = new(1, 1); private readonly SemaphoreSlim _populateUrlsMutex = new(1, 1); private readonly SynchronizationContext _syncContext; /// /// Creates a new instance of . /// /// The to wrap around. public ArtistCollectionViewModel(IArtistCollection collection) { _syncContext = SynchronizationContext.Current; _collection = collection; PopulateMoreArtistsCommand = new AsyncRelayCommand(PopulateMoreArtistsAsync); PopulateMoreImagesCommand = new AsyncRelayCommand(PopulateMoreImagesAsync); PopulateMoreUrlsCommand = new AsyncRelayCommand(PopulateMoreUrlsAsync); PauseArtistCollectionAsyncCommand = new AsyncRelayCommand(PauseArtistCollectionAsync); PlayArtistCollectionAsyncCommand = new AsyncRelayCommand(PlayArtistCollectionAsync); ChangeDescriptionAsyncCommand = new AsyncRelayCommand(ChangeDescriptionAsync); ChangeNameAsyncCommand = new AsyncRelayCommand(ChangeNameInternalAsync); ChangeDurationAsyncCommand = new AsyncRelayCommand(ChangeDurationAsync); ChangeArtistCollectionSortingTypeCommand = new RelayCommand(x => SortArtistCollection(x, CurrentArtistSortingDirection)); ChangeArtistCollectionSortingDirectionCommand = new RelayCommand(x => SortArtistCollection(CurrentArtistSortingType, x)); InitArtistCollectionAsyncCommand = new AsyncRelayCommand(InitArtistCollectionAsync); InitImageCollectionAsyncCommand = new AsyncRelayCommand(InitImageCollectionAsync); PlayArtistAsyncCommand = new AsyncRelayCommand((x, y) => _collection.PlayArtistCollectionAsync(x ?? ThrowHelper.ThrowArgumentNullException(nameof(x)), y)); Artists = new ObservableCollection(); UnsortedArtists = new ObservableCollection(); Images = new ObservableCollection(); Urls = new ObservableCollection(); AttachEvents(); } /// public Task InitAsync(CancellationToken cancellationToken = default) { if (IsInitialized) return Task.CompletedTask; IsInitialized = true; return Task.WhenAll(InitArtistCollectionAsync(cancellationToken), InitImageCollectionAsync(cancellationToken)); } private void AttachEvents() { PlaybackStateChanged += OnPlaybackStateChanged; NameChanged += OnNameChanged; DescriptionChanged += OnDescriptionChanged; LastPlayedChanged += OnLastPlayedChanged; DownloadInfoChanged += OnDownloadInfoChanged; IsPlayArtistCollectionAsyncAvailableChanged += OnIsPlayArtistCollectionAsyncAvailableChanged; IsPauseArtistCollectionAsyncAvailableChanged += OnIsPauseArtistCollectionAsyncAvailableChanged; IsChangeNameAsyncAvailableChanged += OnIsChangeNameAsyncAvailableChanged; IsChangeDurationAsyncAvailableChanged += OnIsChangeDurationAsyncAvailableChanged; IsChangeDescriptionAsyncAvailableChanged += OnIsChangeDescriptionAsyncAvailableChanged; ArtistItemsCountChanged += OnArtistItemsCountChanged; ArtistItemsChanged += ArtistCollectionViewModel_ArtistItemsChanged; ImagesCountChanged += ArtistCollectionViewModel_ImagesCountChanged; ImagesChanged += ArtistCollectionViewModel_ImagesChanged; UrlsCountChanged += ArtistCollectionViewModel_UrlsCountChanged; UrlsChanged += ArtistCollectionViewModel_UrlsChanged; } private void DetachEvents() { PlaybackStateChanged -= OnPlaybackStateChanged; NameChanged -= OnNameChanged; DescriptionChanged -= OnDescriptionChanged; LastPlayedChanged -= OnLastPlayedChanged; DownloadInfoChanged -= OnDownloadInfoChanged; IsPlayArtistCollectionAsyncAvailableChanged -= OnIsPlayArtistCollectionAsyncAvailableChanged; IsPauseArtistCollectionAsyncAvailableChanged -= OnIsPauseArtistCollectionAsyncAvailableChanged; IsChangeNameAsyncAvailableChanged -= OnIsChangeNameAsyncAvailableChanged; IsChangeDurationAsyncAvailableChanged -= OnIsChangeDurationAsyncAvailableChanged; IsChangeDescriptionAsyncAvailableChanged -= OnIsChangeDescriptionAsyncAvailableChanged; ArtistItemsCountChanged -= OnArtistItemsCountChanged; ArtistItemsChanged -= ArtistCollectionViewModel_ArtistItemsChanged; ImagesCountChanged -= ArtistCollectionViewModel_ImagesCountChanged; ImagesChanged -= ArtistCollectionViewModel_ImagesChanged; UrlsCountChanged -= ArtistCollectionViewModel_UrlsCountChanged; UrlsChanged -= ArtistCollectionViewModel_UrlsChanged; } private void OnNameChanged(object sender, string e) => _syncContext.Post(_ => OnPropertyChanged(nameof(Name)), null); private void OnDescriptionChanged(object sender, string? e) => _syncContext.Post(_ => OnPropertyChanged(nameof(Description)), null); private void OnPlaybackStateChanged(object sender, PlaybackState e) => _syncContext.Post(_ => OnPropertyChanged(nameof(PlaybackState)), null); private void OnDownloadInfoChanged(object sender, DownloadInfo e) => _syncContext.Post(_ => OnPropertyChanged(nameof(DownloadInfo)), null); private void ArtistCollectionViewModel_ImagesCountChanged(object sender, int e) => _syncContext.Post(_ => OnPropertyChanged(nameof(TotalImageCount)), null); private void ArtistCollectionViewModel_UrlsCountChanged(object sender, int e) => _syncContext.Post(_ => OnPropertyChanged(nameof(TotalUrlCount)), null); private void OnArtistItemsCountChanged(object sender, int e) => _syncContext.Post(_ => OnPropertyChanged(nameof(TotalArtistItemsCount)), null); private void OnLastPlayedChanged(object sender, DateTime? e) => _syncContext.Post(_ => OnPropertyChanged(nameof(LastPlayed)), null); private void OnIsChangeDescriptionAsyncAvailableChanged(object sender, bool e) => _syncContext.Post(_ => OnPropertyChanged(nameof(IsChangeDescriptionAsyncAvailable)), null); private void OnIsChangeDurationAsyncAvailableChanged(object sender, bool e) => _syncContext.Post(_ => OnPropertyChanged(nameof(IsChangeDurationAsyncAvailable)), null); private void OnIsChangeNameAsyncAvailableChanged(object sender, bool e) => _syncContext.Post(_ => OnPropertyChanged(nameof(IsChangeNameAsyncAvailable)), null); private void OnIsPauseArtistCollectionAsyncAvailableChanged(object sender, bool e) => _syncContext.Post(_ => OnPropertyChanged(nameof(IsPauseArtistCollectionAsyncAvailable)), null); private void OnIsPlayArtistCollectionAsyncAvailableChanged(object sender, bool e) => _syncContext.Post(_ => OnPropertyChanged(nameof(IsPlayArtistCollectionAsyncAvailable)), null); private void ArtistCollectionViewModel_ImagesChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems) => _syncContext.Post(_ => { Images.ChangeCollection(addedItems, removedItems); }, null); private void ArtistCollectionViewModel_UrlsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems) => _syncContext.Post(_ => { Urls.ChangeCollection(addedItems, removedItems); }, null); private void ArtistCollectionViewModel_ArtistItemsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems) => _syncContext.Post(_ => { if (CurrentArtistSortingType == ArtistSortingType.Unsorted) { Artists.ChangeCollection(addedItems, removedItems, item => item.Data switch { IArtist artist => new ArtistViewModel(artist), IArtistCollection collection => new ArtistCollectionViewModel(collection), _ => ThrowHelper.ThrowNotSupportedException( $"{item.Data.GetType()} not supported for adding to {GetType()}") }); } else { // Make sure both ordered and unordered artists are updated. UnsortedArtists.ChangeCollection(addedItems, removedItems, item => item.Data switch { IArtist artist => new ArtistViewModel(artist), IArtistCollection collection => new ArtistCollectionViewModel(collection), _ => ThrowHelper.ThrowNotSupportedException( $"{item.Data.GetType()} not supported for adding to {GetType()}") }); foreach (var item in UnsortedArtists) { if (!Artists.Contains(item)) Artists.Add(item); } foreach (var item in Artists.ToArray()) { if (!UnsortedArtists.Contains(item)) Artists.Remove(item); } SortArtistCollection(CurrentArtistSortingType, CurrentArtistSortingDirection); } }, null); /// public event EventHandler? SourcesChanged { add => _collection.SourcesChanged += value; remove => _collection.SourcesChanged -= value; } /// public event EventHandler? PlaybackStateChanged { add => _collection.PlaybackStateChanged += value; remove => _collection.PlaybackStateChanged -= value; } /// public event EventHandler? DownloadInfoChanged { add => _collection.DownloadInfoChanged += value; remove => _collection.DownloadInfoChanged -= value; } /// public event EventHandler? NameChanged { add => _collection.NameChanged += value; remove => _collection.NameChanged -= value; } /// public event EventHandler? DescriptionChanged { add => _collection.DescriptionChanged += value; remove => _collection.DescriptionChanged -= value; } /// public event EventHandler? DurationChanged { add => _collection.DurationChanged += value; remove => _collection.DurationChanged -= value; } /// public event EventHandler? LastPlayedChanged { add => _collection.LastPlayedChanged += value; remove => _collection.LastPlayedChanged -= value; } /// public event EventHandler? IsChangeNameAsyncAvailableChanged { add => _collection.IsChangeNameAsyncAvailableChanged += value; remove => _collection.IsChangeNameAsyncAvailableChanged -= value; } /// public event EventHandler? IsChangeDescriptionAsyncAvailableChanged { add => _collection.IsChangeDescriptionAsyncAvailableChanged += value; remove => _collection.IsChangeDescriptionAsyncAvailableChanged -= value; } /// public event EventHandler? IsChangeDurationAsyncAvailableChanged { add => _collection.IsChangeDurationAsyncAvailableChanged += value; remove => _collection.IsChangeDurationAsyncAvailableChanged -= value; } /// public event EventHandler? IsPauseArtistCollectionAsyncAvailableChanged { add => _collection.IsPauseArtistCollectionAsyncAvailableChanged += value; remove => _collection.IsPauseArtistCollectionAsyncAvailableChanged -= value; } /// public event EventHandler? IsPlayArtistCollectionAsyncAvailableChanged { add => _collection.IsPlayArtistCollectionAsyncAvailableChanged += value; remove => _collection.IsPlayArtistCollectionAsyncAvailableChanged -= value; } /// public event EventHandler? ArtistItemsCountChanged { add => _collection.ArtistItemsCountChanged += value; remove => _collection.ArtistItemsCountChanged -= value; } /// public event CollectionChangedEventHandler? ArtistItemsChanged { add => _collection.ArtistItemsChanged += value; remove => _collection.ArtistItemsChanged -= value; } /// public event CollectionChangedEventHandler? ImagesChanged { add => _collection.ImagesChanged += value; remove => _collection.ImagesChanged -= value; } /// public event EventHandler? ImagesCountChanged { add => _collection.ImagesCountChanged += value; remove => _collection.ImagesCountChanged -= value; } /// public event CollectionChangedEventHandler? UrlsChanged { add => _collection.UrlsChanged += value; remove => _collection.UrlsChanged -= value; } /// public event EventHandler? UrlsCountChanged { add => _collection.UrlsCountChanged += value; remove => _collection.UrlsCountChanged -= value; } /// public string Id => _collection.Id; /// public bool IsChangeNameAsyncAvailable => _collection.IsChangeNameAsyncAvailable; /// public bool IsChangeDescriptionAsyncAvailable => _collection.IsChangeDescriptionAsyncAvailable; /// public bool IsChangeDurationAsyncAvailable => _collection.IsChangeDurationAsyncAvailable; /// public string Name => _collection.Name; /// public string? Description => _collection.Description; /// public PlaybackState PlaybackState => _collection.PlaybackState; /// public DownloadInfo DownloadInfo => _collection.DownloadInfo; /// public ArtistSortingType CurrentArtistSortingType { get; private set; } /// public SortDirection CurrentArtistSortingDirection { get; private set; } /// public TimeSpan Duration => _collection.Duration; /// public DateTime? LastPlayed => _collection.LastPlayed; /// public DateTime? AddedAt => _collection.AddedAt; /// public ObservableCollection Artists { get; } /// public ObservableCollection UnsortedArtists { get; } /// public ObservableCollection Images { get; } /// public ObservableCollection Urls { get; } /// public int TotalArtistItemsCount => _collection.TotalArtistItemsCount; /// public int TotalImageCount => _collection.TotalImageCount; /// public int TotalUrlCount => _collection.TotalUrlCount; /// public bool IsPlayArtistCollectionAsyncAvailable => _collection.IsPlayArtistCollectionAsyncAvailable; /// public bool IsPauseArtistCollectionAsyncAvailable => _collection.IsPauseArtistCollectionAsyncAvailable; /// public bool IsInitialized { get; private set; } /// /// The sources that were merged to form this member. /// public IReadOnlyList Sources => _collection.GetSources(); /// IReadOnlyList IMerged.Sources => Sources; /// IReadOnlyList IMerged.Sources => Sources; /// IReadOnlyList IMerged.Sources => Sources; /// IReadOnlyList IMerged.Sources => Sources; /// public void SortArtistCollection(ArtistSortingType artistSorting, SortDirection sortDirection) { CurrentArtistSortingType = artistSorting; CurrentArtistSortingDirection = sortDirection; CollectionSorting.SortArtists(Artists, artistSorting, sortDirection, UnsortedArtists); } /// public Task IsAddArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsAddArtistItemAvailableAsync(index, cancellationToken); /// public Task IsRemoveArtistItemAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsRemoveArtistItemAvailableAsync(index, cancellationToken); /// public Task IsAddImageAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsAddImageAvailableAsync(index, cancellationToken); /// public Task IsRemoveImageAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsRemoveImageAvailableAsync(index, cancellationToken); /// public Task IsAddUrlAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsAddUrlAvailableAsync(index, cancellationToken); /// public Task IsRemoveUrlAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsRemoveUrlAvailableAsync(index, cancellationToken); /// public Task StartDownloadOperationAsync(DownloadOperation operation, CancellationToken cancellationToken = default) => _collection.StartDownloadOperationAsync(operation, cancellationToken); /// public Task ChangeNameAsync(string name, CancellationToken cancellationToken = default) => ChangeNameInternalAsync(name, cancellationToken); /// public Task ChangeDescriptionAsync(string? description, CancellationToken cancellationToken = default) => _collection.ChangeDescriptionAsync(description, cancellationToken); /// public Task ChangeDurationAsync(TimeSpan duration, CancellationToken cancellationToken = default) => _collection.ChangeDurationAsync(duration, cancellationToken); /// public Task AddArtistItemAsync(IArtistCollectionItem artistItem, int index, CancellationToken cancellationToken = default) => _collection.AddArtistItemAsync(artistItem, index, cancellationToken); /// public Task RemoveArtistItemAsync(int index, CancellationToken cancellationToken = default) => _collection.RemoveArtistItemAsync(index, cancellationToken); /// public Task AddImageAsync(IImage image, int index, CancellationToken cancellationToken = default) => _collection.AddImageAsync(image, index, cancellationToken); /// public Task RemoveImageAsync(int index, CancellationToken cancellationToken = default) => _collection.RemoveImageAsync(index, cancellationToken); /// public Task AddUrlAsync(IUrl image, int index, CancellationToken cancellationToken = default) => _collection.AddUrlAsync(image, index, cancellationToken); /// public Task RemoveUrlAsync(int index, CancellationToken cancellationToken = default) => _collection.RemoveUrlAsync(index, cancellationToken); /// public Task PlayArtistCollectionAsync(CancellationToken cancellationToken = default) => _collection.PlayArtistCollectionAsync(cancellationToken); /// public Task PauseArtistCollectionAsync(CancellationToken cancellationToken = default) => _collection.PlayArtistCollectionAsync(cancellationToken); /// public Task PlayArtistCollectionAsync(IArtistCollectionItem artistItem, CancellationToken cancellationToken = default) => _collection.PlayArtistCollectionAsync(artistItem, cancellationToken); /// public IAsyncEnumerable GetArtistItemsAsync(int limit, int offset, CancellationToken cancellationToken = default) => _collection.GetArtistItemsAsync(limit, offset, cancellationToken); /// public IAsyncEnumerable GetImagesAsync(int limit, int offset, CancellationToken cancellationToken = default) => _collection.GetImagesAsync(limit, offset, cancellationToken); /// public IAsyncEnumerable GetUrlsAsync(int limit, int offset, CancellationToken cancellationToken = default) => _collection.GetUrlsAsync(limit, offset, cancellationToken); /// public async Task PopulateMoreArtistsAsync(int limit, CancellationToken cancellationToken = default) { using (await Flow.EasySemaphore(_populateArtistsMutex)) { using var releaseReg = cancellationToken.Register(() => _populateArtistsMutex.Release()); _syncContext.Post(async _ => { await foreach (var item in _collection.GetArtistItemsAsync(limit, Artists.Count, cancellationToken)) { switch (item) { case IArtist artist: var avm = new ArtistViewModel(artist); Artists.Add(avm); UnsortedArtists.Add(avm); break; case IArtistCollection collection: var acvm = new ArtistCollectionViewModel(collection); Artists.Add(acvm); UnsortedArtists.Add(acvm); break; } } }, null); } } /// public async Task PopulateMoreImagesAsync(int limit, CancellationToken cancellationToken = default) { using (await Flow.EasySemaphore(_populateImagesMutex)) { using var releaseReg = cancellationToken.Register(() => _populateImagesMutex.Release()); _syncContext.Post(async _ => { await foreach (var item in _collection.GetImagesAsync(limit, Images.Count, cancellationToken)) Images.Add(item); }, null); } } /// public async Task PopulateMoreUrlsAsync(int limit, CancellationToken cancellationToken = default) { using (await Flow.EasySemaphore(_populateUrlsMutex)) { using var releaseReg = cancellationToken.Register(() => _populateUrlsMutex.Release()); _syncContext.Post(async _ => { await foreach (var item in _collection.GetUrlsAsync(limit, Urls.Count, cancellationToken)) Urls.Add(item); }, null); } } /// public Task InitImageCollectionAsync(CancellationToken cancellationToken = default) => CollectionInit.ImageCollection(this, cancellationToken); /// public Task InitArtistCollectionAsync(CancellationToken cancellationToken = default) => CollectionInit.ArtistCollection(this, cancellationToken); /// public IAsyncRelayCommand InitArtistCollectionAsyncCommand { get; } /// public IAsyncRelayCommand PopulateMoreArtistsCommand { get; } /// public IAsyncRelayCommand PopulateMoreImagesCommand { get; } /// public IAsyncRelayCommand PopulateMoreUrlsCommand { get; } /// public IAsyncRelayCommand PlayArtistAsyncCommand { get; } /// public IAsyncRelayCommand PlayArtistCollectionAsyncCommand { get; } /// public IAsyncRelayCommand PauseArtistCollectionAsyncCommand { get; } /// public IRelayCommand ChangeArtistCollectionSortingTypeCommand { get; } /// public IRelayCommand ChangeArtistCollectionSortingDirectionCommand { get; } /// /// Command to change the name. It triggers . /// public IAsyncRelayCommand ChangeNameAsyncCommand { get; } /// /// Command to change the description. It triggers . /// public IAsyncRelayCommand ChangeDescriptionAsyncCommand { get; } /// /// Command to change the duration. It triggers . /// public IAsyncRelayCommand ChangeDurationAsyncCommand { get; } /// public IAsyncRelayCommand InitImageCollectionAsyncCommand { get; } /// public bool Equals(ICoreArtistCollectionItem other) => _collection.Equals(other); /// public bool Equals(ICoreImageCollection other) => _collection.Equals(other); /// public bool Equals(ICoreUrlCollection other) => _collection.Equals(other); /// public bool Equals(ICoreArtistCollection other) => _collection.Equals(other); private Task ChangeNameInternalAsync(string? name, CancellationToken cancellationToken = default) { Guard.IsNotNull(name, nameof(name)); return _collection.ChangeNameAsync(name, cancellationToken); } /// public ValueTask DisposeAsync() { DetachEvents(); return _collection.DisposeAsync(); } } }