更新播放列表编辑

This commit is contained in:
LanZhan
2025-09-02 22:23:21 +08:00
parent 9b9b0d0b8a
commit 032445523e
18 changed files with 608 additions and 192 deletions

View File

@@ -1,10 +1,173 @@
<ContentDialog x:Class="The_Untamed_Music_Player.Controls.EditPlaylistInfoDialog"
<ContentDialog x:Uid="EditPlaylistInfoDialog"
x:Class="The_Untamed_Music_Player.Controls.EditPlaylistInfoDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:The_Untamed_Music_Player.Helpers"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:The_Untamed_Music_Player.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:toolkitConverters="using:CommunityToolkit.WinUI.Converters"
CloseButtonClick="CloseButtonClick"
DefaultButton="Primary"
PrimaryButtonClick="SaveButtonClick"
Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<Grid/>
<ContentDialog.Resources>
<x:Double x:Key="ContentDialogMaxWidth">2000</x:Double>
<toolkitConverters:EmptyObjectToObjectConverter x:Key="InverseEmptyObjectToVisibilityConverter"
EmptyValue="Visible" NotEmptyValue="Collapsed"/>
<DataTemplate x:Key="SongListViewTemplate" x:DataType="local:DisplaySongInfo">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="17"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind Type}"/>
<TextBlock Grid.Column="2"
Text="{x:Bind Song.Title}"
ToolTipService.ToolTip="{x:Bind Song.Title}"/>
</Grid>
</DataTemplate>
</ContentDialog.Resources>
<ScrollViewer Margin="-24,0,-24,0">
<StackPanel>
<Grid Width="625">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="Column0" Width="*"/>
<ColumnDefinition x:Name="Column1" Width="17"/>
<ColumnDefinition x:Name="Column2" Width="280"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0"
Padding="24,0,0,0"
Spacing="12">
<TextBox x:Name="PlaylistNameTextBox"
Text="{x:Bind _name, Mode=TwoWay}"
TextChanged="PlaylistNameTextBox_TextChanged">
<TextBox.Header>
<TextBlock x:Uid="ImportPlaylistDialog_Name"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
</TextBox.Header>
</TextBox>
<StackPanel>
<TextBlock x:Uid="ImportPlaylistDialog_NumberOfSongs"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
<TextBlock Text="{x:Bind SongCount, Mode=OneWay}"/>
</StackPanel>
<StackPanel Spacing="8">
<TextBlock x:Uid="ImportPlaylistDialog_ImportSongs"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
<Button x:Name="ImportFromM3u8FilesButton" x:Uid="ImportPlaylistDialog_ImportFromM3u8Files"
HorizontalAlignment="Stretch"
Click="ImportFromM3u8FilesButton_Click"/>
<Button x:Name="ImportFromFolderButton" x:Uid="ImportPlaylistDialog_ImportFromFolder"
HorizontalAlignment="Stretch"
Click="ImportFromFolderButton_Click"/>
</StackPanel>
<ProgressRing Width="40" Height="40"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsActive="{x:Bind IsImportingProgressRingActive, Mode=OneWay}"/>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="2"
Padding="0,0,24,0" VerticalAlignment="Bottom">
<Border Width="256" Height="256"
Margin="0,4,0,0" Padding="0"
BorderBrush="{ThemeResource SolidBackgroundFillColorBaseAltBrush}"
BorderThickness="1" Canvas.ZIndex="0"
CornerRadius="5">
<Grid>
<StackPanel Margin="2,0,0,2" VerticalAlignment="Bottom"
Canvas.ZIndex="2" Orientation="Horizontal"
Spacing="2">
<Button Background="{ThemeResource SolidBackgroundFillColorBaseBrush}"
Click="ChangeCoverButton_Click"
Style="{StaticResource MenuButtonStyle}">
<FontIcon FontFamily="{StaticResource UntamedFontFamily}" Glyph="&#xE70F;"/>
</Button>
<Button Background="{ThemeResource SolidBackgroundFillColorBaseBrush}"
Click="DeleteCoverButton_Click"
IsEnabled="{x:Bind IsSaveCoverButtonEnabled, Mode=OneWay}"
Style="{StaticResource MenuButtonStyle}">
<FontIcon FontFamily="{StaticResource UntamedFontFamily}" Glyph="&#xE74D;"/>
</Button>
</StackPanel>
<Button x:Name="SaveCoverButton"
Margin="0,0,2,2" HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Background="{ThemeResource SolidBackgroundFillColorBaseBrush}"
Canvas.ZIndex="2" Click="SaveCoverButton_Click"
IsEnabled="{x:Bind IsSaveCoverButtonEnabled, Mode=OneWay}"
Style="{StaticResource MenuButtonStyle}">
<FontIcon FontFamily="{StaticResource UntamedFontFamily}" Glyph="&#xE74E;"/>
</Button>
<ProgressRing Width="48" Height="48"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Canvas.ZIndex="2"
IsActive="{x:Bind IsChangingCoverProgressRingActive, Mode=OneWay}"/>
<Image Canvas.ZIndex="1" Source="{x:Bind Cover, Mode=OneWay}"/>
<Grid Background="{ThemeResource SolidBackgroundFillColorSecondaryBrush}"
Canvas.ZIndex="0"
Visibility="{x:Bind Cover, Mode=OneWay, Converter={StaticResource InverseEmptyObjectToVisibilityConverter}}">
<FontIcon Canvas.ZIndex="0"
FontFamily="{StaticResource UntamedFontFamily}"
FontSize="85"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xED3B;"/>
</Grid>
</Grid>
</Border>
</StackPanel>
<Grid Grid.Row="1" Grid.Column="0"
Grid.ColumnSpan="3"
Margin="24,12,24,0"
Visibility="{x:Bind SongListViewVisibility, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="217"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Uid="ImportPlaylistDialog_Type"
Grid.Column="1"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
<TextBlock x:Uid="PropertiesDialog_Title"
Grid.Column="2"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
</Grid>
<ListView Grid.Row="1" Grid.Column="0"
Grid.ColumnSpan="3"
Margin="24,40,24,0"
helper:ListViewExtensions.ItemCornerRadius="8"
IsItemClickEnabled="False"
ItemTemplate="{StaticResource SongListViewTemplate}"
ItemsSource="{x:Bind Songs, Mode=OneWay}"
SelectionMode="None"
Visibility="{x:Bind SongListViewVisibility, Mode=OneWay}">
<interactivity:Interaction.Behaviors>
<helper:AlternatingListViewBehavior AlternateBackground="{ThemeResource AlternateBackgroundBrush}"
AlternateBorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
AlternateBorderThickness="1"/>
</interactivity:Interaction.Behaviors>
</ListView>
</Grid>
</StackPanel>
</ScrollViewer>
</ContentDialog>

View File

@@ -1,11 +1,383 @@
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Messages;
using The_Untamed_Music_Player.Models;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Pickers;
using WinRT.Interop;
namespace The_Untamed_Music_Player.Controls;
public sealed partial class EditPlaylistInfoDialog : ContentDialog
public sealed partial class EditPlaylistInfoDialog : ContentDialog, INotifyPropertyChanged
{
public EditPlaylistInfoDialog()
private readonly PlaylistInfo _playlist;
private readonly string _originalName;
private string _name;
private ObservableCollection<DisplaySongInfo> Songs { get; set; } = [];
private int SongCount
{
get;
set
{
field = value;
SongListViewVisibility = value > 0 ? Visibility.Visible : Visibility.Collapsed;
OnPropertyChanged(nameof(SongCount));
}
}
private Visibility SongListViewVisibility
{
get;
set
{
field = value;
OnPropertyChanged(nameof(SongListViewVisibility));
}
} = Visibility.Collapsed;
private bool _isCoverEdited;
private readonly List<string> _coverPaths;
private WriteableBitmap? Cover
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Cover));
}
}
private bool IsSaveCoverButtonEnabled
{
get;
set
{
field = value;
OnPropertyChanged(nameof(IsSaveCoverButtonEnabled));
}
} = false;
private bool IsChangingCoverProgressRingActive
{
get;
set
{
field = value;
OnPropertyChanged(nameof(IsChangingCoverProgressRingActive));
}
} = false;
private bool IsImportingProgressRingActive
{
get;
set
{
field = value;
ImportFromM3u8FilesButton.IsEnabled = !value;
ImportFromFolderButton.IsEnabled = !value;
OnPropertyChanged(nameof(IsImportingProgressRingActive));
}
} = false;
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public EditPlaylistInfoDialog(PlaylistInfo info)
{
_playlist = info;
_originalName = info.Name;
_name = info.Name;
foreach (var song in info.SongList)
{
Songs.Add(new DisplaySongInfo(song.Song));
}
SongCount = Songs.Count;
Cover = info.Cover;
_isCoverEdited = info.IsCoverEdited;
_coverPaths = [.. info.CoverPaths]; // 注意要创建副本
IsSaveCoverButtonEnabled = Cover is not null;
RequestedTheme = Data.MainViewModel!.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
InitializeComponent();
}
private void PlaylistNameTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
IsPrimaryButtonEnabled = !string.IsNullOrEmpty((sender as TextBox)!.Text);
}
private async void ChangeCoverButton_Click(object sender, RoutedEventArgs e)
{
(sender as Button)!.IsEnabled = false;
IsChangingCoverProgressRingActive = true;
try
{
var openPicker = new FileOpenPicker
{
SuggestedStartLocation = PickerLocationId.PicturesLibrary,
};
foreach (var type in Data.SupportedCoverTypes)
{
openPicker.FileTypeFilter.Add(type);
}
var hWnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(openPicker, hWnd);
var file = await openPicker.PickSingleFileAsync();
if (file is null)
{
return;
}
var localFolder = ApplicationData.Current.LocalFolder;
var playlistCoverFolder = await localFolder.CreateFolderAsync(
"PlaylistCover",
CreationCollisionOption.OpenIfExists
);
var extension = Path.GetExtension(file.Name);
var newFileName = $"{Guid.CreateVersion7()}{extension}";
var targetFile = await playlistCoverFolder.CreateFileAsync(
newFileName,
CreationCollisionOption.ReplaceExisting
);
await file.CopyAndReplaceAsync(targetFile);
_isCoverEdited = true;
_coverPaths.Clear();
_coverPaths.Add(targetFile.Path);
await GetCover();
}
catch (Exception ex)
{
Debug.WriteLine($"更改封面失败: {ex.Message}");
}
finally
{
IsSaveCoverButtonEnabled = Cover is not null;
IsChangingCoverProgressRingActive = false;
(sender as Button)!.IsEnabled = true;
}
}
private void DeleteCoverButton_Click(object sender, RoutedEventArgs e)
{
_isCoverEdited = true;
_coverPaths.Clear();
Cover = null;
IsSaveCoverButtonEnabled = false;
}
private async void SaveCoverButton_Click(object sender, RoutedEventArgs e)
{
(sender as Button)!.IsEnabled = false;
try
{
if (_coverPaths.Count == 0 || !File.Exists(_coverPaths[0]))
{
return;
}
var extension = Path.GetExtension(_coverPaths[0]);
var fileName = string.IsNullOrWhiteSpace(PlaylistNameTextBox.Text)
? "PlaylistInfo_UntitledPlaylist".GetLocalized()
: PlaylistNameTextBox.Text;
var savePicker = new FileSavePicker
{
SuggestedStartLocation = PickerLocationId.PicturesLibrary,
SuggestedFileName = fileName,
FileTypeChoices =
{
new("EditSongInfoDialog_CoverImage".GetLocalized(), [extension]),
},
};
var hWnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(savePicker, hWnd);
var file = await savePicker.PickSaveFileAsync();
if (file is not null)
{
var imageBytes = await File.ReadAllBytesAsync(_coverPaths[0]);
await FileIO.WriteBytesAsync(file, imageBytes);
}
}
catch (Exception ex)
{
Debug.WriteLine($"保存封面失败: {ex.Message}");
}
finally
{
(sender as Button)!.IsEnabled = true;
}
}
private async void ImportFromM3u8FilesButton_Click(object sender, RoutedEventArgs e)
{
IsImportingProgressRingActive = true;
try
{
var picker = new FileOpenPicker
{
SuggestedStartLocation = PickerLocationId.MusicLibrary,
FileTypeFilter = { ".m3u8", ".m3u" },
};
var hWnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(picker, hWnd);
var files = await picker.PickMultipleFilesAsync();
if (files.Count == 0)
{
return;
}
var shouldSetCoverPath = (Cover is null) && !_isCoverEdited;
string? coverPath = null;
foreach (var file in files)
{
var (_, path, songs) = await M3u8Helper.GetNameAndSongsFromM3u8(file);
foreach (var song in songs)
{
Songs.Add(new DisplaySongInfo(song));
}
if (shouldSetCoverPath && coverPath is null && path is not null)
{
coverPath = path;
}
}
if (shouldSetCoverPath && coverPath is not null)
{
_isCoverEdited = true;
_coverPaths.Clear();
_coverPaths.Add(coverPath);
await GetCover();
}
}
catch { }
finally
{
SongCount = Songs.Count;
IsSaveCoverButtonEnabled = Cover is not null;
IsImportingProgressRingActive = false;
}
}
private async void ImportFromFolderButton_Click(object sender, RoutedEventArgs e)
{
IsImportingProgressRingActive = true;
try
{
var folderPicker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.MusicLibrary,
FileTypeFilter = { "*" },
};
var hWnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(folderPicker, hWnd);
var folder = await folderPicker.PickSingleFolderAsync();
if (folder is null)
{
return;
}
var songList = new ConcurrentBag<IBriefSongInfoBase>();
await ImportPlaylistDialog.LoadMusicAsync(songList, folder);
foreach (var song in songList)
{
Songs.Add(new DisplaySongInfo(song));
}
}
catch { }
finally
{
SongCount = Songs.Count;
IsImportingProgressRingActive = false;
}
}
private async Task GetCover()
{
await Task.Run(async () =>
{
try
{
var imagePath = _coverPaths[0];
if (!File.Exists(imagePath))
{
return;
}
const int canvasSize = 256;
var imageBytes = await File.ReadAllBytesAsync(imagePath);
var resizedImageBytes = await PlaylistInfo.ResizeImageToFitRegionAsync(
imageBytes,
canvasSize,
canvasSize
);
if (resizedImageBytes is null)
{
return;
}
DispatcherQueue.TryEnqueue(
DispatcherQueuePriority.Low,
async () =>
{
try
{
Cover ??= new(256, 256);
using var pixelStream = Cover.PixelBuffer.AsStream();
await pixelStream.WriteAsync(resizedImageBytes);
Cover.Invalidate();
}
catch { }
}
);
}
catch { }
});
}
private async void SaveButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (_originalName != _name)
{
var uniqueName = Data.PlaylistLibrary.GetUniquePlaylistName(_name);
_playlist.Name = uniqueName;
StrongReferenceMessenger.Default.Send(
new PlaylistRenameMessage(_originalName, uniqueName)
);
}
if (_isCoverEdited)
{
_playlist.ClearCover();
_playlist.CoverPaths = _coverPaths;
_playlist.Cover = Cover;
}
_playlist.SongList.Clear();
await _playlist.AddRange([.. Songs.Select(s => s.Song)]);
StrongReferenceMessenger.Default.Send(new HavePlaylistMessage(true));
StrongReferenceMessenger.Default.Send(new PlaylistChangeMessage(_playlist));
_ = FileManager.SavePlaylistDataAsync(Data.PlaylistLibrary.Playlists);
}
private new void CloseButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (_isCoverEdited && _coverPaths.Count > 0)
{
if (File.Exists(_coverPaths[0]))
{
try
{
File.Delete(_coverPaths[0]);
}
catch { }
}
}
}
}

View File

@@ -301,7 +301,7 @@ public sealed partial class ImportPlaylistDialog : ContentDialog, INotifyPropert
}
}
private static async Task LoadMusicAsync(
public static async Task LoadMusicAsync(
ConcurrentBag<IBriefSongInfoBase> songList,
StorageFolder folder
)

View File

@@ -1,50 +0,0 @@
<ContentDialog x:Uid="RenamePlaylistInfoDialog"
x:Class="The_Untamed_Music_Player.Controls.RenamePlaylistInfoDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:The_Untamed_Music_Player.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:toolkitConverters="using:CommunityToolkit.WinUI.Converters"
DefaultButton="Primary"
PrimaryButtonClick="RenameButtonClick"
Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<ContentDialog.Resources>
<toolkitConverters:EmptyObjectToObjectConverter x:Key="InverseEmptyObjectToVisibilityConverter"
EmptyValue="Visible" NotEmptyValue="Collapsed"/>
</ContentDialog.Resources>
<Grid>
<StackPanel Spacing="16">
<Border x:Name="CoverBorder"
Width="120" Height="120"
Padding="0"
CornerRadius="5">
<Grid>
<Image x:Name="ControllerCover"
Width="120" Height="120"
HorizontalAlignment="Center"
Canvas.ZIndex="1"
Source="{x:Bind _playlist.Cover}"
Stretch="UniformToFill"/>
<Grid Width="120" Height="120"
Background="{ThemeResource SolidBackgroundFillColorSecondaryBrush}"
Canvas.ZIndex="0">
<FontIcon Canvas.ZIndex="0"
FontFamily="{StaticResource UntamedFontFamily}"
FontSize="40"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xED3B;"
Visibility="{x:Bind _playlist.Cover, Converter={StaticResource InverseEmptyObjectToVisibilityConverter}}"/>
</Grid>
</Grid>
</Border>
<TextBox x:Name="RenameTextBox"
Width="290"
HorizontalAlignment="Center"
SelectedText="{x:Bind _playlist.Name}"
TextChanged="RenameTextBox_TextChanged"/>
</StackPanel>
</Grid>
</ContentDialog>

View File

@@ -1,37 +0,0 @@
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Services;
namespace The_Untamed_Music_Player.Controls;
public sealed partial class RenamePlaylistInfoDialog : ContentDialog
{
private static readonly ILogger _logger =
LoggingService.CreateLogger<RenamePlaylistInfoDialog>();
private readonly PlaylistInfo _playlist;
public RenamePlaylistInfoDialog(PlaylistInfo info)
{
_playlist = info;
RequestedTheme = Data.MainViewModel!.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
InitializeComponent();
}
private void RenameButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
var newName = RenameTextBox.Text;
var result = Data.PlaylistLibrary.RenamePlaylist(_playlist, newName);
if (!result)
{
_logger.SamePlaylistName();
}
}
private void RenameTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
IsPrimaryButtonEnabled = !string.IsNullOrEmpty((sender as TextBox)!.Text);
}
}

View File

@@ -1,13 +1,11 @@
using The_Untamed_Music_Player.Models;
namespace The_Untamed_Music_Player.Messages;
/// <summary>
/// 用于指示某个播放列表重命名的消息
/// </summary>
/// <param name="playlist"></param>
public sealed class PlaylistRenameMessage(string oldName, PlaylistInfo playlist)
public sealed class PlaylistRenameMessage(string oldName, string newName)
{
public string OldName { get; } = oldName;
public PlaylistInfo Playlist { get; } = playlist;
public string NewName { get; } = newName;
}

View File

@@ -39,6 +39,11 @@ public partial class PlaylistInfo
ModifiedDate = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
}
/// <summary>
/// 仅在创建播放列表时使用
/// </summary>
/// <param name="songs"></param>
/// <returns></returns>
public async Task AddSongs(List<IBriefSongInfoBase> songs)
{
foreach (var song in songs)
@@ -293,32 +298,20 @@ public partial class PlaylistInfo
}
const int canvasSize = 256;
// 使用BitmapDecoder读取图片文件
using var fileStream = new FileStream(
imagePath,
FileMode.Open,
FileAccess.Read
);
var decoder = await BitmapDecoder.CreateAsync(
fileStream.AsRandomAccessStream()
// 读取图片文件为字节数组
var imageBytes = await File.ReadAllBytesAsync(imagePath);
// 使用ResizeImageToFitRegionAsync来正确调整图片尺寸
var resizedImageBytes = await ResizeImageToFitRegionAsync(
imageBytes,
canvasSize,
canvasSize
);
// 调整图片尺寸到256x256
var transform = new BitmapTransform
if (resizedImageBytes is null)
{
ScaledWidth = canvasSize,
ScaledHeight = canvasSize,
};
var pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
transform,
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.DoNotColorManage
);
var pixels = pixelData.DetachPixelData();
return;
}
// 切换到UI线程更新WriteableBitmap
App.MainWindow?.DispatcherQueue.TryEnqueue(
@@ -329,8 +322,7 @@ public partial class PlaylistInfo
{
// 将像素数据写入WriteableBitmap
using var pixelStream = Cover.PixelBuffer.AsStream();
await pixelStream.WriteAsync(pixels);
await pixelStream.WriteAsync(resizedImageBytes);
Cover.Invalidate();
}
catch (Exception ex)
@@ -466,7 +458,7 @@ public partial class PlaylistInfo
});
}
private static async Task<byte[]?> ResizeImageToFitRegionAsync(
public static async Task<byte[]?> ResizeImageToFitRegionAsync(
byte[] imageBytes,
int targetWidth,
int targetHeight

View File

@@ -75,21 +75,6 @@ public partial class PlaylistLibrary : ObservableRecipient
_ = FileManager.SavePlaylistDataAsync(Playlists);
}
public bool RenamePlaylist(PlaylistInfo info, string newName)
{
var oldName = info.Name;
if (string.IsNullOrEmpty(newName) || newName == oldName)
{
return false;
}
var uniqueName = GetUniquePlaylistName(newName);
info.Name = uniqueName;
Messenger.Send(new HavePlaylistMessage(Playlists.Count > 0));
Messenger.Send(new PlaylistRenameMessage(oldName, info));
_ = FileManager.SavePlaylistDataAsync(Playlists);
return true;
}
public void DeletePlaylist(PlaylistInfo info)
{
Playlists.Remove(info);
@@ -168,7 +153,7 @@ public partial class PlaylistLibrary : ObservableRecipient
_ = FileManager.SavePlaylistDataAsync(Playlists);
}
private string GetUniquePlaylistName(string baseName)
public string GetUniquePlaylistName(string baseName)
{
var existingNames = Playlists.AsValueEnumerable().Select(p => p.Name).ToHashSet();
if (!existingNames.Contains(baseName))

View File

@@ -669,8 +669,8 @@
<data name="EditAlbumInfoDialog.Title" xml:space="preserve">
<value>Edit album info</value>
</data>
<data name="RenamePlaylistInfoDialog.Title" xml:space="preserve">
<value>Rename playlist</value>
<data name="EditPlaylistInfoDialog.Title" xml:space="preserve">
<value>Edit playlist info</value>
</data>
<data name="NewPlaylistInfoDialog.Title" xml:space="preserve">
<value>Add to new playlist</value>
@@ -687,7 +687,7 @@
<data name="EditAlbumInfoDialog.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="RenamePlaylistInfoDialog.CloseButtonText" xml:space="preserve">
<data name="EditPlaylistInfoDialog.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="NewPlaylistInfoDialog.CloseButtonText" xml:space="preserve">
@@ -726,8 +726,8 @@
<data name="EditAlbumInfoDialog.PrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="RenamePlaylistInfoDialog.PrimaryButtonText" xml:space="preserve">
<value>Rename</value>
<data name="EditPlaylistInfoDialog.PrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="NewPlaylistInfoDialog.PrimaryButtonText" xml:space="preserve">
<value>Create</value>

View File

@@ -669,8 +669,8 @@
<data name="EditAlbumInfoDialog.Title" xml:space="preserve">
<value>编辑专辑信息</value>
</data>
<data name="RenamePlaylistInfoDialog.Title" xml:space="preserve">
<value>重命名播放列表</value>
<data name="EditPlaylistInfoDialog.Title" xml:space="preserve">
<value>编辑播放列表信息</value>
</data>
<data name="NewPlaylistInfoDialog.Title" xml:space="preserve">
<value>添加到新建播放列表</value>
@@ -687,7 +687,7 @@
<data name="EditAlbumInfoDialog.CloseButtonText" xml:space="preserve">
<value>取消</value>
</data>
<data name="RenamePlaylistInfoDialog.CloseButtonText" xml:space="preserve">
<data name="EditPlaylistInfoDialog.CloseButtonText" xml:space="preserve">
<value>取消</value>
</data>
<data name="NewPlaylistInfoDialog.CloseButtonText" xml:space="preserve">
@@ -726,8 +726,8 @@
<data name="EditAlbumInfoDialog.PrimaryButtonText" xml:space="preserve">
<value>保存</value>
</data>
<data name="RenamePlaylistInfoDialog.PrimaryButtonText" xml:space="preserve">
<value>重命名</value>
<data name="EditPlaylistInfoDialog.PrimaryButtonText" xml:space="preserve">
<value>保存</value>
</data>
<data name="NewPlaylistInfoDialog.PrimaryButtonText" xml:space="preserve">
<value>创建</value>

View File

@@ -52,12 +52,7 @@ public partial class PlayListDetailViewModel
{
if (PlaylistName == message.OldName)
{
Playlist = Data.SelectedPlaylist = message.Playlist;
PlaylistName = Playlist.Name;
TotalSongNumStr = Playlist.TotalSongNumStr;
Cover = Playlist.Cover;
SongList = Playlist.SongList;
IsPlayAllButtonEnabled = SongList.Count > 0;
PlaylistName = message.NewName;
}
}
@@ -65,8 +60,6 @@ public partial class PlayListDetailViewModel
{
if (PlaylistName == message.Playlist.Name)
{
Playlist = Data.SelectedPlaylist = message.Playlist;
PlaylistName = Playlist.Name;
TotalSongNumStr = Playlist.TotalSongNumStr;
Cover = null;
Cover = Playlist.Cover;

View File

@@ -229,7 +229,7 @@ public partial class PlayQueueViewModel : ObservableObject
IsButtonEnabled = PlayQueue.Count > 0;
}
public void PlayqueueListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
public void PlayQueueListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
_currentSong = PlayQueue[Data.MusicPlayer.PlayQueueIndex];
if (e.Items.Count > 0)
@@ -238,7 +238,7 @@ public partial class PlayQueueViewModel : ObservableObject
}
}
public void PlayqueueListView_DragOver(object sender, DragEventArgs e)
public void PlayQueueListView_DragOver(object sender, DragEventArgs e)
{
if (e.DataView.Contains(StandardDataFormats.StorageItems))
{
@@ -250,7 +250,7 @@ public partial class PlayQueueViewModel : ObservableObject
}
}
public void PlayqueueListView_DragItemsCompleted(
public void PlayQueueListView_DragItemsCompleted(
object sender,
DragItemsCompletedEventArgs args
)
@@ -277,7 +277,7 @@ public partial class PlayQueueViewModel : ObservableObject
}
}
public async void PlayqueueListView_Drop(object sender, DragEventArgs e)
public async void PlayQueueListView_Drop(object sender, DragEventArgs e)
{
if (e.DataView.Contains(StandardDataFormats.StorageItems))
{

View File

@@ -163,10 +163,10 @@
<Setter Target="CoverArt.Height" Value="136"/>
<Setter Target="TitleText.FontSize" Value="28"/>
<Setter Target="CaptionText.Margin" Value="0,8,0,0"/>
<Setter Target="RenameButton.Visibility" Value="Collapsed"/>
<Setter Target="EditInfoButton.Visibility" Value="Collapsed"/>
<Setter Target="DeleteButton.Visibility" Value="Collapsed"/>
<Setter Target="MoreButton.Visibility" Value="Visible"/>
<Setter Target="RenameInfoFlyout.Visibility" Value="Visible"/>
<Setter Target="EditInfoInfoFlyout.Visibility" Value="Visible"/>
<Setter Target="DeleteInfoFlyout.Visibility" Value="Visible"/>
<Setter Target="SongListView.Padding" Value="12,0,12,0"/>
<Setter Target="SongListView.Margin" Value="0,68,0,0"/>
@@ -184,10 +184,10 @@
<Setter Target="CoverArt.Height" Value="162"/>
<Setter Target="TitleText.FontSize" Value="28"/>
<Setter Target="CaptionText.Margin" Value="0,8,0,0"/>
<Setter Target="RenameButton.Visibility" Value="Visible"/>
<Setter Target="EditInfoButton.Visibility" Value="Visible"/>
<Setter Target="DeleteButton.Visibility" Value="Collapsed"/>
<Setter Target="MoreButton.Visibility" Value="Visible"/>
<Setter Target="RenameInfoFlyout.Visibility" Value="Collapsed"/>
<Setter Target="EditInfoInfoFlyout.Visibility" Value="Collapsed"/>
<Setter Target="DeleteInfoFlyout.Visibility" Value="Visible"/>
<Setter Target="SongListView.Padding" Value="52,0,52,0"/>
<Setter Target="SongListView.Margin" Value="0,46,0,0"/>
@@ -205,10 +205,10 @@
<Setter Target="CoverArt.Height" Value="208"/>
<Setter Target="TitleText.FontSize" Value="40"/>
<Setter Target="CaptionText.Margin" Value="0,8,0,0"/>
<Setter Target="RenameButton.Visibility" Value="Visible"/>
<Setter Target="EditInfoButton.Visibility" Value="Visible"/>
<Setter Target="DeleteButton.Visibility" Value="Visible"/>
<Setter Target="MoreButton.Visibility" Value="Collapsed"/>
<Setter Target="RenameInfoFlyout.Visibility" Value="Collapsed"/>
<Setter Target="EditInfoInfoFlyout.Visibility" Value="Collapsed"/>
<Setter Target="DeleteInfoFlyout.Visibility" Value="Collapsed"/>
<Setter Target="SongListView.Padding" Value="52,0,52,0"/>
<Setter Target="SongListView.Margin" Value="0,15,0,0"/>
@@ -314,14 +314,14 @@
<TextBlock x:Uid="Songs_AddTo" FontSize="12"/>
</StackPanel>
</Button>
<Button x:Name="RenameButton"
<Button x:Name="EditInfoButton"
Padding="11,9,11,9" HorizontalAlignment="Stretch"
Click="RenameButton_Click"
Click="EditInfoButton_Click"
XYFocusDown="{x:Bind SongListView}">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontFamily="{StaticResource UntamedFontFamily}"
FontSize="16" Glyph="&#xE8AC;"/>
<TextBlock x:Uid="PlayLists_Rename" FontSize="12"/>
FontSize="16" Glyph="&#xE70F;"/>
<TextBlock x:Uid="Songs_EditInfo" FontSize="12"/>
</StackPanel>
</Button>
<Button x:Name="DeleteButton"
@@ -338,11 +338,11 @@
Padding="11,9,11,9" HorizontalAlignment="Stretch">
<Button.Flyout>
<MenuFlyout Placement="Bottom">
<MenuFlyoutItem x:Name="RenameInfoFlyout" x:Uid="PlayLists_Rename"
<MenuFlyoutItem x:Name="EditInfoInfoFlyout" x:Uid="Songs_EditInfo"
Width="158"
Click="RenameButton_Click"
Click="EditInfoButton_Click"
Icon="{ui:FontIcon FontFamily={StaticResource UntamedFontFamily},
Glyph=&#xE8AC;}"/>
Glyph=&#xE70F;}"/>
<MenuFlyoutItem x:Name="DeleteInfoFlyout" x:Uid="PlayLists_Delete"
Width="158"
Click="{x:Bind ViewModel.DeleteButton_Click}"

View File

@@ -289,9 +289,9 @@ public sealed partial class PlayListDetailPage : Page
}
}
private async void RenameButton_Click(object sender, RoutedEventArgs e)
private async void EditInfoButton_Click(object sender, RoutedEventArgs e)
{
var dialog = new RenamePlaylistInfoDialog(ViewModel.Playlist) { XamlRoot = XamlRoot };
var dialog = new EditPlaylistInfoDialog(ViewModel.Playlist) { XamlRoot = XamlRoot };
await dialog.ShowAsync();
}

View File

@@ -49,10 +49,10 @@
Icon="{ui:FontIcon FontFamily={StaticResource UntamedFontFamily},
Glyph=&#xE710;}"/>
</MenuFlyoutSubItem>
<MenuFlyoutItem x:Uid="PlayLists_Rename"
Click="RenameButton_Click"
<MenuFlyoutItem x:Uid="Songs_EditInfo"
Click="EditInfoButton_Click"
Icon="{ui:FontIcon FontFamily={StaticResource UntamedFontFamily},
Glyph=&#xE8AC;}"/>
Glyph=&#xE70F;}"/>
<MenuFlyoutItem x:Uid="PlayLists_Delete"
Click="DeleteButton_Click"
Icon="{ui:FontIcon FontFamily={StaticResource UntamedFontFamily},
@@ -119,10 +119,10 @@
Icon="{ui:FontIcon FontFamily={StaticResource UntamedFontFamily},
Glyph=&#xE710;}"/>
</MenuFlyoutSubItem>
<MenuFlyoutItem x:Uid="PlayLists_Rename"
Click="RenameButton_Click"
<MenuFlyoutItem x:Uid="Songs_EditInfo"
Click="EditInfoButton_Click"
Icon="{ui:FontIcon FontFamily={StaticResource UntamedFontFamily},
Glyph=&#xE8AC;}"/>
Glyph=&#xE70F;}"/>
<MenuFlyoutItem x:Uid="PlayLists_Delete"
Click="DeleteButton_Click"
Icon="{ui:FontIcon FontFamily={StaticResource UntamedFontFamily},

View File

@@ -195,11 +195,11 @@ public sealed partial class PlayListsPage : Page
}
}
private async void RenameButton_Click(object sender, RoutedEventArgs e)
private async void EditInfoButton_Click(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement { DataContext: PlaylistInfo info })
{
var dialog = new RenamePlaylistInfoDialog(info) { XamlRoot = XamlRoot };
var dialog = new EditPlaylistInfoDialog(info) { XamlRoot = XamlRoot };
await dialog.ShowAsync();
}
}

View File

@@ -187,7 +187,7 @@
<Setter Target="AddToButton.Width" Value="68"/>
<Setter Target="ClearTextBlock.Visibility" Value="Collapsed"/>
<Setter Target="AddToTextBlock.Visibility" Value="Collapsed"/>
<Setter Target="PlayqueueListView.Padding" Value="12,0,12,0"/>
<Setter Target="PlayQueueListView.Padding" Value="12,0,12,0"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
@@ -200,7 +200,7 @@
<Setter Target="MenuPanel.Margin" Value="56,22,56,0"/>
<Setter Target="ClearTextBlock.Visibility" Value="Visible"/>
<Setter Target="AddToTextBlock.Visibility" Value="Visible"/>
<Setter Target="PlayqueueListView.Padding" Value="52,0,52,0"/>
<Setter Target="PlayQueueListView.Padding" Value="52,0,52,0"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
@@ -341,7 +341,7 @@
</Button.Flyout>
</Button>
</StackPanel>
<ListView x:Name="PlayqueueListView"
<ListView x:Name="PlayQueueListView"
Grid.Row="2"
Margin="0,22,0,0"
helper:CurrentSongHighlightExtensions.IsPlayQueue="True"
@@ -351,10 +351,10 @@
helper:ListViewExtensions.ItemMargin="0,3,0,3"
AllowDrop="True" CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="{x:Bind ViewModel.PlayqueueListView_DragItemsCompleted}"
DragItemsStarting="{x:Bind ViewModel.PlayqueueListView_DragItemsStarting}"
DragOver="{x:Bind ViewModel.PlayqueueListView_DragOver}"
Drop="{x:Bind ViewModel.PlayqueueListView_Drop}"
DragItemsCompleted="{x:Bind ViewModel.PlayQueueListView_DragItemsCompleted}"
DragItemsStarting="{x:Bind ViewModel.PlayQueueListView_DragItemsStarting}"
DragOver="{x:Bind ViewModel.PlayQueueListView_DragOver}"
Drop="{x:Bind ViewModel.PlayQueueListView_Drop}"
IsItemClickEnabled="True"
ItemClick="{x:Bind ViewModel.PlayQueueListView_ItemClick}"
ItemTemplate="{StaticResource PlayQueueListViewTemplate}"

View File

@@ -102,7 +102,7 @@ public sealed partial class PlayQueuePage : Page
{
return;
}
var listViewSource = PlayqueueListView.ItemsSource;
var listViewSource = PlayQueueListView.ItemsSource;
if (listViewSource is IEnumerable<IndexedPlayQueueSong> songs)
{
var targetSong = currentSong switch
@@ -119,7 +119,7 @@ public sealed partial class PlayQueuePage : Page
};
if (targetSong is not null)
{
PlayqueueListView.ScrollIntoView(targetSong, ScrollIntoViewAlignment.Leading);
PlayQueueListView.ScrollIntoView(targetSong, ScrollIntoViewAlignment.Leading);
}
}
}