Added ContextMenu/Flyout animations. Added platform services. Use bitmap cache for all views.
This commit is contained in:
@@ -1,30 +1,28 @@
|
||||
using Harmonia.Core.Player;
|
||||
using Harmonia.Core.Playlists;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.Media.Imaging;
|
||||
using System.Collections.Concurrent;
|
||||
using Harmonia.UI.Caching;
|
||||
using Harmonia.Core.Scanner;
|
||||
using Harmonia.Core.Models;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Harmonia.Core.Engine;
|
||||
using Harmonia.Core.Models;
|
||||
using Harmonia.Core.Player;
|
||||
using Harmonia.Core.Playlists;
|
||||
using Harmonia.Core.Scanner;
|
||||
using Harmonia.UI.Caching;
|
||||
using Harmonia.UI.Platform;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.VisualTree;
|
||||
using Avalonia.Rendering;
|
||||
using System.Diagnostics;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Harmonia.Core.Engine;
|
||||
using Avalonia.Input;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using System.Windows.Input;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Harmonia.UI.ViewModels;
|
||||
|
||||
@@ -34,8 +32,12 @@ public class PlaylistViewModel : ViewModelBase
|
||||
private readonly IAudioBitmapCache _audioBitmapImageCache;
|
||||
private readonly IAudioFileScanner _audioFileScanner;
|
||||
private readonly IAudioEngine _audioEngine;
|
||||
private readonly IStorageProviderLocator _storageProviderLocator;
|
||||
private readonly IClipboardLocator _clipboardLocator;
|
||||
private readonly ConcurrentDictionary<string, Bitmap> _bitmapDictionary = [];
|
||||
|
||||
private Timer? _filterTimer;
|
||||
|
||||
public Playlist? Playlist { get; private set; }
|
||||
public PlaylistSong? PlayingSong => _audioPlayer.PlayingSong;
|
||||
|
||||
@@ -53,6 +55,35 @@ public class PlaylistViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
private string? _filter;
|
||||
public string? Filter
|
||||
{
|
||||
get
|
||||
{
|
||||
return _filter;
|
||||
}
|
||||
set
|
||||
{
|
||||
_filter = value;
|
||||
OnPropertyChanged();
|
||||
RestartFilterTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private ObservableCollection<PlaylistSong> _filteredPlaylistSongs = [];
|
||||
public ObservableCollection<PlaylistSong> FilteredPlaylistSongs
|
||||
{
|
||||
get
|
||||
{
|
||||
return _filteredPlaylistSongs;
|
||||
}
|
||||
set
|
||||
{
|
||||
_filteredPlaylistSongs = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private ObservableCollection<PlaylistSong> _selectedPlaylistSongs = [];
|
||||
public ObservableCollection<PlaylistSong> SelectedPlaylistSongs
|
||||
{
|
||||
@@ -76,7 +107,15 @@ public class PlaylistViewModel : ViewModelBase
|
||||
public ICommand PasteSongsCommand => new AsyncRelayCommand(PasteSongsAsync, CanPasteSongs);
|
||||
public ICommand OpenFileLocationCommand => new RelayCommand(OpenFileLocation, AreSongsSelected);
|
||||
|
||||
public PlaylistViewModel(IAudioPlayer audioPlayer, IAudioBitmapCache audioBitmapImageCache, IAudioFileScanner audioFileScanner, IAudioEngine audioEngine)
|
||||
public ICommand RefreshTagsCommand => new RelayCommand(RefreshTags);
|
||||
public ICommand RemoveMissingSongsCommand => new RelayCommand(RemoveMissingSongs);
|
||||
public ICommand RemoveDuplicateSongsCommand => new RelayCommand(RemoveDuplicateSongs);
|
||||
|
||||
public PlaylistViewModel(IAudioPlayer audioPlayer, IAudioBitmapCache audioBitmapImageCache,
|
||||
IAudioFileScanner audioFileScanner,
|
||||
IAudioEngine audioEngine,
|
||||
IStorageProviderLocator storageProviderLocator,
|
||||
IClipboardLocator clipboardLocator)
|
||||
{
|
||||
_audioPlayer = audioPlayer;
|
||||
_audioPlayer.PlaylistChanged += OnAudioPlayerPlaylistChanged;
|
||||
@@ -85,6 +124,8 @@ public class PlaylistViewModel : ViewModelBase
|
||||
_audioBitmapImageCache = audioBitmapImageCache;
|
||||
_audioFileScanner = audioFileScanner;
|
||||
_audioEngine = audioEngine;
|
||||
_storageProviderLocator = storageProviderLocator;
|
||||
_clipboardLocator = clipboardLocator;
|
||||
}
|
||||
|
||||
private void OnAudioPlayerPlaylistChanged(object? sender, EventArgs e)
|
||||
@@ -104,6 +145,7 @@ public class PlaylistViewModel : ViewModelBase
|
||||
PlaylistSong[] playlistSongs = _audioPlayer.Playlist?.Songs.ToArray() ?? [];
|
||||
|
||||
PlaylistSongs = [.. playlistSongs];
|
||||
UpdateFilteredSongs();
|
||||
}
|
||||
|
||||
private void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e)
|
||||
@@ -128,6 +170,8 @@ public class PlaylistViewModel : ViewModelBase
|
||||
{
|
||||
PlaylistSongs.Insert(currentIndex++, playlistSong);
|
||||
}
|
||||
|
||||
UpdateFilteredSongs();
|
||||
}
|
||||
|
||||
private void RemoveSongsFromCollection(PlaylistSong[] playlistSongs)
|
||||
@@ -136,6 +180,8 @@ public class PlaylistViewModel : ViewModelBase
|
||||
{
|
||||
PlaylistSongs.Remove(playlistSong);
|
||||
}
|
||||
|
||||
UpdateFilteredSongs();
|
||||
}
|
||||
|
||||
private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e)
|
||||
@@ -153,6 +199,70 @@ public class PlaylistViewModel : ViewModelBase
|
||||
return await _audioBitmapImageCache.GetAsync(playlistSong.Song, cancellationToken);
|
||||
}
|
||||
|
||||
#region Filtering
|
||||
|
||||
private void RestartFilterTimer()
|
||||
{
|
||||
if (_filterTimer == null)
|
||||
{
|
||||
_filterTimer = new Timer(300);
|
||||
_filterTimer.Elapsed += OnFilterTimerElapsed;
|
||||
_filterTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_filterTimer.Interval = 300;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFilterTimerElapsed(object? sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (_filterTimer == null)
|
||||
return;
|
||||
|
||||
_filterTimer.Stop();
|
||||
_filterTimer.Dispose();
|
||||
_filterTimer = null;
|
||||
|
||||
Dispatcher.UIThread.Invoke(UpdateFilteredSongs);
|
||||
}
|
||||
|
||||
private void UpdateFilteredSongs()
|
||||
{
|
||||
if (Playlist == null)
|
||||
return;
|
||||
|
||||
List<PlaylistSong> filteredPlaylistSongs = [.. Playlist.Songs.Where(playlistSong => IsFiltered(playlistSong.Song))];
|
||||
FilteredPlaylistSongs = [.. filteredPlaylistSongs];
|
||||
}
|
||||
|
||||
private bool IsFiltered(Song song)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Filter))
|
||||
return true;
|
||||
|
||||
var shortFileName = Path.GetFileName(song.FileName);
|
||||
|
||||
if (shortFileName.Contains(Filter, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(song.Title) == false && song.Title.Contains(Filter, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(song.Album) == false && song.Album.Contains(Filter, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
if (song.AlbumArtists.Any(x => x.Contains(Filter, StringComparison.OrdinalIgnoreCase)))
|
||||
return true;
|
||||
|
||||
if (song.Artists.Any(x => x.Contains(Filter, StringComparison.OrdinalIgnoreCase)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
private async Task PlaySongAsync()
|
||||
@@ -173,7 +283,7 @@ public class PlaylistViewModel : ViewModelBase
|
||||
if (Playlist == null)
|
||||
return;
|
||||
|
||||
IStorageProvider? storageProvider = StorageProvider.Get();
|
||||
IStorageProvider? storageProvider = _storageProviderLocator.Get();
|
||||
|
||||
if (storageProvider == null)
|
||||
return;
|
||||
@@ -204,7 +314,7 @@ public class PlaylistViewModel : ViewModelBase
|
||||
if (Playlist == null)
|
||||
return;
|
||||
|
||||
IStorageProvider? storageProvider = StorageProvider.Get();
|
||||
IStorageProvider? storageProvider = _storageProviderLocator.Get();
|
||||
|
||||
if (storageProvider == null)
|
||||
return;
|
||||
@@ -277,7 +387,7 @@ public class PlaylistViewModel : ViewModelBase
|
||||
|
||||
private async Task CopySelectedSongsToClipboardAsync()
|
||||
{
|
||||
IClipboard? clipboard = Clipboard.Get();
|
||||
IClipboard? clipboard = _clipboardLocator.Get();
|
||||
|
||||
if (clipboard == null)
|
||||
return;
|
||||
@@ -307,7 +417,7 @@ public class PlaylistViewModel : ViewModelBase
|
||||
if (Playlist == null)
|
||||
return false;
|
||||
|
||||
IClipboard? clipboard = Clipboard.Get();
|
||||
IClipboard? clipboard = _clipboardLocator.Get();
|
||||
|
||||
if (clipboard == null)
|
||||
return false;
|
||||
@@ -336,7 +446,7 @@ public class PlaylistViewModel : ViewModelBase
|
||||
if (Playlist == null)
|
||||
return;
|
||||
|
||||
IClipboard? clipboard = Clipboard.Get();
|
||||
IClipboard? clipboard = _clipboardLocator.Get();
|
||||
|
||||
if (clipboard == null)
|
||||
return;
|
||||
@@ -369,55 +479,20 @@ public class PlaylistViewModel : ViewModelBase
|
||||
Process.Start("explorer.exe", argument);
|
||||
}
|
||||
|
||||
private void RefreshTags()
|
||||
{
|
||||
//Playlist?.RefreshTags();
|
||||
}
|
||||
|
||||
private void RemoveMissingSongs()
|
||||
{
|
||||
Playlist?.RemoveMissingSongs();
|
||||
}
|
||||
|
||||
private void RemoveDuplicateSongs()
|
||||
{
|
||||
Playlist?.RemoveDuplicateSongs();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class Clipboard
|
||||
{
|
||||
public static IClipboard? Get()
|
||||
{
|
||||
|
||||
//Desktop
|
||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
return desktop.MainWindow?.Clipboard;
|
||||
}
|
||||
//Android (and iOS?)
|
||||
else if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
||||
{
|
||||
IRenderRoot? visualRoot = singleViewPlatform.MainView?.GetVisualRoot();
|
||||
|
||||
if (visualRoot is TopLevel topLevel)
|
||||
{
|
||||
return topLevel.Clipboard;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class StorageProvider
|
||||
{
|
||||
public static IStorageProvider? Get()
|
||||
{
|
||||
|
||||
//Desktop
|
||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
return desktop.MainWindow?.StorageProvider;
|
||||
}
|
||||
//Android (and iOS?)
|
||||
else if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
||||
{
|
||||
IRenderRoot? visualRoot = singleViewPlatform.MainView?.GetVisualRoot();
|
||||
|
||||
if (visualRoot is TopLevel topLevel)
|
||||
{
|
||||
return topLevel.StorageProvider;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user