2025/2/16

This commit is contained in:
LanZhan
2025-02-16 19:01:30 +08:00
parent a5d18a9a13
commit 44caf5cf92
65 changed files with 1651 additions and 476 deletions

View File

@@ -45,7 +45,7 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false
#Formatting - wrapping options
#leave code block on separate lines
csharp_preserve_single_line_blocks = false
csharp_preserve_single_line_blocks = true
#Style - Code block preferences

View File

@@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Title>OnlineMusicApi</Title>
<Product>OnlineMusicApi</Product>
<Description>在线音乐API</Description>
<Copyright>Copyright © 2019 Binaryify / Wwh</Copyright>
<Version>3.25.3.0</Version>
<TargetFramework>net9.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyOriginatorKeyFile>../OnlineMusicApi.snk</AssemblyOriginatorKeyFile>
<SignAssembly>False</SignAssembly>
<NoWarn>IDE1006;CS0436;CS3021</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputPath>..\bin\$(Configuration)</OutputPath>
<PackageId>OnlineMusicApi</PackageId>
<Authors>Binaryify / Wwh</Authors>
<PackageVersion>$(Version)</PackageVersion>
<PackageProjectUrl>https://github.com/wwh1004/OnlineMusicApi</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>true</IncludeSymbols>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<LangVersion>preview</LangVersion>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Memory" Version="4.6.0" />
</ItemGroup>
</Project>

View File

@@ -1,8 +0,0 @@
using System.Collections.Generic;
namespace NeteaseCloudMusicApi;
internal sealed class QueryCollection : List<KeyValuePair<string, string>>
{
public void Add(string key, string value) => Add(new KeyValuePair<string, string>(key, value));
}

View File

@@ -5,8 +5,6 @@ VisualStudioVersion = 17.10.35027.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "The Untamed Music Player", "The Untamed Music Player\The Untamed Music Player.csproj", "{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OnlineMusicApi", "OnlineMusicApi\OnlineMusicApi.csproj", "{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -43,22 +41,6 @@ Global
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|x86.ActiveCfg = Release|x86
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|x86.Build.0 = Release|x86
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|x86.Deploy.0 = Release|x86
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Debug|arm64.ActiveCfg = Debug|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Debug|arm64.Build.0 = Debug|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Debug|x64.ActiveCfg = Debug|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Debug|x64.Build.0 = Debug|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Debug|x86.ActiveCfg = Debug|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Debug|x86.Build.0 = Debug|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Release|Any CPU.Build.0 = Release|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Release|arm64.ActiveCfg = Release|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Release|arm64.Build.0 = Release|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Release|x64.ActiveCfg = Release|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Release|x64.Build.0 = Release|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Release|x86.ActiveCfg = Release|Any CPU
{BA37573B-D0FC-4FF6-8D06-5F59F348B90B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -85,6 +85,7 @@ public partial class App : Application
services.AddSingleton<LocalArtistsViewModel>();
services.AddTransient<AlbumDetailViewModel>();
services.AddTransient<ArtistDetailViewModel>();
services.AddTransient<OnlineSongsViewModel>();
services.AddTransient<DesktopLyricViewModel>();
// Configuration

View File

@@ -0,0 +1,90 @@
using Microsoft.UI;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
namespace The_Untamed_Music_Player.Contracts.Models;
public interface IBriefMusicInfoBase
{
string Path { get; set; }
string Title { get; set; }
string Album { get; set; }
string ArtistsStr { get; set; }
string DurationStr { get; set; }
string YearStr { get; set; }
/// <summary>
/// 获取参与创作的艺术家名字符串
/// </summary>
/// <param name="artists"></param>
/// <returns></returns>
static string GetArtistsStr(string[] artists) => string.Join(", ", artists);
/// <summary>
/// 获取时长字符串
/// </summary>
/// <returns></returns>
static string GetDurationStr(TimeSpan duration) => duration.Hours > 0 ? $"{duration:hh\\:mm\\:ss}" : $"{duration:mm\\:ss}";
/// <summary>
/// 获取发行年份字符串
/// </summary>
/// <param name="year"></param>
/// <returns></returns>
static string GetYearStr(ushort year) => year is 0 ? "" : year.ToString();
/// <summary>
/// 获取文本前景色
/// </summary>
/// <param name="currentMusic"></param>
/// <param name="isDarkTheme"></param>
/// <returns>如果是当前播放歌曲, 返回主题色, 如果不是, 根据当前主题返回黑色或白色</returns>
SolidColorBrush GetTextForeground(IDetailedMusicInfoBase currentMusic, bool isDarkTheme)
{
var isCurrentMusic = Path == currentMusic.Path;
if (isCurrentMusic)
{
var color = isDarkTheme ? ColorHelper.FromArgb(0xFF, 0x42, 0x9C, 0xE3) : ColorHelper.FromArgb(0xFF, 0x00, 0x5A, 0x9E);
return new SolidColorBrush(color);
}
return new SolidColorBrush(isDarkTheme ? Colors.White : Colors.Black);
}
}
public interface IDetailedMusicInfoBase : IBriefMusicInfoBase
{
bool IsOnline { get; set; }
string GenreStr { get; set; }
string ItemType { get; set; }
string AlbumArtistsStr { get; set; }
string ArtistAndAlbumStr { get; set; }
BitmapImage? Cover { get; set; }
string BitRate { get; set; }
string Track { get; set; }
string Lyric { get; set; }
/// <summary>
/// 获取专辑艺术家字符串
/// </summary>
/// <param name="albumArtists"></param>
/// <returns></returns>
static string GetAlbumArtistsStr(string[] albumArtists) => string.Join(", ", albumArtists);
/// <summary>
/// 获取艺术家和专辑名字符串
/// </summary>
/// <param name="album"></param>
/// <param name="artistsStr"></param>
/// <returns></returns>
static string GetArtistAndAlbumStr(string album, string artistsStr)
{
if (string.IsNullOrEmpty(artistsStr))
{
return album ?? "";
}
if (string.IsNullOrEmpty(album))
{
return artistsStr;
}
return $"{artistsStr} • {album}";
}
}

View File

@@ -0,0 +1,11 @@
namespace The_Untamed_Music_Player.Contracts.Models;
public interface IBriefOnlineMusicInfo : IBriefMusicInfoBase
{
bool IsAvailable { get; set; }
long ID { get; set; }
long AlbumID { get; set; }
}
public interface IDetailedOnlineMusicInfo : IBriefOnlineMusicInfo, IDetailedMusicInfoBase
{
}

View File

@@ -0,0 +1,13 @@
using System.Collections.ObjectModel;
using The_Untamed_Music_Player.Models;
namespace The_Untamed_Music_Player.Contracts.Models;
public abstract class IBriefOnlineMusicInfoList : ObservableCollection<IBriefOnlineMusicInfo>
{
protected string _keyWords = "";
public bool HasAllLoaded { get; set; } = false;
public abstract Task SearchAsync(string keyWords);
public abstract Task SearchMore();
public abstract Task<List<SearchResult>> GetSearchResultAsync(string keyWords);
}

View File

@@ -7,7 +7,7 @@ public class BriefAlbumInfo(AlbumInfo albumInfo)
public string Name { get; set; } = albumInfo.Name;
public string YearStr { get; set; } = albumInfo.Year == 0 ? "AlbumInfo_UnknownYear".GetLocalized() : albumInfo.Year.ToString();
public BitmapImage? Cover { get; set; } = albumInfo.Cover;
public List<BriefMusicInfo> SongList { get; set; } = [.. Data.MusicLibrary.GetMusicsByAlbum(albumInfo)];
public List<BriefMusicInfo> SongList { get; set; } = [.. Data.MusicLibrary.GetSongsByAlbum(albumInfo)];
}
public class AlbumInfo

View File

@@ -11,14 +11,10 @@ public static class Data
/// </summary>
public static bool NotFirstUsed { get; set; } = false;
public static AlbumInfo? SelectedAlbum
{
get; set;
}
public static ArtistInfo? SelectedArtist
{
get; set;
}
public static bool HasMusicLibraryLoaded { get; set; } = false;
public static AlbumInfo? SelectedAlbum { get; set; }
public static ArtistInfo? SelectedArtist { get; set; }
/// <summary>
/// 软件显示名称
@@ -43,63 +39,25 @@ public static class Data
/// <summary>
/// 是否显示歌词背景
/// </summary>
public static bool IsLyricBackgroundVisible = false;
public static bool IsLyricBackgroundVisible { get; set; } = false;
public static MusicPlayer MusicPlayer { get; set; } = new();
public static OnlineMusicLibrary OnlineMusicLibrary { get; set; } = new();
public static MusicLibrary MusicLibrary { get; set; } = new();
public static bool hasMusicLibraryLoaded { get; set; } = false;
public static MusicPlayer MusicPlayer { get; set; } = new();
public static MainWindow? MainWindow { get; set; }
public static ShellPage? ShellPage { get; set; }
public static HomePage HomePage { get; set; } = null!;
public static MusicLibraryPage? MusicLibraryPage { get; set; }
public static LyricPage? LyricPage { get; set; }
public static RootPlayBarView? RootPlayBarView { get; set; }
public static DesktopLyricWindow? DesktopLyricWindow { get; set; }
public static MainWindow? MainWindow
{
get; set;
}
public static MainViewModel? MainViewModel
{
get; set;
}
public static ShellPage? ShellPage
{
get; set;
}
public static MusicLibraryPage? MusicLibraryPage
{
get; set;
}
public static LyricPage? LyricPage
{
get; set;
}
public static RootPlayBarView? RootPlayBarView
{
get; set;
}
public static DesktopLyricWindow? DesktopLyricWindow
{
get; set;
}
public static SettingsViewModel? SettingsViewModel
{
get; set;
}
public static ShellViewModel? ShellViewModel
{
get; set;
}
public static RootPlayBarViewModel? RootPlayBarViewModel
{
get; set;
}
public static HaveMusicViewModel? HaveMusicViewModel
{
get; set;
}
public static LocalSongsViewModel? LocalSongsViewModel
{
get; set;
}
public static LocalAlbumsViewModel? LocalAlbumsViewModel
{
get; set;
}
}
public static MainViewModel? MainViewModel { get; set; }
public static SettingsViewModel? SettingsViewModel { get; set; }
public static ShellViewModel? ShellViewModel { get; set; }
public static RootPlayBarViewModel? RootPlayBarViewModel { get; set; }
public static HaveMusicViewModel? HaveMusicViewModel { get; set; }
public static LocalSongsViewModel? LocalSongsViewModel { get; set; }
public static LocalAlbumsViewModel? LocalAlbumsViewModel { get; set; }
}

View File

@@ -3,11 +3,12 @@ using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Helpers;
using Windows.Storage.Streams;
namespace The_Untamed_Music_Player.Models;
public class BriefMusicInfo
public class BriefMusicInfo : IBriefMusicInfoBase
{
/// <summary>
/// 歌手分隔符
@@ -25,20 +26,15 @@ public class BriefMusicInfo
public string Folder { get; set; } = "";
/// <summary>
/// 项目类型
/// 歌曲名
/// </summary>
public string ItemType { get; set; } = "";
public string Title { get; set; } = "";
/// <summary>
/// 专辑名, 为空时返回"未知专辑"
/// </summary>
public virtual string Album { get; set; } = "";
/// <summary>
/// 歌曲名
/// </summary>
public string Title { get; set; } = "";
/// <summary>
/// 参与创作的艺术家数组
/// </summary>
@@ -61,7 +57,7 @@ public class BriefMusicInfo
public TimeSpan Duration { get; set; } = TimeSpan.Zero;
/// <summary>
/// 时长字符串
/// 时长字符串, 为空时返回00:00
/// </summary>
public virtual string DurationStr { get; set; } = "";
@@ -71,17 +67,14 @@ public class BriefMusicInfo
public ushort Year { get; set; } = 0;
/// <summary>
/// 发行年份字符串, 为0时返回空字符串
/// 发行年份字符串, 为0时返回""
/// </summary>
public string YearStr { get; set; } = "";
/// <summary>
/// 封面(可能为空)
/// </summary>
public virtual BitmapImage? Cover
{
get; set;
}
public virtual BitmapImage? Cover { get; set; }
/// <summary>
/// 流派数组
@@ -98,9 +91,7 @@ public class BriefMusicInfo
/// </summary>
public long ModifiedDate { get; set; } = 0;
public BriefMusicInfo()
{
}
public BriefMusicInfo() { }
/// <summary>
/// 异步工厂方法
@@ -115,7 +106,6 @@ public class BriefMusicInfo
Path = path,
Folder = folder,
ModifiedDate = new DateTimeOffset(new FileInfo(path).LastWriteTime).ToUnixTimeSeconds(),
ItemType = System.IO.Path.GetExtension(path).ToLower()
};
Task? coverTask = null;
@@ -131,14 +121,14 @@ public class BriefMusicInfo
info.Title = string.IsNullOrEmpty(musicFile.Tag.Title) ? System.IO.Path.GetFileNameWithoutExtension(path) : musicFile.Tag.Title;
string[] combinedArtists = [.. musicFile.Tag.AlbumArtists, .. musicFile.Tag.Performers];
info.Artists = combinedArtists.Length != 0 ? combinedArtists : ["MusicInfo_UnknownArtist".GetLocalized()];
info.ArtistsStr = info.GetArtistsStr();
info.ArtistsStr = IBriefMusicInfoBase.GetArtistsStr(info.Artists);
info.Year = (ushort)musicFile.Tag.Year;
info.YearStr = info.GetYearStr();
info.YearStr = IBriefMusicInfoBase.GetYearStr(info.Year);
var genres = musicFile.Tag.Genres;
info.Genre = genres.Length != 0 ? genres : ["MusicInfo_UnknownGenre".GetLocalized()];
info.GenreStr = info.GetGenreStr();
info.GenreStr = GetGenreStr(info.Genre);
info.Duration = musicFile.Properties.Duration;
info.DurationStr = info.GetDurationStr();
info.DurationStr = IBriefMusicInfoBase.GetDurationStr(info.Duration);
// 等待 LoadCoverAsync 任务完成
if (coverTask != null)
@@ -152,10 +142,10 @@ public class BriefMusicInfo
info.Title = System.IO.Path.GetFileNameWithoutExtension(path);
info.Album = "MusicInfo_UnknownAlbum".GetLocalized();
info.Artists = ["MusicInfo_UnknownArtist".GetLocalized()];
info.ArtistsStr = info.GetArtistsStr();
info.ArtistsStr = IBriefMusicInfoBase.GetArtistsStr(info.Artists);
info.Genre = ["MusicInfo_UnknownGenre".GetLocalized()];
info.GenreStr = info.GetGenreStr();
info.DurationStr = info.GetDurationStr();
info.GenreStr = GetGenreStr(info.Genre);
info.DurationStr = IBriefMusicInfoBase.GetDurationStr(info.Duration);
}
catch (Exception ex)
{
@@ -196,29 +186,11 @@ public class BriefMusicInfo
return tcs.Task;
}
/// <summary>
/// 获取参与创作的艺术家名字符串, 为空时返回"未知艺术家"
/// </summary>
/// <returns></returns>
protected virtual string GetArtistsStr() => string.Join(", ", Artists);
/// <summary>
/// 获取时长字符串
/// </summary>
/// <returns></returns>
protected virtual string GetDurationStr() => Duration.Hours > 0 ? $"{Duration:hh\\:mm\\:ss}" : $"{Duration:mm\\:ss}";
/// <summary>
/// 获取流派字符串
/// </summary>
/// <returns></returns>
protected virtual string GetGenreStr() => string.Join(", ", Genre);
/// <summary>
/// 获取发行年份字符串
/// </summary>
/// <returns></returns>
protected string GetYearStr() => Year is 0 ? "" : Year.ToString();
protected static string GetGenreStr(string[] genre) => string.Join(", ", genre);
/// <summary>
/// 获取文本前景色
@@ -226,7 +198,7 @@ public class BriefMusicInfo
/// <param name="currentMusic"></param>
/// <param name="isDarkTheme"></param>
/// <returns>如果是当前播放歌曲, 返回主题色, 如果不是, 根据当前主题返回黑色或白色</returns>
public SolidColorBrush GetTextForeground(DetailedMusicInfo currentMusic, bool isDarkTheme)
public SolidColorBrush GetTextForeground(IDetailedMusicInfoBase currentMusic, bool isDarkTheme)
{
var isCurrentMusic = Path == currentMusic.Path;
if (isCurrentMusic)
@@ -238,8 +210,10 @@ public class BriefMusicInfo
}
}
public class DetailedMusicInfo : BriefMusicInfo
public class DetailedMusicInfo : BriefMusicInfo, IDetailedMusicInfoBase
{
public bool IsOnline { get; set; } = false;
/// <summary>
/// 专辑名, 为空时返回""
/// </summary>
@@ -256,20 +230,15 @@ public class DetailedMusicInfo : BriefMusicInfo
public override string GenreStr { get; set; } = "";
/// <summary>
/// 时长字符串
/// 时长字符串, 为空时返回""
/// </summary>
public override string DurationStr { get; set; } = "";
/// <summary>
/// 专辑艺术家数组
/// 项目类型, 为空时返回""
/// </summary>
private string[] AlbumArtists
{
get;
set => field = [.. value
.SelectMany(artist => artist.Split(_delimiters, StringSplitOptions.RemoveEmptyEntries))
.Distinct()];
} = [];
public string ItemType { get; set; } = "";
/// <summary>
/// 专辑艺术家字符串, 为空时返回""
@@ -277,17 +246,14 @@ public class DetailedMusicInfo : BriefMusicInfo
public string AlbumArtistsStr { get; set; } = "";
/// <summary>
/// 艺术家和专辑名字符串
/// 艺术家和专辑名字符串, 为空时返回""
/// </summary>
public string ArtistAndAlbumStr { get; set; } = "";
/// <summary>
/// 清晰封面(可能为空)
/// </summary>
public override BitmapImage? Cover
{
get; set;
}
public override BitmapImage? Cover { get; set; }
/// <summary>
/// 封面缓冲数据
@@ -295,24 +261,20 @@ public class DetailedMusicInfo : BriefMusicInfo
public byte[] CoverBuffer { get; set; } = [];
/// <summary>
/// 比特率
/// 比特率, 为空时返回""
/// </summary>
public string BitRate { get; set; } = "";
/// <summary>
/// 曲目
/// 曲目, 为空时返回""
/// </summary>
public string Track { get; set; } = "";
/// <summary>
/// 歌词
/// 歌词, 为空时返回""
/// </summary>
public string Lyric { get; set; } = "";
public DetailedMusicInfo()
{
}
public DetailedMusicInfo(string path)
{
Path = path;
@@ -337,16 +299,17 @@ public class DetailedMusicInfo : BriefMusicInfo
Title = string.IsNullOrEmpty(musicFile.Tag.Title) ? System.IO.Path.GetFileNameWithoutExtension(path) : musicFile.Tag.Title;
Album = musicFile.Tag.Album ?? "";
Artists = [.. musicFile.Tag.AlbumArtists, .. musicFile.Tag.Performers];
ArtistsStr = GetArtistsStr();
AlbumArtists = [.. musicFile.Tag.AlbumArtists];
AlbumArtistsStr = GetAlbumArtistsStr();
ArtistAndAlbumStr = GetArtistAndAlbumStr(ArtistsStr);
ArtistsStr = IBriefMusicInfoBase.GetArtistsStr(Artists);
AlbumArtistsStr = IDetailedMusicInfoBase.GetAlbumArtistsStr([.. musicFile.Tag.AlbumArtists
.SelectMany(artist => artist.Split(_delimiters, StringSplitOptions.RemoveEmptyEntries))
.Distinct()]);
ArtistAndAlbumStr = IDetailedMusicInfoBase.GetArtistAndAlbumStr(Album, ArtistsStr);
Year = (ushort)musicFile.Tag.Year;
YearStr = GetYearStr();
YearStr = IBriefMusicInfoBase.GetYearStr(Year);
Genre = musicFile.Tag.Genres;
GenreStr = GetGenreStr();
GenreStr = GetGenreStr(Genre);
Duration = musicFile.Properties.Duration;
DurationStr = GetDurationStr();
DurationStr = IBriefMusicInfoBase.GetDurationStr(Duration);
Track = musicFile.Tag.Track == 0 ? "" : musicFile.Tag.Track.ToString();
Lyric = musicFile.Tag.Lyrics ?? "";
BitRate = $"{musicFile.Properties.AudioBitrate} kbps";
@@ -360,45 +323,4 @@ public class DetailedMusicInfo : BriefMusicInfo
Debug.WriteLine(ex.StackTrace);
}
}
/// <summary>
/// 获取参与创作的艺术家名字符串, 为空时返回""
/// </summary>
/// <returns></returns>
protected override string GetArtistsStr() => string.Join(", ", Artists);
/// <summary>
/// 获取时长字符串
/// </summary>
/// <returns></returns>
protected override string GetDurationStr() => Duration.Hours > 0 ? $"{Duration:hh\\:mm\\:ss}" : $"{Duration:mm\\:ss}";
/// <summary>
/// 获取流派字符串, 为空时返回""
/// </summary>
/// <returns></returns>
protected override string GetGenreStr() => string.Join(", ", Genre);
/// <summary>
/// 获取专辑艺术家字符串, 为空时返回""
/// </summary>
/// <returns></returns>
protected string GetAlbumArtistsStr() => string.Join(", ", AlbumArtists);
/// <summary>
/// 获取艺术家和专辑名字符串
/// </summary>
/// <returns></returns>
protected string GetArtistAndAlbumStr(string artistsStr)
{
if (string.IsNullOrEmpty(artistsStr))
{
return Album ?? "";
}
if (string.IsNullOrEmpty(Album))
{
return artistsStr;
}
return $"{artistsStr} • {Album}";
}
}

View File

@@ -8,7 +8,7 @@ using The_Untamed_Music_Player.ViewModels;
using Windows.Storage;
namespace The_Untamed_Music_Player.Models;
public partial class MusicLibrary : ObservableObject
public partial class MusicLibrary : ObservableRecipient
{
/// <summary>
/// 调度器队列
@@ -105,7 +105,7 @@ public partial class MusicLibrary : ObservableObject
await _librarySemaphore.WaitAsync(); // 等待信号量, 只允许一个线程访问此函数
try
{
Data.hasMusicLibraryLoaded = true;
Data.HasMusicLibraryLoaded = true;
var loadMusicTasks = new List<Task>();
if (Folders.Any())
{
@@ -120,8 +120,8 @@ public partial class MusicLibrary : ObservableObject
{
OnPropertyChanged(nameof(HasMusics));
Genres = [.. _musicGenres.Keys
.Concat([ResourceExtensions.GetLocalized("MusicInfo_AllGenres")])
.OrderBy(x => x, new GenreComparer())];
.Concat([ResourceExtensions.GetLocalized("MusicInfo_AllGenres")])
.OrderBy(x => x, new GenreComparer())];
_musicGenres.Clear();
});
await Task.Run(AddFolderWatcher);
@@ -142,7 +142,7 @@ public partial class MusicLibrary : ObservableObject
await _librarySemaphore.WaitAsync();
try
{
Data.hasMusicLibraryLoaded = true;
Data.HasMusicLibraryLoaded = true;
_dispatcherQueue.TryEnqueue(() =>
{
IsProgressRingActive = true;
@@ -165,8 +165,8 @@ public partial class MusicLibrary : ObservableObject
{
OnPropertyChanged(nameof(HasMusics));
Genres = new([.. _musicGenres.Keys
.Concat([ResourceExtensions.GetLocalized("MusicInfo_AllGenres")])
.OrderBy(x => x, new GenreComparer())]);
.Concat([ResourceExtensions.GetLocalized("MusicInfo_AllGenres")])
.OrderBy(x => x, new GenreComparer())]);
OnPropertyChanged("LibraryReloaded");
_musicGenres.Clear();
});
@@ -340,33 +340,30 @@ public partial class MusicLibrary : ObservableObject
}
}
public IOrderedEnumerable<BriefMusicInfo> GetMusicsByAlbum(AlbumInfo albumInfo)
{
var list = new List<BriefMusicInfo>();
var albumName = albumInfo.Name;
/// <summary>
/// 根据专辑信息获取歌曲列表
/// </summary>
/// <param name="albumInfo"></param>
/// <returns></returns>
public IOrderedEnumerable<BriefMusicInfo> GetSongsByAlbum(AlbumInfo albumInfo) => Songs
.Where(m => m.Album == albumInfo.Name)
.OrderBy(m => m.Title, new TitleComparer());
foreach (var music in Songs)
{
if (music.Album == albumName)
{
list.Add(music);
}
}
/// <summary>
/// 根据艺术家信息获取专辑列表
/// </summary>
/// <param name="artistInfo"></param>
/// <returns></returns>
public List<BriefAlbumInfo> GetAlbumsByArtist(ArtistInfo artistInfo) => [.. artistInfo.Albums
.Select(album => new BriefAlbumInfo(Albums[album]))
.OrderBy(m => m.Name, new AlbumTitleComparer())];
return list.OrderBy(m => m.Title, new TitleComparer());
}
public List<BriefAlbumInfo> GetAlbumsByArtist(ArtistInfo artistInfo)
{
var list = new List<BriefAlbumInfo>();
var albums = artistInfo.Albums;
foreach (var album in albums)
{
var albumInfo = Albums[album];
list.Add(new BriefAlbumInfo(albumInfo));
}
return [.. list.OrderBy(m => m.Name, new AlbumTitleComparer())];
}
/// <summary>
/// 根据艺术家信息获取歌曲列表
/// </summary>
/// <param name="artistInfo"></param>
/// <returns></returns>
public ObservableCollection<BriefMusicInfo> GetSongsByArtist(ArtistInfo artistInfo) => [.. artistInfo.Albums
.OrderBy(album => album, new AlbumTitleComparer())
.SelectMany(album => GetSongsByAlbum(Albums[album]))];
}

View File

@@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Contracts.Services;
using Windows.Media;
using Windows.Media.Core;
@@ -41,7 +42,6 @@ public partial class MusicPlayer : ObservableRecipient
/// </summary>
private ThreadPoolTimer? _positionUpdateTimer;
/// <summary>
/// 线程锁开启状态, true为开启, false为关闭
/// </summary>
@@ -100,6 +100,11 @@ public partial class MusicPlayer : ObservableRecipient
/// </summary>
public MediaPlayer Player { get; set; } = new() { AudioCategory = MediaPlayerAudioCategory.Media };
/// <summary>
/// 歌曲来源模式, 0为本地, 1为网易
/// </summary>
public byte SourceMode { get; set; } = 0;
/// <summary>
/// 随机播放模式, true为开启, false为关闭.
/// </summary>
@@ -135,8 +140,8 @@ public partial class MusicPlayer : ObservableRecipient
/// 当前播放歌曲
/// </summary>
[ObservableProperty]
public partial DetailedMusicInfo CurrentMusic { get; set; } = new();
partial void OnCurrentMusicChanged(DetailedMusicInfo value)
public partial IDetailedMusicInfoBase CurrentMusic { get; set; } = null!;
partial void OnCurrentMusicChanged(IDetailedMusicInfoBase value)
{
SetSource(value.Path);
CurrentLyric = LyricSlice.GetLyricSlices(value.Lyric);
@@ -283,7 +288,7 @@ public partial class MusicPlayer : ObservableRecipient
Player.Source = MediaSource.CreateFromStorageFile(mediaFile);
Player.PlaybackSession.PlaybackRate = PlaySpeed;
TotalPlayingTime = Player.PlaybackSession.NaturalDuration;
_displayUpdater.MusicProperties.Title = CurrentMusic.Title;
_displayUpdater.MusicProperties.Title = CurrentMusic!.Title;
_displayUpdater.MusicProperties.Artist = CurrentMusic.ArtistsStr == "未知艺术家" ? "" : CurrentMusic.ArtistsStr;
_positionUpdateTimer = ThreadPoolTimer.CreatePeriodicTimer(UpdateTimerHandler, TimeSpan.FromMilliseconds(250));
}
@@ -293,14 +298,14 @@ public partial class MusicPlayer : ObservableRecipient
}
}
if (CurrentMusic.Cover != null && CurrentMusic.CoverBuffer.Length != 0)
if (CurrentMusic!.Cover != null && CurrentMusic is DetailedMusicInfo info && info.CoverBuffer.Length != 0)
{
try
{
var tempFolder = ApplicationData.Current.TemporaryFolder;
var coverFileName = "Cover.jpg";
var coverFile = await tempFolder.CreateFileAsync(coverFileName, CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBytesAsync(coverFile, CurrentMusic.CoverBuffer);
await FileIO.WriteBytesAsync(coverFile, info.CoverBuffer);
_displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromFile(coverFile);
}
catch
@@ -320,14 +325,14 @@ public partial class MusicPlayer : ObservableRecipient
/// </summary>
/// <param name="name"></param>
/// <param name="list"></param>
public async void SetPlayList(string name, ObservableCollection<BriefMusicInfo> list, byte sortmode = 0)
public async void SetPlayList(string name, IEnumerable<BriefMusicInfo> list, byte sortmode = 0)
{
if (PlayQueue.Count != list.Count || PlayQueueName != name || _sortMode != sortmode)
if (PlayQueue.Count != list.Count() || PlayQueueName != name || _sortMode != sortmode)
{
_sortMode = sortmode;
PlayQueueName = name;
PlayQueue = [.. list];
_playQueueLength = list.Count;
_playQueueLength = list.Count();
var hasMusics = PlayQueue.Any();
if (Data.RootPlayBarViewModel != null)
{
@@ -462,7 +467,7 @@ public partial class MusicPlayer : ObservableRecipient
{
if (RepeatMode == 2)
{
PlaySongByPath(CurrentMusic.Path);
PlaySongByPath(CurrentMusic!.Path);
}
else
{
@@ -636,7 +641,7 @@ public partial class MusicPlayer : ObservableRecipient
ShuffledPlayQueue.Clear();
for (var i = 0; i < PlayQueue.Count; i++)
{
if (PlayQueue[i].Path == CurrentMusic.Path)
if (PlayQueue[i].Path == CurrentMusic!.Path)
{
PlayQueueIndex = i;
break;
@@ -665,7 +670,7 @@ public partial class MusicPlayer : ObservableRecipient
ShuffledPlayQueue = new ObservableCollection<BriefMusicInfo>([.. PlayQueue.OrderBy(x => Guid.NewGuid())]);
for (var i = 0; i < ShuffledPlayQueue.Count; i++)
{
if (ShuffledPlayQueue[i].Path == CurrentMusic.Path)
if (ShuffledPlayQueue[i].Path == CurrentMusic!.Path)
{
PlayQueueIndex = i;
break;
@@ -879,7 +884,7 @@ public partial class MusicPlayer : ObservableRecipient
/// </summary>
public async void SaveCurrentStateAsync()
{
await _localSettingsService.SaveSettingAsync("CurrentMusic", CurrentMusic.Path);
await _localSettingsService.SaveSettingAsync("CurrentMusic", CurrentMusic!.Path);
/*var playqueuepaths = PlayQueue.Select(music => music.Path).ToList();
await _localSettingsService.SaveSettingAsync("PlayQueuePaths", playqueuepaths);
var shuffledplayqueuepaths = ShuffledPlayQueue.Select(music => music.Path).ToList();

View File

@@ -0,0 +1,221 @@
using System.Diagnostics;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI;
namespace The_Untamed_Music_Player.Models;
public partial class OnlineMusicLibrary : ObservableRecipient
{
private bool _isSearchingMore = false;
public byte PageIndex { get; set; }
public byte MusicLibraryIndex { get; set; }
public string KeyWords { get; set; } = null!;
[ObservableProperty]
public partial Visibility KeyWordsTextBlockVisibility { get; set; } = Visibility.Collapsed;
[ObservableProperty]
public partial Visibility NetworkErrorVisibility { get; set; } = Visibility.Collapsed;
[ObservableProperty]
public partial double ListViewOpacity { get; set; } = 0;
/// <summary>
/// 是否显示加载进度环
/// </summary>
[ObservableProperty]
public partial bool IsSearchProgressRingActive { get; set; } = false;
/// <summary>
/// 是否显示加载更多进度环
/// </summary>
[ObservableProperty]
public partial bool IsSearchMoreProgressRingActive { get; set; } = false;
[ObservableProperty]
public partial IBriefOnlineMusicInfoList OnlineMusicInfoList { get; set; } = null!;
[ObservableProperty]
public partial List<SearchResult> SearchResultList { get; set; } = [];
public async Task Search()
{
KeyWordsTextBlockVisibility = Visibility.Collapsed;
NetworkErrorVisibility = Visibility.Collapsed;
ListViewOpacity = 0;
if (!await IsInternetAvailableAsync())
{
NetworkErrorVisibility = Visibility.Visible;
return;
}
IsSearchProgressRingActive = true;
try
{
if (PageIndex == 0)
{
if (MusicLibraryIndex == 0)
{
if (OnlineMusicInfoList is not CloudBriefOnlineMusicInfoList)
{
OnlineMusicInfoList = new CloudBriefOnlineMusicInfoList();
}
await OnlineMusicInfoList.SearchAsync(KeyWords);
}
else if (MusicLibraryIndex == 1)
{ }
else if (MusicLibraryIndex == 2)
{ }
else if (MusicLibraryIndex == 3)
{ }
else if (MusicLibraryIndex == 4)
{ }
else
{ }
}
OnPropertyChanged(nameof(KeyWords));
KeyWordsTextBlockVisibility = Visibility.Visible;
ListViewOpacity = 1;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
IsSearchProgressRingActive = false;
}
}
public async Task SearchMore()
{
if (!_isSearchingMore)
{
_isSearchingMore = true;
if (OnlineMusicInfoList.HasAllLoaded || !await IsInternetAvailableAsync())
{
return;
}
IsSearchMoreProgressRingActive = true;
try
{
if (PageIndex == 0)
{
if (MusicLibraryIndex == 0)
{
if (OnlineMusicInfoList is not CloudBriefOnlineMusicInfoList)
{
OnlineMusicInfoList = new CloudBriefOnlineMusicInfoList();
}
await OnlineMusicInfoList.SearchMore();
}
else if (MusicLibraryIndex == 1)
{ }
else if (MusicLibraryIndex == 2)
{ }
else if (MusicLibraryIndex == 3)
{ }
else if (MusicLibraryIndex == 4)
{ }
else
{ }
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
_isSearchingMore = false;
IsSearchMoreProgressRingActive = false;
}
}
}
public async Task UpdateSearchResult()
{
if (!string.IsNullOrWhiteSpace(KeyWords))
{
if (MusicLibraryIndex == 0)
{
if (OnlineMusicInfoList is not CloudBriefOnlineMusicInfoList)
{
OnlineMusicInfoList = new CloudBriefOnlineMusicInfoList();
}
SearchResultList = await OnlineMusicInfoList.GetSearchResultAsync(KeyWords);
}
// 待修改
else if (MusicLibraryIndex == 1)
{
if (OnlineMusicInfoList is not CloudBriefOnlineMusicInfoList)
{
OnlineMusicInfoList = new CloudBriefOnlineMusicInfoList();
}
SearchResultList = await OnlineMusicInfoList.GetSearchResultAsync(KeyWords);
}
else if (MusicLibraryIndex == 2)
{
if (OnlineMusicInfoList is not CloudBriefOnlineMusicInfoList)
{
OnlineMusicInfoList = new CloudBriefOnlineMusicInfoList();
}
SearchResultList = await OnlineMusicInfoList.GetSearchResultAsync(KeyWords);
}
else if (MusicLibraryIndex == 3)
{
if (OnlineMusicInfoList is not CloudBriefOnlineMusicInfoList)
{
OnlineMusicInfoList = new CloudBriefOnlineMusicInfoList();
}
SearchResultList = await OnlineMusicInfoList.GetSearchResultAsync(KeyWords);
}
else if (MusicLibraryIndex == 4)
{
if (OnlineMusicInfoList is not CloudBriefOnlineMusicInfoList)
{
OnlineMusicInfoList = new CloudBriefOnlineMusicInfoList();
}
SearchResultList = await OnlineMusicInfoList.GetSearchResultAsync(KeyWords);
}
else
{
if (OnlineMusicInfoList is not CloudBriefOnlineMusicInfoList)
{
OnlineMusicInfoList = new CloudBriefOnlineMusicInfoList();
}
SearchResultList = await OnlineMusicInfoList.GetSearchResultAsync(KeyWords);
}
}
else
{
ClearSearchResult();
}
}
public void ClearSearchResult()
{
SearchResultList = [];
}
public async void RetryButton_Click(object sender, RoutedEventArgs e)
{
await Search();
}
private static async Task<bool> IsInternetAvailableAsync()
{
try
{
using var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(5);
var response = await client.GetAsync("https://www.baidu.com");
return response.IsSuccessStatusCode;
}
catch
{
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace The_Untamed_Music_Player.Models;
public class SearchResult
{
public string Icon { get; set; } = null!;
public string Label { get; set; } = null!;
public override string ToString()
{
return Label;
}
}

View File

@@ -0,0 +1,188 @@
using System;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Xaml.Media.Imaging;
using Newtonsoft.Json.Linq;
using The_Untamed_Music_Player.Contracts.Models;
using Windows.Storage.Streams;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI;
public class CloudBriefOnlineMusicInfo : IBriefOnlineMusicInfo
{
public bool IsAvailable { get; set; } = false;
public string Path { get; set; } = "";
public string Title { get; set; } = "";
public long ID { get; set; } = 0;
public virtual string Album { get; set; } = "";
public long AlbumID { get; set; } = 0;
public virtual string ArtistsStr { get; set; } = "";
public virtual string DurationStr { get; set; } = "";
public string YearStr { get; set; } = "";
public CloudBriefOnlineMusicInfo() { }
public static async Task<CloudBriefOnlineMusicInfo> CreateAsync(JToken jInfo, NeteaseCloudMusicApi api)
{
var info = new CloudBriefOnlineMusicInfo();
try
{
info.ID = (long)jInfo["id"]!;
var (isOK, songUrlResult) = await api.RequestAsync(CloudMusicApiProviders.SongUrl, new Dictionary<string, string> { { "id", $"{info.ID}" } });
var path = (string)songUrlResult["data"]![0]!["url"]!;
if (string.IsNullOrEmpty(path) || !isOK || path == "null")
{
info.IsAvailable = false;
return info;
}
else
{
info.Path = path;
info.Title = (string)jInfo["name"]!;
info.Album = (string)jInfo["album"]!["name"]!;
info.AlbumID = (long)jInfo["album"]!["id"]!;
string[] artists = [.. jInfo["artists"]!
.Select(t => (string)t["name"]!)
.Distinct()];
info.ArtistsStr = IBriefMusicInfoBase.GetArtistsStr(artists);
info.DurationStr = IBriefMusicInfoBase.GetDurationStr(TimeSpan.FromMilliseconds((long)jInfo["duration"]!));
info.YearStr = IBriefMusicInfoBase.GetYearStr((ushort)DateTimeOffset.FromUnixTimeMilliseconds((long)jInfo["album"]!["publishTime"]!).Year);
info.IsAvailable = true;
return info;
}
}
catch
{
info.IsAvailable = false;
return info;
}
}
public class CloudDetailedOnlineMusicInfo : CloudBriefOnlineMusicInfo, IDetailedOnlineMusicInfo
{
public bool IsOnline { get; set; } = true;
public string GenreStr { get; set; } = "";
public string ItemType { get; set; } = "";
public string AlbumArtistsStr { get; set; } = "";
public string ArtistAndAlbumStr { get; set; } = "";
public BitmapImage? Cover { get; set; }
public string CoverUrl { get; set; } = "";
public string BitRate { get; set; } = "";
public string Track { get; set; } = "";
public string Lyric { get; set; } = "";
public CloudDetailedOnlineMusicInfo() { }
public CloudDetailedOnlineMusicInfo(IBriefOnlineMusicInfo info)
{
var api = new NeteaseCloudMusicApi();
var songUrlTask = api.RequestAsync(CloudMusicApiProviders.SongUrl, new Dictionary<string, string> { { "id", $"{info.ID}" } });
var albumTask = api.RequestAsync(CloudMusicApiProviders.Album, new Dictionary<string, string> { { "id", $"{info.AlbumID}" } });
var lyricTask = api.RequestAsync(CloudMusicApiProviders.Lyric, new Dictionary<string, string> { { "id", $"{info.ID}" } });
songUrlTask.Wait();
albumTask.Wait();
lyricTask.Wait();
var (isOK1, songUrlResult) = songUrlTask.Result;
var (isOK2, albumResult) = albumTask.Result;
var (isOK3, lyricResult) = lyricTask.Result;
ID = info.ID;
Path = info.Path;
Title = info.Title;
Album = info.Album;
AlbumID = info.AlbumID;
ArtistsStr = info.ArtistsStr;
DurationStr = info.DurationStr;
YearStr = info.YearStr;
ItemType = (string)songUrlResult["data"]![0]!["type"]!;
string[] albumArtists = [.. albumResult["album"]!["artists"]!
.Select(t => (string)t["name"]!)
.Distinct()];
AlbumArtistsStr = IDetailedMusicInfoBase.GetAlbumArtistsStr(albumArtists);
ArtistAndAlbumStr = IDetailedMusicInfoBase.GetArtistAndAlbumStr(Album, ArtistsStr);
BitRate = $"{((int)songUrlResult["data"]![0]!["br"]!) / 1000} kbps";
CoverUrl = (string)albumResult["album"]!["picUrl"]!;
if (!string.IsNullOrEmpty(CoverUrl))
{
using var httpClient = new HttpClient();
var imageBytes = httpClient.GetByteArrayAsync(CoverUrl).Result;
using var stream = new MemoryStream(imageBytes);
var bitmap = new BitmapImage();
bitmap.SetSourceAsync(stream.AsRandomAccessStream()).AsTask().Wait();
Cover = bitmap;
}
Lyric = (string)lyricResult["lrc"]!["lyric"]!;
IsAvailable = true;
}
public static async Task<CloudDetailedOnlineMusicInfo> CreateAsync(IBriefOnlineMusicInfo info)
{
var detailedInfo = new CloudDetailedOnlineMusicInfo
{
ID = info.ID,
Path = info.Path,
Title = info.Title,
Album = info.Album,
AlbumID = info.AlbumID,
ArtistsStr = info.ArtistsStr,
DurationStr = info.DurationStr,
YearStr = info.YearStr
};
var api = new NeteaseCloudMusicApi();
var songUrlTask = api.RequestAsync(CloudMusicApiProviders.SongUrl, new Dictionary<string, string> { { "id", $"{info.ID}" } });
var albumTask = api.RequestAsync(CloudMusicApiProviders.Album, new Dictionary<string, string> { { "id", $"{info.AlbumID}" } });
var lyricTask = api.RequestAsync(CloudMusicApiProviders.Lyric, new Dictionary<string, string> { { "id", $"{info.ID}" } });
await Task.WhenAll(songUrlTask, albumTask, lyricTask);
var (isOK1, songUrlResult) = songUrlTask.Result;
var (isOK2, albumResult) = albumTask.Result;
var (isOK3, lyricResult) = lyricTask.Result;
api.Dispose();
detailedInfo.CoverUrl = (string)albumResult["album"]!["picUrl"]!;
Task? coverTask = null;
if (!string.IsNullOrEmpty(detailedInfo.CoverUrl))
{
using var httpClient = new HttpClient();
var coverBuffer = await httpClient.GetByteArrayAsync(detailedInfo.CoverUrl);
coverTask = LoadCoverAsync(coverBuffer, detailedInfo);
}
detailedInfo.ItemType = (string)songUrlResult["data"]![0]!["type"]!;
string[] albumArtists = [.. albumResult["album"]!["artists"]!
.Select(t => (string)t["name"]!)
.Distinct()];
detailedInfo.AlbumArtistsStr = IDetailedMusicInfoBase.GetAlbumArtistsStr(albumArtists);
detailedInfo.ArtistAndAlbumStr = IDetailedMusicInfoBase.GetArtistAndAlbumStr(detailedInfo.Album, detailedInfo.ArtistsStr);
detailedInfo.BitRate = $"{((int)songUrlResult["data"]![0]!["br"]!) / 1000} kbps";
detailedInfo.Lyric = (string)lyricResult["lrc"]!["lyric"]!;
detailedInfo.IsAvailable = true;
if (coverTask != null)
{
await coverTask;
}
return detailedInfo;
}
private static Task<bool> LoadCoverAsync(byte[] coverBuffer, CloudDetailedOnlineMusicInfo info)
{
var tcs = new TaskCompletionSource<bool>();
App.MainWindow?.DispatcherQueue.TryEnqueue(async () =>
{
try
{
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(coverBuffer.AsBuffer());
stream.Seek(0);
var bitmap = new BitmapImage
{
DecodePixelWidth = 400,
DecodePixelHeight = 400
};
await bitmap.SetSourceAsync(stream);
info.Cover = bitmap;
tcs.SetResult(true);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
return tcs.Task;
}
}
}

View File

@@ -0,0 +1,160 @@
using System.Diagnostics;
using Newtonsoft.Json.Linq;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Models;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI;
public partial class CloudBriefOnlineMusicInfoList : IBriefOnlineMusicInfoList
{
private readonly NeteaseCloudMusicApi _api = new();
private const byte _limit = 30;
private ushort _offset = 0;
private int _songCount = 0;
private int _listCount = 0;
public CloudBriefOnlineMusicInfoList()
{
}
public async override Task SearchAsync(string keyWords)
{
_offset = 0;
_listCount = 0;
HasAllLoaded = false;
Clear();
_keyWords = keyWords;
var (isOk, result) = await _api.RequestAsync(CloudMusicApiProviders.Search, new Dictionary<string, string>
{
{ "keywords", keyWords },
{ "limit", _limit.ToString() },
{ "offset", _offset.ToString() }
});
if (!isOk)
{
throw new Exception();
}
try
{
_songCount = (int)result["result"]!["songCount"]!;
if (_songCount == 0)
{
HasAllLoaded = true;
return;
}
await ProcessSongsAsync(result["result"]!["songs"]!);
_offset = 1;
}
catch
{
throw new Exception("搜索失败");
}
}
public async override Task SearchMore()
{
var (isOk, result) = await _api.RequestAsync(CloudMusicApiProviders.Search, new Dictionary<string, string>
{
{ "keywords", _keyWords },
{ "limit", _limit.ToString() },
{ "offset", _offset.ToString() }
});
if (!isOk)
{
throw new Exception();
}
try
{
await ProcessSongsAsync(result["result"]!["songs"]!);
_offset++;
}
catch
{
throw new Exception("搜索失败");
}
}
private async Task ProcessSongsAsync(JToken songs)
{
var actualCount = songs.Count();
var infos = new CloudBriefOnlineMusicInfo[actualCount];
var groupTasks = new List<Task>();
// 每组 8 首歌曲
for (var i = 0; i < actualCount; i += 8)
{
var start = i;
var end = Math.Min(i + 8, actualCount);
// 使用 Task.Run 将每组放在一个线程中执行
groupTasks.Add(Task.Run(async () =>
{
for (var j = start; j < end; j++)
{
try
{
var info = await CloudBriefOnlineMusicInfo.CreateAsync(songs[j]!, _api);
infos[j] = info;
}
catch (Exception ex)
{
_listCount++;
Debug.WriteLine(ex.StackTrace);
}
}
}));
}
await Task.WhenAll(groupTasks);
foreach (var info in infos)
{
Add(info);
}
}
protected new void Add(IBriefOnlineMusicInfo info)
{
_listCount++;
if (info.IsAvailable)
{
base.Add(info);
}
if (_listCount == _songCount)
{
HasAllLoaded = true;
}
}
public async override Task<List<SearchResult>> GetSearchResultAsync(string keyWords)
{
var (isOk, result) = await _api.RequestAsync(CloudMusicApiProviders.SearchSuggest, new Dictionary<string, string> { { "keywords", $"{keyWords}" } });
if (!isOk)
{
Debug.WriteLine("获取网易云音乐搜索建议失败");
return [];
}
var songs = result["result"]!["songs"]?
.Select(t => (string)t["name"]!)
.Distinct() ?? [];
var albums = result["result"]!["albums"]?
.Select(t => (string)t["name"]!)
.Distinct() ?? [];
var artists = result["result"]!["artists"]?
.Select(t => (string)t["name"]!)
.Distinct() ?? [];
var playlists = result["result"]!["playlists"]?
.Select(t => (string)t["name"]!)
.Distinct() ?? [];
var list = new List<SearchResult>();
AddResults(songs, 5, "\uE8D6", list);
AddResults(albums, 3, "\uE93C", list);
AddResults(artists, 3, "\uE77B", list);
AddResults(playlists, 2, "\uE728", list);
return list;
}
private static void AddResults(IEnumerable<string> items, int limit, string icon, List<SearchResult> list)
{
foreach (var item in items.Take(limit))
{
list.Add(new SearchResult { Icon = icon, Label = item });
}
}
}

View File

@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
#pragma warning disable
using System.Security.Cryptography;
using System.Text;
namespace NeteaseCloudMusicApi;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI;
internal static class Extensions
{
private static readonly MD5 _md5 = MD5.Create();

View File

@@ -1,18 +1,14 @@
using System;
using System.Collections.Generic;
using System.Extensions;
using System.IO;
using System.Linq;
#pragma warning disable
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NeteaseCloudMusicApi.util;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Extensions;
using The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.util;
namespace NeteaseCloudMusicApi;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI;
/// <summary>
/// 网易云音乐API
/// </summary>

View File

@@ -1,14 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using NeteaseCloudMusicApi.util;
using Newtonsoft.Json;
using static NeteaseCloudMusicApi.NeteaseCloudMusicApiProvider;
#pragma warning disable
namespace NeteaseCloudMusicApi;
using System.Net;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.util;
using static The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.NeteaseCloudMusicApiProvider;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI;
/// <summary>
/// 网易云音乐API相关信息提供者
/// </summary>
@@ -114,7 +112,7 @@ public sealed class NeteaseCloudMusicApiProvider
SpecialHandle
}
internal sealed class ParameterInfo(string key, NeteaseCloudMusicApiProvider.ParameterType type, string defaultValue)
internal sealed class ParameterInfo(string key, ParameterType type, string defaultValue)
{
public string Key = key;
public ParameterType Type = type;
@@ -311,7 +309,7 @@ public static partial class CloudMusicApiProviders
/// <summary>
/// 发送/删除评论
/// </summary>
public static readonly NeteaseCloudMusicApiProvider Comment = new("/comment", HttpMethod.Post, q => $"https://music.163.com/weapi/resource/comments/{(q["t"] == "1" ? "add" : (q["t"] == "0" ? "delete" : "reply"))}", [], BuildOptions("weapi", [new("os", "pc")]))
public static readonly NeteaseCloudMusicApiProvider Comment = new("/comment", HttpMethod.Post, q => $"https://music.163.com/weapi/resource/comments/{(q["t"] == "1" ? "add" : q["t"] == "0" ? "delete" : "reply")}", [], BuildOptions("weapi", [new("os", "pc")]))
{
DataProvider = queries =>
{

View File

@@ -0,0 +1,5 @@
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI;
internal sealed partial class QueryCollection : List<KeyValuePair<string, string>>
{
public void Add(string key, string value) => Add(new KeyValuePair<string, string>(key, value));
}

View File

@@ -1,7 +1,8 @@
#pragma warning disable
using System.Text;
namespace System.Extensions;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Extensions;
internal static class ExceptionExtensions
{
/// <summary>

View File

@@ -1,10 +1,9 @@
using System.Collections.Generic;
using System.Net.Http;
#pragma warning disable
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace System.Extensions;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Extensions;
internal static class HttpClientExtensions
{

View File

@@ -1,8 +1,4 @@
using System.Collections.Generic;
using System.Linq;
namespace System.Extensions;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Extensions;
internal static class HttpExtensions
{
public static string ToQueryString(this IEnumerable<KeyValuePair<string, string>> queries)

View File

@@ -1,11 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#pragma warning disable
using System.Diagnostics;
namespace System.Numerics;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Numerics;
internal readonly struct BigInteger
{
private const uint kuMaskHighBit = unchecked((uint)int.MinValue);
@@ -133,14 +130,14 @@ internal readonly struct BigInteger
{
for (var i = 0; i < byteCount; i++)
{
_sign = (_sign << 8) | value[i];
_sign = _sign << 8 | value[i];
}
}
else
{
for (var i = byteCount - 1; i >= 0; i--)
{
_sign = (_sign << 8) | value[i];
_sign = _sign << 8 | value[i];
}
}
@@ -177,7 +174,7 @@ internal readonly struct BigInteger
for (var byteInDword = 0; byteInDword < 4; byteInDword++)
{
var curByteValue = value[curByte];
val[curDword] = (val[curDword] << 8) | curByteValue;
val[curDword] = val[curDword] << 8 | curByteValue;
curByte++;
}
@@ -192,7 +189,7 @@ internal readonly struct BigInteger
for (var byteInDword = 0; byteInDword < 4; byteInDword++)
{
var curByteValue = value[curByte];
val[curDword] = (val[curDword] << 8) | curByteValue;
val[curDword] = val[curDword] << 8 | curByteValue;
curByte--;
}
@@ -213,7 +210,7 @@ internal readonly struct BigInteger
for (curByte = 0; curByte < unalignedBytes; curByte++)
{
var curByteValue = value[curByte];
val[curDword] = (val[curDword] << 8) | curByteValue;
val[curDword] = val[curDword] << 8 | curByteValue;
}
}
else
@@ -221,7 +218,7 @@ internal readonly struct BigInteger
for (curByte = byteCountMinus1; curByte >= byteCount - unalignedBytes; curByte--)
{
var curByteValue = value[curByte];
val[curDword] = (val[curDword] << 8) | curByteValue;
val[curDword] = val[curDword] << 8 | curByteValue;
}
}
}
@@ -254,7 +251,7 @@ internal readonly struct BigInteger
default:
if (unchecked((int)val[0]) > 0)
{
_sign = (-1) * ((int)val[0]);
_sign = -1 * (int)val[0];
_bits = null;
AssertValid();
return;
@@ -343,7 +340,7 @@ internal readonly struct BigInteger
get
{
AssertValid();
return (_sign >> (kcbitUint - 1)) - (-_sign >> (kcbitUint - 1));
return (_sign >> kcbitUint - 1) - (-_sign >> kcbitUint - 1);
}
}
@@ -432,7 +429,7 @@ internal readonly struct BigInteger
var bits = _bits;
if (bits == null)
{
highByte = (byte)((sign < 0) ? 0xff : 0x00);
highByte = (byte)(sign < 0 ? 0xff : 0x00);
highDword = unchecked((uint)sign);
}
else if (sign == -1)
@@ -565,9 +562,9 @@ internal readonly struct BigInteger
}
// Assert we're big endian, or little endian consistency holds.
Debug.Assert(isBigEndian || (!needExtraByte && curByte == length - 1) || (needExtraByte && curByte == length - 2));
Debug.Assert(isBigEndian || !needExtraByte && curByte == length - 1 || needExtraByte && curByte == length - 2);
// Assert we're little endian, or big endian consistency holds.
Debug.Assert(!isBigEndian || (!needExtraByte && curByte == 0) || (needExtraByte && curByte == 1));
Debug.Assert(!isBigEndian || !needExtraByte && curByte == 0 || needExtraByte && curByte == 1);
if (needExtraByte)
{

View File

@@ -1,11 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace System.Numerics;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Numerics;
internal static partial class BigIntegerCalculator
{
private static unsafe void Add(uint* left, int leftLength,

View File

@@ -1,15 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace System.Numerics;
// ATTENTION: always pass BitsBuffer by reference,
// it's a structure for performance reasons. Furthermore
// it's a mutable one, so use it only with care!
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Numerics;
internal static partial class BigIntegerCalculator
{
internal struct BitsBuffer

View File

@@ -1,11 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace System.Numerics;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Numerics;
internal static partial class BigIntegerCalculator
{
public static uint Remainder(uint[] left, uint right)

View File

@@ -1,11 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace System.Numerics;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Numerics;
internal static partial class BigIntegerCalculator
{
internal readonly struct FastReducer

View File

@@ -1,11 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace System.Numerics;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Numerics;
internal static partial class BigIntegerCalculator
{
// Executes different exponentiation algorithms, which are

View File

@@ -1,11 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace System.Numerics;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Numerics;
internal static partial class BigIntegerCalculator
{
private static readonly int SquareThreshold = 32;

View File

@@ -1,9 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace System.Numerics;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Numerics;
internal static class NumericsHelpers
{
public static void DangerousMakeTwosComplement(uint[] d)

View File

@@ -1,14 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
#pragma warning disable
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using BigInteger = The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Numerics.BigInteger;
namespace NeteaseCloudMusicApi.util;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.util;
internal static class crypto
{
private static readonly byte[] iv = Encoding.ASCII.GetBytes("0102030405060708");

View File

@@ -1,7 +1,8 @@
#pragma warning disable
using System.Net;
namespace NeteaseCloudMusicApi.util;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.util;
internal sealed class options
{
public string crypto;

View File

@@ -1,19 +1,14 @@
using System;
using System.Collections.Generic;
using System.Extensions;
using System.IO;
#pragma warning disable
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.System.Extensions;
namespace NeteaseCloudMusicApi.util;
namespace The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI.util;
internal static partial class request
{
private static readonly string[] userAgentList = [

View File

@@ -216,6 +216,9 @@
<data name="HaveMusic_Songs.Text" xml:space="preserve">
<value>Songs</value>
</data>
<data name="Home_Playlists.Text" xml:space="preserve">
<value>Playlists</value>
</data>
<data name="HaveMusic_Albums.Text" xml:space="preserve">
<value>Albums</value>
</data>
@@ -606,4 +609,28 @@
<data name="AlbumInfo_UnknownYear" xml:space="preserve">
<value>Unknown year</value>
</data>
<data name="Home_MusicLibrary1.Text" xml:space="preserve">
<value>Music library 1</value>
</data>
<data name="Home_MusicLibrary6.Text" xml:space="preserve">
<value>Music library 6</value>
</data>
<data name="Home_MusicLibrary5.Text" xml:space="preserve">
<value>Music library 5</value>
</data>
<data name="Home_MusicLibrary4.Text" xml:space="preserve">
<value>Music library 4</value>
</data>
<data name="Home_MusicLibrary3.Text" xml:space="preserve">
<value>Music library 3</value>
</data>
<data name="Home_MusicLibrary2.Text" xml:space="preserve">
<value>Music library 2</value>
</data>
<data name="Home_NetworkError.Text" xml:space="preserve">
<value>Network error, please retry later</value>
</data>
<data name="Home_Retry.Text" xml:space="preserve">
<value>Retry</value>
</data>
</root>

View File

@@ -216,6 +216,9 @@
<data name="HaveMusic_Songs.Text" xml:space="preserve">
<value>歌曲</value>
</data>
<data name="Home_Playlists.Text" xml:space="preserve">
<value>歌单</value>
</data>
<data name="HaveMusic_Albums.Text" xml:space="preserve">
<value>专辑</value>
</data>
@@ -606,4 +609,28 @@
<data name="AlbumInfo_UnknownYear" xml:space="preserve">
<value>未知年份</value>
</data>
<data name="Home_MusicLibrary1.Text" xml:space="preserve">
<value>乐库1</value>
</data>
<data name="Home_MusicLibrary6.Text" xml:space="preserve">
<value>乐库6</value>
</data>
<data name="Home_MusicLibrary5.Text" xml:space="preserve">
<value>乐库5</value>
</data>
<data name="Home_MusicLibrary4.Text" xml:space="preserve">
<value>乐库4</value>
</data>
<data name="Home_MusicLibrary3.Text" xml:space="preserve">
<value>乐库3</value>
</data>
<data name="Home_MusicLibrary2.Text" xml:space="preserve">
<value>乐库2</value>
</data>
<data name="Home_NetworkError.Text" xml:space="preserve">
<value>网络异常,请稍后再试</value>
</data>
<data name="Home_Retry.Text" xml:space="preserve">
<value>重试</value>
</data>
</root>

View File

@@ -37,6 +37,12 @@
<ItemGroup>
<None Remove="Assets\**\*" />
</ItemGroup>
<ItemGroup>
<None Remove="Views\OnlineAlbumsPage.xaml" />
<None Remove="Views\OnlineArtistsPage.xaml" />
<None Remove="Views\OnlinePlayListsPage.xaml" />
<None Remove="Views\OnlineSongsPage.xaml" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
@@ -45,16 +51,19 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.250129-pull-636.2034" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250129-preview2" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250129-preview2" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.1.240916" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.241112-preview1" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250129-preview2" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250129-preview2" />
<PackageReference Include="FluentIcons.WinUI" Version="1.1.271" />
<PackageReference Include="hyjiacan.pinyin4net" Version="4.1.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.1" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250108002" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250205002" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
<PackageReference Include="System.Drawing.Common" Version="9.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Drawing.Common" Version="9.0.2" />
<PackageReference Include="System.Memory" Version="4.6.0" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="WinUIEx" Version="2.5.1" />
</ItemGroup>
@@ -63,6 +72,18 @@
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Page Update="Views\OnlinePlayListsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\OnlineArtistsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\OnlineAlbumsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\OnlineSongsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\DesktopLyricWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -74,6 +95,8 @@
<PropertyGroup Label="Globals">
<WebView2EnableCsWinRTProjection>False</WebView2EnableCsWinRTProjection>
<WebView2UseWinRT>False</WebView2UseWinRT>
<WebView2LoaderPreference>Dynamic</WebView2LoaderPreference>
</PropertyGroup>
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
@@ -83,30 +106,36 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<DebugType>full</DebugType>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<NoWarn>CS8981</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DebugType>full</DebugType>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<NoWarn>CS8981</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|arm64'">
<DebugType>full</DebugType>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<NoWarn>CS8981</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<DebugType>full</DebugType>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<NoWarn>CS8981</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>full</DebugType>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<NoWarn>CS8981</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|arm64'">
<DebugType>full</DebugType>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<NoWarn>CS8981</NoWarn>
</PropertyGroup>
</Project>

View File

@@ -16,19 +16,19 @@ public partial class AlbumDetailViewModel : ObservableRecipient
public AlbumDetailViewModel()
{
var tempList = Data.MusicLibrary.GetMusicsByAlbum(Album);
var tempList = Data.MusicLibrary.GetSongsByAlbum(Album);
SongList = [.. tempList];
}
public void PlayAllButton_Click(object sender, RoutedEventArgs e)
{
Data.MusicPlayer.SetPlayList($"Songs:Album:{Album.Name}", SongList);
Data.MusicPlayer.SetPlayList($"LocalSongs:Album:{Album.Name}", SongList);
Data.MusicPlayer.PlaySongByPath(SongList[0].Path);
}
public void SongListView_ItemClick(object sender, ItemClickEventArgs e)
{
Data.MusicPlayer.SetPlayList($"Songs:Album:{Album.Name}", SongList);
Data.MusicPlayer.SetPlayList($"LocalSongs:Album:{Album.Name}", SongList);
if (e.ClickedItem is BriefMusicInfo briefMusicInfo)
{
Data.MusicPlayer.PlaySongByPath(briefMusicInfo.Path);
@@ -37,7 +37,7 @@ public partial class AlbumDetailViewModel : ObservableRecipient
public void PlayButton_Click(object sender, RoutedEventArgs e)
{
Data.MusicPlayer.SetPlayList($"Songs:Album:{Album.Name}", SongList);
Data.MusicPlayer.SetPlayList($"LocalSongs:Album:{Album.Name}", SongList);
if (sender is Button button && button.DataContext is BriefMusicInfo briefMusicInfo)
{
Data.MusicPlayer.PlaySongByPath(briefMusicInfo.Path);

View File

@@ -1,4 +1,6 @@
using System.Collections.ObjectModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Models;
@@ -21,6 +23,41 @@ public class ArtistDetailViewModel
public void PlayAllButton_Click(object sender, RoutedEventArgs e)
{
Data.MusicPlayer.SetPlayList($"LocalSongs:Artist:{Artist.Name}", ConvertAllSongsToFlatList());
Data.MusicPlayer.PlaySongByPath(AlbumList[0].SongList[0].Path);
}
public void SongListView_ItemClick(object sender, ItemClickEventArgs e)
{
Data.MusicPlayer.SetPlayList($"LocalSongs:Artist:{Artist.Name}", ConvertAllSongsToFlatList());
if (e.ClickedItem is BriefMusicInfo briefMusicInfo)
{
Data.MusicPlayer.PlaySongByPath(briefMusicInfo.Path);
}
}
public void SongListViewPlayButton_Click(object sender, RoutedEventArgs e)
{
Data.MusicPlayer.SetPlayList($"LocalSongs:Artist:{Artist.Name}", ConvertAllSongsToFlatList());
if (sender is Button button && button.DataContext is BriefMusicInfo briefMusicInfo)
{
Data.MusicPlayer.PlaySongByPath(briefMusicInfo.Path);
}
}
public void AlbumGridViewPlayButton_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is BriefAlbumInfo briefAlbumInfo)
{
var songList = new ObservableCollection<BriefMusicInfo>(briefAlbumInfo.SongList);
Data.MusicPlayer.SetPlayList($"LocalSongs:Album:{briefAlbumInfo.Name}", songList);
Data.MusicPlayer.PlaySongByPath(songList[0].Path);
}
}
private ObservableCollection<BriefMusicInfo> ConvertAllSongsToFlatList()
{
return [.. AlbumList.SelectMany(album => album.SongList)];
}
public async Task<int> LoadSelectionBarSelectedIndex()

View File

@@ -1,25 +1,132 @@
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Views;
namespace The_Untamed_Music_Player.ViewModels;
public partial class HomeViewModel : ObservableRecipient
{
public byte PageIndex
{
get;
set
{
field = value;
Data.OnlineMusicLibrary.PageIndex = value;
}
}
[ObservableProperty]
public partial byte MusicLibraryIndex { get; set; }
partial void OnMusicLibraryIndexChanged(byte value)
{
Data.OnlineMusicLibrary.MusicLibraryIndex = value;
SaveMusicLibraryIndex();
}
private readonly ILocalSettingsService _localSettingsService = App.GetService<ILocalSettingsService>();
public HomeViewModel()
{
Initialize();
}
public void SuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
private async void Initialize()
{
PageIndex = await LoadPageIndex();
MusicLibraryIndex = await LoadMusicLibraryIndex();
}
public void SuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
public async void SuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
Data.OnlineMusicLibrary.KeyWords = sender.Text;
await Data.OnlineMusicLibrary.UpdateSearchResult();
}
}
public void SuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
public async void SuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
if (args.ChosenSuggestion != null && args.ChosenSuggestion is SearchResult result)
{
var keyWords = result.Label;
Data.OnlineMusicLibrary.ClearSearchResult();
var currentSelectedIndex = result.Icon switch
{
"\uE8D6" => 0,
"\uE93C" => 1,
"\uE77B" => 2,
"\uE728" => 3,
_ => 0
};
Data.OnlineMusicLibrary.KeyWords = keyWords;
Navigate(currentSelectedIndex);
await Data.OnlineMusicLibrary.Search();
}
else
{
Data.OnlineMusicLibrary.KeyWords = args.QueryText;
Data.OnlineMusicLibrary.ClearSearchResult();
await Data.OnlineMusicLibrary.Search();
}
}
public void SelectorBar_Loaded(object sender, RoutedEventArgs e)
{
if (sender is SelectorBar selectorBar)
{
var selectedItem = selectorBar.Items[PageIndex];
selectorBar.SelectedItem = selectedItem;
}
}
public void SelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs args)
{
var selectedItem = sender.SelectedItem;
var currentSelectedIndex = sender.Items.IndexOf(selectedItem);
Navigate(currentSelectedIndex);
}
public void Navigate(int currentSelectedIndex, bool isFirstLoaded = false)
{
if (!isFirstLoaded && PageIndex == currentSelectedIndex)
{
return;
}
var page = currentSelectedIndex switch
{
0 => typeof(OnlineSongsPage),
1 => typeof(OnlineAlbumsPage),
2 => typeof(OnlineArtistsPage),
3 => typeof(OnlinePlayListsPage),
_ => typeof(OnlineSongsPage)
};
var slideNavigationTransitionEffect = currentSelectedIndex - PageIndex > 0 ? SlideNavigationTransitionEffect.FromRight : SlideNavigationTransitionEffect.FromLeft;
PageIndex = (byte)currentSelectedIndex;
Data.HomePage.GetFrame().Navigate(page, null, new SlideNavigationTransitionInfo() { Effect = slideNavigationTransitionEffect });
}
public async Task<byte> LoadPageIndex()
{
return await _localSettingsService.ReadSettingAsync<byte>("HomePageIndex");
}
public async Task<byte> LoadMusicLibraryIndex()
{
return await _localSettingsService.ReadSettingAsync<byte>("HomeMusicLibraryIndex");
}
public async void SavePageIndex()
{
await _localSettingsService.SaveSettingAsync("HomePageIndex", PageIndex);
}
public async void SaveMusicLibraryIndex()
{
await _localSettingsService.SaveSettingAsync("HomeMusicLibraryIndex", MusicLibraryIndex);
}
}

View File

@@ -123,9 +123,9 @@ public partial class LocalAlbumsViewModel : ObservableRecipient
{
if (sender is Button button && button.DataContext is AlbumInfo albumInfo)
{
var tempList = Data.MusicLibrary.GetMusicsByAlbum(albumInfo);
var tempList = Data.MusicLibrary.GetSongsByAlbum(albumInfo);
var songList = new ObservableCollection<BriefMusicInfo>(tempList);
Data.MusicPlayer.SetPlayList($"Songs:Album:{albumInfo.Name}", songList);
Data.MusicPlayer.SetPlayList($"LocalSongs:Album:{albumInfo.Name}", songList);
Data.MusicPlayer.PlaySongByPath(songList[0].Path);
}
}

View File

@@ -73,6 +73,16 @@ public partial class LocalArtistsViewModel : ObservableRecipient
}
}
public void PlayButton_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is ArtistInfo artistInfo)
{
var songList = Data.MusicLibrary.GetSongsByArtist(artistInfo);
Data.MusicPlayer.SetPlayList($"LocalSongs:Artist:{artistInfo.Name}", songList);
Data.MusicPlayer.PlaySongByPath(songList[0].Path);
}
}
public async Task SortArtists()
{
var sortTask = SortMode switch

View File

@@ -160,7 +160,7 @@ public partial class LocalSongsViewModel : ObservableRecipient
public void SongListView_ItemClick(object sender, ItemClickEventArgs e)
{
Data.MusicPlayer.SetPlayList("Songs:All", ConvertGroupedToFlatList(), SortMode);
Data.MusicPlayer.SetPlayList("LocalSongs:All", ConvertGroupedToFlatList(), SortMode);
if (e.ClickedItem is BriefMusicInfo briefMusicInfo)
{
Data.MusicPlayer.PlaySongByPath(briefMusicInfo.Path);
@@ -169,7 +169,7 @@ public partial class LocalSongsViewModel : ObservableRecipient
public void PlayButton_Click(object sender, RoutedEventArgs e)
{
Data.MusicPlayer.SetPlayList("Songs:All", ConvertGroupedToFlatList(), SortMode);
Data.MusicPlayer.SetPlayList("LocalSongs:All", ConvertGroupedToFlatList(), SortMode);
if (sender is Button button && button.DataContext is BriefMusicInfo briefMusicInfo)
{
Data.MusicPlayer.PlaySongByPath(briefMusicInfo.Path);
@@ -253,27 +253,11 @@ public partial class LocalSongsViewModel : ObservableRecipient
await SortSongs();
}
public ObservableCollection<BriefMusicInfo> ConvertGroupedToFlatList()
private ObservableCollection<BriefMusicInfo> ConvertGroupedToFlatList()
{
if (_isGrouped)
{
var flatList = new ObservableCollection<BriefMusicInfo>();
foreach (var group in GroupedSongList)
{
foreach (var item in group)
{
if (item is BriefMusicInfo musicInfo)
{
flatList.Add(musicInfo);
}
}
}
return flatList;
}
else
{
return NotGroupedSongList;
}
return _isGrouped
? [.. GroupedSongList.SelectMany(group => group.OfType<BriefMusicInfo>())]
: NotGroupedSongList;
}
public object GetSongListViewSource(ICollectionView grouped, ObservableCollection<BriefMusicInfo> notgrouped)

View File

@@ -25,7 +25,7 @@ public partial class MusicLibraryViewModel : ObservableRecipient
private async Task InitializeLibraryAsync()
{
Data.MusicLibrary.PropertyChanged += MusicLibrary_PropertyChanged;
if (!Data.hasMusicLibraryLoaded)
if (!Data.HasMusicLibraryLoaded)
{
await Task.Run(Data.MusicLibrary.LoadLibraryAsync);
}

View File

@@ -0,0 +1,9 @@
using CommunityToolkit.Mvvm.ComponentModel;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Models;
namespace The_Untamed_Music_Player.ViewModels;
public partial class OnlineSongsViewModel : ObservableRecipient
{
}

View File

@@ -20,10 +20,7 @@ public partial class SettingsViewModel : ObservableRecipient
private readonly IThemeSelectorService _themeSelectorService;
private readonly ILocalSettingsService _localSettingsService;
public ICommand SwitchThemeCommand
{
get;
}
public ICommand SwitchThemeCommand { get; }
/// <summary>
/// 是否显示文件夹为空信息
@@ -69,19 +66,13 @@ public partial class SettingsViewModel : ObservableRecipient
/// 深浅色主题
/// </summary>
[ObservableProperty]
public partial ElementTheme ElementTheme
{
get; set;
}
public partial ElementTheme ElementTheme { get; set; }
/// <summary>
/// 版本信息
/// </summary>
[ObservableProperty]
public partial string VersionDescription
{
get; set;
}
public partial string VersionDescription { get; set; }
/// <summary>
/// 选中的字体

View File

@@ -57,7 +57,7 @@ public sealed partial class AlbumDetailPage : Page
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
if (e.NavigationMode == NavigationMode.Back)
if (e.NavigationMode == NavigationMode.Back && e.SourcePageType != typeof(ArtistDetailPage))
{
ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("BackConnectedAnimation", CoverArt);
}

View File

@@ -117,6 +117,7 @@
<ListView x:Name="SongListView"
helper:ListViewExtensions.ItemCornerRadius="8"
helper:ListViewExtensions.ItemMargin="0,3,0,3" IsItemClickEnabled="True"
ItemClick="SongListView_ItemClick"
ItemTemplate="{StaticResource SongListViewTemplate}"
ItemsSource="{x:Bind SongList}"
SelectionMode="None">
@@ -197,6 +198,7 @@
IsThreeState="False" Visibility="Collapsed"/>
<Button x:Name="PlayButton" x:Uid="LocalSongs_PlayButton"
Grid.Column="1"
Click="SongListViewPlayButton_Click"
DataContext="{x:Bind}"
Style="{StaticResource SmallPlayButtonStyle}"
Visibility="Collapsed">
@@ -307,6 +309,7 @@
Width="32" Height="32"
Margin="12,0,0,8" HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Click="AlbumGridViewPlayButton_Click"
DataContext="{x:Bind}"
Style="{StaticResource CircularButtonStyle}"
Visibility="Collapsed">
@@ -663,6 +666,7 @@
</ListView>
<GridView x:Name="AlbumGridView"
helper:ListViewExtensions.ItemCornerRadius="8" IsItemClickEnabled="True"
ItemClick="AlbumGridView_ItemClick"
ItemTemplate="{StaticResource AlbumGridViewTemplate}"
ItemsSource="{x:Bind ViewModel.AlbumList}"
SelectionMode="None">

View File

@@ -9,6 +9,7 @@ using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Navigation;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.ViewModels;
using Windows.Storage.Streams;
using EF = CommunityToolkit.WinUI.Animations.Expressions.ExpressionFunctions;
@@ -66,13 +67,24 @@ public sealed partial class ArtistDetailPage : Page
SelectionBarSelectedIndex = await ViewModel.LoadSelectionBarSelectedIndex();
}
/// <summary>
/// 当页面导航到此页时,将调用此方法。
/// </summary>
/// <param name="e"></param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("ForwardConnectedAnimation");
animation?.TryStart(CoverArt);
if (e.NavigationMode == NavigationMode.New)
{
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("ForwardConnectedAnimation");
animation?.TryStart(CoverArt);
}
}
/// <summary>
/// 当页面从此页导航时,将调用此方法。
/// </summary>
/// <param name="e"></param>
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
@@ -346,4 +358,51 @@ public sealed partial class ArtistDetailPage : Page
}
SelectionBarSelectedIndex = currentSelectedIndex;
}
private void SongListViewPlayButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.SongListViewPlayButton_Click(sender, e);
}
private void SongListView_ItemClick(object sender, ItemClickEventArgs e)
{
ViewModel.SongListView_ItemClick(sender, e);
}
private void AlbumGridViewPlayButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.AlbumGridViewPlayButton_Click(sender, e);
}
private void AlbumGridView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is BriefAlbumInfo briefAlbumInfo)
{
var albumInfo = Data.MusicLibrary.Albums[briefAlbumInfo.Name];
if (albumInfo != null)
{
var grid = (Grid)((ContentControl)AlbumGridView.ContainerFromItem(e.ClickedItem)).ContentTemplateRoot;
var border = (Border)grid.Children[1];
ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("ForwardConnectedAnimation", border);
Data.SelectedAlbum = albumInfo;
Data.ShellPage!.GetFrame().Navigate(typeof(AlbumDetailPage), null, new SuppressNavigationTransitionInfo());
}
}
}
/*private void AlbumGridView_Loaded(object sender, RoutedEventArgs e)
{
if (Data.SelectedBriefAlbum != null && sender is GridView gridView)
{
gridView.ScrollIntoView(Data.SelectedBriefAlbum, ScrollIntoViewAlignment.Leading);
gridView.UpdateLayout();
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("BackConnectedAnimation");
if (animation != null)
{
animation.Configuration = new DirectConnectedAnimationConfiguration();
await gridView.TryStartConnectedAnimationAsync(animation, Data.SelectedBriefAlbum, "CoverBorder");
}
gridView.Focus(FocusState.Programmatic);
}
}*/
}

View File

@@ -1,15 +1,37 @@
<Page x:Class="The_Untamed_Music_Player.Views.HomePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:The_Untamed_Music_Player.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:The_Untamed_Music_Player.Models"
Background="Transparent"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="SearchResultTemplate" x:DataType="model:SearchResult">
<Grid AutomationProperties.Name="{x:Bind Label}" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<FontIcon Grid.RowSpan="2"
VerticalAlignment="Center"
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="{x:Bind Icon}"/>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Text="{x:Bind Label}"
TextTrimming="WordEllipsis"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid x:Name="ContentArea">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
@@ -21,14 +43,34 @@
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="16,36,16,0"/>
<Setter Target="AutoSuggestBox.Width" Value="140"/>
<Setter Target="Segmented.Margin" Value="16,24,16,0"/>
<Setter Target="NetworkErrorFontIcon.FontSize" Value="75"/>
<Setter Target="NetworkErrorStackPanel.Spacing" Value="18"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState x:Name="Medium">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="641"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="56,36,56,0"/>
<Setter Target="AutoSuggestBox.Width" Value="190"/>
<Setter Target="Segmented.Margin" Value="56,24,56,0"/>
<Setter Target="NetworkErrorFontIcon.FontSize" Value="130"/>
<Setter Target="NetworkErrorStackPanel.Spacing" Value="38"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Large">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="930"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="56,36,56,0"/>
<Setter Target="AutoSuggestBox.Width" Value="400"/>
<Setter Target="Segmented.Margin" Value="56,24,56,0"/>
<Setter Target="NetworkErrorFontIcon.FontSize" Value="130"/>
<Setter Target="NetworkErrorStackPanel.Spacing" Value="38"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
@@ -43,25 +85,84 @@
Grid.Column="0"
FontSize="40"
FontWeight="{x:Bind helper:LanguageRelated.GetTitleFontWeight()}"/>
<SelectorBar x:Name="SelectorBar"
Grid.Column="1"
Margin="18,0,0,0"
SelectionChanged="SelectorBar_SelectionChanged">
<SelectorBarItem x:Name="SelectorBarItemPage1" x:Uid="HaveMusic_Songs"
FontSize="18" IsSelected="True"/>
<SelectorBarItem x:Name="SelectorBarItemPage2" x:Uid="HaveMusic_Albums"
FontSize="18"/>
<SelectorBarItem x:Name="SelectorBarItemPage3" x:Uid="HaveMusic_Artists"
FontSize="18"/>
</SelectorBar>
<AutoSuggestBox x:Uid="Home_SearchOnlineSongs"
<ScrollViewer x:Name="SelectorBar"
Grid.Column="1"
Margin="18,0,10,-9" Padding="0,0,0,9"
HorizontalAlignment="Left"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"
VerticalScrollMode="Disabled">
<SelectorBar Grid.Column="1"
Loaded="{x:Bind ViewModel.SelectorBar_Loaded}"
SelectionChanged="{x:Bind ViewModel.SelectorBar_SelectionChanged}">
<SelectorBarItem x:Name="SelectorBarItemPage1" x:Uid="HaveMusic_Songs"
FontSize="18" IsSelected="True"/>
<SelectorBarItem x:Name="SelectorBarItemPage2" x:Uid="HaveMusic_Albums"
FontSize="18"/>
<SelectorBarItem x:Name="SelectorBarItemPage3" x:Uid="HaveMusic_Artists"
FontSize="18"/>
<SelectorBarItem x:Name="SelectorBarItemPage4" x:Uid="Home_PlayLists"
FontSize="18"/>
</SelectorBar>
</ScrollViewer>
<AutoSuggestBox x:Name="AutoSuggestBox" x:Uid="Home_SearchOnlineSongs"
Grid.Column="2"
Width="400" Height="32"
Margin="0,7,0,0" VerticalAlignment="Center"
ItemTemplate="{StaticResource SearchResultTemplate}"
ItemsSource="{x:Bind model:Data.OnlineMusicLibrary.SearchResultList, Mode=OneWay}"
QueryIcon="Find"
QuerySubmitted="{x:Bind ViewModel.SuggestBox_QuerySubmitted}"
SuggestionChosen="{x:Bind ViewModel.SuggestBox_SuggestionChosen}"
TextChanged="{x:Bind ViewModel.SuggestBox_TextChanged}"/>
</Grid>
<ScrollViewer x:Name="Segmented"
Grid.Row="1"
Padding="0,0,0,13"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"
VerticalScrollMode="Disabled">
<controls:Segmented SelectedIndex="{x:Bind ViewModel.MusicLibraryIndex, Mode=TwoWay}" SelectionMode="Single">
<controls:SegmentedItem>
<TextBlock x:Uid="Home_MusicLibrary1"
Width="90"
HorizontalTextAlignment="Center"/>
</controls:SegmentedItem>
<controls:SegmentedItem>
<TextBlock x:Uid="Home_MusicLibrary2"/>
</controls:SegmentedItem>
<controls:SegmentedItem>
<TextBlock x:Uid="Home_MusicLibrary3"/>
</controls:SegmentedItem>
<controls:SegmentedItem>
<TextBlock x:Uid="Home_MusicLibrary4"/>
</controls:SegmentedItem>
<controls:SegmentedItem>
<TextBlock x:Uid="Home_MusicLibrary5"/>
</controls:SegmentedItem>
<controls:SegmentedItem>
<TextBlock x:Uid="Home_MusicLibrary6"/>
</controls:SegmentedItem>
</controls:Segmented>
</ScrollViewer>
<ProgressRing Grid.Row="2"
Width="50" Height="50"
Margin="0,24,0,0"
Canvas.ZIndex="1"
IsActive="{x:Bind model:Data.OnlineMusicLibrary.IsSearchProgressRingActive, Mode=OneWay}"/>
<StackPanel x:Name="NetworkErrorStackPanel"
Grid.Row="2"
HorizontalAlignment="Center" VerticalAlignment="Center"
Canvas.ZIndex="1" Orientation="Horizontal"
Visibility="{x:Bind model:Data.OnlineMusicLibrary.NetworkErrorVisibility, Mode=OneWay}">
<FontIcon x:Name="NetworkErrorFontIcon" Glyph="&#xEB56;"/>
<StackPanel VerticalAlignment="Center" Spacing="8">
<TextBlock x:Uid="Home_NetworkError" FontSize="29"/>
<Button Click="{x:Bind model:Data.OnlineMusicLibrary.RetryButton_Click}" Style="{StaticResource AccentButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="12" Glyph="&#xE72C;"/>
<TextBlock x:Uid="Home_Retry"/>
</StackPanel>
</Button>
</StackPanel>
</StackPanel>
<Frame x:Name="SelectFrame" Grid.Row="2"/>
</Grid>
</Page>

View File

@@ -1,5 +1,7 @@
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
@@ -15,10 +17,12 @@ public sealed partial class HomePage : Page
{
ViewModel = App.GetService<HomeViewModel>();
InitializeComponent();
Data.HomePage = this;
ViewModel.Navigate(ViewModel.PageIndex, true);
}
private void SelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs args)
public Frame GetFrame()
{
return SelectFrame;
}
}

View File

@@ -92,7 +92,7 @@
Width="32" Height="32"
Margin="13,0,0,8" HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Canvas.ZIndex="2"
Canvas.ZIndex="2" Click="PlayButton_Click"
Style="{StaticResource CircularButtonStyle}"
Visibility="Collapsed">
<FontIcon FontSize="12" Glyph="&#xE768;"/>

View File

@@ -87,4 +87,9 @@ public sealed partial class LocalArtistsPage : Page
gridView.Focus(FocusState.Programmatic);
}
}
private void PlayButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.PlayButton_Click(sender, e);
}
}

View File

@@ -3,6 +3,7 @@
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:contract="using:The_Untamed_Music_Player.Contracts.Models"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:The_Untamed_Music_Player.Models"

View File

@@ -0,0 +1,10 @@
<Page x:Class="The_Untamed_Music_Player.Views.OnlineAlbumsPage"
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.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid/>
</Page>

View File

@@ -0,0 +1,10 @@
using Microsoft.UI.Xaml.Controls;
namespace The_Untamed_Music_Player.Views;
public sealed partial class OnlineAlbumsPage : Page
{
public OnlineAlbumsPage()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,10 @@
<Page x:Class="The_Untamed_Music_Player.Views.OnlineArtistsPage"
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.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid/>
</Page>

View File

@@ -0,0 +1,10 @@
using Microsoft.UI.Xaml.Controls;
namespace The_Untamed_Music_Player.Views;
public sealed partial class OnlineArtistsPage : Page
{
public OnlineArtistsPage()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,10 @@
<Page x:Class="The_Untamed_Music_Player.Views.OnlinePlayListsPage"
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.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid/>
</Page>

View File

@@ -0,0 +1,10 @@
using Microsoft.UI.Xaml.Controls;
namespace The_Untamed_Music_Player.Views;
public sealed partial class OnlinePlayListsPage : Page
{
public OnlinePlayListsPage()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,185 @@
<Page x:Class="The_Untamed_Music_Player.Views.OnlineSongsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:contract="using:The_Untamed_Music_Player.Contracts.Models"
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.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:The_Untamed_Music_Player.Models"
Loaded="OnlineSongsPage_Loaded"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="SongListViewTemplate" x:DataType="contract:IBriefOnlineMusicInfo">
<Grid Height="52"
Background="Transparent" PointerEntered="Grid_PointerEntered"
PointerExited="Grid_PointerExited">
<Grid.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="LocalSongs_Play" Width="216">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE768;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem x:Uid="LocalSongs_PlayNext">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xECC8;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<MenuFlyoutSubItem x:Uid="LocalSongs_AddTo">
<MenuFlyoutSubItem.Icon>
<FontIcon Glyph="&#xE710;"/>
</MenuFlyoutSubItem.Icon>
<MenuFlyoutItem x:Uid="LocalSongs_AddTo_PlayQueue" Width="216">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE90B;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<MenuFlyoutItem x:Uid="LocalSongs_AddTo_NewPlaylist">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE710;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyoutSubItem>
<MenuFlyoutItem x:Uid="LocalSongs_Properties">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE946;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem x:Uid="LocalSongs_ShowAlbum">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE93C;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem x:Uid="LocalSongs_ShowArtist">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE77B;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<MenuFlyoutItem x:Uid="LocalSongs_Select">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE762;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyout>
</Grid.ContextFlyout>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="32"/>
<ColumnDefinition Width="2.2*"/>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="1.3*"/>
<ColumnDefinition Width="55"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="ItemCheckBox"
Grid.Column="0"
IsThreeState="False" Visibility="Collapsed"/>
<Button x:Name="PlayButton" x:Uid="LocalSongs_PlayButton"
Grid.Column="1"
DataContext="{x:Bind}"
Style="{StaticResource SmallPlayButtonStyle}"
Visibility="Collapsed">
<FontIcon FontSize="16"
Foreground="{ThemeResource AccentTextFillColorTertiaryBrush}"
Glyph="&#xE768;"/>
</Button>
<TextBlock Grid.Column="2"
VerticalAlignment="Center"
FontSize="12"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.MainViewModel.IsDarkTheme), Mode=OneWay}"
Text="{x:Bind Title}"
TextTrimming="CharacterEllipsis"
ToolTipService.ToolTip="{x:Bind Title}"/>
<Button Grid.Column="3"
Background="Transparent" BorderBrush="Transparent">
<TextBlock FontSize="12"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.MainViewModel.IsDarkTheme), Mode=OneWay}"
Text="{x:Bind ArtistsStr}"
TextTrimming="CharacterEllipsis"
ToolTipService.ToolTip="{x:Bind ArtistsStr}"/>
</Button>
<Button Grid.Column="4"
Background="Transparent" BorderBrush="Transparent">
<TextBlock FontSize="12"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.MainViewModel.IsDarkTheme), Mode=OneWay}"
Text="{x:Bind Album}"
TextTrimming="CharacterEllipsis"
ToolTipService.ToolTip="{x:Bind Album}"/>
</Button>
<TextBlock Grid.Column="5"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="12"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.MainViewModel.IsDarkTheme), Mode=OneWay}"
Text="{x:Bind YearStr}"
TextTrimming="CharacterEllipsis"
ToolTipService.ToolTip="{x:Bind YearStr}"/>
<TextBlock Grid.Column="6"
Margin="0,0,10,0" HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="12"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.MainViewModel.IsDarkTheme), Mode=OneWay}"
Text="{x:Bind DurationStr}"
TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SongListView.Padding" Value="12,0,12,0"/>
<Setter Target="KeyWordsTextBlock.Margin" Value="12,0,12,0"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="641"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SongListView.Padding" Value="52,0,52,0"/>
<Setter Target="KeyWordsTextBlock.Margin" Value="52,0,52,0"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock x:Name="KeyWordsTextBlock"
Grid.Row="0"
Visibility="{x:Bind model:Data.OnlineMusicLibrary.KeyWordsTextBlockVisibility, Mode=OneWay}">
<Run FontSize="30" FontWeight="Bold"
Text="{x:Bind model:Data.OnlineMusicLibrary.KeyWords, Mode=OneWay}"/>
<Run FontSize="20" Text="的搜索结果"/>
</TextBlock>
<ListView x:Name="SongListView"
Grid.Row="1"
helper:ListViewExtensions.ItemCornerRadius="8"
helper:ListViewExtensions.ItemMargin="0,3,0,3" CanDragItems="True"
IsItemClickEnabled="True"
ItemTemplate="{StaticResource SongListViewTemplate}"
ItemsSource="{x:Bind model:Data.OnlineMusicLibrary.OnlineMusicInfoList, Mode=OneWay}"
Opacity="{x:Bind model:Data.OnlineMusicLibrary.ListViewOpacity, Mode=OneWay}"
SelectionMode="None">
<ListView.Footer>
<ProgressRing Width="25" Height="25"
Margin="0,5,0,5"
Visibility="{x:Bind model:Data.OnlineMusicLibrary.IsSearchMoreProgressRingActive, Mode=OneWay}"/>
</ListView.Footer>
<interactivity:Interaction.Behaviors>
<helper:AlternatingListViewBehavior AlternateBackground="{x:Bind helper:AlternatingListViewBehavior.GetAlternateBackgroundBrush(model:Data.MainViewModel.IsDarkTheme), Mode=OneWay}"/>
</interactivity:Interaction.Behaviors>
</ListView>
</Grid>
</Page>

View File

@@ -0,0 +1,67 @@
using CommunityToolkit.WinUI;
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.ViewModels;
namespace The_Untamed_Music_Player.Views;
public sealed partial class OnlineSongsPage : Page
{
private ScrollViewer? _scrollViewer;
public OnlineSongsViewModel ViewModel
{
get; set;
}
public OnlineSongsPage()
{
ViewModel = App.GetService<OnlineSongsViewModel>();
InitializeComponent();
}
private void Grid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
var grid = sender as Grid;
var checkBox = grid?.FindName("ItemCheckBox") as CheckBox;
var playButton = grid?.FindName("PlayButton") as Button;
if (checkBox != null)
{
checkBox.Visibility = Visibility.Visible;
}
if (playButton != null)
{
playButton.Visibility = Visibility.Visible;
}
}
private void Grid_PointerExited(object sender, PointerRoutedEventArgs e)
{
var grid = sender as Grid;
var checkBox = grid?.FindName("ItemCheckBox") as CheckBox;
var playButton = grid?.FindName("PlayButton") as Button;
if (checkBox != null)
{
checkBox.Visibility = Visibility.Collapsed;
}
if (playButton != null)
{
playButton.Visibility = Visibility.Collapsed;
}
}
private void OnlineSongsPage_Loaded(object sender, RoutedEventArgs e)
{
_scrollViewer = SongListView.FindDescendant<ScrollViewer>() ?? throw new Exception("Cannot find ScrollViewer in ListView"); // 检索 ListView 内部使用的 ScrollViewer
_scrollViewer.ViewChanged += async (s, e) =>
{
if (!Data.OnlineMusicLibrary.OnlineMusicInfoList.HasAllLoaded && _scrollViewer.VerticalOffset + _scrollViewer.ViewportHeight >= _scrollViewer.ExtentHeight - 50)
{
await Data.OnlineMusicLibrary.SearchMore();
await Task.Delay(3000);
}
};
}
}

View File

@@ -1,13 +1,14 @@
using System.Diagnostics;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Models;
namespace The_Untamed_Music_Player.Views;
public sealed partial class PropertiesDialog : ContentDialog
{
public DetailedMusicInfo Music { get; set; } = Data.MusicPlayer.CurrentMusic;
public IDetailedMusicInfoBase Music { get; set; } = Data.MusicPlayer.CurrentMusic!;
public PropertiesDialog()
{

View File

@@ -1,5 +1,6 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.ViewModels;
@@ -90,7 +91,7 @@ public sealed partial class RootPlayBarView : Page
};
}
public Visibility GetArtistAndAlbumStrVisibility(DetailedMusicInfo detailedmusicinfo)
public Visibility GetArtistAndAlbumStrVisibility(IDetailedMusicInfoBase detailedmusicinfo)
{
return detailedmusicinfo.ArtistAndAlbumStr == "" ? Visibility.Collapsed : Visibility.Visible;
}