添加项目文件。

This commit is contained in:
LanZhan
2024-08-27 15:07:06 +08:00
parent 41f9806e3a
commit a24d304c5a
104 changed files with 9231 additions and 0 deletions

202
.editorconfig Normal file
View File

@@ -0,0 +1,202 @@
# Rules in this file were initially inferred by Visual Studio IntelliCode from the Template Studio codebase.
# You can modify the rules from these initially generated values to suit your own policies.
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference.
[*.cs]
#Core editorconfig formatting - indentation
#use soft tabs (spaces) for indentation
indent_style = space
#Formatting - new line options
#place else statements on a new line
csharp_new_line_before_else = true
#require braces to be on a new line for lambdas, methods, control_blocks, types, properties, and accessors (also known as "Allman" style)
csharp_new_line_before_open_brace = all
#Formatting - organize using options
#sort System.* using directives alphabetically, and place them before other usings
dotnet_sort_system_directives_first = true
#Formatting - spacing options
#require NO space between a cast and the value
csharp_space_after_cast = false
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_after_colon_in_inheritance_clause = true
#require a space after a keyword in a control flow statement such as a for loop
csharp_space_after_keywords_in_control_flow_statements = true
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_before_colon_in_inheritance_clause = true
#remove space within empty argument list parentheses
csharp_space_between_method_call_empty_parameter_list_parentheses = false
#remove space between method call name and opening parenthesis
csharp_space_between_method_call_name_and_opening_parenthesis = false
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
csharp_space_between_method_call_parameter_list_parentheses = false
#remove space within empty parameter list parentheses for a method declaration
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
csharp_space_between_method_declaration_parameter_list_parentheses = false
#Formatting - wrapping options
#leave code block on separate lines
csharp_preserve_single_line_blocks = false
#Style - Code block preferences
#prefer curly braces even for one line of code
csharp_prefer_braces = true:suggestion
#Style - expression bodied member options
#prefer expression bodies for accessors
csharp_style_expression_bodied_accessors = true:warning
#prefer block bodies for constructors
csharp_style_expression_bodied_constructors = false:suggestion
#prefer expression bodies for methods
csharp_style_expression_bodied_methods = when_on_single_line:silent
#prefer expression-bodied members for properties
csharp_style_expression_bodied_properties = true:warning
#Style - expression level options
#prefer out variables to be declared before the method call
csharp_style_inlined_variable_declaration = false:suggestion
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_member_access = true:suggestion
#Style - Expression-level preferences
#prefer default over default(T)
csharp_prefer_simple_default_expression = true:suggestion
#prefer objects to be initialized using object initializers when possible
dotnet_style_object_initializer = true:suggestion
#Style - implicit and explicit types
#prefer var over explicit type in all cases, unless overridden by another code style rule
csharp_style_var_elsewhere = true:suggestion
#prefer var is used to declare variables with built-in system types such as int
csharp_style_var_for_built_in_types = true:suggestion
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
csharp_style_var_when_type_is_apparent = true:suggestion
#Style - language keyword and framework type options
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
#Style - Language rules
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
csharp_style_var_for_built_in_types = true:warning
#Style - modifier options
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
#Style - Modifier preferences
#when this rule is set to a list of modifiers, prefer the specified ordering.
csharp_preferred_modifier_order = public,private,protected,internal,static,async,readonly,override,sealed,abstract,virtual:warning
dotnet_style_readonly_field = true:warning
#Style - Pattern matching
#prefer pattern matching instead of is expression with type casts
csharp_style_pattern_matching_over_as_with_null_check = true:warning
#Style - qualification options
#prefer events not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_event = false:suggestion
#prefer fields not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_field = false:suggestion
#prefer methods not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_method = false:suggestion
#prefer properties not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_property = false:suggestion
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:warning
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
[*.{cs,vb}]
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
[*.{cs,vb}]
#Style - Unnecessary code rules
csharp_style_unused_value_assignment_preference = discard_variable:warning
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_simplified_interpolation = true:suggestion

63
.gitattributes vendored Normal file
View File

@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

363
.gitignore vendored Normal file
View File

@@ -0,0 +1,363 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

16
.vsconfig Normal file
View File

@@ -0,0 +1,16 @@
{
"version": "1.0",
"components": [
"Microsoft.Component.MSBuild",
"Microsoft.NetCore.Component.Runtime.7.0",
"Microsoft.NetCore.Component.SDK",
"Microsoft.VisualStudio.Component.ManagedDesktop.Core",
"Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites",
"Microsoft.VisualStudio.Component.NuGet",
"Microsoft.VisualStudio.Component.Windows10SDK.19041",
"Microsoft.VisualStudio.Component.Windows10SDK",
"Microsoft.VisualStudio.ComponentGroup.MSIX.Packaging",
"Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs",
"Microsoft.VisualStudio.Workload.ManagedDesktop"
]
}

View File

@@ -0,0 +1,10 @@
namespace The_Untamed_Music_Player.Core.Contracts.Services;
public interface IFileService
{
T Read<T>(string folderPath, string fileName);
void Save<T>(string folderPath, string fileName, T content);
void Delete(string folderPath, string fileName);
}

View File

@@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace The_Untamed_Music_Player.Core.Helpers;
public static class Json
{
public static async Task<T> ToObjectAsync<T>(string value)
{
return await Task.Run<T>(() =>
{
return JsonConvert.DeserializeObject<T>(value);
});
}
public static async Task<string> StringifyAsync(object value)
{
return await Task.Run<string>(() =>
{
return JsonConvert.SerializeObject(value);
});
}
}

View File

@@ -0,0 +1,5 @@
*Recommended Markdown Viewer: [Markdown Editor](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MarkdownEditor2)*
## Getting Started
The Core project contains code that can be [reused across multiple application projects](https://docs.microsoft.com/dotnet/standard/net-standard#net-5-and-net-standard).

View File

@@ -0,0 +1,41 @@
using System.Text;
using Newtonsoft.Json;
using The_Untamed_Music_Player.Core.Contracts.Services;
namespace The_Untamed_Music_Player.Core.Services;
public class FileService : IFileService
{
public T Read<T>(string folderPath, string fileName)
{
var path = Path.Combine(folderPath, fileName);
if (File.Exists(path))
{
var json = File.ReadAllText(path);
return JsonConvert.DeserializeObject<T>(json);
}
return default;
}
public void Save<T>(string folderPath, string fileName, T content)
{
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
var fileContent = JsonConvert.SerializeObject(content);
File.WriteAllText(Path.Combine(folderPath, fileName), fileContent, Encoding.UTF8);
}
public void Delete(string folderPath, string fileName)
{
if (fileName != null && File.Exists(Path.Combine(folderPath, fileName)))
{
File.Delete(Path.Combine(folderPath, fileName));
}
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>The_Untamed_Music_Player.Core</RootNamespace>
<Platforms>AnyCPU;x64;x86</Platforms>
<Platforms>x86;x64;arm64;AnyCPU</Platforms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,69 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "The Untamed Music Player.Core", "The Untamed Music Player.Core\The Untamed Music Player.Core.csproj", "{75B34C4A-032F-4EE8-9201-AA6E951514B9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|arm64 = Debug|arm64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|arm64 = Release|arm64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|Any CPU.ActiveCfg = Debug|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|Any CPU.Build.0 = Debug|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|Any CPU.Deploy.0 = Debug|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|arm64.ActiveCfg = Debug|arm64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|arm64.Build.0 = Debug|arm64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|arm64.Deploy.0 = Debug|arm64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|x64.ActiveCfg = Debug|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|x64.Build.0 = Debug|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|x64.Deploy.0 = Debug|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|x86.ActiveCfg = Debug|x86
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|x86.Build.0 = Debug|x86
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Debug|x86.Deploy.0 = Debug|x86
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|Any CPU.ActiveCfg = Release|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|Any CPU.Build.0 = Release|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|Any CPU.Deploy.0 = Release|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|arm64.ActiveCfg = Release|arm64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|arm64.Build.0 = Release|arm64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|arm64.Deploy.0 = Release|arm64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|x64.ActiveCfg = Release|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|x64.Build.0 = Release|x64
{FAF574F3-DB0A-4B33-BF19-45CF6396C9F7}.Release|x64.Deploy.0 = Release|x64
{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
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Debug|arm64.ActiveCfg = Debug|arm64
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Debug|arm64.Build.0 = Debug|arm64
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Debug|x64.ActiveCfg = Debug|x64
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Debug|x64.Build.0 = Debug|x64
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Debug|x86.ActiveCfg = Debug|x86
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Debug|x86.Build.0 = Debug|x86
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Release|Any CPU.Build.0 = Release|Any CPU
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Release|arm64.ActiveCfg = Release|arm64
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Release|arm64.Build.0 = Release|arm64
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Release|x64.ActiveCfg = Release|x64
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Release|x64.Build.0 = Release|x64
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Release|x86.ActiveCfg = Release|x86
{75B34C4A-032F-4EE8-9201-AA6E951514B9}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AEF480F6-CB86-454F-98FB-9A7464624FB5}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,17 @@
namespace The_Untamed_Music_Player.Activation;
// Extend this class to implement new ActivationHandlers. See DefaultActivationHandler for an example.
// https://github.com/microsoft/TemplateStudio/blob/main/docs/WinUI/activation.md
public abstract class ActivationHandler<T> : IActivationHandler
where T : class
{
// Override this method to add the logic for whether to handle the activation.
protected virtual bool CanHandleInternal(T args) => true;
// Override this method to add the logic for your activation handler.
protected abstract Task HandleInternalAsync(T args);
public bool CanHandle(object args) => args is T && CanHandleInternal((args as T)!);
public async Task HandleAsync(object args) => await HandleInternalAsync((args as T)!);
}

View File

@@ -0,0 +1,29 @@
using Microsoft.UI.Xaml;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Activation;
public class DefaultActivationHandler : ActivationHandler<LaunchActivatedEventArgs>
{
private readonly INavigationService _navigationService;
public DefaultActivationHandler(INavigationService navigationService)
{
_navigationService = navigationService;
}
protected override bool CanHandleInternal(LaunchActivatedEventArgs args)
{
// None of the ActivationHandlers has handled the activation.
return _navigationService.Frame?.Content == null;
}
protected async override Task HandleInternalAsync(LaunchActivatedEventArgs args)
{
_navigationService.NavigateTo(typeof(ViewModel).FullName!, args.Arguments);
await Task.CompletedTask;
}
}

View File

@@ -0,0 +1,8 @@
namespace The_Untamed_Music_Player.Activation;
public interface IActivationHandler
{
bool CanHandle(object args);
Task HandleAsync(object args);
}

View File

@@ -0,0 +1,15 @@
<Application x:Class="The_Untamed_Music_Player.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls"/>
<ResourceDictionary Source="/Styles/FontSizes.xaml"/>
<ResourceDictionary Source="/Styles/Thickness.xaml"/>
<ResourceDictionary Source="/Styles/TextBlock.xaml"/>
<ResourceDictionary Source="/Styles/ButtonStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,125 @@
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Activation;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Core.Contracts.Services;
using The_Untamed_Music_Player.Core.Services;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Services;
using The_Untamed_Music_Player.ViewModels;
using The_Untamed_Music_Player.Views;
namespace The_Untamed_Music_Player;
// To learn more about WinUI 3, see https://docs.microsoft.com/windows/apps/winui/winui3/.
public partial class App : Application
{
// The .NET Generic Host provides dependency injection, configuration, logging, and other services.
// https://docs.microsoft.com/dotnet/core/extensions/generic-host
// https://docs.microsoft.com/dotnet/core/extensions/dependency-injection
// https://docs.microsoft.com/dotnet/core/extensions/configuration
// https://docs.microsoft.com/dotnet/core/extensions/logging
public IHost Host
{
get;
}
public static T GetService<T>()
where T : class
{
if ((App.Current as App)!.Host.Services.GetService(typeof(T)) is not T service)
{
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
}
return service;
}
public static WindowEx? MainWindow
{
get; private set;
}
public static UIElement? AppTitlebar
{
get; set;
}
public App()
{
Debug.WriteLine("App.xaml.cs: Constructor");
InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.
CreateDefaultBuilder().
UseContentRoot(AppContext.BaseDirectory).
ConfigureServices((context, services) =>//注册服务信息
{
// Default Activation Handler
services.AddTransient<ActivationHandler<LaunchActivatedEventArgs>, DefaultActivationHandler>();
// Other Activation Handlers
// Services
services.AddSingleton<ILocalSettingsService, LocalSettingsService>();
services.AddSingleton<IThemeSelectorService, ThemeSelectorService>();
services.AddTransient<INavigationViewService, NavigationViewService>();
services.AddSingleton<IActivationService, ActivationService>();
services.AddSingleton<IPageService, PageService>();
services.AddSingleton<INavigationService, NavigationService>();
// Core Services
services.AddSingleton<IFileService, FileService>();
// Views and ViewModels
services.AddTransient<SettingsViewModel>();
services.AddTransient<SettingsPage>();
services.AddTransient<ViewModel>();
services.AddTransient<Page>();
services.AddTransient<ViewModel>();
services.AddTransient<Page>();
services.AddTransient<ViewModel>();
services.AddTransient<Page>();
services.AddTransient<ViewModel>();
services.AddTransient<Page>();
services.AddTransient<ShellPage>();
services.AddTransient<ShellViewModel>();
services.AddTransient<RootPlayBarView>();
services.AddTransient<RootPlayBarViewModel>();
services.AddTransient<Page>();
services.AddTransient<ViewModel>();
services.AddTransient<Page>();
services.AddTransient<ViewModel>();
services.AddTransient<Page>();
services.AddTransient<ViewModel>();
services.AddTransient<Page>();
services.AddTransient<ViewModel>();
services.AddTransient<Page>();
services.AddTransient<ViewModel>();
services.AddTransient<Page>();
services.AddTransient<ViewModel>();
// Configuration
services.Configure<LocalSettingsOptions>(context.Configuration.GetSection(nameof(LocalSettingsOptions)));
}).
Build();//生成容器
Debug.WriteLine($"AppMainWindow is null: {MainWindow == null}");
Debug.WriteLine("App.xaml.cs: Constructor End");
}
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
MainWindow = new MainWindow(GetService<ILocalSettingsService>());
Debug.WriteLine($"OnMainWindow is null: {MainWindow == null}");
await GetService<IActivationService>().ActivateAsync(args);
}
}

View File

@@ -0,0 +1,122 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.Xaml.Interactivity;
using The_Untamed_Music_Player.Contracts.Services;
namespace The_Untamed_Music_Player.Behaviors;
public class NavigationViewHeaderBehavior : Behavior<NavigationView>
{
private static NavigationViewHeaderBehavior? _current;
private Page? _currentPage;
public DataTemplate? DefaultHeaderTemplate
{
get; set;
}
public object DefaultHeader
{
get => GetValue(DefaultHeaderProperty);
set => SetValue(DefaultHeaderProperty, value);
}
public static readonly DependencyProperty DefaultHeaderProperty =
DependencyProperty.Register("DefaultHeader", typeof(object), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _current!.UpdateHeader()));
public static NavigationViewHeaderMode GetHeaderMode(Page item) => (NavigationViewHeaderMode)item.GetValue(HeaderModeProperty);
public static void SetHeaderMode(Page item, NavigationViewHeaderMode value) => item.SetValue(HeaderModeProperty, value);
public static readonly DependencyProperty HeaderModeProperty =
DependencyProperty.RegisterAttached("HeaderMode", typeof(bool), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(NavigationViewHeaderMode.Always, (d, e) => _current!.UpdateHeader()));
public static object GetHeaderContext(Page item) => item.GetValue(HeaderContextProperty);
public static void SetHeaderContext(Page item, object value) => item.SetValue(HeaderContextProperty, value);
public static readonly DependencyProperty HeaderContextProperty =
DependencyProperty.RegisterAttached("HeaderContext", typeof(object), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _current!.UpdateHeader()));
public static DataTemplate GetHeaderTemplate(Page item) => (DataTemplate)item.GetValue(HeaderTemplateProperty);
public static void SetHeaderTemplate(Page item, DataTemplate value) => item.SetValue(HeaderTemplateProperty, value);
public static readonly DependencyProperty HeaderTemplateProperty =
DependencyProperty.RegisterAttached("HeaderTemplate", typeof(DataTemplate), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _current!.UpdateHeaderTemplate()));
protected override void OnAttached()
{
base.OnAttached();
var navigationService = App.GetService<INavigationService>();
navigationService.Navigated += OnNavigated;
_current = this;
}
protected override void OnDetaching()
{
base.OnDetaching();
var navigationService = App.GetService<INavigationService>();
navigationService.Navigated -= OnNavigated;
}
private void OnNavigated(object sender, NavigationEventArgs e)
{
if (sender is Frame frame && frame.Content is Page page)
{
_currentPage = page;
UpdateHeader();
UpdateHeaderTemplate();
}
}
private void UpdateHeader()
{
if (_currentPage != null)
{
var headerMode = GetHeaderMode(_currentPage);
if (headerMode == NavigationViewHeaderMode.Never)
{
AssociatedObject.Header = null;
AssociatedObject.AlwaysShowHeader = false;
}
else
{
var headerFromPage = GetHeaderContext(_currentPage);
if (headerFromPage != null)
{
AssociatedObject.Header = headerFromPage;
}
else
{
AssociatedObject.Header = DefaultHeader;
}
if (headerMode == NavigationViewHeaderMode.Always)
{
AssociatedObject.AlwaysShowHeader = true;
}
else
{
AssociatedObject.AlwaysShowHeader = false;
}
}
}
}
private void UpdateHeaderTemplate()
{
if (_currentPage != null)
{
var headerTemplate = GetHeaderTemplate(_currentPage);
AssociatedObject.HeaderTemplate = headerTemplate ?? DefaultHeaderTemplate;
}
}
}

View File

@@ -0,0 +1,8 @@
namespace The_Untamed_Music_Player.Behaviors;
public enum NavigationViewHeaderMode
{
Always,
Never,
Minimal
}

View File

@@ -0,0 +1,6 @@
namespace The_Untamed_Music_Player.Contracts.Services;
public interface IActivationService
{
Task ActivateAsync(object activationArgs);
}

View File

@@ -0,0 +1,8 @@
namespace The_Untamed_Music_Player.Contracts.Services;
public interface ILocalSettingsService
{
Task<T?> ReadSettingAsync<T>(string key);
Task SaveSettingAsync<T>(string key, T value);
}

View File

@@ -0,0 +1,23 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
namespace The_Untamed_Music_Player.Contracts.Services;
public interface INavigationService
{
event NavigatedEventHandler Navigated;
bool CanGoBack
{
get;
}
Frame? Frame
{
get; set;
}
bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false);
bool GoBack();
}

View File

@@ -0,0 +1,22 @@
using Microsoft.UI.Xaml.Controls;
namespace The_Untamed_Music_Player.Contracts.Services;
public interface INavigationViewService
{
IList<object>? MenuItems
{
get;
}
object? SettingsItem
{
get;
}
void Initialize(NavigationView navigationView);
void UnregisterEvents();
NavigationViewItem? GetSelectedItem(Type pageType);
}

View File

@@ -0,0 +1,6 @@
namespace The_Untamed_Music_Player.Contracts.Services;
public interface IPageService
{
Type GetPageType(string key);
}

View File

@@ -0,0 +1,17 @@
using Microsoft.UI.Xaml;
namespace The_Untamed_Music_Player.Contracts.Services;
public interface IThemeSelectorService
{
ElementTheme Theme
{
get;
}
Task InitializeAsync();
Task SetThemeAsync(ElementTheme theme);
Task SetRequestedThemeAsync();
}

View File

@@ -0,0 +1,8 @@
namespace The_Untamed_Music_Player.Contracts.ViewModels;
public interface INavigationAware
{
void OnNavigatedTo(object parameter);
void OnNavigatedFrom();
}

View File

@@ -0,0 +1,177 @@
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Media;
using Microsoft.Xaml.Interactivity;
using The_Untamed_Music_Player.Models;
using Windows.Foundation.Collections;
namespace The_Untamed_Music_Player.Helpers;
internal class AlternatingListViewBehavior : Behavior<ListViewBase>
{
public static readonly DependencyProperty AlternateBackgroundProperty = DependencyProperty.Register(
nameof(AlternateBackground),
typeof(Brush),
typeof(AlternatingListViewBehavior),
new PropertyMetadata(default(Brush)));
public static readonly DependencyProperty AlternateBorderThicknessProperty = DependencyProperty.Register(
nameof(AlternateBorderThickness),
typeof(Thickness),
typeof(AlternatingListViewBehavior),
new PropertyMetadata(default(Thickness)));
public static readonly DependencyProperty AlternateBorderBrushProperty = DependencyProperty.Register(
nameof(AlternateBorderBrush),
typeof(Brush),
typeof(AlternatingListViewBehavior),
new PropertyMetadata(default(Brush?)));
public static readonly DependencyProperty LightThemeBackgroundProperty = DependencyProperty.Register(
nameof(LightThemeBackground),
typeof(Brush),
typeof(AlternatingListViewBehavior),
new PropertyMetadata(new SolidColorBrush(Microsoft.UI.Colors.White)));
public static readonly DependencyProperty DarkThemeBackgroundProperty = DependencyProperty.Register(
nameof(DarkThemeBackground),
typeof(Brush),
typeof(AlternatingListViewBehavior),
new PropertyMetadata(new SolidColorBrush(Microsoft.UI.Colors.Black)));
public Brush LightThemeBackground
{
get => (Brush)GetValue(LightThemeBackgroundProperty);
set => SetValue(LightThemeBackgroundProperty, value);
}
public Brush DarkThemeBackground
{
get => (Brush)GetValue(DarkThemeBackgroundProperty);
set => SetValue(DarkThemeBackgroundProperty, value);
}
public Brush? AlternateBorderBrush
{
get => (Brush?)GetValue(AlternateBorderBrushProperty);
set => SetValue(AlternateBorderBrushProperty, value);
}
public Thickness AlternateBorderThickness
{
get => (Thickness)GetValue(AlternateBorderThicknessProperty);
set => SetValue(AlternateBorderThicknessProperty, value);
}
public Brush? AlternateBackground
{
get => (Brush?)GetValue(AlternateBackgroundProperty);
set => SetValue(AlternateBackgroundProperty, value);
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.ActualThemeChanged += OnActualThemeChanged;
AssociatedObject.ContainerContentChanging += OnContainerContentChanging;
if (AssociatedObject.Items != null)
{
AssociatedObject.Items.VectorChanged += ItemsOnVectorChanged;
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.ActualThemeChanged -= OnActualThemeChanged;
AssociatedObject.ContainerContentChanging -= OnContainerContentChanging;
if (AssociatedObject.Items != null)
{
AssociatedObject.Items.VectorChanged -= ItemsOnVectorChanged;
}
}
private void OnActualThemeChanged(FrameworkElement sender, object args)
{
if (AssociatedObject.Items == null)
{
return;
}
var currentTheme = Data.SettingsViewModel?.ElementTheme;
AlternateBackground = currentTheme == ElementTheme.Dark ? DarkThemeBackground : LightThemeBackground;
for (var i = 0; i < AssociatedObject.Items.Count; i++)
{
if (AssociatedObject.ContainerFromIndex(i) is SelectorItem itemContainer)
{
UpdateAlternateLayout(itemContainer, i);
}
}
}
private void ItemsOnVectorChanged(IObservableVector<object> sender, IVectorChangedEventArgs args)
{
// If the index is at the end we can ignore
if (args.Index == sender.Count - 1)
{
return;
}
// Only need to handle Inserted and Removed because we'll handle everything else in the
// OnContainerContentChanging method
if (args.CollectionChange is CollectionChange.ItemInserted or CollectionChange.ItemRemoved)
{
for (var i = (int)args.Index; i < sender.Count; i++)
{
if (AssociatedObject.ContainerFromIndex(i) is SelectorItem itemContainer)
{
UpdateAlternateLayout(itemContainer, i);
}
}
}
}
private void OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase > 0 || args.InRecycleQueue)
{
return;
}
UpdateAlternateLayout(args.ItemContainer, args.ItemIndex);
}
private void UpdateAlternateLayout(SelectorItem itemContainer, int itemIndex)
{
if (itemIndex < 0 || AlternateBackground == null)
{
return;
}
var evenBackground = AlternateBackground;
itemContainer.Background = itemIndex % 2 == 0 ? evenBackground : null;
if (itemContainer.FindDescendant<Border>() is not { } border)
{
return;
}
if (itemIndex % 2 == 0)
{
border.Background = evenBackground;
border.BorderBrush = AlternateBorderBrush;
border.BorderThickness = AlternateBorderThickness;
}
else
{
border.Background = null;
border.BorderThickness = default;
}
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.UI;
using Windows.UI;
using Windows.UI.Composition;
namespace The_Untamed_Music_Player.Helpers;
public class ColorAnimatedBackdrop : CompositionBrushBackdrop
{
protected override CompositionBrush CreateBrush(Compositor compositor)
{
var brush = compositor.CreateColorBrush(Color.FromArgb(255, 255, 0, 0));
var animation = compositor.CreateColorKeyFrameAnimation();
var easing = compositor.CreateLinearEasingFunction();
animation.InsertKeyFrame(0, Colors.Red, easing);
animation.InsertKeyFrame(.333f, Colors.Green, easing);
animation.InsertKeyFrame(.667f, Colors.Blue, easing);
animation.InsertKeyFrame(1, Colors.Red, easing);
animation.InterpolationColorSpace = CompositionColorSpace.Hsl;
animation.Duration = TimeSpan.FromSeconds(15);
animation.IterationBehavior = AnimationIterationBehavior.Forever;
brush.StartAnimation("Color", animation);
return brush;
}
}
public class BlurredBackdrop : CompositionBrushBackdrop
{
protected override CompositionBrush CreateBrush(Windows.UI.Composition.Compositor compositor)
=> compositor.CreateHostBackdropBrush();
}

View File

@@ -0,0 +1,38 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace The_Untamed_Music_Player.Helpers;
public class EnumToBooleanConverter : IValueConverter
{
public EnumToBooleanConverter()
{
}
public object Convert(object value, Type targetType, object parameter, string language)
{
if (parameter is string enumString)
{
if (!Enum.IsDefined(typeof(ElementTheme), value))
{
throw new ArgumentException("ExceptionEnumToBooleanConverterValueMustBeAnEnum");
}
var enumValue = Enum.Parse(typeof(ElementTheme), enumString);
return enumValue.Equals(value);
}
throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName");
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (parameter is string enumString)
{
return Enum.Parse(typeof(ElementTheme), enumString);
}
throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName");
}
}

View File

@@ -0,0 +1,8 @@
using Microsoft.UI.Xaml.Controls;
namespace The_Untamed_Music_Player.Helpers;
public static class FrameExtensions
{
public static object? GetPageViewModel(this Frame frame) => frame?.Content?.GetType().GetProperty("ViewModel")?.GetValue(frame.Content, null);
}

View File

@@ -0,0 +1,21 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace The_Untamed_Music_Player.Helpers;
// Helper class to set the navigation target for a NavigationViewItem.
//
// Usage in XAML:
// <NavigationViewItem x:Uid="Shell_Main" Icon="Document" helpers:NavigationHelper.NavigateTo="AppName.ViewModels.MainViewModel" />
//
// Usage in code:
// NavigationHelper.SetNavigateTo(navigationViewItem, typeof(MainViewModel).FullName);
public class NavigationHelper
{
public static string GetNavigateTo(NavigationViewItem item) => (string)item.GetValue(NavigateToProperty);
public static void SetNavigateTo(NavigationViewItem item, string value) => item.SetValue(NavigateToProperty, value);
public static readonly DependencyProperty NavigateToProperty =
DependencyProperty.RegisterAttached("NavigateTo", typeof(string), typeof(NavigationHelper), new PropertyMetadata(null));
}

View File

@@ -0,0 +1,10 @@
using Microsoft.Windows.ApplicationModel.Resources;
namespace The_Untamed_Music_Player.Helpers;
public static class ResourceExtensions
{
private static readonly ResourceLoader _resourceLoader = new();
public static string GetLocalized(this string resourceKey) => _resourceLoader.GetString(resourceKey);
}

View File

@@ -0,0 +1,20 @@
using System.Runtime.InteropServices;
using System.Text;
namespace The_Untamed_Music_Player.Helpers;
public class RuntimeHelper
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder? packageFullName);
public static bool IsMSIX
{
get
{
var length = 0;
return GetCurrentPackageFullName(ref length, null) != 15700L;
}
}
}

View File

@@ -0,0 +1,112 @@
using The_Untamed_Music_Player.Core.Helpers;
using Windows.Storage;
using Windows.Storage.Streams;
namespace The_Untamed_Music_Player.Helpers;
// Use these extension methods to store and retrieve local and roaming app data
// More details regarding storing and retrieving app data at https://docs.microsoft.com/windows/apps/design/app-settings/store-and-retrieve-app-data
public static class SettingsStorageExtensions
{
private const string FileExtension = ".json";
public static bool IsRoamingStorageAvailable(this ApplicationData appData)
{
return appData.RoamingStorageQuota == 0;
}
public static async Task SaveAsync<T>(this StorageFolder folder, string name, T content)
{
var file = await folder.CreateFileAsync(GetFileName(name), CreationCollisionOption.ReplaceExisting);
var fileContent = await Json.StringifyAsync(content);
await FileIO.WriteTextAsync(file, fileContent);
}
public static async Task<T?> ReadAsync<T>(this StorageFolder folder, string name)
{
if (!File.Exists(Path.Combine(folder.Path, GetFileName(name))))
{
return default;
}
var file = await folder.GetFileAsync($"{name}.json");
var fileContent = await FileIO.ReadTextAsync(file);
return await Json.ToObjectAsync<T>(fileContent);
}
public static async Task SaveAsync<T>(this ApplicationDataContainer settings, string key, T value)
{
settings.SaveString(key, await Json.StringifyAsync(value));
}
public static void SaveString(this ApplicationDataContainer settings, string key, string value)
{
settings.Values[key] = value;
}
public static async Task<T?> ReadAsync<T>(this ApplicationDataContainer settings, string key)
{
object? obj;
if (settings.Values.TryGetValue(key, out obj))
{
return await Json.ToObjectAsync<T>((string)obj);
}
return default;
}
public static async Task<StorageFile> SaveFileAsync(this StorageFolder folder, byte[] content, string fileName, CreationCollisionOption options = CreationCollisionOption.ReplaceExisting)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
if (string.IsNullOrEmpty(fileName))
{
throw new ArgumentException("File name is null or empty. Specify a valid file name", nameof(fileName));
}
var storageFile = await folder.CreateFileAsync(fileName, options);
await FileIO.WriteBytesAsync(storageFile, content);
return storageFile;
}
public static async Task<byte[]?> ReadFileAsync(this StorageFolder folder, string fileName)
{
var item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
if ((item != null) && item.IsOfType(StorageItemTypes.File))
{
var storageFile = await folder.GetFileAsync(fileName);
var content = await storageFile.ReadBytesAsync();
return content;
}
return null;
}
public static async Task<byte[]?> ReadBytesAsync(this StorageFile file)
{
if (file != null)
{
using IRandomAccessStream stream = await file.OpenReadAsync();
using var reader = new DataReader(stream.GetInputStreamAt(0));
await reader.LoadAsync((uint)stream.Size);
var bytes = new byte[stream.Size];
reader.ReadBytes(bytes);
return bytes;
}
return null;
}
private static string GetFileName(string name)
{
return string.Concat(name, FileExtension);
}
}

View File

@@ -0,0 +1,95 @@
using System.Runtime.InteropServices;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Windows.UI;
using Windows.UI.ViewManagement;
namespace The_Untamed_Music_Player.Helpers;
// Helper class to workaround custom title bar bugs.
// DISCLAIMER: The resource key names and color values used below are subject to change. Do not depend on them.
// https://github.com/microsoft/TemplateStudio/issues/4516
internal class TitleBarHelper
{
private const int WAINACTIVE = 0x00;
private const int WAACTIVE = 0x01;
private const int WMACTIVATE = 0x0006;
[DllImport("user32.dll")]
private static extern IntPtr GetActiveWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
public static void UpdateTitleBar(ElementTheme theme)
{
if (App.MainWindow.ExtendsContentIntoTitleBar)
{
if (theme == ElementTheme.Default)
{
var uiSettings = new UISettings();
var background = uiSettings.GetColorValue(UIColorType.Background);
theme = background == Colors.White ? ElementTheme.Light : ElementTheme.Dark;
}
if (theme == ElementTheme.Default)
{
theme = Application.Current.RequestedTheme == ApplicationTheme.Light ? ElementTheme.Light : ElementTheme.Dark;
}
App.MainWindow.AppWindow.TitleBar.ButtonForegroundColor = theme switch
{
ElementTheme.Dark => Colors.White,
ElementTheme.Light => Colors.Black,
_ => Colors.Transparent
};
App.MainWindow.AppWindow.TitleBar.ButtonHoverForegroundColor = theme switch
{
ElementTheme.Dark => Colors.White,
ElementTheme.Light => Colors.Black,
_ => Colors.Transparent
};
App.MainWindow.AppWindow.TitleBar.ButtonHoverBackgroundColor = theme switch
{
ElementTheme.Dark => Color.FromArgb(0x33, 0xFF, 0xFF, 0xFF),
ElementTheme.Light => Color.FromArgb(0x33, 0x00, 0x00, 0x00),
_ => Colors.Transparent
};
App.MainWindow.AppWindow.TitleBar.ButtonPressedBackgroundColor = theme switch
{
ElementTheme.Dark => Color.FromArgb(0x66, 0xFF, 0xFF, 0xFF),
ElementTheme.Light => Color.FromArgb(0x66, 0x00, 0x00, 0x00),
_ => Colors.Transparent
};
App.MainWindow.AppWindow.TitleBar.BackgroundColor = Colors.Transparent;
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
if (hwnd == GetActiveWindow())
{
SendMessage(hwnd, WMACTIVATE, WAINACTIVE, IntPtr.Zero);
SendMessage(hwnd, WMACTIVATE, WAACTIVE, IntPtr.Zero);
}
else
{
SendMessage(hwnd, WMACTIVATE, WAACTIVE, IntPtr.Zero);
SendMessage(hwnd, WMACTIVATE, WAINACTIVE, IntPtr.Zero);
}
}
}
public static void ApplySystemThemeToCaptionButtons()
{
var frame = App.AppTitlebar as FrameworkElement;
if (frame != null)
{
UpdateTitleBar(frame.ActualTheme);
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Runtime.InteropServices;
namespace The_Untamed_Music_Player.Helpers;
public class WindowsSystemDispatcherQueueHelper
{
[StructLayout(LayoutKind.Sequential)]
private struct DispatcherQueueOptions
{
internal int dwSize;
internal int threadType;
internal int apartmentType;
}
[DllImport("CoreMessaging.dll")]
private static extern int CreateDispatcherQueueController([In] DispatcherQueueOptions options, [In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object dispatcherQueueController);
private object m_dispatcherQueueController = null;
public void EnsureWindowsSystemDispatcherQueueController()
{
if (Windows.System.DispatcherQueue.GetForCurrentThread() != null)
{
// one already exists, so we'll just use it.
return;
}
if (m_dispatcherQueueController == null)
{
DispatcherQueueOptions options;
options.dwSize = Marshal.SizeOf<DispatcherQueueOptions>();
options.threadType = 2; // DQTYPE_THREAD_CURRENT
options.apartmentType = 2; // DQTAT_COM_STA
_ = CreateDispatcherQueueController(options, ref m_dispatcherQueueController);
}
}
}

View File

@@ -0,0 +1,26 @@
<windowex:WindowEx x:Class="The_Untamed_Music_Player.MainWindow"
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:helpers="using:The_Untamed_Music_Player.Helpers" xmlns:local="using:The_Untamed_Music_Player"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:The_Untamed_Music_Player.Models" xmlns:windowex="using:WinUIEx"
MinWidth="500" MinHeight="500"
PersistenceId="MainWindow"
mc:Ignorable="d">
<Grid x:Name="RootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="117"/>
</Grid.RowDefinitions>
<Frame x:Name="ShellFrame"
Grid.Row="0"
Width="auto" Height="auto"/>
<Frame x:Name="RootPlayBarFrame"
Grid.Row="1"
Width="auto" Height="auto"/>
</Grid>
</windowex:WindowEx>

View File

@@ -0,0 +1,361 @@
using Microsoft.UI;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Views;
using Windows.UI.ViewManagement;
using WinRT;
namespace The_Untamed_Music_Player;
public sealed partial class MainWindow : WindowEx
{
private readonly Microsoft.UI.Dispatching.DispatcherQueue dispatcherQueue;
private readonly UISettings settings;
private readonly ILocalSettingsService _localSettingsService;
private WindowsSystemDispatcherQueueHelper? m_wsdqHelper;
private DesktopAcrylicController? m_acrylicController;
private SystemBackdropConfiguration? m_configurationSource;
private string _selectedMaterial = "Desktop Acrylic";
/// <summary>
/// 选定的窗口材质
/// </summary>
public string SelectedMaterial
{
get => _selectedMaterial;
set => _selectedMaterial = value;
}
public MainWindow(ILocalSettingsService localSettingsService)
{
InitializeComponent();
_localSettingsService = localSettingsService;
InitializeAsync();
AppWindow.SetIcon(Path.Combine(AppContext.BaseDirectory, "Assets/WindowIcon.ico"));
Title = "AppDisplayName".GetLocalized();
ExtendsContentIntoTitleBar = true;
dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
settings = new UISettings();
settings.ColorValuesChanged += Settings_ColorValuesChanged;
Data.MainWindow = this;
ShellFrame.Navigate(typeof(ShellPage));
RootPlayBarFrame.Navigate(typeof(RootPlayBarView));
Activated += Window_Activated;
Closed += Window_Closed;
}
/// <summary>
/// 初始化窗口材质
/// </summary>
public async void InitializeAsync()
{
await LoadSelectedMaterialAsync();
ChangeMaterial(SelectedMaterial);
}
/// <summary>
/// 获取导航页(ShellFrame)
/// </summary>
/// <returns></returns>
public Frame GetShellFrame()
{
return ShellFrame;
}
/// <summary>
/// 处理在应用程序打开时主题改变时正确更新标题按钮颜色
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void Settings_ColorValuesChanged(UISettings sender, object args)
{
// 这个调用来自线程外,因此我们需要将其调度到当前应用程序的线程
dispatcherQueue.TryEnqueue(() =>
{
TitleBarHelper.ApplySystemThemeToCaptionButtons();
});
}
/// <summary>
/// 尝试设置系统背景为无
/// </summary>
/// <returns></returns>
public bool TrySetNoneBackdrop()
{
try
{
SystemBackdrop = null;
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 尝试设置Mica背景
/// </summary>
/// <param name="useMicaAlt"></param>
/// <returns></returns>
public bool TrySetMicaBackdrop(bool useMicaAlt = false)
{
try
{
if (MicaController.IsSupported())
{
var micaBackdrop = new Microsoft.UI.Xaml.Media.MicaBackdrop
{
Kind = useMicaAlt ? MicaKind.BaseAlt : MicaKind.Base
};
SystemBackdrop = micaBackdrop;
return true; // Succeeded.
}
return false; // Mica is not supported on this system.
}
catch
{
return false;
}
}
/// <summary>
/// 尝试设置桌面亚克力背景
/// </summary>
/// <returns></returns>
public bool TrySetDesktopAcrylicBackdrop()
{
try
{
if (DesktopAcrylicController.IsSupported())
{
var DesktopAcrylicBackdrop = new Microsoft.UI.Xaml.Media.DesktopAcrylicBackdrop();
SystemBackdrop = DesktopAcrylicBackdrop;
return true; // 成功
}
return false; // 桌面亚克力不受此系统支持
}
catch
{
return false;
}
}
/// <summary>
/// 尝试设置亚克力背景
/// </summary>
/// <param name="useAcrylicThin"></param>
/// <returns></returns>
public bool TrySetAcrylicBackdrop(bool useAcrylicThin = false)
{
try
{
if (DesktopAcrylicController.IsSupported())
{
m_wsdqHelper = new WindowsSystemDispatcherQueueHelper();
m_wsdqHelper.EnsureWindowsSystemDispatcherQueueController();
// 挂钩策略对象到窗口
m_configurationSource = new SystemBackdropConfiguration();
((FrameworkElement)Content).ActualThemeChanged += Window_ThemeChanged;
// 初始化配置状态
m_configurationSource.IsInputActive = true;
SetConfigurationSourceTheme();
m_acrylicController = new DesktopAcrylicController
{
Kind = useAcrylicThin ? DesktopAcrylicKind.Thin : DesktopAcrylicKind.Base
};
// 启用系统背景
// 注意确保有“using WinRT;”以支持Window.As<...>()调用。
m_acrylicController.AddSystemBackdropTarget(this.As<Microsoft.UI.Composition.ICompositionSupportsSystemBackdrop>());
m_acrylicController.SetSystemBackdropConfiguration(m_configurationSource);
return true; // 成功
}
return false; // 亚克力不受此系统支持
}
catch
{
return false;
}
}
/// <summary>
/// 尝试设置模糊背景
/// </summary>
/// <returns></returns>
public bool TrySetBlurBackdrop()
{
try
{
var blurBackdrop = new BlurredBackdrop();
SystemBackdrop = blurBackdrop;
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 尝试设置透明背景
/// </summary>
/// <param name="tintColor"></param>
/// <returns></returns>
public bool TrySetTransparentBackdrop(Windows.UI.Color? tintColor = null)
{
try
{
var color = tintColor ?? Colors.Transparent;
var transparentBackdrop = new TransparentTintBackdrop(color);
SystemBackdrop = transparentBackdrop;
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 尝试设置动画背景
/// </summary>
/// <returns></returns>
public bool TrySetAnimatedBackdrop()
{
try
{
var animatedBackdrop = new ColorAnimatedBackdrop();
SystemBackdrop = animatedBackdrop;
return true;
}
catch
{
return false;
}
}
private void Window_Activated(object sender, WindowActivatedEventArgs args)
{
if (m_configurationSource != null)
{
m_configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated;
}
}
private void Window_Closed(object sender, WindowEventArgs args)
{
// 确保任何Mica/Acrylic控制器都被释放以便它不会尝试使用此关闭的窗口。
if (m_acrylicController != null)
{
m_acrylicController.Dispose();
m_acrylicController = null;
}
Activated -= Window_Activated;
m_configurationSource = null;
Data.MusicPlayer.SaveCurrentStateAsync();
}
public void ReleaseCurrentBackdropResources()
{
// 释放m_acrylicController的资源并将其设置为null
if (m_acrylicController != null)
{
m_acrylicController.Dispose();
m_acrylicController = null;
}
}
private void Window_ThemeChanged(FrameworkElement sender, object args)
{
if (m_configurationSource != null)
{
SetConfigurationSourceTheme();
}
}
private void SetConfigurationSourceTheme()
{
if (m_configurationSource != null)
{
switch (((FrameworkElement)Content).ActualTheme)
{
case ElementTheme.Dark: m_configurationSource.Theme = SystemBackdropTheme.Dark; break;
case ElementTheme.Light: m_configurationSource.Theme = SystemBackdropTheme.Light; break;
case ElementTheme.Default: m_configurationSource.Theme = SystemBackdropTheme.Default; break;
}
}
}
/// <summary>
/// 启动应用时更改选定的材质
/// </summary>
/// <param name="material"></param>
public void ChangeMaterial(string material)
{
try
{
switch (material)
{
case "None":
TrySetNoneBackdrop();
break;
case "Mica":
TrySetMicaBackdrop(false);
break;
case "Mica Alt":
TrySetMicaBackdrop(true);
break;
case "Desktop Acrylic":
TrySetDesktopAcrylicBackdrop();
break;
case "Acrylic Base":
TrySetAcrylicBackdrop(false);
break;
case "Acrylic Thin":
TrySetAcrylicBackdrop(true);
break;
case "Blur":
TrySetBlurBackdrop();
break;
case "Transparent":
TrySetTransparentBackdrop();
break;
case "Animated":
TrySetAnimatedBackdrop();
break;
}
}
catch { }
}
/// <summary>
/// 从设置存储读取选定的材质
/// </summary>
/// <returns></returns>
public async Task LoadSelectedMaterialAsync()
{
var material = await _localSettingsService.ReadSettingAsync<string>("SelectedMaterial");
if (!string.IsNullOrEmpty(material))
{
SelectedMaterial = material;
}
}
}

View File

@@ -0,0 +1,117 @@
using System.ComponentModel;
using Microsoft.UI.Xaml.Media.Imaging;
namespace The_Untamed_Music_Player.Models;
public class AlbumInfo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
//专辑名
private string? _name;
public string? Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
//专辑封面路径
private BitmapImage? _cover;
public BitmapImage? Cover
{
get => _cover;
set
{
_cover = value;
OnPropertyChanged(nameof(Cover));
}
}
private string? _artist;
public string? Artist
{
get => _artist;
set
{
_artist = value;
OnPropertyChanged(nameof(Artist));
}
}
//专辑包含的歌曲数量
private int _totalNum;
public int TotalNum
{
get => _totalNum;
set
{
_totalNum = value;
OnPropertyChanged(nameof(TotalNum));
}
}
private TimeSpan _totalDuration;
public TimeSpan TotalDuration
{
get => _totalDuration;
set
{
_totalDuration = value;
OnPropertyChanged(nameof(TotalDuration));
}
}
//专辑发布年份
private string _year = "";
public string Year
{
get => _year;
set
{
_year = value;
OnPropertyChanged(nameof(Year));
}
}
public AlbumInfo()
{
}
public AlbumInfo(BriefMusicInfo briefmusicInfo)
{
Name = briefmusicInfo.Album;
Year = briefmusicInfo.YearStr;
Cover = briefmusicInfo.Cover;
Artist = briefmusicInfo.ArtistsStr;
TotalDuration = briefmusicInfo.Duration;
TotalNum = 1;
}
public void Update(BriefMusicInfo briefmusicInfo)
{
TotalNum++;
TotalDuration += briefmusicInfo.Duration;
}
public string? GetCountAndDuration()
{
if (TotalDuration.Hours > 0)
{
return string.IsNullOrEmpty(Year)
? $"{TotalNum} 首歌曲·{TotalDuration:hh\\:mm\\:ss} 歌曲长度"
: $"{Year}·{TotalNum} 首歌曲·{TotalDuration:hh\\:mm\\:ss} 歌曲长度";
}
else
{
return string.IsNullOrEmpty(Year)
? $"{TotalNum} 首歌曲·{TotalDuration:mm\\:ss} 歌曲长度"
: $"{Year}·{TotalNum} 首歌曲·{TotalDuration:mm\\:ss} 歌曲长度";
}
}
}

View File

@@ -0,0 +1,142 @@
using System.ComponentModel;
using Microsoft.UI.Xaml.Media.Imaging;
namespace The_Untamed_Music_Player.Models;
public class ArtistInfo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
//艺术家名
private string? _name;
public string? Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
private Dictionary<string, AlbumInfo>? _albums = [];
public Dictionary<string, AlbumInfo>? Albums
{
get => _albums;
set
{
_albums = value;
OnPropertyChanged(nameof(Albums));
}
}
public string? _genre;
public string? Genre
{
get => _genre;
set
{
_genre = value;
OnPropertyChanged(nameof(Genre));
}
}
//封面路径(当作头像,默认为检索到的第一张)
private BitmapImage? _cover;
public BitmapImage? Cover
{
get => _cover;
set
{
_cover = value;
OnPropertyChanged(nameof(Cover));
}
}
private TimeSpan _totalDuration;
public TimeSpan TotalDuration
{
get => _totalDuration;
set
{
_totalDuration = value;
OnPropertyChanged(nameof(TotalDuration));
}
}
//歌曲数量
private int _totalMusicNum;
public int TotalMusicNum
{
get => _totalMusicNum;
set
{
_totalMusicNum = value;
OnPropertyChanged(nameof(TotalMusicNum));
}
}
private int _totalAlbumNum;
public int TotalAlbumNum
{
get => _totalAlbumNum;
set
{
_totalAlbumNum = value;
OnPropertyChanged(nameof(TotalAlbumNum));
}
}
public ArtistInfo(BriefMusicInfo briefMusicInfo, string name)
{
Name = name;
Cover = briefMusicInfo.Cover;
TotalDuration = briefMusicInfo.Duration;
Genre = briefMusicInfo.GenreStr;
TotalMusicNum = 1;
TotalAlbumNum = 1;
if (briefMusicInfo.Album != null && Albums != null)
{
Albums[briefMusicInfo.Album] = new AlbumInfo(briefMusicInfo);
}
}
public void Update(BriefMusicInfo briefMusicInfo)
{
TotalDuration += briefMusicInfo.Duration;
TotalMusicNum++;
var album = briefMusicInfo.Album;
if (album != null && Albums != null)
{
if (!Albums.TryGetValue(album, out var value))
{
Albums[album] = new AlbumInfo(briefMusicInfo);
TotalAlbumNum++;
}
else
{
value.Update(briefMusicInfo);
}
}
}
public string? GetCount()
{
return $"{TotalAlbumNum} 个相册·{TotalMusicNum} 首歌曲·";
}
public string? GetDuration()
{
if (TotalDuration.Hours > 0)
{
return $"{TotalDuration.Hours} 小时 {TotalDuration.Minutes} 分钟 {TotalDuration.Seconds} 秒";
}
else
{
return $"{TotalDuration.Minutes} 分钟 {TotalDuration.Seconds} 秒";
}
}
}

View File

@@ -0,0 +1,21 @@
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.ViewModels;
using The_Untamed_Music_Player.Views;
namespace The_Untamed_Music_Player.Models;
public static class Data
{
public static readonly string[] SupportedAudioTypes = [".flac", ".wav", ".m4a", ".aac", ".mp3", ".wma", ".ogg", ".oga", ".opus"];
public static MusicPlayer MusicPlayer { get; set; } = new(App.GetService<ILocalSettingsService>());
public static MusicLibrary MusicLibrary { get; set; } = new();
public static string? SelectedAlbum;
public static string? SelectedArtist;
public static bool IsDetail = false;
public static MainWindow? MainWindow;
public static ShellPage? ShellPage;
public static SettingsViewModel? SettingsViewModel;
public static RootPlayBarViewModel? RootPlayBarViewModel;
public static ViewModel? ViewModel;
}

View File

@@ -0,0 +1,14 @@
namespace The_Untamed_Music_Player.Models;
public class LocalSettingsOptions
{
public string? ApplicationDataFolder
{
get; set;
}
public string? LocalSettingsFile
{
get; set;
}
}

View File

@@ -0,0 +1,126 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Text.RegularExpressions;
namespace The_Untamed_Music_Player.Models;
public partial class LyricSlice : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _content = "";
public string Content
{
get => _content;
set
{
_content = value;
OnPropertyChanged(nameof(Content));
}
}
private double _time;
public double Time
{
get => _time;
set
{
_time = value;
OnPropertyChanged(nameof(Time));
}
}
public LyricSlice(double time, string content)
{
Time = time;
Content = content;
}
[GeneratedRegex(@".*\](.*)")]
private static partial Regex RegexWord();
[GeneratedRegex(@"\[([0-9.:]*)\]", RegexOptions.Compiled)]
private static partial Regex RegexTime();
public static ObservableCollection<LyricSlice> GetLyricSlices(string lyric)
{
var lyricSlices = new List<LyricSlice>();
if (string.IsNullOrEmpty(lyric))
{
return [];
}
var lines = lyric.Split('\n');
double? lastTime = null;//上一行有歌词的行的时间
double emptyStartTime = 0;//空白行开始时间
var inEmptyBlock = false;
var emptyLines = new List<LyricSlice>();
foreach (var line in lines)
{
if (line != null)
{
if (line.StartsWith("[ti:") || line.StartsWith("[ar:") || line.StartsWith("[al:") || line.StartsWith("[by:") || line.StartsWith("[offset:"))//歌曲名、艺人名、专辑名、歌词制作人、歌词时间补偿值
{
continue;
}
try
{
var regexword = RegexWord();//获取歌词文本的正则表达式
var regextime = RegexTime();//获取时间戳的正则表达式
var WORD = regexword.Match(line).Groups[1].Value;//获取歌词文本
var TIME = regextime.Matches(line);//获取时间戳
if (string.IsNullOrWhiteSpace(WORD))//歌词文本为空
{
if (!inEmptyBlock)
{
emptyStartTime = TIME.Count > 0 ? TimeSpan.Parse("00:" + TIME[0].Groups[1].Value).TotalMilliseconds : lastTime ?? 0;//将时间戳转换为毫秒(只保留第一组, 防止一行歌词里面有多个时间)
if (emptyStartTime == lastTime)
{
emptyStartTime += 1; // 如果空白行和上一行有歌词的行时间相同空白行的时间加1毫秒
}
inEmptyBlock = true;
}
}
else
{
if (inEmptyBlock)
{
var emptyEndTime = TIME.Count > 0 ? TimeSpan.Parse("00:" + TIME[0].Groups[1].Value).TotalMilliseconds : lastTime ?? 0;
if (emptyEndTime - emptyStartTime > 5000)
{
lyricSlices.Add(new LyricSlice(emptyStartTime, "•••"));
}
inEmptyBlock = false;
}
foreach (Match item in TIME)
{
var time = TimeSpan.Parse("00:" + item.Groups[1].Value).TotalMilliseconds;
lyricSlices.Add(new LyricSlice(time, WORD));
lastTime = time;
}
}
}
catch
{
continue;
}
}
}
if (inEmptyBlock && emptyLines.Count > 0)
{
var emptyEndTime = emptyLines.Last().Time;
if (emptyEndTime - emptyStartTime > 5000)
{
lyricSlices.Add(new LyricSlice(emptyStartTime, "•••"));
}
}
return new ObservableCollection<LyricSlice>(lyricSlices.OrderBy(t => t.Time));//将 lyricSlices 列表按时间戳排序。
}
}

View File

@@ -0,0 +1,505 @@
using System.ComponentModel;
using System.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
namespace The_Untamed_Music_Player.Models;
public class BriefMusicInfo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _path = "";
/// <summary>
/// 文件位置
/// </summary>
public string Path
{
get => _path;
set => _path = value;
}
private string _itemType = "";
/// <summary>
/// 项目类型
/// </summary>
public string ItemType
{
get => _itemType;
set => _itemType = value;
}
private string _album = "";
/// <summary>
/// 专辑名, 为空时返回"未知专辑"
/// </summary>
public string Album
{
get => _album;
set => _album = value;
}
private string _title = "";
/// <summary>
/// 歌曲名
/// </summary>
public string Title
{
get => _title;
set => _title = value;
}
private string[] _artists = [];
/// <summary>
/// 参与创作的艺术家数组
/// </summary>
public string[] Artists
{
get => _artists;
set
{
if (value?.Length > 0)
{
var tempArtists = Array.Empty<string>();
foreach (var i in value)
{
if (i.Contains('、'))
{
tempArtists = [.. tempArtists, .. i.Split('、')];//将艺术家名字分割开并存到数组中
}
else if (i.Contains(','))
{
tempArtists = [.. tempArtists, .. i.Split(',')];
}
else if (i.Contains(''))
{
tempArtists = [.. tempArtists, .. i.Split('')];
}
else if (i.Contains('|'))
{
tempArtists = [.. tempArtists, .. i.Split('|')];
}
else if (i.Contains('/'))
{
tempArtists = [.. tempArtists, .. i.Split('/')];
}
else
{
tempArtists = [.. tempArtists, .. new[] { i }];
}
}
_artists = tempArtists.Distinct().ToArray();
}
else
{
_artists = [];
}
ArtistsStr = GetArtists();
}
}
private string _artistsStr = "";
/// <summary>
/// 参与创作的艺术家名, 为空时返回"未知艺术家"
/// </summary>
public string ArtistsStr
{
get => _artistsStr;
set => _artistsStr = value;
}
/// <summary>
/// 获取参与创作的艺术家名
/// </summary>
/// <returns></returns>
public string GetArtists()
{
if (_artists == null || _artists.Length == 0)
{
return "未知艺术家";
}
var sb = new StringBuilder();
foreach (var artist in _artists)
{
sb.Append(artist);
sb.Append(", ");
}
if (sb.Length > 0)
{
sb.Length -= 2; // 去掉最后一个逗号
}
return sb.ToString();
}
private TimeSpan _duration;
/// <summary>
/// 时长
/// </summary>
public TimeSpan Duration
{
get => _duration;
set
{
_duration = value;
DurationStr = GetDurationStr();
}
}
private string _durationStr = "";
/// <summary>
/// 时长字符串
/// </summary>
public string DurationStr
{
get => _durationStr;
set => _durationStr = value;
}
/// <summary>
/// 获取时长字符串
/// </summary>
/// <returns></returns>
public string GetDurationStr()
{
if (Duration.Hours > 0)
{
return $"{Duration:hh\\:mm\\:ss}";
}
else
{
return $"{Duration:mm\\:ss}";
}
}
private uint _year;
/// <summary>
/// 发行年份
/// </summary>
public uint Year
{
get => _year;
set
{
_year = value;
YearStr = value.ToString();
}
}
private string _yearStr = "";
/// <summary>
/// 发行年份字符串
/// </summary>
public string YearStr
{
get => _yearStr;
set
{
if (value == "0")
{
_yearStr = "";
}
else
{
_yearStr = value;
}
}
}
private BitmapImage? _cover;
/// <summary>
/// 封面(可能为空)
/// </summary>
public BitmapImage? Cover
{
get => _cover;
set => _cover = value;
}
private string[] _genre = [];
/// <summary>
/// 流派数组
/// </summary>
public string[] Genre
{
get => _genre;
set
{
_genre = value;
GenreStr = GetGenre();
}
}
private string _genreStr = "";
/// <summary>
/// 流派字符串, 为空时返回"未知流派"
/// </summary>
public string GenreStr
{
get => _genreStr;
set
{
_genreStr = value;
OnPropertyChanged(nameof(GenreStr));
}
}
/// <summary>
/// 获取流派字符串
/// </summary>
/// <returns></returns>
public string GetGenre()
{
if (_genre == null || _genre.Length == 0)
{
return "未知流派";
}
var sb = new StringBuilder();
foreach (var genre in _genre)
{
sb.Append(genre);
sb.Append(", ");
}
if (sb.Length > 0)
{
sb.Length -= 2;
}
return sb.ToString();
}
private DateTimeOffset _modifiedDate;
/// <summary>
/// 修改日期
/// </summary>
public DateTimeOffset ModifiedDate
{
get => _modifiedDate;
set => _modifiedDate = value;
}
/// <summary>
/// 获取文本前景色
/// </summary>
/// <param name="currentMusic"></param>
/// <param name="elementTheme"></param>
/// <returns>如果是当前播放歌曲, 返回主题色, 如果不是, 根据当前主题返回黑色或白色</returns>
public SolidColorBrush GetTextForeground(DetailedMusicInfo currentMusic, ElementTheme elementTheme)
{
if (Path == currentMusic.Path)
{
return (SolidColorBrush)App.Current.Resources["AccentTextFillColorTertiaryBrush"];
}
if (elementTheme == ElementTheme.Dark)
{
return new SolidColorBrush(Microsoft.UI.Colors.White);
}
else
{
return new SolidColorBrush(Microsoft.UI.Colors.Black);
}
}
public BriefMusicInfo()
{
}
public BriefMusicInfo(string path)
{
try
{
var musicFile = TagLib.File.Create(path);
Path = path;
ModifiedDate = new FileInfo(path).LastWriteTime;
ItemType = System.IO.Path.GetExtension(path).ToLower();
Album = musicFile.Tag.Album ?? "未知专辑";
Title = string.IsNullOrEmpty(musicFile.Tag.Title) ? System.IO.Path.GetFileNameWithoutExtension(path) : musicFile.Tag.Title;
Artists = musicFile.Tag.AlbumArtists.Concat(musicFile.Tag.Performers).ToArray().Length != 0 ? [.. musicFile.Tag.AlbumArtists, .. musicFile.Tag.Performers] : ["未知艺术家"];
Year = musicFile.Tag.Year;
Genre = musicFile.Tag.Genres.Length != 0 ? [.. musicFile.Tag.Genres] : ["未知流派"];
if (musicFile.Tag.Pictures != null && musicFile.Tag.Pictures.Length != 0)
{
var coverBuffer = musicFile.Tag.Pictures[0].Data.Data;
using var stream = new MemoryStream(coverBuffer);
stream.Seek(0, SeekOrigin.Begin);
Cover = new BitmapImage
{
DecodePixelWidth = 600,
DecodePixelHeight = 600
};
Cover.SetSource(stream.AsRandomAccessStream());
}
Duration = musicFile.Properties.Duration;
}
catch (Exception)
{
Path = path;
ModifiedDate = new FileInfo(path).LastWriteTime;
ItemType = System.IO.Path.GetExtension(path).ToLower();
Album = "未知专辑";
Title = System.IO.Path.GetFileNameWithoutExtension(path);
Artists = ["未知艺术家"];
Genre = ["未知流派"];
Duration = TimeSpan.Zero;
}
}
}
public class DetailedMusicInfo : BriefMusicInfo
{
private string[] _albumArtists = [];
/// <summary>
/// 专辑艺术家数组
/// </summary>
public string[] AlbumArtists
{
get => _albumArtists;
set
{
if (value?.Length > 0)
{
var tempArtists = Array.Empty<string>();
foreach (var i in value)
{
if (i.Contains('、'))
{
tempArtists = [.. tempArtists, .. i.Split('、')];
}
else if (i.Contains(','))
{
tempArtists = [.. tempArtists, .. i.Split(',')];
}
else if (i.Contains(''))
{
tempArtists = [.. tempArtists, .. i.Split('')];
}
else if (i.Contains('|'))
{
tempArtists = [.. tempArtists, .. i.Split('|')];
}
else if (i.Contains('/'))
{
tempArtists = [.. tempArtists, .. i.Split('/')];
}
else
{
tempArtists = [.. tempArtists, .. new[] { i }];
}
}
_albumArtists = tempArtists.Distinct().ToArray();
}
else
{
_albumArtists = [];
}
}
}
/// <summary>
/// 获取专辑艺术家字符串
/// </summary>
/// <returns></returns>
public string GetAlbumArtists()
{
if (_albumArtists == null || _albumArtists.Length == 0)
{
return "";
}
var sb = new StringBuilder();
foreach (var artist in _albumArtists)
{
sb.Append(artist);
sb.Append(", ");
}
if (sb.Length > 0)
{
sb.Length -= 2; // 去掉最后一个逗号
}
return sb.ToString();
}
public string _artistAndAlbumStr = "";
/// <summary>
/// 艺术家和专辑名字符串
/// </summary>
public string ArtistAndAlbumStr
{
get => _artistAndAlbumStr;
set => _artistAndAlbumStr = value;
}
/// <summary>
/// 获取艺术家和专辑名字符串
/// </summary>
/// <returns></returns>
public string GetArtistAndAlbumStr()
{
var artistsStr = GetArtists();
if (string.IsNullOrEmpty(artistsStr))
{
return Album ?? "";
}
if (string.IsNullOrEmpty(Album))
{
return artistsStr;
}
return $"{artistsStr} • {Album}";
}
private int _bitRate;
/// <summary>
/// 比特率
/// </summary>
public int BitRate
{
get => _bitRate;
set => _bitRate = value;
}
private int _track;
/// <summary>
/// 曲目
/// </summary>
public int Track
{
get => _track;
set => _track = value;
}
private string _lyric = "";
/// <summary>
/// 歌词
/// </summary>
public string Lyric
{
get => _lyric;
set => _lyric = value;
}
public DetailedMusicInfo()
{
}
public DetailedMusicInfo(string path) : base(path)
{
try
{
var musicFile = TagLib.File.Create(path);
AlbumArtists = [.. musicFile.Tag.AlbumArtists];
ArtistAndAlbumStr = GetArtistAndAlbumStr();
Track = (int)musicFile.Tag.Track;
Lyric = musicFile.Tag.Lyrics ?? "";
BitRate = musicFile.Properties.AudioBitrate;
}
catch (Exception)
{
AlbumArtists = [];
Track = 0;
BitRate = 0;
}
}
}

View File

@@ -0,0 +1,347 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.ViewModels;
using Windows.Storage;
namespace The_Untamed_Music_Player.Models;
public class MusicLibrary : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<StorageFolder> _folders = [];
public ObservableCollection<StorageFolder> Folders
{
get => _folders;
set
{
_folders = value;
OnPropertyChanged(nameof(SettingsViewModel.EmptyFolderMessageVisibility));
}
}
private ObservableCollection<BriefMusicInfo> _musics = [];
public ObservableCollection<BriefMusicInfo> Musics
{
get => _musics;
set
{
_musics = value;
OnPropertyChanged(nameof(HasMusics));
}
}
public bool HasMusics => Musics.Any();
private Dictionary<string, AlbumInfo> _albums = [];
public Dictionary<string, AlbumInfo> Albums
{
get => _albums;
set
{
_albums = value;
OnPropertyChanged(nameof(Albums));
}
}
private Dictionary<string, ArtistInfo> _artists = [];
public Dictionary<string, ArtistInfo> Artists
{
get => _artists;
set
{
_artists = value;
OnPropertyChanged(nameof(Artists));
}
}
public MusicLibrary()
{
LoadFoldersAsync();
}
public async Task LoadLibrary()
{
if (Folders != null && Folders.Any())
{
foreach (var folder in Folders)
{
await LoadMusic(folder);
}
}
SortMusicsByModifiedTimeDecending();
OnPropertyChanged(nameof(Artists));
OnPropertyChanged(nameof(Albums));
OnPropertyChanged(nameof(HasMusics));
}
public async Task LoadLibraryAgain()
{
Musics?.Clear();
Artists?.Clear();
Albums?.Clear();
if (Folders != null && Folders.Any())
{
foreach (var folder in Folders)
{
await LoadMusic(folder);
}
}
SortMusicsByModifiedTimeDecending();
OnPropertyChanged(nameof(Artists));
OnPropertyChanged(nameof(Albums));
OnPropertyChanged(nameof(HasMusics));
}
public async void LoadFoldersAsync()
{
var folderPaths = await ApplicationData.Current.LocalFolder.ReadAsync<List<string>>("MusicFolders");// ApplicationData.Current.LocalFolder获取应用程序的本地存储文件夹。ReadAsync<List<string>>("MusicFolders"):调用 SettingsStorageExtensions 类中的扩展方法 ReadAsync从名为 "MusicFolders" 的文件中读取数据,并将其反序列化为 List<string> 类型。
if (folderPaths != null)
{
foreach (var path in folderPaths)
{
var folder = await StorageFolder.GetFolderFromPathAsync(path);
Folders?.Add(folder);
}
OnPropertyChanged(nameof(SettingsViewModel.EmptyFolderMessageVisibility));
await Data.MusicLibrary.LoadLibrary();
}
}
private async Task LoadMusic(StorageFolder folder)
{
try
{
var allFiles = await folder.GetFilesAsync();
foreach (var file in allFiles)
{
if (Data.SupportedAudioTypes.Contains(file.FileType.ToLower()) && Musics != null)
{
// 检查 Musics 集合中是否已经存在相同路径的音乐
if (!Musics.Any(m => m.Path == file.Path))
{
var briefMusicInfo = new BriefMusicInfo(file.Path);
Musics?.Add(briefMusicInfo);
UpdateAlbumInfo(briefMusicInfo);
UpdateArtistInfo(briefMusicInfo);
}
}
}
var subFolders = await folder.GetFoldersAsync();
foreach (var subFolder in subFolders)
{
await LoadMusic(subFolder);
}
}
catch (Exception e)
{
Debug.WriteLine(e);
}
}
private void UpdateAlbumInfo(BriefMusicInfo briefMusicInfo)
{
var album = briefMusicInfo.Album;
if (!string.IsNullOrEmpty(album) && Albums != null)
{
if (!Albums.TryGetValue(album, out var albumInfo))
{
albumInfo = new AlbumInfo(briefMusicInfo);
Albums[album] = albumInfo;
}
else
{
albumInfo.Update(briefMusicInfo);
}
}
}
private void UpdateArtistInfo(BriefMusicInfo briefMusicInfo)
{
if (briefMusicInfo.Artists != null && Artists != null)
{
foreach (var artist in briefMusicInfo.Artists)
{
if (!Artists.TryGetValue(artist, out var artistInfo))
{
artistInfo = new ArtistInfo(briefMusicInfo, artist);
Artists[artist] = artistInfo;
}
else
{
artistInfo.Update(briefMusicInfo);
}
}
}
}
public ObservableCollection<BriefMusicInfo> GetMusicByAlbum(AlbumInfo albumInfo)
{
var list = new ObservableCollection<BriefMusicInfo>();
var albumName = albumInfo.Name;
if (Musics != null)
{
foreach (var music in Musics)
{
if (music.Album == albumName && music.Path != null)
{
list.Add(new BriefMusicInfo(music.Path));
}
}
}
return list;
}
public ObservableCollection<AlbumInfo> GetAlbumByArtist(ArtistInfo artistInfo)
{
var list = new ObservableCollection<AlbumInfo>();
var artistName = artistInfo.Name;
var albumDict = artistInfo.Albums;
if (Musics != null)
{
foreach (var music in Musics)
{
if (music.Artists != null && music.Artists.Contains(artistName) && music.Album != null && albumDict != null && albumDict.TryGetValue(music.Album, out var albumInfo))
{
list.Add(albumInfo);
}
}
}
return list;
}
/// <summary>
/// 根据歌曲名升序排序
/// </summary>
public void SortMusicsByTitle()
{
if (Musics != null)
{
var sortedMusics = new ObservableCollection<BriefMusicInfo>(Musics.OrderBy(m => m.Title));
Musics = sortedMusics;
}
GC.Collect();
}
/// <summary>
/// 根据歌曲名降序排序
/// </summary>
public void SortMusicsByTitleDescending()
{
if (Musics != null)
{
var sortedMusics = new ObservableCollection<BriefMusicInfo>(Musics.OrderByDescending(m => m.Title));
Musics = sortedMusics;
}
GC.Collect();
}
/// <summary>
/// 根据艺术家名升序排序
/// </summary>
public void SortMusicsByArtist()
{
if (Musics != null)
{
var sortedMusics = new ObservableCollection<BriefMusicInfo>(Musics.OrderBy(m => m.ArtistsStr));
Musics = sortedMusics;
}
GC.Collect();
}
/// <summary>
/// 根据艺术家名降序排序
/// </summary>
public void SortMusicsByArtistDescending()
{
if (Musics != null)
{
var sortedMusics = new ObservableCollection<BriefMusicInfo>(Musics.OrderByDescending(m => m.ArtistsStr));
Musics = sortedMusics;
}
GC.Collect();
}
/// <summary>
/// 根据专辑名升序排序
/// </summary>
public void SortMusicsByAlbum()
{
if (Musics != null)
{
var sortedMusics = new ObservableCollection<BriefMusicInfo>(Musics.OrderBy(m => m.Album));
Musics = sortedMusics;
}
GC.Collect();
}
/// <summary>
/// 根据专辑名降序排序
/// </summary>
public void SortMusicsByAlbumDescending()
{
if (Musics != null)
{
var sortedMusics = new ObservableCollection<BriefMusicInfo>(Musics.OrderByDescending(m => m.Album));
Musics = sortedMusics;
}
GC.Collect();
}
/// <summary>
/// 根据发行年份升序排序
/// </summary>
public void SortMusicsByYear()
{
if (Musics != null)
{
var sortedMusics = new ObservableCollection<BriefMusicInfo>(Musics.OrderBy(m => m.Year));
Musics = sortedMusics;
}
GC.Collect();
}
/// <summary>
/// 根据发行年份降序排序
/// </summary>
public void SortMusicsByYearDescending()
{
if (Musics != null)
{
var sortedMusics = new ObservableCollection<BriefMusicInfo>(Musics.OrderByDescending(m => m.Year));
Musics = sortedMusics;
}
GC.Collect();
}
/// <summary>
/// 根据修改日期升序排序
/// </summary>
public void SortMusicsByModifiedTime()
{
if (Musics != null)
{
var sortedMusics = new ObservableCollection<BriefMusicInfo>(Musics.OrderBy(m => m.ModifiedDate));
Musics = sortedMusics;
}
GC.Collect();
}
/// <summary>
/// 根据修改日期降序排序
/// </summary>
public void SortMusicsByModifiedTimeDecending()
{
if (Musics != null)
{
var sortedMusics = new ObservableCollection<BriefMusicInfo>(Musics.OrderByDescending(m => m.ModifiedDate));
Musics = sortedMusics;
}
GC.Collect();
}
}

View File

@@ -0,0 +1,975 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Views;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Windows.System.Threading;
namespace The_Untamed_Music_Player.Models;
public class MusicPlayer : INotifyPropertyChanged
{
private readonly ILocalSettingsService _localSettingsService;
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private MediaPlayer _player = new()
{
AudioCategory = MediaPlayerAudioCategory.Media,
};
/// <summary>
/// 音乐播放器
/// </summary>
public MediaPlayer Player
{
get => _player;
set => _player = value;
}
private string? _playQueueName;
/// <summary>
/// 播放队列名
/// </summary>
public string? PlayQueueName
{
get => _playQueueName;
set
{
_playQueueName = value;
OnPropertyChanged(nameof(PlayQueueName));
}
}
private ObservableCollection<BriefMusicInfo> _playQueue = [];
/// <summary>
/// 播放队列集合
/// </summary>
public ObservableCollection<BriefMusicInfo> PlayQueue
{
get => _playQueue;
set => _playQueue = value;
}
private ObservableCollection<BriefMusicInfo> _shuffledPlayQueue = [];
/// <summary>
/// 随机播放队列集合
/// </summary>
public ObservableCollection<BriefMusicInfo> ShuffledPlayQueue
{
get => _shuffledPlayQueue;
set => _shuffledPlayQueue = value;
}
private int _playQueueLength;
/// <summary>
/// 播放队列歌曲数量
/// </summary>
public int PlayQueueLength
{
get => _playQueueLength;
set
{
_playQueueLength = value;
OnPropertyChanged(nameof(PlayQueueLength));
}
}
private int _playQueueIndex;
/// <summary>
/// 当前歌曲在播放队列中的索引
/// </summary>
public int PlayQueueIndex
{
get => _playQueueIndex;
set
{
_playQueueIndex = value;
OnPropertyChanged(nameof(PlayQueueIndex));
}
}
private bool _shuffleMode = false;
/// <summary>
/// 随机播放模式, true为开启, false为关闭.
/// </summary>
public bool ShuffleMode
{
get => _shuffleMode;
set => _shuffleMode = value;
}
private int _repeatMode = 0;
/// <summary>
/// 循环播放模式, 0为不循环, 1为列表循环, 2为单曲循环
/// </summary>
public int RepeatMode
{
get => _repeatMode;
set
{
_repeatMode = value;
OnPropertyChanged(nameof(RepeatMode));
}
}
private int _playState;
/// <summary>
/// 播放状态, 0为暂停, 1为播放, 2为加载中
/// </summary>
public int PlayState
{
get => _playState;
set
{
_playState = value;
OnPropertyChanged(nameof(PlayState));
}
}
private DetailedMusicInfo _currentMusic = new();
/// <summary>
/// 当前播放歌曲
/// </summary>
public DetailedMusicInfo CurrentMusic
{
get => _currentMusic;
set
{
_currentMusic = value;
//给播放器设置音乐源
SetSource(value.Path);
CurrentLyric = LyricSlice.GetLyricSlices(value.Lyric);
OnPropertyChanged(nameof(CurrentMusic));
}
}
private ObservableCollection<LyricSlice> _currentLyric = [];
/// <summary>
/// 当前歌词切片集合
/// </summary>
public ObservableCollection<LyricSlice> CurrentLyric
{
get => _currentLyric;
set
{
_currentLyric = value;
OnPropertyChanged(nameof(CurrentLyric));
}
}
private int _currentLyricIndex;
/// <summary>
/// 当前歌词切片在集合中的索引
/// </summary>
public int CurrentLyricIndex
{
get => _currentLyricIndex;
set
{
_currentLyricIndex = value;
OnPropertyChanged(nameof(CurrentLyricIndex));
}
}
/// <summary>
/// 线程计时器
/// </summary>
private ThreadPoolTimer? positionUpdateTimer;
/// <summary>
/// 线程锁开启状态, true为开启, false为关闭
/// </summary>
private bool lockable = false;
/// <summary>
/// 播放栏UI
/// </summary>
public static RootPlayBarView? PlayBarUI
{
get; set;
}
/// <summary>
/// 歌词页UI
/// </summary>
public static Page? UI
{
get; set;
}
private TimeSpan _current;
/// <summary>
/// 当前播放时间
/// </summary>
public TimeSpan Current
{
get => _current;
set
{
_current = value;
OnPropertyChanged(nameof(Current));
}
}
private TimeSpan _total;
/// <summary>
/// 当前歌曲总时长
/// </summary>
public TimeSpan Total
{
get => _total;
set
{
_total = value;
OnPropertyChanged(nameof(Total));
}
}
private double _currentPosition;
/// <summary>
/// 当前播放进度(百分比)
/// </summary>
public double CurrentPosition
{
get => _currentPosition;
set
{
_currentPosition = value;
OnPropertyChanged(nameof(CurrentPosition));
}
}
private double _currentVolume = 100;
/// <summary>
/// 当前音量
/// </summary>
public double CurrentVolume
{
get => _currentVolume;
set
{
_currentVolume = value;
Player.Volume = CurrentVolume / 100;
OnPropertyChanged(nameof(CurrentVolume));
}
}
/// <summary>
/// 线程锁, 用于限制对Player的访问
/// </summary>
private readonly Lock mediaLock = new();
public MusicPlayer(ILocalSettingsService localSettingsService)
{
_localSettingsService = localSettingsService;
LoadCurrentStateAsync();
Player.PlaybackSession.PlaybackStateChanged += PlaybackSession_PlaybackStateChanged;
Player.MediaEnded += OnPlaybackStopped;
Player.Volume = CurrentVolume / 100;
Player.CommandManager.IsEnabled = true;
}
~MusicPlayer()
{
Player.Dispose();
}
/// <summary>
/// 按路径播放歌曲
/// </summary>
/// <param name="path"></param>
/// <param name="isLast">是否是播放列表中最后一曲</param>
public void PlaySongByPath(string path, bool isLast = false)
{
lock (mediaLock)
{
if (!isLast)
{
Stop();
CurrentMusic = new DetailedMusicInfo(path);
if (ShuffleMode)
{
PlayQueueIndex = ShuffledPlayQueue.ToList().FindIndex(x => x.Path == path);
}
else
{
PlayQueueIndex = PlayQueue.ToList().FindIndex(x => x.Path == path);
}
Play();
}
else
{
Stop();
CurrentMusic = new DetailedMusicInfo(path);
PlayQueueIndex = PlayQueue.ToList().FindIndex(x => x.Path == path);
}
}
}
/// <summary>
/// 为播放器设置音乐源
/// </summary>
/// <param name="path"></param>
private void SetSource(string path)
{
lock (mediaLock)
{
try
{
var mediaFileTask = StorageFile.GetFileFromPathAsync(path).AsTask();
mediaFileTask.Wait();
var mediaFile = mediaFileTask.Result;
Player.Source = MediaSource.CreateFromStorageFile(mediaFile);
Total = Player.PlaybackSession.NaturalDuration;
positionUpdateTimer = ThreadPoolTimer.CreatePeriodicTimer(UpdateTimerHandler, TimeSpan.FromMilliseconds(250), UpdateTimerDestoyed);
}
catch { }
}
}
/// <summary>
/// 设置播放队列
/// </summary>
/// <param name="name"></param>
/// <param name="list"></param>
public async void SetPlayList(string name, ObservableCollection<BriefMusicInfo> list)
{
PlayQueueName = name;
PlayQueue = new ObservableCollection<BriefMusicInfo>(list);
PlayQueueLength = list.Count;
if (Data.RootPlayBarViewModel != null)
{
Data.RootPlayBarViewModel.ButtonVisibility = PlayQueue.Any() ? Visibility.Visible : Visibility.Collapsed;
Data.RootPlayBarViewModel.Availability = PlayQueue.Any();
}
await UpdateShufflePlayQueue();
}
/// <summary>
/// 计时器更新事件
/// </summary>
/// <param name="timer"></param>
private void UpdateTimerHandler(ThreadPoolTimer timer)
{
lock (mediaLock)
{
if (Player != null && !lockable && Player.PlaybackSession.PlaybackState != MediaPlaybackState.None && Player.PlaybackSession.PlaybackState == MediaPlaybackState.Playing)
{
try
{
PlayBarUI?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
Current = Player.PlaybackSession?.Position ?? TimeSpan.Zero;
Total = Player.PlaybackSession?.NaturalDuration ?? TimeSpan.Zero;
CurrentPosition = 100 * (Current.TotalMilliseconds / Total.TotalMilliseconds);
});
}
catch { }
try
{
UI?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
CurrentLyricIndex = GetCurrentLyricIndex((Player.PlaybackSession?.Position ?? TimeSpan.Zero).TotalMilliseconds);
});
}
catch { }
}
}
}
/// <summary>
/// 获取当前歌词切片索引
/// </summary>
/// <param name="currentTime"></param>
/// <returns></returns>
public int GetCurrentLyricIndex(double currentTime)
{
var index = CurrentLyric.ToList().FindIndex(x => x.Time > currentTime);
if (index > 0)
{
index--;
}
if (index < 0)
{
index = CurrentLyric.Count - 1;
}
return index;
}
/// <summary>
/// 计时器销毁事件
/// </summary>
/// <param name="timer"></param>
private void UpdateTimerDestoyed(ThreadPoolTimer timer)
{
timer.Cancel();
positionUpdateTimer?.Cancel();
positionUpdateTimer = null;
}
/// <summary>
/// 播放状态改变事件
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void PlaybackSession_PlaybackStateChanged(MediaPlaybackSession sender, object args)
{
try
{
switch (Player?.PlaybackSession.PlaybackState)
{
case MediaPlaybackState.None:
break;
case MediaPlaybackState.Opening:
PlayBarUI?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
PlayState = 2;
});
break;
case MediaPlaybackState.Buffering:
PlayBarUI?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
PlayState = 2;
});
break;
case MediaPlaybackState.Playing:
PlayBarUI?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
PlayState = 1;
});
break;
case MediaPlaybackState.Paused:
PlayBarUI?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
PlayState = 0;
});
break;
default:
break;
}
}
catch { }
}
/// <summary>
/// 播放结束事件
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void OnPlaybackStopped(MediaPlayer sender, object args)
{
PlayBarUI?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
if (Player?.PlaybackSession.PlaybackState == MediaPlaybackState.Paused && !lockable)
{
if (RepeatMode == 2)
{
PlaySongByPath(CurrentMusic.Path);
}
else
{
PlayNextSong();
}
}
});
}
/// <summary>
/// 播放
/// </summary>
private void Play()
{
Player?.Play();
}
/// <summary>
/// 暂停
/// </summary>
private void Pause()
{
Player?.Pause();
}
/// <summary>
/// 停止
/// </summary>
private void Stop()
{
Player?.Pause();
Current = TimeSpan.Zero;
CurrentPosition = 0;
positionUpdateTimer?.Cancel();
positionUpdateTimer = null;
}
/// <summary>
/// 播放上一曲
/// </summary>
public void PlayPreviousSong()
{
try
{
switch (ShuffleMode, RepeatMode)
{
case (false, 0):
{
if (PlayQueueIndex > 0)
{
PlaySongByPath(PlayQueue[PlayQueueIndex - 1].Path);
}
else
{
PlaySongByPath(PlayQueue[PlayQueueIndex].Path);
}
break;
}
case (false, 1):
{
{
PlaySongByPath(PlayQueue[(PlayQueueIndex + PlayQueueLength - 1) % PlayQueueLength].Path);
break;
}
}
case (false, 2):
{
if (PlayQueueIndex > 0)
{
PlaySongByPath(PlayQueue[PlayQueueIndex - 1].Path);
}
else
{
PlaySongByPath(PlayQueue[PlayQueueIndex].Path);
}
break;
}
case (true, 0):
{
if (PlayQueueIndex > 0)
{
PlaySongByPath(ShuffledPlayQueue[PlayQueueIndex - 1].Path);
}
else
{
PlaySongByPath(ShuffledPlayQueue[PlayQueueIndex].Path);
}
break;
}
case (true, 1):
{
{
PlaySongByPath(ShuffledPlayQueue[(PlayQueueIndex + PlayQueueLength - 1) % PlayQueueLength].Path);
break;
}
}
case (true, 2):
{
if (PlayQueueIndex > 0)
{
PlaySongByPath(ShuffledPlayQueue[PlayQueueIndex - 1].Path);
}
else
{
PlaySongByPath(ShuffledPlayQueue[PlayQueueIndex].Path);
}
break;
}
default:
break;
}
}
catch { }
}
/// <summary>
/// 播放下一曲
/// </summary>
public void PlayNextSong()
{
try
{
switch (ShuffleMode, RepeatMode)
{
case (false, 0):
{
if (PlayQueueIndex < PlayQueueLength - 1)
{
PlaySongByPath(PlayQueue[PlayQueueIndex + 1].Path);
}
else
{
PlaySongByPath(PlayQueue[0].Path, true);
}
break;
}
case (false, 1):
{
{
PlaySongByPath(PlayQueue[(PlayQueueIndex + 1) % PlayQueueLength].Path);
break;
}
}
case (false, 2):
{
if (PlayQueueIndex < PlayQueueLength - 1)
{
PlaySongByPath(PlayQueue[PlayQueueIndex + 1].Path);
}
else
{
PlaySongByPath(PlayQueue[0].Path, true);
}
break;
}
case (true, 0):
{
if (PlayQueueIndex < PlayQueueLength - 1)
{
PlaySongByPath(ShuffledPlayQueue[PlayQueueIndex + 1].Path);
}
else
{
PlaySongByPath(ShuffledPlayQueue[0].Path, true);
}
break;
}
case (true, 1):
{
{
PlaySongByPath(ShuffledPlayQueue[(PlayQueueIndex + 1) % PlayQueueLength].Path);
break;
}
}
case (true, 2):
{
if (PlayQueueIndex < PlayQueueLength - 1)
{
PlaySongByPath(ShuffledPlayQueue[PlayQueueIndex + 1].Path);
}
else
{
PlaySongByPath(ShuffledPlayQueue[0].Path, true);
}
break;
}
default:
break;
}
}
catch { }
}
/// <summary>
/// 播放按钮更新
/// </summary>
public void PlayPauseUpdate()
{
if (PlayState == 1 || PlayState == 2)
{
Pause();
}
else
{
Play();
}
}
/// <summary>
/// 随机播放模式更新
/// </summary>
public async void ShuffleModeUpdate()
{
ShuffleMode = !ShuffleMode;
if (ShuffleMode)
{
await UpdateShufflePlayQueue();
}
else
{
ShuffledPlayQueue.Clear();
PlayQueueIndex = PlayQueue.ToList().FindIndex(x => x.Path == CurrentMusic.Path);
}
OnPropertyChanged(nameof(ShuffleMode));
}
/// <summary>
/// 循环播放模式更新
/// </summary>
public void RepeatModeUpdate()
{
RepeatMode = (RepeatMode + 1) % 3;
}
/// <summary>
/// 随机播放队列更新
/// </summary>
/// <returns></returns>
public async Task UpdateShufflePlayQueue()
{
await Task.Run(() =>
{
ShuffledPlayQueue = new ObservableCollection<BriefMusicInfo>([.. PlayQueue.OrderBy(x => Guid.NewGuid())]);
PlayQueueIndex = ShuffledPlayQueue.ToList().FindIndex(x => x.Path == CurrentMusic.Path);
});
}
/// <summary>
/// 清空播放队列
/// </summary>
public void ClearPlayQueue()
{
Stop();
Total = TimeSpan.Zero;
PlayQueue.Clear();
ShuffledPlayQueue.Clear();
CurrentLyric.Clear();
PlayQueueName = "";
PlayQueueIndex = 0;
PlayQueueLength = 0;
if (Data.RootPlayBarViewModel != null)
{
Data.RootPlayBarViewModel.ButtonVisibility = PlayQueue.Any() ? Visibility.Visible : Visibility.Collapsed;
Data.RootPlayBarViewModel.Availability = PlayQueue.Any();
}
}
/// <summary>
/// 按下滑动条事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void ProgressLock(object sender, PointerRoutedEventArgs e)
{
lockable = true;
Current = TimeSpan.FromMilliseconds((double)((Slider)sender).Value * Total.TotalMilliseconds / 100);
}
/// <summary>
/// 滑动滑动条事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void SliderUpdate(object sender, PointerRoutedEventArgs e)
{
Current = TimeSpan.FromMilliseconds((double)((Slider)sender).Value * Total.TotalMilliseconds / 100);
}
/// <summary>
/// 松开滑动条更新播放进度
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void ProgressUpdate(object sender, PointerRoutedEventArgs e)
{
Player.PlaybackSession.Position = TimeSpan.FromMilliseconds((double)((Slider)sender).Value * Total.TotalMilliseconds / 100);
Current = Player.PlaybackSession?.Position ?? TimeSpan.Zero;
Total = Player.PlaybackSession?.NaturalDuration ?? TimeSpan.Zero;
CurrentPosition = 100 * (Current.TotalMilliseconds / Total.TotalMilliseconds);
CurrentLyricIndex = GetCurrentLyricIndex((Player.PlaybackSession?.Position ?? TimeSpan.Zero).TotalMilliseconds);
lockable = false;
}
/// <summary>
/// 点击歌词更新播放进度
/// </summary>
/// <param name="time"></param>
public void LyricProgressUpdate(double time)
{
lockable = true;
Player.PlaybackSession.Position = TimeSpan.FromMilliseconds(time);
Current = Player.PlaybackSession?.Position ?? TimeSpan.Zero;
Total = Player.PlaybackSession?.NaturalDuration ?? TimeSpan.Zero;
CurrentPosition = 100 * (Current.TotalMilliseconds / Total.TotalMilliseconds);
CurrentLyricIndex = GetCurrentLyricIndex((Player.PlaybackSession?.Position ?? TimeSpan.Zero).TotalMilliseconds);
lockable = false;
}
/// <summary>
/// 获取歌词字体大小
/// </summary>
/// <param name="itemTime"></param>
/// <param name="CurrentLyricIdx"></param>
/// <returns></returns>
public double GetLyricFont(double itemTime, int CurrentLyricIdx)
{
try
{
if (Data.MainWindow?.Width <= 1000)
{
if (itemTime == CurrentLyric[CurrentLyricIdx].Time)
{
return 24;
}
else
{
return 16;
}
}
else
{
if (itemTime == CurrentLyric[CurrentLyricIdx].Time)
{
return 50;
}
else
{
return 20;
}
}
}
catch
{
return 20;
}
}
/// <summary>
/// 获取歌词边距
/// </summary>
/// <param name="itemTime"></param>
/// <param name="CurrentLyricIdx"></param>
/// <returns></returns>
public Thickness GetLyricMargin(double itemTime, int CurrentLyricIdx)
{
try
{
if (itemTime == CurrentLyric[CurrentLyricIdx].Time)
{
return new Thickness(0, 40, 0, 40);
}
else
{
return new Thickness(0, 20, 0, 20);
}
}
catch
{
return new Thickness(0, 20, 0, 20);
}
}
/// <summary>
/// 获取歌词透明度
/// </summary>
/// <param name="itemTime"></param>
/// <param name="CurrentLyricIdx"></param>
/// <returns></returns>
public double GetLyricOpacity(double itemTime, int CurrentLyricIdx)
{
try
{
if (itemTime == CurrentLyric[CurrentLyricIdx].Time)
{
return 1;
}
else
{
return 0.5;
}
}
catch
{
return 0.5;
}
}
/// <summary>
/// 获取播放队列
/// </summary>
/// <param name="PlayQueueName"></param>
/// <param name="ShuffleMode"></param>
/// <returns></returns>
public ObservableCollection<BriefMusicInfo> GetPlayQueue(string PlayQueueName, bool ShuffleMode)
{
if (ShuffleMode)
{
return ShuffledPlayQueue;
}
else
{
return PlayQueue;
}
}
/// <summary>
/// 保存当前播放状态至设置存储
/// </summary>
public async void SaveCurrentStateAsync()
{
await _localSettingsService.SaveSettingAsync("NotFirstUsed", true);
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();
await _localSettingsService.SaveSettingAsync("ShuffledPlayQueuePaths", shuffledplayqueuepaths);
await _localSettingsService.SaveSettingAsync("PlayQueueIndex", PlayQueueIndex);*/
await _localSettingsService.SaveSettingAsync("ShuffleMode", ShuffleMode);
await _localSettingsService.SaveSettingAsync("RepeatMode", RepeatMode);
await _localSettingsService.SaveSettingAsync("CurrentVolume", CurrentVolume);
}
/// <summary>
/// 从设置存储中读取当前播放状态
/// </summary>
public async void LoadCurrentStateAsync()
{
var notFirstUsed = await _localSettingsService.ReadSettingAsync<bool>("NotFirstUsed");
var currentMusicPath = await _localSettingsService.ReadSettingAsync<string>("CurrentMusic");
if (!string.IsNullOrEmpty(currentMusicPath))
{
CurrentMusic = new DetailedMusicInfo(currentMusicPath);
}
/*var playqueuepaths = await _localSettingsService.ReadSettingAsync<List<string>>("PlayQueuePaths");
if (playqueuepaths != null)
{
PlayQueue.Clear();
foreach (var path in playqueuepaths)
{
if (!string.IsNullOrEmpty(path))
{
var musicInfo = new BriefMusicInfo(path);
PlayQueue.Add(musicInfo);
}
}
}
var shuffledplayqueuepaths = await _localSettingsService.ReadSettingAsync<List<string>>("ShuffledPlayQueuePaths");
if (shuffledplayqueuepaths != null)
{
ShuffledPlayQueue.Clear();
foreach (var path in shuffledplayqueuepaths)
{
if (!string.IsNullOrEmpty(path))
{
var musicInfo = new BriefMusicInfo(path);
ShuffledPlayQueue.Add(musicInfo);
}
}
}
PlayQueueIndex = await _localSettingsService.ReadSettingAsync<int>("PlayQueueIndex");*/
ShuffleMode = await _localSettingsService.ReadSettingAsync<bool>("ShuffleMode");
RepeatMode = await _localSettingsService.ReadSettingAsync<int>("RepeatMode");
if (notFirstUsed)
{
CurrentVolume = await _localSettingsService.ReadSettingAsync<double>("CurrentVolume");
}
else
{
CurrentVolume = 100;
}
/*if (Data.RootPlayBarViewModel != null)
{
Data.RootPlayBarViewModel.ButtonVisibility = PlayQueue.Any() ? Visibility.Visible : Visibility.Collapsed;
Data.RootPlayBarViewModel.Availability = PlayQueue.Any();
}*/
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<AppInstaller Uri="{AppInstallerUri}"
Version="{Version}"
xmlns="http://schemas.microsoft.com/appx/appinstaller/2017/2">
<!-- Read more about updating settings with Package.appinstaller at https://docs.microsoft.com/windows/msix/app-installer/update-settings -->
<MainBundle Name="{Name}"
Version="{Version}"
Publisher="{Publisher}"
Uri="{MainPackageUri}"/>
<UpdateSettings>
<OnLaunch HoursBetweenUpdateChecks="0" />
</UpdateSettings>
</AppInstaller>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:genTemplate="http://schemas.microsoft.com/appx/developer/templatestudio"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
IgnorableNamespaces="uap rescap genTemplate">
<Identity
Name="d3ac1dea-8f94-48ad-a56e-768e396dd9dd"
Publisher="CN=Admin"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="d3ac1dea-8f94-48ad-a56e-768e396dd9dd" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>The Untamed Music Player</DisplayName>
<PublisherDisplayName>Admin</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="ms-resource:AppDisplayName"
Description="ms-resource:AppDescription"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<desktop:Extension Category="windows.toastNotificationActivation">
<desktop:ToastNotificationActivation ToastActivatorCLSID="a76c0e5a-b343-4b7b-9c1e-01a6a0f1fc39" />
</desktop:Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="The Untamed Music Player.exe" Arguments="----AppNotificationActivated:" DisplayName="Toast activator">
<com:Class Id="a76c0e5a-b343-4b7b-9c1e-01a6a0f1fc39" DisplayName="Toast activator"/>
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
<genTemplate:Metadata>
<genTemplate:Item Name="generator" Value="Template Studio"/>
<genTemplate:Item Name="wizardVersion" Version="v5.5" />
<genTemplate:Item Name="projectType" Value="NavView" />
<genTemplate:Item Name="framework" Value="MVVMToolkit" />
<genTemplate:Item Name="platform" Value="WinUI" />
<genTemplate:Item Name="appmodel" Value="Desktop" />
</genTemplate:Metadata>
</Package>

View File

@@ -0,0 +1,10 @@
{
"profiles": {
"The Untamed Music Player (Package)": {
"commandName": "MsixPackage"
},
"The Untamed Music Player (Unpackaged)": {
"commandName": "Project"
}
}
}

View File

@@ -0,0 +1,27 @@
*Recommended Markdown Viewer: [Markdown Editor](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MarkdownEditor2)*
## Getting Started
Browse and address `TODO:` comments in `View -> Task List` to learn the codebase and understand next steps for turning the generated code into production code.
Explore the [WinUI Gallery](https://www.microsoft.com/store/productId/9P3JFPWWDZRC) to learn about available controls and design patterns.
Relaunch Template Studio to modify the project by right-clicking on the project in `View -> Solution Explorer` then selecting `Add -> New Item (Template Studio)`.
## Publishing
For projects with MSIX packaging, right-click on the application project and select `Package and Publish -> Create App Packages...` to create an MSIX package.
For projects without MSIX packaging, follow the [deployment guide](https://docs.microsoft.com/windows/apps/windows-app-sdk/deploy-unpackaged-apps) or add the `Self-Contained` Feature to enable xcopy deployment.
## CI Pipelines
See [README.md](https://github.com/microsoft/TemplateStudio/blob/main/docs/WinUI/pipelines/README.md) for guidance on building and testing projects in CI pipelines.
## Changelog
See [releases](https://github.com/microsoft/TemplateStudio/releases) and [milestones](https://github.com/microsoft/TemplateStudio/milestones).
## Feedback
Bugs and feature requests should be filed at https://aka.ms/templatestudio.

View File

@@ -0,0 +1,72 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Activation;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Views;
namespace The_Untamed_Music_Player.Services;
public class ActivationService : IActivationService
{
private readonly ActivationHandler<LaunchActivatedEventArgs> _defaultHandler;
private readonly IEnumerable<IActivationHandler> _activationHandlers;
private readonly IThemeSelectorService _themeSelectorService;
private UIElement? _shell = null;
public ActivationService(ActivationHandler<LaunchActivatedEventArgs> defaultHandler, IEnumerable<IActivationHandler> activationHandlers, IThemeSelectorService themeSelectorService)
{
_defaultHandler = defaultHandler;
_activationHandlers = activationHandlers;
_themeSelectorService = themeSelectorService;
}
public async Task ActivateAsync(object activationArgs)
{
// Execute tasks before activation.
await InitializeAsync();
// Set the MainWindow Content.
if (App.MainWindow.Content == null)
{
_shell = App.GetService<ShellPage>();
App.MainWindow.Content = _shell ?? new Frame();
}
// Handle activation via ActivationHandlers.
await HandleActivationAsync(activationArgs);
// Activate the MainWindow.
App.MainWindow.Activate();
// Execute tasks after activation.
await StartupAsync();
}
private async Task HandleActivationAsync(object activationArgs)
{
var activationHandler = _activationHandlers.FirstOrDefault(h => h.CanHandle(activationArgs));
if (activationHandler != null)
{
await activationHandler.HandleAsync(activationArgs);
}
if (_defaultHandler.CanHandle(activationArgs))
{
await _defaultHandler.HandleAsync(activationArgs);
}
}
private async Task InitializeAsync()
{
await _themeSelectorService.InitializeAsync().ConfigureAwait(false);
await Task.CompletedTask;
}
private async Task StartupAsync()
{
await _themeSelectorService.SetRequestedThemeAsync();
await Task.CompletedTask;
}
}

View File

@@ -0,0 +1,86 @@
using Microsoft.Extensions.Options;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Core.Contracts.Services;
using The_Untamed_Music_Player.Core.Helpers;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using Windows.Storage;
namespace The_Untamed_Music_Player.Services;
public class LocalSettingsService : ILocalSettingsService
{
private const string _defaultApplicationDataFolder = "The Untamed Music Player/ApplicationData";
private const string _defaultLocalSettingsFile = "LocalSettings.json";
private readonly IFileService _fileService;
private readonly LocalSettingsOptions _options;
private readonly string _localApplicationData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
private readonly string _applicationDataFolder;
private readonly string _localsettingsFile;
private IDictionary<string, object> _settings;
private bool _isInitialized;
public LocalSettingsService(IFileService fileService, IOptions<LocalSettingsOptions> options)
{
_fileService = fileService;
_options = options.Value;
_applicationDataFolder = Path.Combine(_localApplicationData, _options.ApplicationDataFolder ?? _defaultApplicationDataFolder);
_localsettingsFile = _options.LocalSettingsFile ?? _defaultLocalSettingsFile;
_settings = new Dictionary<string, object>();
}
private async Task InitializeAsync()
{
if (!_isInitialized)
{
_settings = await Task.Run(() => _fileService.Read<IDictionary<string, object>>(_applicationDataFolder, _localsettingsFile)) ?? new Dictionary<string, object>();
_isInitialized = true;
}
}
public async Task<T?> ReadSettingAsync<T>(string key)
{
if (RuntimeHelper.IsMSIX)
{
if (ApplicationData.Current.LocalSettings.Values.TryGetValue(key, out var obj))
{
return await Json.ToObjectAsync<T>((string)obj);
}
}
else
{
await InitializeAsync();
if (_settings != null && _settings.TryGetValue(key, out var obj))
{
return await Json.ToObjectAsync<T>((string)obj);
}
}
return default;
}
public async Task SaveSettingAsync<T>(string key, T value)
{
if (RuntimeHelper.IsMSIX)
{
ApplicationData.Current.LocalSettings.Values[key] = await Json.StringifyAsync(value);
}
else
{
await InitializeAsync();
_settings[key] = await Json.StringifyAsync(value);
await Task.Run(() => _fileService.Save(_applicationDataFolder, _localsettingsFile, _settings));
}
}
}

View File

@@ -0,0 +1,126 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Contracts.ViewModels;
using The_Untamed_Music_Player.Helpers;
namespace The_Untamed_Music_Player.Services;
// For more information on navigation between pages see
// https://github.com/microsoft/TemplateStudio/blob/main/docs/WinUI/navigation.md
public class NavigationService : INavigationService
{
private readonly IPageService _pageService;
private object? _lastParameterUsed;
private Frame? _frame;
public event NavigatedEventHandler? Navigated;
public Frame? Frame
{
get
{
if (_frame == null)
{
_frame = App.MainWindow.Content as Frame;
RegisterFrameEvents();
}
return _frame;
}
set
{
UnregisterFrameEvents();
_frame = value;
RegisterFrameEvents();
}
}
[MemberNotNullWhen(true, nameof(Frame), nameof(_frame))]
public bool CanGoBack => Frame != null && Frame.CanGoBack;
public NavigationService(IPageService pageService)
{
_pageService = pageService;
}
private void RegisterFrameEvents()
{
if (_frame != null)
{
_frame.Navigated += OnNavigated;
}
}
private void UnregisterFrameEvents()
{
if (_frame != null)
{
_frame.Navigated -= OnNavigated;
}
}
public bool GoBack()
{
if (CanGoBack)
{
var vmBeforeNavigation = _frame.GetPageViewModel();
_frame.GoBack();
if (vmBeforeNavigation is INavigationAware navigationAware)
{
navigationAware.OnNavigatedFrom();
}
return true;
}
return false;
}
public bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false)
{
var pageType = _pageService.GetPageType(pageKey);
if (_frame != null && (_frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(_lastParameterUsed))))
{
_frame.Tag = clearNavigation;
var vmBeforeNavigation = _frame.GetPageViewModel();
var navigated = _frame.Navigate(pageType, parameter);
if (navigated)
{
_lastParameterUsed = parameter;
if (vmBeforeNavigation is INavigationAware navigationAware)
{
navigationAware.OnNavigatedFrom();
}
}
return navigated;
}
return false;
}
private void OnNavigated(object sender, NavigationEventArgs e)
{
if (sender is Frame frame)
{
var clearNavigation = (bool)frame.Tag;
if (clearNavigation)
{
frame.BackStack.Clear();
}
if (frame.GetPageViewModel() is INavigationAware navigationAware)
{
navigationAware.OnNavigatedTo(e.Parameter);
}
Navigated?.Invoke(sender, e);
}
}
}

View File

@@ -0,0 +1,103 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Services;
public class NavigationViewService : INavigationViewService
{
private readonly INavigationService _navigationService;
private readonly IPageService _pageService;
private NavigationView? _navigationView;
public IList<object>? MenuItems => _navigationView?.MenuItems;
public object? SettingsItem => _navigationView?.SettingsItem;
public NavigationViewService(INavigationService navigationService, IPageService pageService)
{
_navigationService = navigationService;
_pageService = pageService;
}
[MemberNotNull(nameof(_navigationView))]
public void Initialize(NavigationView navigationView)
{
_navigationView = navigationView;
_navigationView.BackRequested += OnBackRequested;
_navigationView.ItemInvoked += OnItemInvoked;
}
public void UnregisterEvents()
{
if (_navigationView != null)
{
_navigationView.BackRequested -= OnBackRequested;
_navigationView.ItemInvoked -= OnItemInvoked;
}
}
public NavigationViewItem? GetSelectedItem(Type pageType)
{
if (_navigationView != null)
{
return GetSelectedItem(_navigationView.MenuItems, pageType) ?? GetSelectedItem(_navigationView.FooterMenuItems, pageType);
}
return null;
}
private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) => _navigationService.GoBack();
private void OnItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
_navigationService.NavigateTo(typeof(SettingsViewModel).FullName!);
}
else
{
var selectedItem = args.InvokedItemContainer as NavigationViewItem;
if (selectedItem?.GetValue(NavigationHelper.NavigateToProperty) is string pageKey)
{
_navigationService.NavigateTo(pageKey);
}
}
}
private NavigationViewItem? GetSelectedItem(IEnumerable<object> menuItems, Type pageType)
{
foreach (var item in menuItems.OfType<NavigationViewItem>())
{
if (IsMenuItemForPageType(item, pageType))
{
return item;
}
var selectedChild = GetSelectedItem(item.MenuItems, pageType);
if (selectedChild != null)
{
return selectedChild;
}
}
return null;
}
private bool IsMenuItemForPageType(NavigationViewItem menuItem, Type sourcePageType)
{
if (menuItem.GetValue(NavigationHelper.NavigateToProperty) is string pageKey)
{
return _pageService.GetPageType(pageKey) == sourcePageType;
}
return false;
}
}

View File

@@ -0,0 +1,59 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.ViewModels;
using The_Untamed_Music_Player.Views;
namespace The_Untamed_Music_Player.Services;
public class PageService : IPageService
{
private readonly Dictionary<string, Type> _pages = new();
public PageService()
{
Configure<ViewModel, Page>();
Configure<ViewModel, Page>();
Configure<ViewModel, Page>();
Configure<ViewModel, Page>();
Configure<SettingsViewModel, SettingsPage>();
}
public Type GetPageType(string key)
{
Type? pageType;
lock (_pages)
{
if (!_pages.TryGetValue(key, out pageType))
{
throw new ArgumentException($"Page not found: {key}. Did you forget to call PageService.Configure?");
}
}
return pageType;
}
private void Configure<VM, V>()
where VM : ObservableObject
where V : Page
{
lock (_pages)
{
var key = typeof(VM).FullName!;
if (_pages.ContainsKey(key))
{
throw new ArgumentException($"The key {key} is already configured in PageService");
}
var type = typeof(V);
if (_pages.ContainsValue(type))
{
throw new ArgumentException($"This type is already configured with key {_pages.First(p => p.Value == type).Key}");
}
_pages.Add(key, type);
}
}
}

View File

@@ -0,0 +1,63 @@
using Microsoft.UI.Xaml;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Helpers;
namespace The_Untamed_Music_Player.Services;
public class ThemeSelectorService : IThemeSelectorService
{
private const string SettingsKey = "AppBackgroundRequestedTheme";
public ElementTheme Theme { get; set; } = ElementTheme.Default;
private readonly ILocalSettingsService _localSettingsService;
public ThemeSelectorService(ILocalSettingsService localSettingsService)
{
_localSettingsService = localSettingsService;
}
public async Task InitializeAsync()
{
Theme = await LoadThemeFromSettingsAsync();
await Task.CompletedTask;
}
public async Task SetThemeAsync(ElementTheme theme)
{
Theme = theme;
await SetRequestedThemeAsync();
await SaveThemeInSettingsAsync(Theme);
}
public async Task SetRequestedThemeAsync()
{
if (App.MainWindow.Content is FrameworkElement rootElement)
{
rootElement.RequestedTheme = Theme;
TitleBarHelper.UpdateTitleBar(Theme);
}
await Task.CompletedTask;
}
private async Task<ElementTheme> LoadThemeFromSettingsAsync()
{
var themeName = await _localSettingsService.ReadSettingAsync<string>(SettingsKey);
if (Enum.TryParse(themeName, out ElementTheme cacheTheme))
{
return cacheTheme;
}
return ElementTheme.Default;
}
private async Task SaveThemeInSettingsAsync(ElementTheme theme)
{
await _localSettingsService.SaveSettingAsync(SettingsKey, theme.ToString());
}
}

View File

@@ -0,0 +1,339 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AppDisplayName" xml:space="preserve">
<value>Untamed Music Player</value>
</data>
<data name="AppDescription" xml:space="preserve">
<value>Untamed Music Player</value>
</data>
<data name="Shell_主页.Content" xml:space="preserve">
<value>Home</value>
</data>
<data name="Shell_音乐库.Content" xml:space="preserve">
<value>Music library</value>
</data>
<data name="Shell_播放队列.Content" xml:space="preserve">
<value>Play queue</value>
</data>
<data name="Shell_播放列表.Content" xml:space="preserve">
<value>Playlists</value>
</data>
<data name="Settings_Personalization.Text" xml:space="preserve">
<value>Personalization</value>
</data>
<data name="Settings_Theme.Header" xml:space="preserve">
<value>App theme</value>
</data>
<data name="Settings_Theme_Light.Content" xml:space="preserve">
<value>Light</value>
</data>
<data name="Settings_Theme_Dark.Content" xml:space="preserve">
<value>Dark</value>
</data>
<data name="Settings_Theme_Default.Content" xml:space="preserve">
<value>Use system setting</value>
</data>
<data name="Settings_About.Text" xml:space="preserve">
<value>About this application</value>
</data>
<data name="Settings_AboutDescription.Description" xml:space="preserve">
<value>© 2024 Untamed Corporation. All rights reserved.</value>
</data>
<data name="Settings_Library.Text" xml:space="preserve">
<value>Libraries</value>
</data>
<data name="Settings_MusicLibraryLocation.Header" xml:space="preserve">
<value>Music library locations</value>
</data>
<data name="Settings_AddFolder.Text" xml:space="preserve">
<value>Add folder</value>
</data>
<data name="Settings_RefreshLibrary.Header" xml:space="preserve">
<value>Refresh libraries</value>
</data>
<data name="Settings_Refresh.Content" xml:space="preserve">
<value>Refresh</value>
</data>
<data name="Settings_NoFolder.Text" xml:space="preserve">
<value>No folders are included in this library.</value>
</data>
<data name="Settings_ReIndex.Text" xml:space="preserve">
<value>If you're not seeing all the media from your library locations, you can re-index your libraries now.</value>
</data>
<data name="Shell_Search.PlaceholderText" xml:space="preserve">
<value>Search</value>
</data>
<data name="PlayBar_Property.Text" xml:space="preserve">
<value>Properties</value>
</data>
<data name="PlayBar_Equalizer.Text" xml:space="preserve">
<value>Equalizer</value>
</data>
<data name="PlayBar_Speed.Text" xml:space="preserve">
<value>Speed</value>
</data>
<data name="PlayBar_CastDevice.Text" xml:space="preserve">
<value>Cast to device</value>
</data>
<data name="PlayBar_SkipBack10s.Text" xml:space="preserve">
<value>Skip back 10 seconds</value>
</data>
<data name="PlayBar_SkipForw30s.Text" xml:space="preserve">
<value>Skip forward 30 seconds</value>
</data>
<data name="Shell_主页1.Text" xml:space="preserve">
<value>Home</value>
</data>
<data name="Shell_播放列表1.Text" xml:space="preserve">
<value>Playlists</value>
</data>
<data name="Shell_播放队列1.Text" xml:space="preserve">
<value>Play queue</value>
</data>
<data name="Shell_音乐库1.Text" xml:space="preserve">
<value>Music</value>
</data>
<data name="Shell_Settings.Text" xml:space="preserve">
<value>Settings</value>
</data>
<data name="无音乐_MusicNotFound.Text" xml:space="preserve">
<value>We couldn't find any music</value>
</data>
<data name="无音乐_NoMusicContent.Text" xml:space="preserve">
<value>Your music library doesn't contain any music content.</value>
</data>
<data name="有音乐_Songs.Text" xml:space="preserve">
<value>Songs</value>
</data>
<data name="有音乐_Albums.Text" xml:space="preserve">
<value>Albums</value>
</data>
<data name="有音乐_Artists.Text" xml:space="preserve">
<value>Artists</value>
</data>
<data name="Settings_Dialog1Title" xml:space="preserve">
<value>Revome this folder?</value>
</data>
<data name="Settings_Dialog1Content1" xml:space="preserve">
<value>If you remove the “</value>
</data>
<data name="Settings_Dialog1Content2" xml:space="preserve">
<value>“ folder from Music, it won't appear in Music anymore, but won't be deleted.</value>
</data>
<data name="Settings_Dialog1Primary" xml:space="preserve">
<value>Remove Folder</value>
</data>
<data name="Settings_Dialog1Close" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="Settings_SearchSetting.Content" xml:space="preserve">
<value>Manage system indexing settings</value>
</data>
<data name="Settings_Dialog2Title" xml:space="preserve">
<value>Did you mean to switch apps?</value>
</data>
<data name="Settings_Dialog2Content" xml:space="preserve">
<value> “Media Player” is trying to open “Settings”.</value>
</data>
<data name="Settings_Dialog2Primary" xml:space="preserve">
<value>Yes</value>
</data>
<data name="Settings_Dialog2Close" xml:space="preserve">
<value>No</value>
</data>
<data name="PlayBar_Volume.ToolTipService.ToolTip" xml:space="preserve">
<value>Volume</value>
</data>
<data name="PlayBar_FullScreen.ToolTipService.ToolTip" xml:space="preserve">
<value>FullScreen (F11)</value>
</data>
<data name="PlayBar_Mini.ToolTipService.ToolTip" xml:space="preserve">
<value>Mini player (Ctrl+M)</value>
</data>
<data name="PlayBar_More.ToolTipService.ToolTip" xml:space="preserve">
<value>More options</value>
</data>
<data name="PlayBar_More_025.ToolTipService.ToolTip" xml:space="preserve">
<value>Ctrl+Shift+G</value>
</data>
<data name="PlayBar_More_05.ToolTipService.ToolTip" xml:space="preserve">
<value>Ctrl+Shift+H</value>
</data>
<data name="PlayBar_More_1.ToolTipService.ToolTip" xml:space="preserve">
<value>Ctrl+Shift+J</value>
</data>
<data name="PlayBar_More_15.ToolTipService.ToolTip" xml:space="preserve">
<value>Ctrl+Shift+K</value>
</data>
<data name="PlayBar_More_2.ToolTipService.ToolTip" xml:space="preserve">
<value>Ctrl+Shift+L</value>
</data>
<data name="PlayBar_Prev.ToolTipService.ToolTip" xml:space="preserve">
<value>Previous (Ctrl+B)</value>
</data>
<data name="PlayBar_Next.ToolTipService.ToolTip" xml:space="preserve">
<value>Next (Ctrl+F)</value>
</data>
<data name="PlayBar_NowPlaying.ToolTipService.ToolTip" xml:space="preserve">
<value>NowPlaying (Ctrl+N)</value>
</data>
<data name="PlayBar_ShuffleOff" xml:space="preserve">
<value>Shuffle: Off (Ctrl+H)</value>
</data>
<data name="PlayBar_ShuffleOn" xml:space="preserve">
<value>Shuffle: On (Ctrl+H)</value>
</data>
<data name="PlayBar_RepeatOff" xml:space="preserve">
<value>Repeat: Off (Ctrl+T)</value>
</data>
<data name="PlayBar_RepeatAll" xml:space="preserve">
<value>Repeat: All (Ctrl+T)</value>
</data>
<data name="PlayBar_RepeatOne" xml:space="preserve">
<value>Repeat: One (Ctrl+T)</value>
</data>
<data name="Shell_主页.ToolTipService.ToolTip" xml:space="preserve">
<value>Home (Ctrl+Shift+F)</value>
</data>
<data name="Shell_音乐库.ToolTipService.ToolTip" xml:space="preserve">
<value>Music library (Ctrl+R)</value>
</data>
<data name="Shell_播放队列.ToolTipService.ToolTip" xml:space="preserve">
<value>Play queue (Ctrl+Q)</value>
</data>
<data name="Shell_播放列表.ToolTipService.ToolTip" xml:space="preserve">
<value>Playlists (Ctrl+Y)</value>
</data>
<data name="PlayBar_Play" xml:space="preserve">
<value>Play (Ctrl+P)</value>
</data>
<data name="PlayBar_Pause" xml:space="preserve">
<value>Pause (Ctrl+P)</value>
</data>
<data name="PlayBar_Seek.ToolTipService.ToolTip" xml:space="preserve">
<value>Seek</value>
</data>
<data name="Settings_RemoveFolder.ToolTipService.ToolTip" xml:space="preserve">
<value>Remove folder</value>
</data>
<data name="有音乐_AddFolder.ToolTipService.ToolTip" xml:space="preserve">
<value>Add a folder to your music library</value>
</data>
</root>

View File

@@ -0,0 +1,339 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AppDisplayName" xml:space="preserve">
<value>Untamed Music Player</value>
</data>
<data name="AppDescription" xml:space="preserve">
<value>Untamed Music Player</value>
</data>
<data name="Shell_主页.Content" xml:space="preserve">
<value>主页</value>
</data>
<data name="Shell_音乐库.Content" xml:space="preserve">
<value>音乐库</value>
</data>
<data name="Shell_播放队列.Content" xml:space="preserve">
<value>播放队列</value>
</data>
<data name="Shell_播放列表.Content" xml:space="preserve">
<value>播放列表</value>
</data>
<data name="Settings_Personalization.Text" xml:space="preserve">
<value>个性化</value>
</data>
<data name="Settings_Theme.Header" xml:space="preserve">
<value>应用主题</value>
</data>
<data name="Settings_Theme_Light.Content" xml:space="preserve">
<value>浅色</value>
</data>
<data name="Settings_Theme_Dark.Content" xml:space="preserve">
<value>深色</value>
</data>
<data name="Settings_Theme_Default.Content" xml:space="preserve">
<value>使用系统设置</value>
</data>
<data name="Settings_About.Text" xml:space="preserve">
<value>关于</value>
</data>
<data name="Settings_AboutDescription.Description" xml:space="preserve">
<value>© 2024 Untamed Corporation。保留所有权利。</value>
</data>
<data name="Settings_Library.Text" xml:space="preserve">
<value>库</value>
</data>
<data name="Settings_AddFolder.Text" xml:space="preserve">
<value>添加文件夹</value>
</data>
<data name="Settings_MusicLibraryLocation.Header" xml:space="preserve">
<value>音乐库位置</value>
</data>
<data name="Settings_NoFolder.Text" xml:space="preserve">
<value>此库中不包含任何文件夹</value>
</data>
<data name="Settings_Refresh.Content" xml:space="preserve">
<value>刷新</value>
</data>
<data name="Settings_RefreshLibrary.Header" xml:space="preserve">
<value>刷新库</value>
</data>
<data name="Settings_ReIndex.Text" xml:space="preserve">
<value>如果看不到库位置中的所有媒体,可以立即重新索引库。</value>
</data>
<data name="Shell_Search.PlaceholderText" xml:space="preserve">
<value>搜索</value>
</data>
<data name="PlayBar_Property.Text" xml:space="preserve">
<value>属性</value>
</data>
<data name="PlayBar_Equalizer.Text" xml:space="preserve">
<value>均衡器</value>
</data>
<data name="PlayBar_Speed.Text" xml:space="preserve">
<value>速度</value>
</data>
<data name="PlayBar_CastDevice.Text" xml:space="preserve">
<value>投放到设备</value>
</data>
<data name="PlayBar_SkipBack10s.Text" xml:space="preserve">
<value>快退10秒</value>
</data>
<data name="PlayBar_SkipForw30s.Text" xml:space="preserve">
<value>快进30秒</value>
</data>
<data name="Shell_主页1.Text" xml:space="preserve">
<value>主页</value>
</data>
<data name="Shell_播放列表1.Text" xml:space="preserve">
<value>播放列表</value>
</data>
<data name="Shell_播放队列1.Text" xml:space="preserve">
<value>播放队列</value>
</data>
<data name="Shell_音乐库1.Text" xml:space="preserve">
<value>音乐</value>
</data>
<data name="Shell_Settings.Text" xml:space="preserve">
<value>设置</value>
</data>
<data name="无音乐_MusicNotFound.Text" xml:space="preserve">
<value>找不到任何音乐</value>
</data>
<data name="无音乐_NoMusicContent.Text" xml:space="preserve">
<value>你的音乐库不包含任何音乐内容</value>
</data>
<data name="有音乐_Songs.Text" xml:space="preserve">
<value>歌曲</value>
</data>
<data name="有音乐_Albums.Text" xml:space="preserve">
<value>专辑</value>
</data>
<data name="有音乐_Artists.Text" xml:space="preserve">
<value>艺术家</value>
</data>
<data name="Settings_Dialog1Title" xml:space="preserve">
<value>删除此文件夹吗?</value>
</data>
<data name="Settings_Dialog1Content1" xml:space="preserve">
<value>如果将“</value>
</data>
<data name="Settings_Dialog1Content2" xml:space="preserve">
<value>”文件夹从音乐中移除,则该文件夹不会再出现在音乐中,但不会被删除。</value>
</data>
<data name="Settings_Dialog1Primary" xml:space="preserve">
<value>删除文件夹</value>
</data>
<data name="Settings_Dialog1Close" xml:space="preserve">
<value>取消</value>
</data>
<data name="Settings_SearchSetting.Content" xml:space="preserve">
<value>管理系统索引设置</value>
</data>
<data name="Settings_Dialog2Title" xml:space="preserve">
<value>你指的是要切换应用吗?</value>
</data>
<data name="Settings_Dialog2Content" xml:space="preserve">
<value>“媒体播放器”正在尝试打开“设置”。</value>
</data>
<data name="Settings_Dialog2Primary" xml:space="preserve">
<value>是</value>
</data>
<data name="Settings_Dialog2Close" xml:space="preserve">
<value>否</value>
</data>
<data name="PlayBar_Volume.ToolTipService.ToolTip" xml:space="preserve">
<value>音量</value>
</data>
<data name="PlayBar_FullScreen.ToolTipService.ToolTip" xml:space="preserve">
<value>全屏 (F11)</value>
</data>
<data name="PlayBar_Mini.ToolTipService.ToolTip" xml:space="preserve">
<value>最小播放器 (Ctrl+M)</value>
</data>
<data name="PlayBar_More.ToolTipService.ToolTip" xml:space="preserve">
<value>更多选项</value>
</data>
<data name="PlayBar_More_1.ToolTipService.ToolTip" xml:space="preserve">
<value>Ctrl+Shift+J</value>
</data>
<data name="PlayBar_More_025.ToolTipService.ToolTip" xml:space="preserve">
<value>Ctrl+Shift+G</value>
</data>
<data name="PlayBar_More_05.ToolTipService.ToolTip" xml:space="preserve">
<value>Ctrl+Shift+H</value>
</data>
<data name="PlayBar_More_15.ToolTipService.ToolTip" xml:space="preserve">
<value>Ctrl+Shift+K</value>
</data>
<data name="PlayBar_More_2.ToolTipService.ToolTip" xml:space="preserve">
<value>Ctrl+Shift+L</value>
</data>
<data name="PlayBar_Prev.ToolTipService.ToolTip" xml:space="preserve">
<value>上一个 (Ctrl+B)</value>
</data>
<data name="PlayBar_Next.ToolTipService.ToolTip" xml:space="preserve">
<value>下一个 (Ctrl+F)</value>
</data>
<data name="PlayBar_NowPlaying.ToolTipService.ToolTip" xml:space="preserve">
<value>正在播放 (Ctrl+N)</value>
</data>
<data name="PlayBar_ShuffleOff" xml:space="preserve">
<value>随机播放: 关 (Ctrl+H)</value>
</data>
<data name="PlayBar_ShuffleOn" xml:space="preserve">
<value>随机播放: 开 (Ctrl+H)</value>
</data>
<data name="PlayBar_RepeatOff" xml:space="preserve">
<value>重复: 关 (Ctrl+T)</value>
</data>
<data name="PlayBar_RepeatAll" xml:space="preserve">
<value>重复: 全部 (Ctrl+T)</value>
</data>
<data name="PlayBar_RepeatOne" xml:space="preserve">
<value>重复: 单曲 (Ctrl+T)</value>
</data>
<data name="Shell_主页.ToolTipService.ToolTip" xml:space="preserve">
<value>主页 (Ctrl+Shift+F)</value>
</data>
<data name="Shell_音乐库.ToolTipService.ToolTip" xml:space="preserve">
<value>音乐库 (Ctrl+R)</value>
</data>
<data name="Shell_播放队列.ToolTipService.ToolTip" xml:space="preserve">
<value>播放队列 (Ctrl+Q)</value>
</data>
<data name="Shell_播放列表.ToolTipService.ToolTip" xml:space="preserve">
<value>播放列表 (Ctrl+Y)</value>
</data>
<data name="PlayBar_Play" xml:space="preserve">
<value>播放 (Ctrl+P)</value>
</data>
<data name="PlayBar_Pause" xml:space="preserve">
<value>暂停 (Ctrl+P)</value>
</data>
<data name="PlayBar_Seek.ToolTipService.ToolTip" xml:space="preserve">
<value>定位</value>
</data>
<data name="Settings_RemoveFolder.ToolTipService.ToolTip" xml:space="preserve">
<value>删除文件夹</value>
</data>
<data name="有音乐_AddFolder.ToolTipService.ToolTip" xml:space="preserve">
<value>将文件夹添加到音乐库</value>
</data>
</root>

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="PlayButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource AppBarButtonBackground}"/>
<Setter Property="BorderBrush" Value="{ThemeResource AppBarButtonBorderBrush}"/>
<Setter Property="CornerRadius" Value="5"/>
<Setter Property="Foreground" Value="{ThemeResource AppBarButtonForeground}"/>
<Setter Property="Height" Value="37"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="Root"
MinWidth="{TemplateBinding MinWidth}"
MaxWidth="{TemplateBinding MaxWidth}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="Root.Background" Value="{ThemeResource AppBarButtonBackgroundPointerOver}"/>
<Setter Target="Root.BorderBrush" Value="{ThemeResource AppBarButtonBorderBrushPointerOver}"/>
<Setter Target="Content.Foreground" Value="{ThemeResource AppBarButtonForegroundPointerOver}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="Root.Background" Value="{ThemeResource AppBarButtonBackgroundPressed}"/>
<Setter Target="Root.BorderBrush" Value="{ThemeResource AppBarButtonBorderBrushPressed}"/>
<Setter Target="Content.Foreground" Value="{ThemeResource AppBarButtonForegroundPressed}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="Root.Background" Value="{ThemeResource AppBarButtonBackgroundDisabled}"/>
<Setter Target="Root.BorderBrush" Value="{ThemeResource AppBarButtonBorderBrushDisabled}"/>
<Setter Target="Content.Foreground" Value="{ThemeResource AppBarButtonForegroundDisabled}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="Content"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Width" Value="37"/>
</Style>
<Style x:Key="CoverButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource AppBarButtonBackground}"/>
<Setter Property="BorderBrush" Value="{ThemeResource AppBarButtonBorderBrush}"/>
<Setter Property="CornerRadius" Value="5"/>
<Setter Property="Foreground" Value="{ThemeResource AppBarButtonForeground}"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="Root"
MinWidth="{TemplateBinding MinWidth}"
MaxWidth="{TemplateBinding MaxWidth}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="Root.Background" Value="{ThemeResource AppBarButtonBackgroundPointerOver}"/>
<Setter Target="Root.BorderBrush" Value="{ThemeResource AppBarButtonBorderBrushPointerOver}"/>
<Setter Target="Content.Foreground" Value="{ThemeResource AppBarButtonForegroundPointerOver}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="Root.Background" Value="{ThemeResource AppBarButtonBackgroundPressed}"/>
<Setter Target="Root.BorderBrush" Value="{ThemeResource AppBarButtonBorderBrushPressed}"/>
<Setter Target="Content.Foreground" Value="{ThemeResource AppBarButtonForegroundPressed}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="Root.Background" Value="{ThemeResource AppBarButtonBackgroundDisabled}"/>
<Setter Target="Root.BorderBrush" Value="{ThemeResource AppBarButtonBorderBrushDisabled}"/>
<Setter Target="Content.Foreground" Value="{ThemeResource AppBarButtonForegroundDisabled}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="Content"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SmallPlayButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource AppBarButtonBackground}"/>
<Setter Property="BorderBrush" Value="{ThemeResource AppBarButtonBorderBrush}"/>
<Setter Property="CornerRadius" Value="5"/>
<Setter Property="Foreground" Value="{ThemeResource AppBarButtonForeground}"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="Root"
MinWidth="{TemplateBinding MinWidth}"
MaxWidth="{TemplateBinding MaxWidth}"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="Root.Background" Value="{ThemeResource AppBarButtonBackgroundPointerOver}"/>
<Setter Target="Root.BorderBrush" Value="{ThemeResource AppBarButtonBorderBrushPointerOver}"/>
<Setter Target="Content.Foreground" Value="{ThemeResource AppBarButtonForegroundPointerOver}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="Root.Background" Value="{ThemeResource AppBarButtonBackgroundPressed}"/>
<Setter Target="Root.BorderBrush" Value="{ThemeResource AppBarButtonBorderBrushPressed}"/>
<Setter Target="Content.Foreground" Value="{ThemeResource AppBarButtonForegroundPressed}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="Root.Background" Value="{ThemeResource AppBarButtonBackgroundDisabled}"/>
<Setter Target="Root.BorderBrush" Value="{ThemeResource AppBarButtonBorderBrushDisabled}"/>
<Setter Target="Content.Foreground" Value="{ThemeResource AppBarButtonForegroundDisabled}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="Content"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,9 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<x:Double x:Key="LargeFontSize">24</x:Double>
<x:Double x:Key="MediumFontSize">16</x:Double>
</ResourceDictionary>

View File

@@ -0,0 +1,20 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="PageTitleStyle" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontWeight" Value="SemiLight" />
<Setter Property="FontSize" Value="{StaticResource LargeFontSize}" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
<Setter Property="TextWrapping" Value="NoWrap" />
</Style>
<Style x:Key="BodyTextStyle" TargetType="TextBlock">
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{StaticResource MediumFontSize}" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,36 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Thickness x:Key="LargeTopMargin">0,36,0,0</Thickness>
<Thickness x:Key="LargeTopBottomMargin">0,36,0,36</Thickness>
<Thickness x:Key="MediumTopMargin">0,24,0,0</Thickness>
<Thickness x:Key="MediumTopBottomMargin">0,24,0,24</Thickness>
<Thickness x:Key="MediumLeftRightMargin">24,0,24,0</Thickness>
<Thickness x:Key="MediumBottomMargin">0,0,0,24</Thickness>
<Thickness x:Key="SmallLeftMargin">12,0,0,0</Thickness>
<Thickness x:Key="SmallLeftRightMargin">12,0,12,0</Thickness>
<Thickness x:Key="SmallTopMargin">0,12,0,0</Thickness>
<Thickness x:Key="SmallRightMargin">0,0,12,0</Thickness>
<Thickness x:Key="SmallTopBottomMargin">0,12,0,12</Thickness>
<Thickness x:Key="XSmallLeftMargin">8,0,0,0</Thickness>
<Thickness x:Key="XSmallTopMargin">0,8,0,0</Thickness>
<Thickness x:Key="XSmallLeftTopRightBottomMargin">8,8,8,8</Thickness>
<Thickness x:Key="XXSmallTopMargin">0,4,0,0</Thickness>
<Thickness x:Key="XXSmallLeftTopRightBottomMargin">4,4,4,4</Thickness>
<Thickness x:Key="NavigationViewContentGridBorderThickness">1,1,0,0</Thickness>
<CornerRadius x:Key="NavigationViewContentGridCornerRadius">8,0,0,0</CornerRadius>
<Thickness x:Key="NavigationViewContentMargin">0,48,0,0</Thickness>
<Thickness x:Key="NavigationViewHeaderMargin">56,34,0,0</Thickness>
<Thickness x:Key="NavigationViewPageContentMargin">56,24,56,0</Thickness>
<Thickness x:Key="MenuBarContentMargin">36,24,36,0</Thickness>
<Thickness x:Key="SettingsPageHyperlinkButtonMargin">-12,4,0,0</Thickness>
</ResourceDictionary>

View File

@@ -0,0 +1,11 @@
<xml
xmlns:genTemplate="http://schemas.microsoft.com/appx/developer/templatestudio">
<genTemplate:Metadata>
<genTemplate:Item Name="generator" Value="Template Studio"/>
<genTemplate:Item Name="wizardVersion" Version="v5.5" />
<genTemplate:Item Name="projectType" Value="NavView" />
<genTemplate:Item Name="framework" Value="MVVMToolkit" />
<genTemplate:Item Name="platform" Value="WinUI" />
<genTemplate:Item Name="appmodel" Value="Desktop" />
</genTemplate:Metadata>
</xml>

View File

@@ -0,0 +1,113 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
<TargetPlatformMinVersion>10.0.22621.0</TargetPlatformMinVersion>
<RootNamespace>The_Untamed_Music_Player</RootNamespace>
<ApplicationIcon>Assets/WindowIcon.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;arm64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<PublishProfile>Properties\PublishProfiles\win10-$(Platform).pubxml</PublishProfile>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWinUI>true</UseWinUI>
<EnableMsixTooling>true</EnableMsixTooling>
<SupportedOSPlatformVersion>10.0.22621.0</SupportedOSPlatformVersion>
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Content Remove="Assets\WindowIcon.png" />
</ItemGroup>
<ItemGroup>
<None Remove="Styles\ButtonStyle.xaml" />
<None Remove="Views\专辑Page.xaml" />
<None Remove="Views\无音乐Page.xaml" />
<None Remove="Views\有音乐Page.xaml" />
<None Remove="Views\歌曲Page.xaml" />
<None Remove="Views\歌词Page.xaml" />
<None Remove="Views\艺术家Page.xaml" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.1.240801-pull-458.1110" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.2.0" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240802000" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageReference Include="System.Drawing.Common" Version="8.0.8" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="WinUIEx" Version="2.3.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\The Untamed Music Player.Core\The Untamed Music Player.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Page Update="Styles\ButtonStyle.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\艺术家Page.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\专辑Page.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\歌曲Page.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\有音乐Page.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\无音乐Page.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\歌词Page.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|arm64'">
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|arm64'">
<DebugType>none</DebugType>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1 @@
global using WinUIEx;

View File

@@ -0,0 +1,248 @@
using System.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Animation;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Views;
namespace The_Untamed_Music_Player.ViewModels;
public partial class RootPlayBarViewModel : INotifyPropertyChanged
{
public static RootPlayBarView? RootPlayBarView;
private Visibility _buttonVisibility = Visibility.Collapsed;
public Visibility ButtonVisibility
{
get => _buttonVisibility;
set
{
_buttonVisibility = value;
OnPropertyChanged(nameof(ButtonVisibility));
}
}
private bool _availability = false;
public bool Availability
{
get => _availability;
set
{
_availability = value;
OnPropertyChanged(nameof(Availability));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public RootPlayBarViewModel()
{
Data.RootPlayBarViewModel = this;
RootPlayBarView?.GetProgressSlider().AddHandler(UIElement.PointerPressedEvent, new PointerEventHandler(Data.MusicPlayer.ProgressLock), true);
RootPlayBarView?.GetProgressSlider().AddHandler(UIElement.PointerMovedEvent, new PointerEventHandler(Data.MusicPlayer.SliderUpdate), true);
RootPlayBarView?.GetProgressSlider().AddHandler(UIElement.PointerReleasedEvent, new PointerEventHandler(Data.MusicPlayer.ProgressUpdate), true);
}
public string GetCurrent(TimeSpan current)
{
if (current.Hours > 0)
{
return $"{current:hh\\:mm\\:ss}";
}
else
{
return $"0:{current:mm\\:ss}";
}
}
public string GetRemaining(TimeSpan current, TimeSpan total)
{
var remaining = total - current;
if (remaining.Hours > 0)
{
return $"{remaining:hh\\:mm\\:ss}";
}
else
{
return $"0:{remaining:mm\\:ss}";
}
}
public string GetPlayPauseIcon(int playstate)
{
return playstate switch
{
0 => "\uF5B0",
1 => "\uF8AE",
2 => "\uF5B0",
_ => "\uF5B0",
};
}
public Visibility GetSliderVisibility(int playstate)
{
return playstate switch
{
0 => Visibility.Visible,
1 => Visibility.Visible,
2 => Visibility.Collapsed,
_ => Visibility.Visible,
};
}
public Visibility GetProgressVisibility(int playstate)
{
return playstate switch
{
0 => Visibility.Collapsed,
1 => Visibility.Collapsed,
2 => Visibility.Visible,
_ => Visibility.Collapsed,
};
}
public Visibility GetArtistAndAlbumStrVisibility(DetailedMusicInfo detailedmusicinfo)
{
return detailedmusicinfo.ArtistAndAlbumStr == "" ? Visibility.Collapsed : Visibility.Visible;
}
public Visibility GetShuffleModeIcon(bool shufflemode)
{
return shufflemode ? Visibility.Collapsed : Visibility.Visible;
}
public string GetRepeatModeIcon(int repeatmode)
{
return repeatmode switch
{
0 => "\uF5E7",
1 => "\uE8EE",
2 => "\uE8ED",
_ => "\uF5E7",
};
}
public string GetVolumeIcon(double volume)
{
if (volume >= 67)
{
return "\uE995";
}
else if (volume >= 34)
{
return "\uE994";
}
else if (volume >= 1)
{
return "\uE993";
}
else
{
return "\uE74F";
}
}
public void CoverBtnClickToDetail(object sender, RoutedEventArgs e)
{
if (!Data.IsDetail)
{
if (Data.MainWindow != null)
{
var newPage = new Page();
var frame = Data.MainWindow.GetShellFrame();
// 创建渐变动画
var fadeOutAnimation = new DoubleAnimation
{
From = 1.0,
To = 0,
Duration = new Duration(TimeSpan.FromSeconds(0.1))
};
var fadeInAnimation = new DoubleAnimation
{
From = 0,
To = 1.0,
Duration = new Duration(TimeSpan.FromSeconds(0.2))
};
var fadeOutStoryboard = new Storyboard();
fadeOutStoryboard.Children.Add(fadeOutAnimation);
Storyboard.SetTarget(fadeOutAnimation, frame);
Storyboard.SetTargetProperty(fadeOutAnimation, "Opacity");
var fadeInStoryboard = new Storyboard();
fadeInStoryboard.Children.Add(fadeInAnimation);
Storyboard.SetTarget(fadeInAnimation, frame);
Storyboard.SetTargetProperty(fadeInAnimation, "Opacity");
// 动画完成后设置新内容
fadeOutAnimation.Completed += (s, a) =>
{
fadeInStoryboard.Begin();
frame.Content = newPage;
};
fadeOutStoryboard.Begin();
if (RootPlayBarView != null)
{
RootPlayBarView.GetCoverBorder().Visibility = Visibility.Collapsed;
}
Data.IsDetail = true;
}
}
else
{
if (Data.MainWindow != null)
{
var mainPage = Data.ShellPage;
var frame = Data.MainWindow.GetShellFrame();
// 创建渐变动画
var fadeOutAnimation = new DoubleAnimation
{
From = 1.0,
To = 0,
Duration = new Duration(TimeSpan.FromSeconds(0.1))
};
var fadeInAnimation = new DoubleAnimation
{
From = 0,
To = 1.0,
Duration = new Duration(TimeSpan.FromSeconds(0.2))
};
var fadeOutStoryboard = new Storyboard();
fadeOutStoryboard.Children.Add(fadeOutAnimation);
Storyboard.SetTarget(fadeOutAnimation, frame);
Storyboard.SetTargetProperty(fadeOutAnimation, "Opacity");
var fadeInStoryboard = new Storyboard();
fadeInStoryboard.Children.Add(fadeInAnimation);
Storyboard.SetTarget(fadeInAnimation, frame);
Storyboard.SetTargetProperty(fadeInAnimation, "Opacity");
// 动画完成后设置新内容
fadeOutAnimation.Completed += (s, a) =>
{
fadeInStoryboard.Begin();
frame.Content = mainPage;
};
fadeOutStoryboard.Begin();
if (RootPlayBarView != null)
{
RootPlayBarView.GetCoverBorder().Visibility = Visibility.Visible;
}
Data.IsDetail = false;
GC.Collect();
}
}
}
}

View File

@@ -0,0 +1,390 @@
using System.ComponentModel;
using System.Reflection;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.Windows.ApplicationModel.Resources;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Storage.Pickers;
namespace The_Untamed_Music_Player.ViewModels;
public partial class SettingsViewModel : ObservableRecipient, INotifyPropertyChanged
{
private readonly IThemeSelectorService _themeSelectorService;
private readonly ILocalSettingsService _localSettingsService;
private static readonly ResourceLoader _resourceLoader = new();
public new event PropertyChangedEventHandler? PropertyChanged;
protected new void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
[ObservableProperty]
public ElementTheme _elementTheme;
[ObservableProperty]
private string _versionDescription;
public ICommand SwitchThemeCommand
{
get;
}
private bool _isProgressRingActive;
public bool IsProgressRingActive
{
get => _isProgressRingActive;
set
{
SetProperty(ref _isProgressRingActive, value);
OnPropertyChanged(nameof(IsProgressRingActive));
}
}
private bool _isRefreshButtonEnabled = true;
public bool IsRefreshButtonEnabled
{
get => _isRefreshButtonEnabled;
set
{
SetProperty(ref _isRefreshButtonEnabled, value);
OnPropertyChanged(nameof(IsRefreshButtonEnabled));
}
}
public Visibility EmptyFolderMessageVisibility => Data.MusicLibrary.Folders?.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
private List<string> _fonts = [];
public List<string> Fonts
{
get => _fonts;
set => _fonts = value;
}
private FontFamily _selectedFont = new("Microsoft YaHei");
public FontFamily SelectedFont
{
get => _selectedFont;
set
{
_selectedFont = value;
OnPropertyChanged(nameof(SelectedFont));
SaveSelectedFontAsync(value.Source); // 保存字体设置
}
}
private List<string> _materials =
[
"None",
"Mica",
"Mica Alt",
"Desktop Acrylic",
"Acrylic Base",
"Acrylic Thin",
"Blur",
"Transparent",
"Animated"
];
public List<string> Materials
{
get => _materials;
set => _materials = value;
}
private string _selectedMaterial = Data.MainWindow?.SelectedMaterial ?? "";
public string SelectedMaterial
{
get => _selectedMaterial;
set
{
_selectedMaterial = value;
OnPropertyChanged(nameof(SelectedMaterial));
ChangeMaterial(SelectedMaterial);
SaveSelectedMaterialAsync(value);
}
}
private bool _isLyricBackgroundVisible;
public bool IsLyricBackgroundVisible
{
get => _isLyricBackgroundVisible;
set
{
_isLyricBackgroundVisible = value;
OnPropertyChanged(nameof(IsLyricBackgroundVisible));
SaveLyricBackgroundVisibilityAsync(value);
}
}
public SettingsViewModel(IThemeSelectorService themeSelectorService, ILocalSettingsService localSettingsService)
{
_themeSelectorService = themeSelectorService;
_localSettingsService = localSettingsService;
_elementTheme = _themeSelectorService.Theme;
_versionDescription = GetVersionDescription();
SwitchThemeCommand = new RelayCommand<ElementTheme>(
async (param) =>
{
if (ElementTheme != param)
{
ElementTheme = param;
await _themeSelectorService.SetThemeAsync(param);
}
});
LoadFonts();
LoadSelectedFontAsync();
LoadLyricBackgroundVisibilityAsync();
Data.SettingsViewModel = this;
}
public async void PickMusicFolderButton_Click(object sender, RoutedEventArgs e)
{
var openPicker = new FolderPicker();
var window = App.MainWindow;
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
WinRT.Interop.InitializeWithWindow.Initialize(openPicker, hWnd);
openPicker.SuggestedStartLocation = PickerLocationId.Desktop;
openPicker.FileTypeFilter.Add("*");
var folder = await openPicker.PickSingleFolderAsync();
if (folder != null)
{
if (!Data.MusicLibrary.Folders.Any(f => f.Path == folder.Path))
{
Data.MusicLibrary.Folders.Add(folder);
OnPropertyChanged(nameof(EmptyFolderMessageVisibility));
await SaveFoldersAsync();
await Data.MusicLibrary.LoadLibraryAgain(); // 重新加载音乐库
}
}
}
public void RemoveMusicFolderButtonClick(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is StorageFolder folder)
{
RemoveMusicFolder(folder);
}
}
private async void RemoveMusicFolder(StorageFolder folder)
{
Data.MusicLibrary.Folders?.Remove(folder);
OnPropertyChanged(nameof(EmptyFolderMessageVisibility));
await SaveFoldersAsync();
await Data.MusicLibrary.LoadLibraryAgain();
}
public async void RefreshButton_Click(object sender, RoutedEventArgs e)
{
IsProgressRingActive = true;
IsRefreshButtonEnabled = false;
await Data.MusicLibrary.LoadLibraryAgain();
IsProgressRingActive = false;
IsRefreshButtonEnabled = true;
}
public void LoadFonts()
{
var fontFamilies = Microsoft.Graphics.Canvas.Text.CanvasTextFormat.GetSystemFontFamilies();
Fonts = [.. fontFamilies.OrderBy(f => f)];
}
public void FontComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0 && e.AddedItems[0] is string selectedFont)
{
SelectedFont = new FontFamily(selectedFont);
}
}
public void MaterialComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0 && e.AddedItems[0] is string selectedMaterial)
{
SelectedMaterial = selectedMaterial;
if (Data.MainWindow != null)
{
Data.MainWindow.SelectedMaterial = selectedMaterial;
}
}
}
public void ChangeMaterial(string material)
{
if (Data.MainWindow?.SelectedMaterial == SelectedMaterial)
{
return;
}
try
{
switch (material)
{
case "None":
Data.MainWindow?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () => Data.MainWindow?.TrySetNoneBackdrop());
break;
case "Mica":
Data.MainWindow?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
Data.MainWindow?.TrySetNoneBackdrop();
Data.MainWindow?.TrySetMicaBackdrop(false);
});
break;
case "Mica Alt":
Data.MainWindow?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
Data.MainWindow?.TrySetNoneBackdrop();
Data.MainWindow?.TrySetMicaBackdrop(true);
});
break;
case "Desktop Acrylic":
Data.MainWindow?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
Data.MainWindow?.TrySetNoneBackdrop();
Data.MainWindow?.TrySetDesktopAcrylicBackdrop();
});
break;
case "Acrylic Base":
Data.MainWindow?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
Data.MainWindow?.TrySetNoneBackdrop();
Data.MainWindow?.TrySetAcrylicBackdrop(false);
});
break;
case "Acrylic Thin":
Data.MainWindow?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
Data.MainWindow?.TrySetNoneBackdrop();
Data.MainWindow?.TrySetAcrylicBackdrop(true);
});
break;
case "Blur":
Data.MainWindow?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
Data.MainWindow?.TrySetNoneBackdrop();
Data.MainWindow?.TrySetBlurBackdrop();
});
break;
case "Transparent":
Data.MainWindow?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
Data.MainWindow?.TrySetNoneBackdrop();
Data.MainWindow?.TrySetTransparentBackdrop();
});
break;
case "Animated":
Data.MainWindow?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
Data.MainWindow?.TrySetNoneBackdrop();
Data.MainWindow?.TrySetAnimatedBackdrop();
});
break;
default:
break;
}
}
catch { }
}
private static string GetVersionDescription()
{
Version version;
if (RuntimeHelper.IsMSIX)
{
var packageVersion = Package.Current.Id.Version;
version = new(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision);
}
else
{
version = Assembly.GetExecutingAssembly().GetName().Version!;
}
return $"{"AppDisplayName".GetLocalized()} - {version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
private static async Task SaveFoldersAsync()
{
var folderPaths = Data.MusicLibrary.Folders?.Select(f => f.Path).ToList();//Folders.Select(f => f.Path):使用 LINQ 查询从 Folders 集合中提取每个 StorageFolder 的路径Path 属性)。.ToList():将提取的路径集合转换为 List<string>。
await ApplicationData.Current.LocalFolder.SaveAsync("MusicFolders", folderPaths);// ApplicationData.Current.LocalFolder获取应用程序的本地存储文件夹。SaveAsync("MusicFolders", folderPaths):调用 SettingsStorageExtensions 类中的扩展方法 SaveAsync将 folderPaths 列表保存到名为 "MusicFolders" 的文件中。
}
private async void LoadSelectedFontAsync()
{
var fontName = await _localSettingsService.ReadSettingAsync<string>("SelectedFont");
if (!string.IsNullOrEmpty(fontName))
{
SelectedFont = new FontFamily(fontName);
}
}
private async void LoadLyricBackgroundVisibilityAsync()
{
var isLyricBackgroundVisible = await _localSettingsService.ReadSettingAsync<bool>("IsLyricBackgroundVisible");
IsLyricBackgroundVisible = isLyricBackgroundVisible;
}
public void FontComboBox_Loaded(object sender, RoutedEventArgs e)
{
if (sender is ComboBox comboBox)
{
var selectedFontName = SelectedFont.Source;
var index = Fonts.IndexOf(selectedFontName);
if (index >= 0)
{
comboBox.SelectedIndex = index;
}
}
}
public void MaterialComboBox_Loaded(object sender, RoutedEventArgs e)
{
if (sender is ComboBox comboBox)
{
var index = Materials.IndexOf(SelectedMaterial);
if (index >= 0)
{
comboBox.SelectedIndex = index;
}
}
}
public void LyricBackgroundCheckBox_Loaded(object sender, RoutedEventArgs e)
{
if (sender is ToggleSwitch toggleSwitch)
{
toggleSwitch.IsOn = IsLyricBackgroundVisible;
}
}
private async void SaveSelectedFontAsync(string fontName)
{
await _localSettingsService.SaveSettingAsync("SelectedFont", fontName);
}
private async void SaveSelectedMaterialAsync(string material)
{
await _localSettingsService.SaveSettingAsync("SelectedMaterial", material);
}
private async void SaveLyricBackgroundVisibilityAsync(bool isLyricBackgroundVisible)
{
await _localSettingsService.SaveSettingAsync("IsLyricBackgroundVisible", isLyricBackgroundVisible);
}
}

View File

@@ -0,0 +1,51 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml.Navigation;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Views;
namespace The_Untamed_Music_Player.ViewModels;
public partial class ShellViewModel : ObservableRecipient
{
[ObservableProperty]
private bool isBackEnabled;
[ObservableProperty]
private object? selected;
public INavigationService NavigationService
{
get;
}
public INavigationViewService NavigationViewService
{
get;
}
public ShellViewModel(INavigationService navigationService, INavigationViewService navigationViewService)
{
NavigationService = navigationService;
NavigationService.Navigated += OnNavigated;
NavigationViewService = navigationViewService;
}
private void OnNavigated(object sender, NavigationEventArgs e)
{
IsBackEnabled = NavigationService.CanGoBack;
if (e.SourcePageType == typeof(SettingsPage))
{
Selected = NavigationViewService.SettingsItem;
return;
}
var selectedItem = NavigationViewService.GetSelectedItem(e.SourcePageType);
if (selectedItem != null)
{
Selected = selectedItem;
}
}
}

View File

@@ -0,0 +1,8 @@
namespace The_Untamed_Music_Player.ViewModels;
public class ViewModel
{
public ViewModel()
{
}
}

View File

@@ -0,0 +1,10 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace The_Untamed_Music_Player.ViewModels;
public partial class ViewModel : ObservableRecipient
{
public ViewModel()
{
}
}

View File

@@ -0,0 +1,10 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace The_Untamed_Music_Player.ViewModels;
public partial class ViewModel : ObservableRecipient
{
public ViewModel()
{
}
}

View File

@@ -0,0 +1,75 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using The_Untamed_Music_Player.Models;
namespace The_Untamed_Music_Player.ViewModels;
public partial class ViewModel : ObservableRecipient
{
public ViewModel()
{
}
public 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;
var fontIcon = grid?.FindName("MusicFontIcon") as FontIcon;
if (checkBox != null)
{
checkBox.Visibility = Visibility.Visible;
}
if (playButton != null)
{
playButton.Visibility = Visibility.Visible;
}
if (fontIcon != null)
{
fontIcon.Visibility = Visibility.Collapsed;
}
}
public 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;
var fontIcon = grid?.FindName("MusicFontIcon") as FontIcon;
if (checkBox != null)
{
checkBox.Visibility = Visibility.Collapsed;
}
if (playButton != null)
{
playButton.Visibility = Visibility.Collapsed;
}
if (fontIcon != null)
{
fontIcon.Visibility = Visibility.Visible;
}
}
public void PlayQueueListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is BriefMusicInfo briefMusicInfo)
{
Data.MusicPlayer.PlaySongByPath(briefMusicInfo.Path);
}
}
public void PlayButton_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is BriefMusicInfo briefMusicInfo)
{
Data.MusicPlayer.PlaySongByPath(briefMusicInfo.Path);
}
}
public void ClearButton_Click(object sender, RoutedEventArgs e)
{
Data.MusicPlayer.ClearPlayQueue();
}
}

View File

@@ -0,0 +1,7 @@
namespace The_Untamed_Music_Player.ViewModels;
public class ViewModel
{
public ViewModel()
{
}
}

View File

@@ -0,0 +1,8 @@
namespace The_Untamed_Music_Player.ViewModels;
public class ViewModel
{
public ViewModel()
{
}
}

View File

@@ -0,0 +1,75 @@
using System.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using The_Untamed_Music_Player.Models;
namespace The_Untamed_Music_Player.ViewModels;
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModel()
{
Data.ViewModel = this;
}
public void SongListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (Data.MusicPlayer.PlayQueueName != "Songs:All" || Data.MusicPlayer.PlayQueue.Count != Data.MusicLibrary.Musics.Count)
{
Data.MusicPlayer.SetPlayList("Songs:All", Data.MusicLibrary.Musics);
}
if (e.ClickedItem is BriefMusicInfo briefMusicInfo)
{
Data.MusicPlayer.PlaySongByPath(briefMusicInfo.Path);
}
}
public void PlayButton_Click(object sender, RoutedEventArgs e)
{
if (Data.MusicPlayer.PlayQueueName != "Songs:All" || Data.MusicPlayer.PlayQueue.Count != Data.MusicLibrary.Musics.Count)
{
Data.MusicPlayer.SetPlayList("Songs:All", Data.MusicLibrary.Musics);
}
if (sender is Button button && button.DataContext is BriefMusicInfo briefMusicInfo)
{
Data.MusicPlayer.PlaySongByPath(briefMusicInfo.Path);
}
}
public 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;
}
}
public 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;
}
}
}

View File

@@ -0,0 +1,21 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Models;
namespace The_Untamed_Music_Player.ViewModels;
public partial class ViewModel : ObservableRecipient
{
public ViewModel()
{
}
public void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is LyricSlice lyricSlice)
{
var time = lyricSlice.Time;
Data.MusicPlayer.LyricProgressUpdate(time);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace The_Untamed_Music_Player.ViewModels;
public class ViewModel
{
public ViewModel()
{
}
}

View File

@@ -0,0 +1,10 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace The_Untamed_Music_Player.ViewModels;
public partial class ViewModel : ObservableRecipient
{
public ViewModel()
{
}
}

View File

@@ -0,0 +1,375 @@
<Page x:Class="The_Untamed_Music_Player.Views.RootPlayBarView"
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.Models"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
NavigationCacheMode="Enabled"
mc:Ignorable="d">
<Grid Width="Auto" Height="Auto"
HorizontalAlignment="Stretch" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="0.4*" MinWidth="0"/>
<ColumnDefinition Width="0.4*" MinWidth="130"/>
<ColumnDefinition Width="0.4*" MinWidth="0"/>
<ColumnDefinition Width="2*" MinWidth="80"/>
</Grid.ColumnDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="ShuffleButton.Visibility" Value="Collapsed"/>
<Setter Target="RepeatButton.Visibility" Value="Collapsed"/>
<Setter Target="FullScreenButton.Visibility" Value="Collapsed"/>
<Setter Target="ShuffleMenu.Visibility" Value="Visible"/>
<Setter Target="RepeatMenu.Visibility" Value="Visible"/>
<Setter Target="FullScreenMenu.Visibility" Value="Visible"/>
<Setter Target="PlayStackPanel.(Grid.Column)" Value="2"/>
<Setter Target="PlayStackPanel.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="PlayStackPanel.(Canvas.ZIndex)" Value="3"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="641"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="ShuffleButton.Visibility" Value="Visible"/>
<Setter Target="RepeatButton.Visibility" Value="Visible"/>
<Setter Target="FullScreenButton.Visibility" Value="Visible"/>
<Setter Target="ShuffleMenu.Visibility" Value="Collapsed"/>
<Setter Target="RepeatMenu.Visibility" Value="Collapsed"/>
<Setter Target="FullScreenMenu.Visibility" Value="Collapsed"/>
<Setter Target="PlayStackPanel.(Grid.Column)" Value="1"/>
<Setter Target="PlayStackPanel.(Grid.ColumnSpan)" Value="3"/>
<Setter Target="PlayStackPanel.HorizontalAlignment" Value="Center"/>
<Setter Target="PlayStackPanel.(Canvas.ZIndex)" Value="3"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- 进度条 -->
<Grid Grid.Row="0" Grid.ColumnSpan="5">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Margin="16,0,0,0" HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="12"
Text="{x:Bind ViewModel.GetCurrent(local:Data.MusicPlayer.Current), Mode=OneWay}"/>
<Slider x:Name="ProgressSlider" x:Uid="PlayBar_Seek"
Grid.Row="0" Grid.Column="1"
Margin="25,0,5,0" VerticalAlignment="Center"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
IsThumbToolTipEnabled="False" Maximum="100"
Minimum="0" Orientation="Horizontal"
Visibility="{x:Bind ViewModel.GetSliderVisibility(local:Data.MusicPlayer.PlayState), Mode=OneWay}"
Value="{x:Bind local:Data.MusicPlayer.CurrentPosition, Mode=OneWay}"/>
<ProgressBar Grid.Row="0" Grid.Column="1"
Margin="25,0,5,0"
IsIndeterminate="True" ShowError="False"
ShowPaused="False"
Visibility="{x:Bind ViewModel.GetProgressVisibility(local:Data.MusicPlayer.PlayState), Mode=OneWay}"/>
<TextBlock Grid.Row="0" Grid.Column="2"
Margin="16,0,16,0" HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="12"
Text="{x:Bind ViewModel.GetRemaining(local:Data.MusicPlayer.Current, local:Data.MusicPlayer.Total), Mode=OneWay}"/>
</Grid>
<!-- 播放控制 -->
<StackPanel x:Name="PlayStackPanel"
Grid.Row="1"
HorizontalAlignment="Center"
Orientation="Horizontal" Spacing="10">
<!-- 随机播放 -->
<Button x:Name="ShuffleButton"
Click="{x:Bind local:Data.MusicPlayer.ShuffleModeUpdate}"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
Style="{StaticResource PlayButtonStyle}">
<Grid>
<FontIcon FontSize="16" Glyph="&#xE8B1;"/>
<TextBlock Margin="0,3,0,0"
FontFamily="等线" FontSize="16"
Text="╲"
Visibility="{x:Bind ViewModel.GetShuffleModeIcon(local:Data.MusicPlayer.ShuffleMode), Mode=OneWay}"/>
</Grid>
</Button>
<!-- 上一曲 -->
<Button x:Uid="PlayBar_Prev"
Click="{x:Bind local:Data.MusicPlayer.PlayPreviousSong}"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
Style="{StaticResource PlayButtonStyle}">
<FontIcon FontSize="16" Glyph="&#xF8AC;"/>
</Button>
<!-- 播放/暂停 -->
<Button x:Name="PlayButton"
Click="{x:Bind local:Data.MusicPlayer.PlayPauseUpdate}"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
Style="{StaticResource PlayButtonStyle}">
<FontIcon x:Name="PlayIcon"
FontSize="24"
Glyph="{x:Bind ViewModel.GetPlayPauseIcon(local:Data.MusicPlayer.PlayState), Mode=OneWay}"/>
</Button>
<!-- 下一曲 -->
<Button x:Uid="PlayBar_Next"
Click="{x:Bind local:Data.MusicPlayer.PlayNextSong}"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
Style="{StaticResource PlayButtonStyle}">
<FontIcon FontSize="16" Glyph="&#xF8AD;"/>
</Button>
<!-- 循环播放 -->
<Button x:Name="RepeatButton"
Click="{x:Bind local:Data.MusicPlayer.RepeatModeUpdate}"
Style="{StaticResource PlayButtonStyle}">
<FontIcon FontSize="16" Glyph="{x:Bind ViewModel.GetRepeatModeIcon(local:Data.MusicPlayer.RepeatMode), Mode=OneWay}"/>
</Button>
</StackPanel>
<!-- 歌曲信息 -->
<Button x:Uid="PlayBar_NowPlaying"
Grid.Row="1" Grid.Column="0"
Height="80"
Margin="4,0,0,4" HorizontalAlignment="Left"
Click="{x:Bind ViewModel.CoverBtnClickToDetail}"
Style="{StaticResource CoverButtonStyle}">
<StackPanel HorizontalAlignment="Left"
Orientation="Horizontal"
Visibility="{x:Bind ViewModel.ButtonVisibility, Mode=OneWay}">
<StackPanel.ChildrenTransitions>
<EntranceThemeTransition FromHorizontalOffset="50" IsStaggeringEnabled="True"/>
<RepositionThemeTransition IsStaggeringEnabled="False"/>
</StackPanel.ChildrenTransitions>
<Border x:Name="CoverBorder"
Width="70" Height="70"
Margin="4,0,0,0" Padding="0"
HorizontalAlignment="Left"
CornerRadius="3">
<Canvas>
<Image x:Name="ControllerCover"
Width="70" Height="70"
HorizontalAlignment="Left" VerticalAlignment="Center"
Canvas.ZIndex="1"
Source="{x:Bind local:Data.MusicPlayer.CurrentMusic.Cover, Mode=OneWay}"/>
<Grid Width="70" Height="70"
Background="{ThemeResource SolidBackgroundFillColorSecondaryBrush}"
Canvas.ZIndex="0">
<FontIcon Canvas.ZIndex="0" FontSize="25"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE8D6;"/>
</Grid>
</Canvas>
</Border>
<StackPanel Margin="6,0,4,0" HorizontalAlignment="Left"
VerticalAlignment="Center">
<TextBlock Margin="10,0,0,4"
FontSize="20" FontWeight="Medium"
Text="{x:Bind local:Data.MusicPlayer.CurrentMusic.Title, Mode=OneWay}"
TextTrimming="CharacterEllipsis" TextWrapping="NoWrap"/>
<TextBlock Margin="10,0,0,0"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind local:Data.MusicPlayer.CurrentMusic.ArtistAndAlbumStr, Mode=OneWay}"
TextTrimming="CharacterEllipsis" TextWrapping="NoWrap"
Visibility="{x:Bind ViewModel.GetArtistAndAlbumStrVisibility(local:Data.MusicPlayer.CurrentMusic), Mode=OneWay}"/>
</StackPanel>
</StackPanel>
</Button>
<!-- 其他控制 -->
<StackPanel Grid.Row="1" Grid.Column="4"
Margin="0,0,4,0" HorizontalAlignment="Right"
Orientation="Horizontal" Spacing="7">
<!-- 音量 -->
<Button x:Uid="PlayBar_Volume" Style="{StaticResource PlayButtonStyle}">
<FontIcon FontSize="16" Glyph="{x:Bind ViewModel.GetVolumeIcon(local:Data.MusicPlayer.CurrentVolume), Mode=OneWay}"/>
<Button.Flyout>
<Flyout>
<StackPanel Height="28"
Orientation="Horizontal" Spacing="15">
<FontIcon Margin="8,0,7,0"
FontSize="16"
Glyph="{x:Bind ViewModel.GetVolumeIcon(local:Data.MusicPlayer.CurrentVolume), Mode=OneWay}"/>
<Slider x:Name="VolumeSlider" x:Uid="PlayBar_Volume"
Width="180"
Margin="0,-1,0,0"
IsThumbToolTipEnabled="False" Maximum="100"
Minimum="0"
Value="{x:Bind local:Data.MusicPlayer.CurrentVolume, Mode=TwoWay}"/>
<TextBlock Width="20"
VerticalAlignment="Center"
FontSize="12"
Text="{x:Bind local:Data.MusicPlayer.CurrentVolume, Mode=OneWay}"
TextAlignment="Center"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<!-- 全屏 -->
<Button x:Name="FullScreenButton" x:Uid="PlayBar_FullScreen"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
Style="{StaticResource PlayButtonStyle}">
<FontIcon FontSize="16" Glyph="&#xE740;"/>
</Button>
<!-- 最小化置顶 -->
<Button x:Uid="PlayBar_Mini"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
Style="{StaticResource PlayButtonStyle}">
<FontIcon FontSize="16" Glyph="&#xEE49;"/>
</Button>
<!-- 更多菜单 -->
<Button x:Uid="PlayBar_More" Style="{StaticResource PlayButtonStyle}">
<FontIcon FontSize="16" Glyph="&#xE712;"/>
<Button.Flyout>
<MenuFlyout>
<!-- 歌曲属性 -->
<MenuFlyoutItem x:Uid="PlayBar_Property" IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE946;"/>
</MenuFlyoutItem.Icon>
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="I" Modifiers="Control"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<!-- 均衡器 -->
<MenuFlyoutItem x:Uid="PlayBar_Equalizer" IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE9E9;"/>
</MenuFlyoutItem.Icon>
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="E" Modifiers="Control, Shift"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<!-- 播放速度 -->
<MenuFlyoutSubItem x:Uid="PlayBar_Speed" IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}">
<MenuFlyoutSubItem.Icon>
<FontIcon Glyph="&#xEC4A;"/>
</MenuFlyoutSubItem.Icon>
<MenuFlyoutItem x:Uid="PlayBar_More_025" Text="0.25 ×">
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="G" Modifiers="Control, Shift"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<MenuFlyoutItem x:Uid="PlayBar_More_05" Text="0.5 ×">
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="H" Modifiers="Control, Shift"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<MenuFlyoutItem x:Uid="PlayBar_More_1" Text="1 ×">
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="J" Modifiers="Control, Shift"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<MenuFlyoutItem x:Uid="PlayBar_More_15" Text="1.5 ×">
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="K" Modifiers="Control, Shift"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<MenuFlyoutItem x:Uid="PlayBar_More_2" Text="2 ×">
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="L" Modifiers="Control, Shift"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
</MenuFlyoutSubItem>
<!-- 投屏 -->
<MenuFlyoutItem x:Uid="PlayBar_CastDevice" IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xEC15;"/>
</MenuFlyoutItem.Icon>
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="K" Modifiers="Control"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<!-- 快退10s -->
<MenuFlyoutItem x:Uid="PlayBar_SkipBack10s" IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xED3C;"/>
</MenuFlyoutItem.Icon>
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="Left" Modifiers="Control"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<!-- 快进30s -->
<MenuFlyoutItem x:Uid="PlayBar_SkipForw30s" IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xED3D;"/>
</MenuFlyoutItem.Icon>
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="Right" Modifiers="Control"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<!-- 随机播放 -->
<MenuFlyoutItem x:Name="ShuffleMenu"
Click="{x:Bind local:Data.MusicPlayer.ShuffleModeUpdate}"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
Text="随机播放">
<MenuFlyoutItem.Icon>
<FontIcon FontSize="16" Glyph="&#xE8B1;"/>
</MenuFlyoutItem.Icon>
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="H" Modifiers="Control"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<!-- 循环播放 -->
<MenuFlyoutItem x:Name="RepeatMenu"
Click="{x:Bind local:Data.MusicPlayer.RepeatModeUpdate}"
Text="循环播放">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xF5E7;"/>
</MenuFlyoutItem.Icon>
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="T" Modifiers="Control"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<!-- 全屏 -->
<MenuFlyoutItem x:Name="FullScreenMenu" x:Uid="PlayBar_SkipForw30s"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE740;"/>
</MenuFlyoutItem.Icon>
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="F11"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
</MenuFlyout>
</Button.Flyout>
</Button>
</StackPanel>
</Grid>
</Page>

View File

@@ -0,0 +1,31 @@
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
public sealed partial class RootPlayBarView : Page
{
public RootPlayBarViewModel ViewModel
{
get;
}
public RootPlayBarView()
{
RootPlayBarViewModel.RootPlayBarView = this;
InitializeComponent();
ViewModel = App.GetService<RootPlayBarViewModel>();
MusicPlayer.PlayBarUI = this;
DataContext = ViewModel;
}
public Slider GetProgressSlider()
{
return ProgressSlider;
}
public Border GetCoverBorder()
{
return CoverBorder;
}
}

View File

@@ -0,0 +1,244 @@
<Page x:Name="SettingPage"
x:Class="The_Untamed_Music_Player.Views.SettingsPage"
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="using:The_Untamed_Music_Player.Helpers"
xmlns:local="using:The_Untamed_Music_Player.ViewModels"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:The_Untamed_Music_Player.Models"
xmlns:muxc="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:muxs="using:Microsoft.UI.Xaml.Controls" xmlns:storage="using:Windows.Storage"
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls" xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:xaml="using:Microsoft.UI.Xaml"
mc:Ignorable="d">
<Page.Resources>
<helpers:EnumToBooleanConverter x:Key="EnumToBooleanConverter"/>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="16,35"/>
<Setter Target="ContentArea.Margin" Value="16,0,16,0"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="641"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="{StaticResource NavigationViewPageContentMargin}"/>
<Setter Target="ContentArea.Margin" Value="{StaticResource NavigationViewPageContentMargin}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="TitleGrid" Grid.Row="0">
<TextBlock x:Uid="Shell_Settings"
FontSize="40" FontWeight="SemiLight"/>
</Grid>
<ScrollViewer Grid.Row="1" Padding="0,0,0,16">
<StackPanel x:Name="ContentArea"
Orientation="Vertical" Spacing="34">
<!-- 动画 -->
<StackPanel.ChildrenTransitions>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True"/>
<RepositionThemeTransition IsStaggeringEnabled="False"/>
</StackPanel.ChildrenTransitions>
<!-- 库 -->
<StackPanel Spacing="2">
<StackPanel.ChildrenTransitions>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True"/>
<RepositionThemeTransition IsStaggeringEnabled="False"/>
</StackPanel.ChildrenTransitions>
<TextBlock x:Uid="Settings_Library"
Margin="0,12,0,5"
Style="{StaticResource BodyStrongTextBlockStyle}"/>
<!-- 音乐库位置 -->
<toolkit:SettingsExpander x:Name="musicFolder" x:Uid="Settings_MusicLibraryLocation"
ItemsSource="{x:Bind model:Data.MusicLibrary.Folders, Mode=OneWay}">
<toolkit:SettingsExpander.HeaderIcon>
<FontIcon Glyph="&#xE8B7;"/>
</toolkit:SettingsExpander.HeaderIcon>
<Button x:Name="addMusicFolder" Click="{x:Bind ViewModel.PickMusicFolderButton_Click}">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="16" Glyph="&#xE8F4;"/>
<TextBlock x:Uid="Settings_AddFolder"/>
</StackPanel>
</Button>
<toolkit:SettingsExpander.ItemTemplate>
<DataTemplate x:DataType="storage:StorageFolder">
<toolkit:SettingsCard Header="{x:Bind Path}">
<Button x:Name="removeFolder" x:Uid="Settings_RemoveFolder"
Width="27" Height="27"
Margin="0" Padding="0"
Click="RemoveMusicFolderButton_Click"
DataContext="{x:Bind}">
<FontIcon FontSize="15" Glyph="&#xE711;"/>
</Button>
</toolkit:SettingsCard>
</DataTemplate>
</toolkit:SettingsExpander.ItemTemplate>
<toolkit:SettingsExpander.ItemsFooter>
<Grid Height="35" Visibility="{x:Bind ViewModel.EmptyFolderMessageVisibility, Mode=OneWay}">
<toolkit:SettingsCard x:Name="emptyFolderMessage"
Background="Transparent" BorderBrush="Transparent"
ContentAlignment="Left"
Visibility="{x:Bind ViewModel.EmptyFolderMessageVisibility, Mode=OneWay}">
<TextBlock x:Uid="Settings_NoFolder" Margin="42,0,0,8"/>
</toolkit:SettingsCard>
</Grid>
</toolkit:SettingsExpander.ItemsFooter>
</toolkit:SettingsExpander>
<!-- 刷新库 -->
<toolkit:SettingsExpander x:Name="Refresh" x:Uid="Settings_RefreshLibrary">
<toolkit:SettingsExpander.HeaderIcon>
<FontIcon Glyph="&#xE90F;"/>
</toolkit:SettingsExpander.HeaderIcon>
<StackPanel Orientation="Horizontal" Spacing="8">
<ProgressRing Width="24" Height="24"
IsActive="{x:Bind ViewModel.IsProgressRingActive, Mode=OneWay}"/>
<Button x:Name="refresh" x:Uid="Settings_Refresh"
Click="{x:Bind ViewModel.RefreshButton_Click}"
IsEnabled="{x:Bind ViewModel.IsRefreshButtonEnabled, Mode=OneWay}"/>
</StackPanel>
<toolkit:SettingsExpander.Items>
<toolkit:SettingsCard ContentAlignment="Left">
<StackPanel>
<TextBlock x:Uid="Settings_ReIndex" Margin="0,8,0,8"/>
<HyperlinkButton x:Uid="Settings_SearchSetting"
Margin="-12,0,0,0"
Click="HyperlinkButton_Click"/>
</StackPanel>
</toolkit:SettingsCard>
</toolkit:SettingsExpander.Items>
</toolkit:SettingsExpander>
</StackPanel>
<!-- 个性化 -->
<StackPanel Spacing="2">
<StackPanel.ChildrenTransitions>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True"/>
<RepositionThemeTransition IsStaggeringEnabled="False"/>
</StackPanel.ChildrenTransitions>
<TextBlock x:Uid="Settings_Personalization"
Margin="0,12,0,5"
Style="{StaticResource BodyStrongTextBlockStyle}"/>
<toolkit:SettingsExpander x:Name="AppThemeExpander1" x:Uid="Settings_Theme">
<toolkit:SettingsExpander.HeaderIcon>
<FontIcon Glyph="&#xE790;"/>
</toolkit:SettingsExpander.HeaderIcon>
<toolkit:SettingsExpander.Items>
<toolkit:SettingsCard ContentAlignment="Left">
<StackPanel>
<RadioButton x:Uid="Settings_Theme_Light"
Command="{x:Bind ViewModel.SwitchThemeCommand}"
FontSize="15" GroupName="AppTheme"
IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Light, Mode=OneWay}">
<RadioButton.CommandParameter>
<xaml:ElementTheme>Light</xaml:ElementTheme>
</RadioButton.CommandParameter>
</RadioButton>
<RadioButton x:Uid="Settings_Theme_Dark"
Command="{x:Bind ViewModel.SwitchThemeCommand}"
FontSize="15" GroupName="AppTheme"
IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Dark, Mode=OneWay}">
<RadioButton.CommandParameter>
<xaml:ElementTheme>Dark</xaml:ElementTheme>
</RadioButton.CommandParameter>
</RadioButton>
<RadioButton x:Uid="Settings_Theme_Default"
Command="{x:Bind ViewModel.SwitchThemeCommand}"
FontSize="15" GroupName="AppTheme"
IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Default, Mode=OneWay}">
<RadioButton.CommandParameter>
<xaml:ElementTheme>Default</xaml:ElementTheme>
</RadioButton.CommandParameter>
</RadioButton>
</StackPanel>
</toolkit:SettingsCard>
</toolkit:SettingsExpander.Items>
</toolkit:SettingsExpander>
<toolkit:SettingsCard Header="窗口材质(更改后建议重启本应用)">
<toolkit:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xF5ED;"/>
</toolkit:SettingsCard.HeaderIcon>
<ComboBox x:Name="MaterialComboBox"
ItemsSource="{x:Bind ViewModel.Materials}"
Loaded="{x:Bind ViewModel.MaterialComboBox_Loaded}"
PlaceholderText="选择一个材质"
SelectionChanged="{x:Bind ViewModel.MaterialComboBox_SelectionChanged}"/>
</toolkit:SettingsCard>
</StackPanel>
<StackPanel Spacing="2">
<StackPanel.ChildrenTransitions>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True"/>
<RepositionThemeTransition IsStaggeringEnabled="False"/>
</StackPanel.ChildrenTransitions>
<TextBlock Margin="0,12,0,5"
Style="{StaticResource BodyStrongTextBlockStyle}"
Text="歌词设置"/>
<toolkit:SettingsCard Header="歌词字体">
<toolkit:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE8D2;"/>
</toolkit:SettingsCard.HeaderIcon>
<ComboBox x:Name="FontComboBox"
ItemsSource="{x:Bind ViewModel.Fonts}"
Loaded="{x:Bind ViewModel.FontComboBox_Loaded}"
PlaceholderText="选择一个字体"
SelectionChanged="{x:Bind ViewModel.FontComboBox_SelectionChanged}"/>
</toolkit:SettingsCard>
<toolkit:SettingsCard Header="歌词背景设为模糊的封面">
<toolkit:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE7C4;"/>
</toolkit:SettingsCard.HeaderIcon>
<ToggleSwitch x:Name="LyricBackgroundSwitch"
IsOn="{x:Bind ViewModel.IsLyricBackgroundVisible, Mode=TwoWay}"
Loaded="{x:Bind ViewModel.LyricBackgroundCheckBox_Loaded}"/>
</toolkit:SettingsCard>
</StackPanel>
<!-- 关于 -->
<StackPanel Spacing="2">
<TextBlock x:Uid="Settings_About"
Margin="0,12,0,5"
Style="{StaticResource BodyStrongTextBlockStyle}"/>
<toolkit:SettingsCard x:Name="About" x:Uid="Settings_AboutDescription"
Header="Untamed Music Player">
<toolkit:SettingsCard.HeaderIcon>
<BitmapIcon AutomationProperties.AccessibilityView="Raw" ShowAsMonochrome="False"
UriSource="ms-appx:///Assets/WindowIcon.ico"/>
</toolkit:SettingsCard.HeaderIcon>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.VersionDescription, Mode=OneWay}"/>
</toolkit:SettingsCard>
</StackPanel>
<TextBlock Height="20"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Page>

View File

@@ -0,0 +1,83 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.ApplicationModel.Resources;
using The_Untamed_Music_Player.ViewModels;
using Windows.Storage;
using Windows.System;
namespace The_Untamed_Music_Player.Views;
// TODO: Set the URL for your privacy policy by updating SettingsPage_PrivacyTermsLink.NavigateUri in Resources.resw.
public sealed partial class SettingsPage : Page
{
public SettingsViewModel ViewModel
{
get; set;
}
public SettingsPage()
{
ViewModel = App.GetService<SettingsViewModel>();
InitializeComponent();
}
private async void RemoveMusicFolderButton_Click(object sender, RoutedEventArgs e)
{
var folderName = "";
if (sender is Button button && button.DataContext is StorageFolder folder)
{
folderName = folder.DisplayName;
}
var resourceLoader = new ResourceLoader();
var titleTextBlock = new TextBlock
{
Text = resourceLoader.GetString("Settings_Dialog1Title"),
FontWeight = Microsoft.UI.Text.FontWeights.Normal
};
var dialog = new ContentDialog
{
XamlRoot = XamlRoot,
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
Title = titleTextBlock,
Content = resourceLoader.GetString("Settings_Dialog1Content1") + folderName + resourceLoader.GetString("Settings_Dialog1Content2"),
PrimaryButtonText = resourceLoader.GetString("Settings_Dialog1Primary"),
CloseButtonText = resourceLoader.GetString("Settings_Dialog1Close"),
DefaultButton = ContentDialogButton.Primary
};
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
ViewModel.RemoveMusicFolderButtonClick(sender, e);
}
}
public async void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
var resourceLoader = new ResourceLoader();
var titleTextBlock = new TextBlock
{
Text = resourceLoader.GetString("Settings_Dialog2Title"),
FontWeight = Microsoft.UI.Text.FontWeights.Normal
};
var dialog = new ContentDialog
{
XamlRoot = XamlRoot,
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
Title = titleTextBlock,
Content = resourceLoader.GetString("Settings_Dialog2Content"),
PrimaryButtonText = resourceLoader.GetString("Settings_Dialog2Primary"),
CloseButtonText = resourceLoader.GetString("Settings_Dialog2Close"),
DefaultButton = ContentDialogButton.Close
};
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
await Launcher.LaunchUriAsync(new Uri("ms-settings:search"));
}
}
}

View File

@@ -0,0 +1,72 @@
<Page x:Class="The_Untamed_Music_Player.Views.ShellPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="using:The_Untamed_Music_Player.Behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="using:The_Untamed_Music_Player.Helpers" xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Loaded="OnLoaded">
<Page.Resources>
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent" />
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid x:Name="AppTitleBar"
Height="{Binding ElementName=NavigationViewControl, Path=CompactPaneLength}"
VerticalAlignment="Top"
Canvas.ZIndex="1" IsHitTestVisible="True">
<Image Width="16" Height="16"
Margin="12,0,0,0" HorizontalAlignment="Left"
Source="/Assets/WindowIcon.ico"/>
<TextBlock x:Name="AppTitleBarText"
Margin="40,0,0,0" VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
Text="Untamed Music Player" TextWrapping="NoWrap"/>
</Grid>
<NavigationView x:Name="NavigationViewControl"
Canvas.ZIndex="1" DisplayModeChanged="NavigationViewControl_DisplayModeChanged"
ExpandedModeThresholdWidth="1280" IsBackButtonVisible="Visible"
IsBackEnabled="{x:Bind ViewModel.IsBackEnabled, Mode=OneWay}"
IsSettingsVisible="True"
Background="{StaticResource NavigationViewContentBackground}"
SelectedItem="{x:Bind ViewModel.Selected, Mode=OneWay}">
<NavigationView.AutoSuggestBox>
<AutoSuggestBox x:Uid="Shell_Search"
AutomationProperties.Name="Search" QueryIcon="Find"/>
</NavigationView.AutoSuggestBox>
<NavigationView.MenuItems>
<NavigationViewItem x:Uid="Shell_主页" helpers:NavigationHelper.NavigateTo="The_Untamed_Music_Player.ViewModels.主页ViewModel">
<NavigationViewItem.Icon>
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE80F;"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem x:Uid="Shell_音乐库" helpers:NavigationHelper.NavigateTo="The_Untamed_Music_Player.ViewModels.音乐库ViewModel">
<NavigationViewItem.Icon>
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE8D6;"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItemSeparator/>
<NavigationViewItem x:Uid="Shell_播放队列" helpers:NavigationHelper.NavigateTo="The_Untamed_Music_Player.ViewModels.播放队列ViewModel">
<NavigationViewItem.Icon>
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE90B;"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem x:Uid="Shell_播放列表" helpers:NavigationHelper.NavigateTo="The_Untamed_Music_Player.ViewModels.播放列表ViewModel">
<NavigationViewItem.Icon>
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE93C;"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
</NavigationView.MenuItems>
<Grid>
<Frame x:Name="NavigationFrame"/>
</Grid>
</NavigationView>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,95 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.ViewModels;
using Windows.System;
namespace The_Untamed_Music_Player.Views;
// TODO: Update NavigationViewItem titles and icons in ShellPage.xaml.
public sealed partial class ShellPage : Page
{
public ShellViewModel ViewModel
{
get;
}
public ShellPage()
{
ViewModel = App.GetService<ShellViewModel>();
InitializeComponent();
ViewModel.NavigationService.Frame = NavigationFrame;
ViewModel.NavigationViewService.Initialize(NavigationViewControl);
// TODO: Set the title bar icon by updating /Assets/WindowIcon.ico.
// A custom title bar is required for full window theme and Mica support.
// https://docs.microsoft.com/windows/apps/develop/title-bar?tabs=winui3#full-customization
if (Data.MainWindow != null)
{
Data.MainWindow.SetTitleBar(AppTitleBar);
Data.MainWindow.Activated += MainWindow_Activated;
}
AppTitleBarText.Text = "Untamed Music Player";
Data.ShellPage = this;
}
private void OnLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
TitleBarHelper.UpdateTitleBar(RequestedTheme);
KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.Left, VirtualKeyModifiers.Menu));
KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.GoBack));
}
private void MainWindow_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args)
{
App.AppTitlebar = AppTitleBarText as UIElement;
}
public void NavigationViewControl_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
{
AppTitleBar.Margin = new Thickness()
{
Left = sender.CompactPaneLength * (sender.DisplayMode == NavigationViewDisplayMode.Minimal ? 2 : 1),
Top = AppTitleBar.Margin.Top,
Right = AppTitleBar.Margin.Right,
Bottom = AppTitleBar.Margin.Bottom
};
}
private static KeyboardAccelerator BuildKeyboardAccelerator(VirtualKey key, VirtualKeyModifiers? modifiers = null)
{
var keyboardAccelerator = new KeyboardAccelerator() { Key = key };
if (modifiers.HasValue)
{
keyboardAccelerator.Modifiers = modifiers.Value;
}
keyboardAccelerator.Invoked += OnKeyboardAcceleratorInvoked;
return keyboardAccelerator;
}
private static void OnKeyboardAcceleratorInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
var navigationService = App.GetService<INavigationService>();
var result = navigationService.GoBack();
args.Handled = result;
}
}

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" ?>
<Page x:Class="The_Untamed_Music_Player.Views.专辑Page"
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"
NavigationCacheMode="Enabled"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="{StaticResource NavigationViewPageContentMargin}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Style="{StaticResource AccentButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="12" Glyph="&#xE8B1;"/>
<TextBlock Text=" 随机播放"/>
</StackPanel>
</Button>
<StackPanel Grid.Column="2"
Orientation="Horizontal" Spacing="16">
<Button Background="Transparent" BorderBrush="Transparent">
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="排序方式:"/>
<TextBlock Foreground="{ThemeResource AccentTextFillColorTertiaryBrush}" Text="已添加日期"/>
<FontIcon Margin="8,0,0,0"
FontSize="12" Glyph="&#xE70D;"/>
</StackPanel>
<Button.Flyout>
<Flyout Placement="Bottom">
<ListView x:Name="SortListView" Margin="-12,-13,-12,-15">
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="A-Z"/>
</ListViewItem>
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="发行年份"/>
</ListViewItem>
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="歌手"/>
</ListViewItem>
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="已添加日期"/>
</ListViewItem>
</ListView>
</Flyout>
</Button.Flyout>
</Button>
<Button Background="Transparent" BorderBrush="Transparent">
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="流派:"/>
<TextBlock Foreground="{ThemeResource AccentTextFillColorTertiaryBrush}" Text="所有流派"/>
<FontIcon Margin="8,0,0,0"
FontSize="12" Glyph="&#xE70D;"/>
</StackPanel>
<Button.Flyout>
<Flyout Placement="Bottom">
<ListView x:Name="GenreListView" Margin="-12,-13,-12,-15">
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="所有流派"/>
</ListViewItem>
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="未知流派"/>
</ListViewItem>
</ListView>
</Flyout>
</Button.Flyout>
</Button>
</StackPanel>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,18 @@
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
public sealed partial class Page : Page
{
public ViewModel ViewModel
{
get;
}
public Page()
{
ViewModel = App.GetService<ViewModel>();
InitializeComponent();
}
}

View File

@@ -0,0 +1,40 @@
<Page x:Class="The_Untamed_Music_Player.Views.主页Page"
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="Transparent"
mc:Ignorable="d">
<Grid x:Name="ContentArea">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="16,35"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="641"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="{StaticResource NavigationViewPageContentMargin}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="TitleGrid" Grid.Row="0">
<TextBlock x:Uid="Shell_主页1"
FontSize="40" FontWeight="SemiLight"/>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,19 @@
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
public sealed partial class Page : Page
{
public ViewModel ViewModel
{
get;
}
public Page()
{
ViewModel = App.GetService<ViewModel>();
InitializeComponent();
}
}

View File

@@ -0,0 +1,40 @@
<Page x:Class="The_Untamed_Music_Player.Views.播放列表Page"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
NavigationCacheMode="Enabled"
mc:Ignorable="d">
<Grid x:Name="ContentArea">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="16,35"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="641"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="{StaticResource NavigationViewPageContentMargin}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="TitleGrid" Grid.Row="0">
<TextBlock x:Uid="Shell_播放列表1"
FontSize="40" FontWeight="SemiLight"/>
</Grid>
<ScrollViewer Grid.Row="1" Padding="0,0,0,16"/>
</Grid>
</Page>

View File

@@ -0,0 +1,19 @@
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
public sealed partial class Page : Page
{
public ViewModel ViewModel
{
get;
}
public Page()
{
ViewModel = App.GetService<ViewModel>();
InitializeComponent();
}
}

View File

@@ -0,0 +1,308 @@
<Page x:Class="The_Untamed_Music_Player.Views.播放队列Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:The_Untamed_Music_Player.Helpers"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:The_Untamed_Music_Player.Models"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="PlayQueueListViewTemplate" x:DataType="model:BriefMusicInfo">
<Grid Height="46"
Background="Transparent" PointerEntered="Grid_PointerEntered"
PointerExited="Grid_PointerExited">
<Grid.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="播放">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE768;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="加入播放队列">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xECC8;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<MenuFlyoutSubItem Text="添加到">
<MenuFlyoutSubItem.Icon>
<FontIcon Glyph="&#xE710;"/>
</MenuFlyoutSubItem.Icon>
<MenuFlyoutItem Text="播放队列">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE90B;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<MenuFlyoutItem Text="新建播放列表">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE710;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyoutSubItem>
<MenuFlyoutItem Text="删除">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE711;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="上移">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE74A;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="下移">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE74B;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="属性">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE946;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="显示专辑">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE93C;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="显示艺术家">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE77B;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<MenuFlyoutItem Text="选择">
<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="*"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="ItemCheckBox"
Grid.Column="0"
IsThreeState="False" Visibility="Collapsed"/>
<Button x:Name="PlayButton"
Grid.Column="1"
Padding="5"
Background="Transparent" BorderBrush="Transparent"
Canvas.ZIndex="1" Click="PlayButton_Click"
DataContext="{x:Bind}"
Style="{StaticResource SmallPlayButtonStyle}"
ToolTipService.ToolTip="播放" Visibility="Collapsed">
<FontIcon FontSize="16"
Foreground="{ThemeResource AccentTextFillColorTertiaryBrush}"
Glyph="&#xE768;"/>
</Button>
<FontIcon x:Name="MusicFontIcon"
Grid.Column="1"
Canvas.ZIndex="0" FontSize="16"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.SettingsViewModel.ElementTheme), Mode=OneWay}"
Glyph="&#xE8D6;"/>
<TextBlock Grid.Column="2"
VerticalAlignment="Center"
FontSize="12"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.SettingsViewModel.ElementTheme), 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.SettingsViewModel.ElementTheme), 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.SettingsViewModel.ElementTheme), 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.SettingsViewModel.ElementTheme), Mode=OneWay}"
Text="{x:Bind YearStr}"
TextTrimming="CharacterEllipsis"
ToolTipService.ToolTip="{x:Bind YearStr}"/>
<TextBlock Grid.Column="6"
HorizontalAlignment="Left" VerticalAlignment="Center"
FontSize="12"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.SettingsViewModel.ElementTheme), Mode=OneWay}"
Text="{x:Bind GenreStr}"
TextTrimming="CharacterEllipsis"
ToolTipService.ToolTip="{x:Bind GenreStr}"/>
<TextBlock Grid.Column="7"
Margin="0,0,10,0" HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="12"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.SettingsViewModel.ElementTheme), Mode=OneWay}"
Text="{x:Bind DurationStr}"
TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid x:Name="ContentArea">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="16,35"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="641"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="{StaticResource NavigationViewPageContentMargin}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="TitleGrid" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Uid="Shell_播放队列1"
Grid.Column="0"
FontSize="40" FontWeight="SemiLight"/>
<SplitButton Grid.Column="2">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="12" Glyph="&#xE838;"/>
<TextBlock Text="添加文件"/>
</StackPanel>
<SplitButton.Flyout>
<MenuFlyout>
<MenuFlyoutItem Margin="3,1,3,2">
<MenuFlyoutItem.Template>
<ControlTemplate>
<Button Style="{StaticResource CoverButtonStyle}">
<StackPanel Margin="10,4,178,5"
Orientation="Horizontal" Spacing="12">
<FontIcon FontSize="16" Glyph="&#xE838;"/>
<StackPanel>
<TextBlock Text="添加文件到播放队列"/>
<TextBlock FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="浏览要添加到播放队列的文件"/>
</StackPanel>
</StackPanel>
</Button>
</ControlTemplate>
</MenuFlyoutItem.Template>
</MenuFlyoutItem>
<MenuFlyoutItem Margin="3,0,3,2">
<MenuFlyoutItem.Template>
<ControlTemplate>
<Button Style="{StaticResource CoverButtonStyle}">
<StackPanel Margin="10,4,10,5"
Orientation="Horizontal" Spacing="12">
<FontIcon FontSize="16" Glyph="&#xE8B7;"/>
<StackPanel>
<TextBlock Text="添加文件夹到播放队列"/>
<TextBlock FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="选择一个文件夹,并将该文件夹中的所有媒体添加到播放队列"/>
</StackPanel>
</StackPanel>
</Button>
</ControlTemplate>
</MenuFlyoutItem.Template>
</MenuFlyoutItem>
<MenuFlyoutItem Margin="3,0,3,1">
<MenuFlyoutItem.Template>
<ControlTemplate>
<Button Style="{StaticResource CoverButtonStyle}">
<StackPanel Margin="10,4,84,5"
Orientation="Horizontal" Spacing="12">
<FontIcon FontSize="16" Glyph="&#xE774;"/>
<StackPanel>
<TextBlock Text="从URL将媒体添加到播放队列"/>
<TextBlock FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="输入URL并将该地址中的媒体添加到播放队列"/>
</StackPanel>
</StackPanel>
</Button>
</ControlTemplate>
</MenuFlyoutItem.Template>
</MenuFlyoutItem>
</MenuFlyout>
</SplitButton.Flyout>
</SplitButton>
</Grid>
<StackPanel Grid.Row="1"
Margin="{StaticResource NavigationViewPageContentMargin}"
Orientation="Horizontal" Spacing="16">
<Button Width="72" Height="36"
Padding="0"
Click="{x:Bind ViewModel.ClearButton_Click}">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="16" Glyph="&#xE74D;"/>
<TextBlock FontSize="12" Text="清除"/>
</StackPanel>
</Button>
<Button Width="84" Height="36"
Padding="0">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="16" Glyph="&#xE710;"/>
<TextBlock FontSize="12" Text="添加到"/>
</StackPanel>
<Button.Flyout>
<MenuFlyout Placement="Bottom">
<MenuFlyoutItem Text="播放队列">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE90B;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<MenuFlyoutItem Text="新建播放列表">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE710;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyout>
</Button.Flyout>
</Button>
</StackPanel>
<ListView x:Name="PlayqueueListView"
Grid.Row="2"
Margin="0,24,0,0" Padding="52,0,52,0"
CanDragItems="True" IsItemClickEnabled="True"
ItemClick="{x:Bind ViewModel.PlayQueueListView_ItemClick}"
ItemTemplate="{StaticResource PlayQueueListViewTemplate}"
ItemsSource="{x:Bind model:Data.MusicPlayer.GetPlayQueue(model:Data.MusicPlayer.PlayQueueName, model:Data.MusicPlayer.ShuffleMode), Mode=OneWay}"
SelectionMode="None">
<interactivity:Interaction.Behaviors>
<helper:AlternatingListViewBehavior AlternateBackground="#FDFEFE" DarkThemeBackground="#303539"/>
</interactivity:Interaction.Behaviors>
</ListView>
</Grid>
</Page>

View File

@@ -0,0 +1,34 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
public sealed partial class Page : Page
{
public ViewModel ViewModel
{
get;
}
public Page()
{
ViewModel = App.GetService<ViewModel>();
InitializeComponent();
}
private void Grid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
ViewModel.Grid_PointerEntered(sender, e);
}
private void Grid_PointerExited(object sender, PointerRoutedEventArgs e)
{
ViewModel.Grid_PointerExited(sender, e);
}
private void PlayButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.PlayButton_Click(sender, e);
}
}

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" ?>
<Page x:Class="The_Untamed_Music_Player.Views.无音乐Page"
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>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="Image.Height" Value="90"/>
<Setter Target="Image.Width" Value="90"/>
<Setter Target="StackPanel.Spacing" Value="8"/>
<Setter Target="TitleGrid.Margin" Value="16,35"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="641"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="Image.Height" Value="150"/>
<Setter Target="Image.Width" Value="150"/>
<Setter Target="StackPanel.Spacing" Value="18"/>
<Setter Target="TitleGrid.Margin" Value="{StaticResource NavigationViewPageContentMargin}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="TitleGrid" Grid.Row="0">
<TextBlock x:Uid="Shell_音乐库1"
FontSize="40" FontWeight="SemiLight"/>
</Grid>
<Grid Grid.Row="1"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel x:Name="StackPanel"
HorizontalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal">
<Image x:Name="Image"
HorizontalAlignment="Left" VerticalAlignment="Center"
Source="ms-appx:///Assets/MusicGradient.svg"/>
<StackPanel VerticalAlignment="Center"
Orientation="Vertical" Spacing="8">
<TextBlock x:Uid="无音乐_MusicNotFound" FontSize="29"/>
<TextBlock x:Uid="无音乐_NoMusicContent" Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
<Button x:Name="addMusicFolder"
Click="{x:Bind SettingsViewModel.PickMusicFolderButton_Click}"
Style="{StaticResource AccentButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="12" Glyph="&#xE8F4;"/>
<TextBlock x:Uid="Settings_AddFolder"/>
</StackPanel>
</Button>
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,20 @@
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
public sealed partial class Page : Page
{
private readonly SettingsViewModel SettingsViewModel;
public ViewModel ViewModel
{
get;
}
public Page()
{
ViewModel = App.GetService<ViewModel>();
InitializeComponent();
SettingsViewModel = App.GetService<SettingsViewModel>();
}
}

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" ?>
<Page x:Class="The_Untamed_Music_Player.Views.有音乐Page"
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>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="16,35"/>
<Setter Target="AddTextBlock.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="641"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="TitleGrid.Margin" Value="{StaticResource NavigationViewPageContentMargin}"/>
<Setter Target="AddTextBlock.Visibility" Value="Visible"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="TitleGrid" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Uid="Shell_音乐库1"
Grid.Column="0"
FontSize="40" FontWeight="SemiLight"/>
<SelectorBar x:Name="SelectorBar"
Grid.Column="1"
Margin="18,0,0,0"
SelectionChanged="SelectorBar_SelectionChanged">
<SelectorBarItem x:Name="SelectorBarItemPage1" x:Uid="有音乐_Songs"
FontSize="18" IsSelected="True"/>
<SelectorBarItem x:Name="SelectorBarItemPage2" x:Uid="有音乐_Albums"
FontSize="18"/>
<SelectorBarItem x:Name="SelectorBarItemPage3" x:Uid="有音乐_Artists"
FontSize="18"/>
</SelectorBar>
<Button x:Name="addMusicFolder" x:Uid="有音乐_AddFolder"
Grid.Column="2"
HorizontalAlignment="Right"
Click="{x:Bind SettingsViewModel.PickMusicFolderButton_Click}">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="16" Glyph="&#xE8F4;"/>
<TextBlock x:Name="AddTextBlock" x:Uid="Settings_AddFolder"/>
</StackPanel>
</Button>
</Grid>
<Frame x:Name="SelectFrame"
Grid.Row="1"
IsNavigationStackEnabled="False"/>
</Grid>
</Page>

View File

@@ -0,0 +1,39 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
public sealed partial class Page : Page
{
private readonly SettingsViewModel SettingsViewModel;
private int previousSelectedIndex = 0;
public ViewModel ViewModel
{
get;
}
public Page()
{
ViewModel = App.GetService<ViewModel>();
InitializeComponent();
SettingsViewModel = App.GetService<SettingsViewModel>();
}
private void SelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs args)
{
var selectedItem = sender.SelectedItem;
var currentSelectedIndex = sender.Items.IndexOf(selectedItem);
var pageType = currentSelectedIndex switch
{
0 => typeof(Page),
1 => typeof(Page),
_ => typeof(Page),
};
var slideNavigationTransitionEffect = currentSelectedIndex - previousSelectedIndex > 0 ? SlideNavigationTransitionEffect.FromRight : SlideNavigationTransitionEffect.FromLeft;
SelectFrame.Navigate(pageType, null, new SlideNavigationTransitionInfo() { Effect = slideNavigationTransitionEffect });
previousSelectedIndex = currentSelectedIndex;
}
}

View File

@@ -0,0 +1,236 @@
<?xml version="1.0" ?>
<Page x:Class="The_Untamed_Music_Player.Views.歌曲Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:The_Untamed_Music_Player.Helpers"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:The_Untamed_Music_Player.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:The_Untamed_Music_Player.Models"
xmlns:viewmodel="using:The_Untamed_Music_Player.ViewModels"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="SongListViewTemplate" x:DataType="model:BriefMusicInfo">
<Grid Height="46"
Background="Transparent" PointerEntered="Grid_PointerEntered"
PointerExited="Grid_PointerExited">
<Grid.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="播放">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE768;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="加入播放队列">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xECC8;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<MenuFlyoutSubItem Text="添加到">
<MenuFlyoutSubItem.Icon>
<FontIcon Glyph="&#xE710;"/>
</MenuFlyoutSubItem.Icon>
<MenuFlyoutItem Text="播放队列">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE90B;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<MenuFlyoutItem Text="新建播放列表">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE710;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyoutSubItem>
<MenuFlyoutItem Text="编辑信息">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE70F;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="属性">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE946;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="显示专辑">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE93C;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="显示艺术家">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE77B;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator/>
<MenuFlyoutItem Text="选择">
<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="*"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="ItemCheckBox"
Grid.Column="0"
IsThreeState="False" Visibility="Collapsed"/>
<Button x:Name="PlayButton"
Grid.Column="1"
Click="PlayButton_Click"
DataContext="{x:Bind}"
Style="{StaticResource SmallPlayButtonStyle}"
ToolTipService.ToolTip="播放" 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.SettingsViewModel.ElementTheme), 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.SettingsViewModel.ElementTheme), 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.SettingsViewModel.ElementTheme), 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.SettingsViewModel.ElementTheme), Mode=OneWay}"
Text="{x:Bind YearStr}"
TextTrimming="CharacterEllipsis"
ToolTipService.ToolTip="{x:Bind YearStr}"/>
<TextBlock Grid.Column="6"
HorizontalAlignment="Left" VerticalAlignment="Center"
FontSize="12"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.SettingsViewModel.ElementTheme), Mode=OneWay}"
Text="{x:Bind GenreStr}"
TextTrimming="CharacterEllipsis"
ToolTipService.ToolTip="{x:Bind GenreStr}"/>
<TextBlock Grid.Column="7"
Margin="0,0,10,0" HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="12"
Foreground="{x:Bind GetTextForeground(model:Data.MusicPlayer.CurrentMusic, model:Data.SettingsViewModel.ElementTheme), Mode=OneWay}"
Text="{x:Bind DurationStr}"
TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="{StaticResource NavigationViewPageContentMargin}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Click="{x:Bind model:Data.MusicPlayer.ShuffleModeUpdate}"
Style="{StaticResource AccentButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="12" Glyph="&#xE8B1;"/>
<TextBlock Text=" 随机播放"/>
</StackPanel>
</Button>
<StackPanel Grid.Column="2"
Orientation="Horizontal" Spacing="16">
<Button Background="Transparent" BorderBrush="Transparent">
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="排序方式:"/>
<TextBlock Foreground="{ThemeResource AccentTextFillColorTertiaryBrush}" Text="已添加日期"/>
<FontIcon Margin="8,0,0,0"
FontSize="12" Glyph="&#xE70D;"/>
</StackPanel>
<Button.Flyout>
<Flyout Placement="Bottom">
<ListView x:Name="SortListView"
Margin="-12,-13,-12,-15"
SelectedIndex="4">
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="A-Z"/>
</ListViewItem>
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="歌手"/>
</ListViewItem>
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="专辑"/>
</ListViewItem>
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="发行年份"/>
</ListViewItem>
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="已添加日期"/>
</ListViewItem>
</ListView>
</Flyout>
</Button.Flyout>
</Button>
<Button Background="Transparent" BorderBrush="Transparent">
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="流派:"/>
<TextBlock Foreground="{ThemeResource AccentTextFillColorTertiaryBrush}" Text="所有流派"/>
<FontIcon Margin="8,0,0,0"
FontSize="12" Glyph="&#xE70D;"/>
</StackPanel>
<Button.Flyout>
<Flyout Placement="Bottom">
<ListView x:Name="GenreListView"
Margin="-12,-13,-12,-15"
SelectedIndex="0">
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="所有流派"/>
</ListViewItem>
<ListViewItem Margin="0,2,0,2">
<TextBlock Text="未知流派"/>
</ListViewItem>
</ListView>
</Flyout>
</Button.Flyout>
</Button>
</StackPanel>
</Grid>
<ListView x:Name="SongListView"
Grid.Row="2"
Margin="0,24,0,0" Padding="52,0,52,0"
CanDragItems="True" IsItemClickEnabled="True"
ItemClick="{x:Bind ViewModel.SongListView_ItemClick}"
ItemTemplate="{StaticResource SongListViewTemplate}"
ItemsSource="{x:Bind model:Data.MusicLibrary.Musics, Mode=OneWay}"
SelectionMode="None">
<interactivity:Interaction.Behaviors>
<helper:AlternatingListViewBehavior AlternateBackground="#FDFEFE" DarkThemeBackground="#303539"/>
</interactivity:Interaction.Behaviors>
</ListView>
</Grid>
</Page>

View File

@@ -0,0 +1,35 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
public sealed partial class Page : Page
{
public ViewModel ViewModel
{
get;
}
public Page()
{
ViewModel = App.GetService<ViewModel>();
InitializeComponent();
//SongListView.ContainerContentChanging += SongListView_Loaded;
}
private void Grid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
ViewModel.Grid_PointerEntered(sender, e);
}
private void Grid_PointerExited(object sender, PointerRoutedEventArgs e)
{
ViewModel.Grid_PointerExited(sender, e);
}
private void PlayButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.PlayButton_Click(sender, e);
}
}

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" ?>
<Page x:Class="The_Untamed_Music_Player.Views.歌词Page"
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.Models"
xmlns:localmodel="using:The_Untamed_Music_Player.ViewModels"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:media="using:CommunityToolkit.WinUI.Media" xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Page.Resources>
<media:AttachedCardShadow x:Key="CommonShadow" Offset="4"/>
<DataTemplate x:Key="LyricViewTemplate" x:DataType="local:LyricSlice">
<TextBlock Margin="{x:Bind local:Data.MusicPlayer.GetLyricMargin(Time, local:Data.MusicPlayer.CurrentLyricIndex), Mode=OneWay}"
FontFamily="{x:Bind local:Data.SettingsViewModel.SelectedFont, Mode=OneWay}"
FontSize="{x:Bind local:Data.MusicPlayer.GetLyricFont(Time, local:Data.MusicPlayer.CurrentLyricIndex), Mode=OneWay}"
Opacity="{x:Bind local:Data.MusicPlayer.GetLyricOpacity(Time, local:Data.MusicPlayer.CurrentLyricIndex), Mode=OneWay}"
SizeChanged="TextBlock_SizeChanged"
Text="{x:Bind Content, Mode=OneWay}"
TextWrapping="WrapWholeWords"/>
</DataTemplate>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="AppTitleBar"
Grid.Row="0"
Height="33"
VerticalAlignment="Center"
Canvas.ZIndex="1" CornerRadius="8,8,0,0"
IsHitTestVisible="True">
<StackPanel Orientation="Horizontal">
<Button Width="48" Height="34"
Click="{x:Bind local:Data.RootPlayBarViewModel.CoverBtnClickToDetail}"
FontSize="12"
Style="{StaticResource NavigationBackButtonNormalStyle}"/>
<Image Width="16" Height="16"
Margin="12,0,0,0" HorizontalAlignment="Left"
Source="/Assets/WindowIcon.ico"/>
<TextBlock x:Name="AppTitleBarText"
Margin="12,0,0,0" VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
Text="Untamed Music Player" TextWrapping="NoWrap"/>
</StackPanel>
</Grid>
<Grid x:Name="ContentGrid"
Grid.Row="1" Grid.ColumnSpan="2">
<Grid.Background>
<ImageBrush x:Name="ContentGridBackground"
ImageSource="{x:Bind local:Data.MusicPlayer.CurrentMusic.Cover, Mode=OneWay}"
Stretch="UniformToFill"/>
</Grid.Background>
<Grid x:Name="SecondaryContentGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.2*"/>
</Grid.ColumnDefinitions>
<Border x:Name="ShadowTarget"/>
<Border Grid.Column="0"
Width="400" Height="400"
Margin="0,0,0,0" Padding="0"
CornerRadius="10">
<Canvas>
<Image x:Name="ControllerCover"
Width="400" Height="400"
HorizontalAlignment="Left" VerticalAlignment="Center"
Canvas.ZIndex="1"
Source="{x:Bind local:Data.MusicPlayer.CurrentMusic.Cover, Mode=OneWay}"/>
<Grid Width="400" Height="400"
Background="{ThemeResource SolidBackgroundFillColorSecondaryBrush}"
Canvas.ZIndex="0">
<FontIcon Canvas.ZIndex="0" FontSize="100"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE8D6;"/>
</Grid>
</Canvas>
<ui:Effects.Shadow>
<ui:AttachedDropShadow BlurRadius="15"
CastTo="{x:Bind ShadowTarget}"
CornerRadius="10" Opacity="0.2"
Offset="20,20"/>
</ui:Effects.Shadow>
</Border>
<ScrollViewer x:Name="LyricViewer"
Grid.Column="1"
Margin="20,20,50,20"
VerticalScrollBarVisibility="Hidden">
<StackPanel>
<StackPanel Height="600"/>
<ListView IsItemClickEnabled="True"
ItemClick="{x:Bind ViewModel.ListView_ItemClick}"
ItemTemplate="{StaticResource LyricViewTemplate}"
ItemsSource="{x:Bind local:Data.MusicPlayer.CurrentLyric, Mode=OneWay}"
SelectionMode="None"/>
<StackPanel Height="600"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,98 @@
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.ViewModels;
using Windows.Foundation;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace The_Untamed_Music_Player.Views;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class Page : Page
{
public ViewModel ViewModel
{
get;
}
public Page()
{
ViewModel = App.GetService<ViewModel>();
InitializeComponent();
MusicPlayer.UI = this;
// 设置 ContentGridBackground 的绑定
/*var contentGridBinding = new Binding
{
Path = new PropertyPath("CurrentMusic.Cover"),
Source = Data.MusicPlayer,
Mode = BindingMode.OneWay
};
BindingOperations.SetBinding(ContentGridBackground, ImageBrush.ImageSourceProperty, contentGridBinding);*/
var isLyricBackgroundVisible = Data.SettingsViewModel?.IsLyricBackgroundVisible;
if (isLyricBackgroundVisible == false)
{
ContentGridBackground.Opacity = 0;
}
else
{
var acrylicBrush = new AcrylicBrush
{
TintOpacity = 0.8,
};
var currentTheme = Data.SettingsViewModel?.ElementTheme;
if (currentTheme == ElementTheme.Dark)
{
acrylicBrush.TintColor = Colors.Black;
}
else
{
acrylicBrush.TintColor = Colors.White;
}
SecondaryContentGrid.Background = acrylicBrush;
}
}
private void TextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
{
var textblock = (TextBlock)sender;
if (Data.MainWindow != null)
{
try
{
LyricViewer.Height = Data.MainWindow.Height - 117 - 40;
}
catch//切换到后台
{
LyricViewer.Height = 0;
}
}
else
{
LyricViewer.Height = 0;
}
if (textblock.FontSize == 50 || textblock.FontSize == 24)
{
var currentScrollPosition = LyricViewer.VerticalOffset;
var point = new Point(0, currentScrollPosition);
// 计算出目标位置并滚动
var targetPosition = textblock.TransformToVisual(LyricViewer).TransformPoint(point);
if (textblock.FontSize == 24)
{
LyricViewer.ChangeView(null, targetPosition.Y, null, disableAnimation: false);
}
else
{
LyricViewer.ChangeView(null, targetPosition.Y - LyricViewer.Height / 2 + 40, null, disableAnimation: false);
}
}
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" ?>
<Page x:Class="The_Untamed_Music_Player.Views.艺术家Page"
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"
NavigationCacheMode="Enabled"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0"
Margin="{StaticResource NavigationViewPageContentMargin}"
Style="{StaticResource AccentButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="12" Glyph="&#xE8B1;"/>
<TextBlock Text=" 随机播放"/>
</StackPanel>
</Button>
</Grid>
</Page>

View File

@@ -0,0 +1,17 @@
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
public sealed partial class Page : Page
{
public ViewModel ViewModel
{
get;
}
public Page()
{
ViewModel = App.GetService<ViewModel>();
InitializeComponent();
}
}

Some files were not shown because too many files have changed in this diff Show More