using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CommunityToolkit.Diagnostics;
using CommunityToolkit.Mvvm.Collections;
using Microsoft.Toolkit.Uwp.UI.Animations;
using StrixMusic.Sdk.AppModels;
using StrixMusic.Sdk.ViewModels;
using StrixMusic.Sdk.WinUI.Controls.Collections;
using StrixMusic.Shells.ZuneDesktop.Controls.Views.Collections;
using StrixMusic.Shells.ZuneDesktop.Controls.Views.Items;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
namespace StrixMusic.Shells.ZuneDesktop.Controls.Views.Collection
{
///
/// The collection to perform zune specific behaviors.
///
public class ZuneAlbumCollection : AlbumCollection
{
private object _lockObj = new object();
private ResourceLoader _loacalizationService;
///
/// Creates a new instance of .
///
public ZuneAlbumCollection()
{
AlbumGroupedByCollection = new ObservableGroupedCollection();
_loacalizationService = ResourceLoader.GetForCurrentView("StrixMusic.Shells.ZuneDesktop/ZuneSettings");
}
///
/// Holds the current state of the zune .
///
public ObservableGroupedCollection AlbumGroupedByCollection
{
get { return (ObservableGroupedCollection)GetValue(AlbumGroupedByCollectionProperty); }
set { SetValue(AlbumGroupedByCollectionProperty, value); }
}
///
/// Dependency property for .
///
public static readonly DependencyProperty AlbumGroupedByCollectionProperty =
DependencyProperty.Register(nameof(AlbumGroupedByCollection), typeof(ObservableGroupedCollection>), typeof(ZuneAlbumCollection), new PropertyMetadata(null, null));
///
/// Holds the current state of the zune .
///
public CollectionContentType ZuneCollectionType
{
get { return (CollectionContentType)GetValue(ZuneCollectionTypeProperty); }
set { SetValue(ZuneCollectionTypeProperty, value); }
}
///
/// Dependency property for .
///
public static readonly DependencyProperty ZuneCollectionTypeProperty =
DependencyProperty.Register(nameof(ZuneCollectionType), typeof(CollectionContent), typeof(ZuneAlbumCollection), new PropertyMetadata(CollectionContentType.Albums, null));
///
/// Holds the instance of the sort textblock.
///
public TextBlock? PART_SortLbl { get; private set; }
///
/// Flag to determine if albums are already loaded.
///
public bool AlbumsLoaded { get; private set; }
///
/// The AlbumCollection GridView control.
///
public GridView? PART_Selector { get; private set; }
///
/// Dependency property for .
///
public static readonly DependencyProperty SortStateProperty =
DependencyProperty.Register(nameof(SortState), typeof(ZuneSortState), typeof(ZuneArtistCollection), new PropertyMetadata(ZuneSortState.AZ, null));
///
/// Holds the current sort state of the zune .
///
public ZuneSortState SortState
{
get { return (ZuneSortState)GetValue(SortStateProperty); }
set { SetValue(SortStateProperty, value); }
}
///
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
PART_Selector = GetTemplateChild(nameof(PART_Selector)) as GridView;
PART_SortLbl = GetTemplateChild(nameof(PART_SortLbl)) as TextBlock;
Guard.IsNotNull(PART_SortLbl, nameof(PART_SortLbl));
Guard.IsNotNull(PART_Selector, nameof(PART_Selector));
PART_Selector.Loaded += PART_Selector_Loaded;
Unloaded += ZuneAlbumCollection_Unloaded;
PART_SortLbl.Tapped += PART_SortLbl_Tapped;
}
private void PART_SortLbl_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
Guard.IsNotNull(PART_Selector, nameof(PART_Selector));
Guard.IsNotNull(PART_SortLbl, nameof(PART_SortLbl));
Guard.IsNotNull(Collection, nameof(Collection));
switch (SortState)
{
// State defines the next sort state in queue which differs from the current state.
case ZuneSortState.AZ:
Collection.SortAlbumCollection(AlbumSortingType.Alphanumerical, SortDirection.Descending);
PART_Selector.ItemsSource = Collection.Albums;
SortState = ZuneSortState.ZA;
PART_SortLbl.Text = _loacalizationService.GetString("Z-A");
break;
case ZuneSortState.ZA:
PopulateAlbumGroupedByArtists();
SortState = ZuneSortState.Artists;
PART_SortLbl.Text = _loacalizationService.GetString("By Artists");
break;
case ZuneSortState.Artists:
PopulateAlbumGroupedByReleaseYear();
SortState = ZuneSortState.ReleaseYear;
PART_SortLbl.Text = _loacalizationService.GetString("By Release Year");
break;
case ZuneSortState.ReleaseYear:
Collection.SortAlbumCollection(AlbumSortingType.DateAdded, SortDirection.Ascending);
PART_Selector.ItemsSource = Collection.Albums;
SortState = ZuneSortState.DateAdded;
PART_SortLbl.Text = _loacalizationService.GetString("By Date Added");
break;
case ZuneSortState.DateAdded:
Collection.SortAlbumCollection(AlbumSortingType.Alphanumerical, SortDirection.Ascending);
PART_Selector.ItemsSource = Collection.Albums;
SortState = ZuneSortState.AZ;
PART_SortLbl.Text = _loacalizationService.GetString("A-Z");
break;
default:
throw new InvalidOperationException();
}
}
private async void Albums_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
// Intentional delay (for safe side), this doesn't freeze anything as event attachment can be done silently, it waits for the emitted album to load in Visual Tree.
// There is no event on GridView that tells when a UI Element is added to Items list.
// ------------
// TODO: We shouldn't be using a delay to do this.
// ------------
// TODO: this method of getting a loaded item does not work with virtualization.
// Instead
// - Create a custom ViewModel that wraps around AlbumViewModel
// - Give it a "RequestPlaybackCommand" we can invoke from XAML, and a "PlaybackRequested" event that's raised by the command.
// - Put it in a new ObservableCollection property. Bind to that in the UI instead of what we use now.
// - Use behaviors in XAML to invoke this new command when the "ZuneAlbumItem.AlbumPlaybackTriggered" event fires.
// - Sync the new ObservableCollection with the source, wrapping the data with our new ViewModel.
// - Listen to the event on each item in this new ViewModel and invoke playback when it's called
// - Clean up all the code from the existing approach.
await Task.Delay(1000);
Guard.IsNotNull(PART_Selector);
Guard.IsNotNull(Collection);
foreach (var item in e.NewItems)
{
Guard.IsNotNull(item, nameof(item));
var index = Collection.Albums.IndexOf((IAlbumCollectionItem)item);
var gridViewItem = (GridViewItem)PART_Selector.ContainerFromIndex(index);
// PATCH! HACK! See above TODO.
if (gridViewItem is null)
{
continue;
}
var uiElement = gridViewItem.ContentTemplateRoot;
if (uiElement is ZuneAlbumItem zuneAlbumItem)
{
zuneAlbumItem.AlbumPlaybackTriggered += ZuneAlbumItem_AlbumPlaybackTriggered;
zuneAlbumItem.Unloaded += ZuneAlbumItem_Unloaded;
zuneAlbumItem.ZuneCollectionType = ZuneCollectionType;
}
}
try
{
// Gives breathing space to the processor and reduces the odds the where observable collection changed during CollectionChanged event to minimum.
lock (_lockObj)
Collection.SortAlbumCollection(AlbumSortingType.Alphanumerical, SortDirection.Ascending);
}
catch (InvalidOperationException)
{
// It handles a rare case where observable collection changed during CollectionChanged event.
// More precisely "Cannot change ObservableCollection during a CollectionChanged event."
}
}
}
///
protected override void OnCollectionChanged(IAlbumCollectionViewModel? oldValue, IAlbumCollectionViewModel? newValue)
{
if (oldValue is IAlbumCollectionViewModel oldCollection)
{
oldCollection.Albums.CollectionChanged -= Albums_CollectionChanged;
}
if (newValue is IAlbumCollectionViewModel newCollection)
{
newCollection.Albums.CollectionChanged += Albums_CollectionChanged;
}
base.OnCollectionChanged(oldValue, newValue);
}
private void ZuneAlbumItem_Unloaded(object sender, RoutedEventArgs e)
{
if (sender is ZuneAlbumItem zuneAlbum)
zuneAlbum.AlbumPlaybackTriggered -= ZuneAlbumItem_AlbumPlaybackTriggered;
}
private void ZuneAlbumCollection_Unloaded(object sender, RoutedEventArgs e)
{
Guard.IsNotNull(PART_Selector, nameof(PART_Selector));
Guard.IsNotNull(PART_SortLbl, nameof(PART_SortLbl));
PART_Selector.Loaded -= PART_Selector_Loaded;
Unloaded -= ZuneAlbumCollection_Unloaded;
PART_SortLbl.Tapped -= PART_SortLbl_Tapped;
}
private void PART_Selector_Loaded(object sender, RoutedEventArgs e)
{
AnimateCollection();
}
///
/// Gets the list of the and animates it.
///
public void AnimateCollection()
{
if (Collection is null)
return;
Guard.IsNotNull(PART_Selector, nameof(PART_Selector));
var uiElments = new List();
int itemIndex = 0;
foreach (var item in Collection.Albums)
{
// NOTE: ContainerFromItem doesn't work in uno.
var gridViewItem = (GridViewItem)PART_Selector.ContainerFromIndex(itemIndex);
if (gridViewItem == null)
break;
var uiElement = gridViewItem.ContentTemplateRoot;
if (uiElement is ZuneAlbumItem zuneAlbumItem)
{
zuneAlbumItem.AlbumPlaybackTriggered += ZuneAlbumItem_AlbumPlaybackTriggered;
zuneAlbumItem.ZuneCollectionType = ZuneCollectionType;
}
// This needs to be explicitly casted to UIElement to avoid a compiler error specific to android in uno.
uiElments.Add((UIElement)uiElement);
itemIndex++;
}
FadeInAlbumCollectionItems(uiElments);
}
private void PopulateAlbumGroupedByReleaseYear()
{
Guard.IsNotNull(PART_Selector, nameof(PART_Selector));
Guard.IsNotNull(Collection, nameof(Collection));
AlbumGroupedByCollection.Clear();
var typecastedAlbums = Collection.Albums.Where(c => c is AlbumViewModel).Cast();
AlbumGroupedByCollection = new ObservableGroupedCollection(
typecastedAlbums.GroupBy(c => c.DatePublished?.Year.ToString() ?? "Unknown")
.OrderBy(g => g.Key));
// Set up the CollectionViewSource.
var cvs = new CollectionViewSource
{
IsSourceGrouped = true,
Source = AlbumGroupedByCollection,
};
PART_Selector.ItemsSource = cvs.View;
}
private void PopulateAlbumGroupedByArtists()
{
Guard.IsNotNull(PART_Selector, nameof(PART_Selector));
Guard.IsNotNull(Collection, nameof(Collection));
AlbumGroupedByCollection.Clear();
var typecastedAlbums = Collection.Albums.Where(c => c is AlbumViewModel).Cast();
AlbumGroupedByCollection = new ObservableGroupedCollection(
typecastedAlbums.GroupBy(c => c.Artists.FirstOrDefault()?.Name ?? "Untitled")
.OrderBy(g => g.Key));
// Set up the CollectionViewSource.
var cvs = new CollectionViewSource
{
IsSourceGrouped = true,
Source = AlbumGroupedByCollection,
};
PART_Selector.ItemsSource = cvs.View;
}
private async void ZuneAlbumItem_AlbumPlaybackTriggered(object sender, AlbumViewModel e)
{
Guard.IsNotNull(Collection);
await Collection.PlayAlbumCollectionAsync(e);
}
private void FadeInAlbumCollectionItems(List uiElements)
{
double delay = 0;
foreach (var item in uiElements)
{
var animationSet = new AnimationSet();
var duration = 250;
animationSet.Add(new OpacityAnimation()
{
From = 0,
To = 1,
Duration = TimeSpan.FromMilliseconds(duration),
Delay = TimeSpan.FromMilliseconds(delay),
EasingMode = Windows.UI.Xaml.Media.Animation.EasingMode.EaseInOut,
EasingType = EasingType.Linear
});
delay += 75;
animationSet.Start(item);
}
AlbumsLoaded = true;
}
}
}