// 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 ViewModel for .
///
public sealed class PlaylistCollectionViewModel : ObservableObject, ISdkViewModel, IPlaylistCollectionViewModel, IImageCollectionViewModel, IUrlCollectionViewModel
{
private readonly IPlaylistCollection _collection;
private readonly SemaphoreSlim _populatePlaylistsMutex = 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 PlaylistCollectionViewModel(IPlaylistCollection collection)
{
_syncContext = SynchronizationContext.Current;
_collection = collection;
Playlists = new ObservableCollection();
Images = new ObservableCollection();
Urls = new ObservableCollection();
UnsortedPlaylists = new ObservableCollection();
PlayPlaylistCollectionAsyncCommand = new AsyncRelayCommand(PlayPlaylistCollectionAsync);
PausePlaylistCollectionAsyncCommand = new AsyncRelayCommand(PausePlaylistCollectionAsync);
ChangePlaylistCollectionSortingTypeCommand = new RelayCommand(x => SortPlaylistCollection(x, CurrentPlaylistSortingDirection));
ChangePlaylistCollectionSortingDirectionCommand = new RelayCommand(x => SortPlaylistCollection(CurrentPlaylistSortingType, x));
PlayPlaylistAsyncCommand = new AsyncRelayCommand((x, y) => _collection.PlayPlaylistCollectionAsync(x ?? ThrowHelper.ThrowArgumentNullException(), y));
PopulateMorePlaylistsCommand = new AsyncRelayCommand(PopulateMorePlaylistsAsync);
PopulateMoreImagesCommand = new AsyncRelayCommand(PopulateMoreImagesAsync);
PopulateMoreUrlsCommand = new AsyncRelayCommand(PopulateMoreUrlsAsync);
ChangeNameAsyncCommand = new AsyncRelayCommand(ChangeNameInternalAsync);
ChangeDescriptionAsyncCommand = new AsyncRelayCommand(ChangeDescriptionAsync);
ChangeDurationAsyncCommand = new AsyncRelayCommand(ChangeDurationAsync);
InitImageCollectionAsyncCommand = new AsyncRelayCommand(InitImageCollectionAsync);
InitPlaylistCollectionAsyncCommand = new AsyncRelayCommand(InitPlaylistCollectionAsync);
AttachEvents();
}
private void AttachEvents()
{
PlaybackStateChanged += OnPlaybackStateChanged;
NameChanged += OnNameChanged;
DescriptionChanged += OnDescriptionChanged;
LastPlayedChanged += OnLastPlayedChanged;
DownloadInfoChanged -= OnDownloadInfoChanged;
IsPlayPlaylistCollectionAsyncAvailableChanged += OnIsPlayPlaylistCollectionAsyncAvailableChanged;
IsPausePlaylistCollectionAsyncAvailableChanged += OnIsPausePlaylistCollectionAsyncAvailableChanged;
IsChangeNameAsyncAvailableChanged += OnIsChangeNameAsyncAvailableChanged;
IsChangeDurationAsyncAvailableChanged += OnIsChangeDurationAsyncAvailableChanged;
IsChangeDescriptionAsyncAvailableChanged += OnIsChangeDescriptionAsyncAvailableChanged;
PlaylistItemsCountChanged += OnPlaylistItemsCountChanged;
ImagesCountChanged += PlaylistCollectionViewModel_ImagesCountChanged;
PlaylistItemsChanged += PlaylistCollectionViewModel_PlaylistItemsChanged;
ImagesChanged += PlaylistCollectionViewModel_ImagesChanged;
}
private void DetachEvents()
{
PlaybackStateChanged -= OnPlaybackStateChanged;
NameChanged -= OnNameChanged;
DescriptionChanged -= OnDescriptionChanged;
LastPlayedChanged -= OnLastPlayedChanged;
DownloadInfoChanged -= OnDownloadInfoChanged;
IsPlayPlaylistCollectionAsyncAvailableChanged -= OnIsPlayPlaylistCollectionAsyncAvailableChanged;
IsPausePlaylistCollectionAsyncAvailableChanged -= OnIsPausePlaylistCollectionAsyncAvailableChanged;
IsChangeNameAsyncAvailableChanged -= OnIsChangeNameAsyncAvailableChanged;
IsChangeDurationAsyncAvailableChanged -= OnIsChangeDurationAsyncAvailableChanged;
IsChangeDescriptionAsyncAvailableChanged -= OnIsChangeDescriptionAsyncAvailableChanged;
PlaylistItemsCountChanged -= OnPlaylistItemsCountChanged;
ImagesCountChanged -= PlaylistCollectionViewModel_ImagesCountChanged;
PlaylistItemsChanged -= PlaylistCollectionViewModel_PlaylistItemsChanged;
ImagesChanged -= PlaylistCollectionViewModel_ImagesChanged;
}
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 OnPlaylistItemsCountChanged(object sender, int e) => _syncContext.Post(_ => OnPropertyChanged(nameof(TotalPlaylistItemsCount)), null);
private void PlaylistCollectionViewModel_ImagesCountChanged(object sender, int e) => _syncContext.Post(_ => OnPropertyChanged(nameof(TotalImageCount)), 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 OnIsPausePlaylistCollectionAsyncAvailableChanged(object sender, bool e) => _syncContext.Post(_ => OnPropertyChanged(nameof(IsPausePlaylistCollectionAsyncAvailable)), null);
private void OnIsPlayPlaylistCollectionAsyncAvailableChanged(object sender, bool e) => _syncContext.Post(_ => OnPropertyChanged(nameof(IsPlayPlaylistCollectionAsyncAvailable)), null);
private void PlaylistCollectionViewModel_ImagesChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems) => _syncContext.Post(_ =>
{
Images.ChangeCollection(addedItems, removedItems);
}, null);
private void PlaylistCollectionViewModel_PlaylistItemsChanged(object sender, IReadOnlyList> addedItems, IReadOnlyList> removedItems) => _syncContext.Post(_ =>
{
if (CurrentPlaylistSortingType == PlaylistSortingType.Unsorted)
{
Playlists.ChangeCollection(addedItems, removedItems, item => item.Data switch
{
IPlaylist playlist => new PlaylistViewModel(playlist),
IPlaylistCollection collection => new PlaylistCollectionViewModel(collection),
_ => ThrowHelper.ThrowNotSupportedException(
$"{item.Data.GetType()} not supported for adding to {GetType()}")
});
}
else
{
// Make sure both ordered and unordered playlists are updated.
UnsortedPlaylists.ChangeCollection(addedItems, removedItems, item => item.Data switch
{
IPlaylist playlist => new PlaylistViewModel(playlist),
IPlaylistCollection collection => new PlaylistCollectionViewModel(collection),
_ => ThrowHelper.ThrowNotSupportedException(
$"{item.Data.GetType()} not supported for adding to {GetType()}")
});
foreach (var item in UnsortedPlaylists)
{
if (!Playlists.Contains(item))
Playlists.Add(item);
}
foreach (var item in Playlists.ToArray())
{
if (!UnsortedPlaylists.Contains(item))
Playlists.Remove(item);
}
SortPlaylistCollection(CurrentPlaylistSortingType, CurrentPlaylistSortingDirection);
}
}, 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? IsPlayPlaylistCollectionAsyncAvailableChanged
{
add => _collection.IsPlayPlaylistCollectionAsyncAvailableChanged += value;
remove => _collection.IsPlayPlaylistCollectionAsyncAvailableChanged -= value;
}
///
public event EventHandler? IsPausePlaylistCollectionAsyncAvailableChanged
{
add => _collection.IsPausePlaylistCollectionAsyncAvailableChanged += value;
remove => _collection.IsPausePlaylistCollectionAsyncAvailableChanged -= value;
}
///
public event EventHandler? PlaylistItemsCountChanged
{
add => _collection.PlaylistItemsCountChanged += value;
remove => _collection.PlaylistItemsCountChanged -= value;
}
///
public event CollectionChangedEventHandler? PlaylistItemsChanged
{
add => _collection.PlaylistItemsChanged += value;
remove => _collection.PlaylistItemsChanged -= 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 string Name => _collection.Name;
///
public int TotalPlaylistItemsCount => _collection.TotalPlaylistItemsCount;
///
public int TotalImageCount => _collection.TotalImageCount;
///
public int TotalUrlCount => _collection.TotalUrlCount;
///
public string? Description => _collection.Description;
///
public PlaybackState PlaybackState => _collection.PlaybackState;
///
public DownloadInfo DownloadInfo => _collection.DownloadInfo;
///
public TimeSpan Duration => _collection.Duration;
///
public DateTime? LastPlayed => _collection.LastPlayed;
///
public DateTime? AddedAt => _collection.AddedAt;
///
public PlaylistSortingType CurrentPlaylistSortingType { get; private set; }
///
public SortDirection CurrentPlaylistSortingDirection { get; private set; }
///
public ObservableCollection UnsortedPlaylists { get; }
///
public ObservableCollection Playlists { get; }
///
public ObservableCollection Images { get; }
///
public ObservableCollection Urls { get; }
///
IReadOnlyList IMerged.Sources => Sources;
///
IReadOnlyList IMerged.Sources => Sources;
///
IReadOnlyList IMerged.Sources => Sources;
///
IReadOnlyList IMerged.Sources => Sources;
///
/// The items that were merged to form this .
///
public IReadOnlyList Sources => _collection.GetSources();
///
public bool IsPlayPlaylistCollectionAsyncAvailable => _collection.IsPlayPlaylistCollectionAsyncAvailable;
///
public bool IsPausePlaylistCollectionAsyncAvailable => _collection.IsPausePlaylistCollectionAsyncAvailable;
///
public bool IsChangeNameAsyncAvailable => _collection.IsChangeNameAsyncAvailable;
///
public bool IsChangeDescriptionAsyncAvailable => _collection.IsChangeDescriptionAsyncAvailable;
///
public bool IsChangeDurationAsyncAvailable => _collection.IsChangeDurationAsyncAvailable;
///
public Task IsAddPlaylistItemAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsAddPlaylistItemAvailableAsync(index, cancellationToken);
///
public Task IsAddImageAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsAddImageAvailableAsync(index, cancellationToken);
///
public Task IsAddUrlAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsAddUrlAvailableAsync(index, cancellationToken);
///
public Task IsRemovePlaylistItemAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsRemovePlaylistItemAvailableAsync(index, cancellationToken);
///
public Task IsRemoveImageAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsRemoveImageAvailableAsync(index, cancellationToken);
///
public Task IsRemoveUrlAvailableAsync(int index, CancellationToken cancellationToken = default) => _collection.IsRemoveUrlAvailableAsync(index, 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 ChangeNameAsync(string name, CancellationToken cancellationToken = default) => ChangeNameInternalAsync(name, cancellationToken);
///
public Task StartDownloadOperationAsync(DownloadOperation operation, CancellationToken cancellationToken = default) => _collection.StartDownloadOperationAsync(operation, cancellationToken);
///
public Task AddPlaylistItemAsync(IPlaylistCollectionItem playlistItem, int index, CancellationToken cancellationToken = default) => _collection.AddPlaylistItemAsync(playlistItem, index, cancellationToken);
///
public Task AddImageAsync(IImage image, int index, CancellationToken cancellationToken = default) => _collection.AddImageAsync(image, index, cancellationToken);
///
public Task AddUrlAsync(IUrl image, int index, CancellationToken cancellationToken = default) => _collection.AddUrlAsync(image, index, cancellationToken);
///
public Task RemovePlaylistItemAsync(int index, CancellationToken cancellationToken = default) => _collection.RemovePlaylistItemAsync(index, cancellationToken);
///
public Task RemoveImageAsync(int index, CancellationToken cancellationToken = default) => _collection.RemoveImageAsync(index, cancellationToken);
///
public Task RemoveUrlAsync(int index, CancellationToken cancellationToken = default) => _collection.RemoveUrlAsync(index, cancellationToken);
///
public Task PlayPlaylistCollectionAsync(CancellationToken cancellationToken = default) => _collection.PlayPlaylistCollectionAsync(cancellationToken);
///
public Task PlayPlaylistCollectionAsync(IPlaylistCollectionItem playlistItem, CancellationToken cancellationToken = default) => _collection.PlayPlaylistCollectionAsync(playlistItem, cancellationToken);
///
public Task PausePlaylistCollectionAsync(CancellationToken cancellationToken = default) => _collection.PausePlaylistCollectionAsync(cancellationToken);
///
public IAsyncEnumerable GetPlaylistItemsAsync(int limit, int offset, CancellationToken cancellationToken = default) => _collection.GetPlaylistItemsAsync(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 PopulateMorePlaylistsAsync(int limit, CancellationToken cancellationToken = default)
{
using (await Flow.EasySemaphore(_populatePlaylistsMutex))
{
using var releaseReg = cancellationToken.Register(() => _populatePlaylistsMutex.Release());
_syncContext.Post(async _ =>
{
await foreach (var item in _collection.GetPlaylistItemsAsync(limit, Playlists.Count, cancellationToken))
{
switch (item)
{
case IPlaylist playlist:
var pvm = new PlaylistViewModel(playlist);
Playlists.Add(pvm);
UnsortedPlaylists.Add(pvm);
break;
case IPlaylistCollection collection:
var pcvm = new PlaylistCollectionViewModel(collection);
Playlists.Add(pcvm);
UnsortedPlaylists.Add(pcvm);
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 InitPlaylistCollectionAsync(CancellationToken cancellationToken = default) => CollectionInit.PlaylistCollection(this, cancellationToken);
///
public Task InitImageCollectionAsync(CancellationToken cancellationToken = default) => CollectionInit.ImageCollection(this, cancellationToken);
///
public void SortPlaylistCollection(PlaylistSortingType playlistSorting, SortDirection sortDirection)
{
CurrentPlaylistSortingType = playlistSorting;
CurrentPlaylistSortingDirection = sortDirection;
CollectionSorting.SortPlaylists(Playlists, playlistSorting, sortDirection, UnsortedPlaylists);
}
///
/// 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 IRelayCommand ChangePlaylistCollectionSortingTypeCommand { get; }
///
public IRelayCommand ChangePlaylistCollectionSortingDirectionCommand { get; }
///
public IAsyncRelayCommand PlayPlaylistAsyncCommand { get; }
///
public IAsyncRelayCommand PlayPlaylistCollectionAsyncCommand { get; }
///
public IAsyncRelayCommand PausePlaylistCollectionAsyncCommand { get; }
///
public IAsyncRelayCommand PopulateMorePlaylistsCommand { get; }
///
public IAsyncRelayCommand PopulateMoreImagesCommand { get; }
///
public IAsyncRelayCommand PopulateMoreUrlsCommand { get; }
///
public Task InitAsync(CancellationToken cancellationToken = default)
{
if (IsInitialized)
return Task.CompletedTask;
IsInitialized = true;
return Task.WhenAll(InitImageCollectionAsync(cancellationToken), InitPlaylistCollectionAsync(cancellationToken));
}
///
public bool IsInitialized { get; private set; }
///
public IAsyncRelayCommand InitPlaylistCollectionAsyncCommand { get; }
///
public IAsyncRelayCommand InitImageCollectionAsyncCommand { get; }
///
public bool Equals(ICorePlaylistCollectionItem other) => _collection.Equals(other);
///
public bool Equals(ICorePlaylistCollection other) => _collection.Equals(other);
///
public bool Equals(ICoreImageCollection other) => _collection.Equals(other);
///
public bool Equals(ICoreUrlCollection 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();
}
}
}