mirror of
https://github.com/LanZhan-Harmony/WindowsMusicPlayer-TheUntamedMusicPlayer.git
synced 2026-05-06 11:10:16 +08:00
添加项目文件。
This commit is contained in:
202
.editorconfig
Normal file
202
.editorconfig
Normal 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
63
.gitattributes
vendored
Normal 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
363
.gitignore
vendored
Normal 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
16
.vsconfig
Normal 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"
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
22
The Untamed Music Player.Core/Helpers/Json.cs
Normal file
22
The Untamed Music Player.Core/Helpers/Json.cs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
5
The Untamed Music Player.Core/README.md
Normal file
5
The Untamed Music Player.Core/README.md
Normal 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).
|
||||
41
The Untamed Music Player.Core/Services/FileService.cs
Normal file
41
The Untamed Music Player.Core/Services/FileService.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
69
The Untamed Music Player.sln
Normal file
69
The Untamed Music Player.sln
Normal 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
|
||||
17
The Untamed Music Player/Activation/ActivationHandler.cs
Normal file
17
The Untamed Music Player/Activation/ActivationHandler.cs
Normal 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)!);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace The_Untamed_Music_Player.Activation;
|
||||
|
||||
public interface IActivationHandler
|
||||
{
|
||||
bool CanHandle(object args);
|
||||
|
||||
Task HandleAsync(object args);
|
||||
}
|
||||
15
The Untamed Music Player/App.xaml
Normal file
15
The Untamed Music Player/App.xaml
Normal 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>
|
||||
125
The Untamed Music Player/App.xaml.cs
Normal file
125
The Untamed Music Player/App.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace The_Untamed_Music_Player.Behaviors;
|
||||
|
||||
public enum NavigationViewHeaderMode
|
||||
{
|
||||
Always,
|
||||
Never,
|
||||
Minimal
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace The_Untamed_Music_Player.Contracts.Services;
|
||||
|
||||
public interface IActivationService
|
||||
{
|
||||
Task ActivateAsync(object activationArgs);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace The_Untamed_Music_Player.Contracts.Services;
|
||||
|
||||
public interface IPageService
|
||||
{
|
||||
Type GetPageType(string key);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace The_Untamed_Music_Player.Contracts.ViewModels;
|
||||
|
||||
public interface INavigationAware
|
||||
{
|
||||
void OnNavigatedTo(object parameter);
|
||||
|
||||
void OnNavigatedFrom();
|
||||
}
|
||||
177
The Untamed Music Player/Helpers/AlternatingListViewBehavior.cs
Normal file
177
The Untamed Music Player/Helpers/AlternatingListViewBehavior.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
The Untamed Music Player/Helpers/Backdrop.cs
Normal file
30
The Untamed Music Player/Helpers/Backdrop.cs
Normal 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();
|
||||
}
|
||||
38
The Untamed Music Player/Helpers/EnumToBooleanConverter.cs
Normal file
38
The Untamed Music Player/Helpers/EnumToBooleanConverter.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
8
The Untamed Music Player/Helpers/FrameExtensions.cs
Normal file
8
The Untamed Music Player/Helpers/FrameExtensions.cs
Normal 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);
|
||||
}
|
||||
21
The Untamed Music Player/Helpers/NavigationHelper.cs
Normal file
21
The Untamed Music Player/Helpers/NavigationHelper.cs
Normal 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));
|
||||
}
|
||||
10
The Untamed Music Player/Helpers/ResourceExtensions.cs
Normal file
10
The Untamed Music Player/Helpers/ResourceExtensions.cs
Normal 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);
|
||||
}
|
||||
20
The Untamed Music Player/Helpers/RuntimeHelper.cs
Normal file
20
The Untamed Music Player/Helpers/RuntimeHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
112
The Untamed Music Player/Helpers/SettingsStorageExtensions.cs
Normal file
112
The Untamed Music Player/Helpers/SettingsStorageExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
95
The Untamed Music Player/Helpers/TitleBarHelper.cs
Normal file
95
The Untamed Music Player/Helpers/TitleBarHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
The Untamed Music Player/MainWindow.xaml
Normal file
26
The Untamed Music Player/MainWindow.xaml
Normal 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>
|
||||
361
The Untamed Music Player/MainWindow.xaml.cs
Normal file
361
The Untamed Music Player/MainWindow.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
The Untamed Music Player/Models/AlbumInfo.cs
Normal file
117
The Untamed Music Player/Models/AlbumInfo.cs
Normal 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} 歌曲长度";
|
||||
}
|
||||
}
|
||||
}
|
||||
142
The Untamed Music Player/Models/ArtistInfo.cs
Normal file
142
The Untamed Music Player/Models/ArtistInfo.cs
Normal 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} 秒";
|
||||
}
|
||||
}
|
||||
}
|
||||
21
The Untamed Music Player/Models/Data.cs
Normal file
21
The Untamed Music Player/Models/Data.cs
Normal 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;
|
||||
}
|
||||
14
The Untamed Music Player/Models/LocalSettingsOptions.cs
Normal file
14
The Untamed Music Player/Models/LocalSettingsOptions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace The_Untamed_Music_Player.Models;
|
||||
|
||||
public class LocalSettingsOptions
|
||||
{
|
||||
public string? ApplicationDataFolder
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string? LocalSettingsFile
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
126
The Untamed Music Player/Models/LyricSlice.cs
Normal file
126
The Untamed Music Player/Models/LyricSlice.cs
Normal 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 列表按时间戳排序。
|
||||
}
|
||||
}
|
||||
505
The Untamed Music Player/Models/MusicInfo.cs
Normal file
505
The Untamed Music Player/Models/MusicInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
347
The Untamed Music Player/Models/MusicLibrary.cs
Normal file
347
The Untamed Music Player/Models/MusicLibrary.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
975
The Untamed Music Player/Models/MusicPlayer.cs
Normal file
975
The Untamed Music Player/Models/MusicPlayer.cs
Normal 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();
|
||||
}*/
|
||||
}
|
||||
}
|
||||
17
The Untamed Music Player/Package.appinstaller
Normal file
17
The Untamed Music Player/Package.appinstaller
Normal 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>
|
||||
76
The Untamed Music Player/Package.appxmanifest
Normal file
76
The Untamed Music Player/Package.appxmanifest
Normal 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>
|
||||
10
The Untamed Music Player/Properties/launchsettings.json
Normal file
10
The Untamed Music Player/Properties/launchsettings.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"The Untamed Music Player (Package)": {
|
||||
"commandName": "MsixPackage"
|
||||
},
|
||||
"The Untamed Music Player (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
27
The Untamed Music Player/README.md
Normal file
27
The Untamed Music Player/README.md
Normal 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.
|
||||
72
The Untamed Music Player/Services/ActivationService.cs
Normal file
72
The Untamed Music Player/Services/ActivationService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
86
The Untamed Music Player/Services/LocalSettingsService.cs
Normal file
86
The Untamed Music Player/Services/LocalSettingsService.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
126
The Untamed Music Player/Services/NavigationService.cs
Normal file
126
The Untamed Music Player/Services/NavigationService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
103
The Untamed Music Player/Services/NavigationViewService.cs
Normal file
103
The Untamed Music Player/Services/NavigationViewService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
59
The Untamed Music Player/Services/PageService.cs
Normal file
59
The Untamed Music Player/Services/PageService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
The Untamed Music Player/Services/ThemeSelectorService.cs
Normal file
63
The Untamed Music Player/Services/ThemeSelectorService.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
339
The Untamed Music Player/Strings/en-us/Resources.resw
Normal file
339
The Untamed Music Player/Strings/en-us/Resources.resw
Normal 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>
|
||||
339
The Untamed Music Player/Strings/zh-cn/Resources.resw
Normal file
339
The Untamed Music Player/Strings/zh-cn/Resources.resw
Normal 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>
|
||||
153
The Untamed Music Player/Styles/ButtonStyle.xaml
Normal file
153
The Untamed Music Player/Styles/ButtonStyle.xaml
Normal 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>
|
||||
9
The Untamed Music Player/Styles/FontSizes.xaml
Normal file
9
The Untamed Music Player/Styles/FontSizes.xaml
Normal 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>
|
||||
20
The Untamed Music Player/Styles/TextBlock.xaml
Normal file
20
The Untamed Music Player/Styles/TextBlock.xaml
Normal 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>
|
||||
36
The Untamed Music Player/Styles/Thickness.xaml
Normal file
36
The Untamed Music Player/Styles/Thickness.xaml
Normal 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>
|
||||
11
The Untamed Music Player/TemplateStudio.xml
Normal file
11
The Untamed Music Player/TemplateStudio.xml
Normal 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>
|
||||
113
The Untamed Music Player/The Untamed Music Player.csproj
Normal file
113
The Untamed Music Player/The Untamed Music Player.csproj
Normal 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>
|
||||
1
The Untamed Music Player/Usings.cs
Normal file
1
The Untamed Music Player/Usings.cs
Normal file
@@ -0,0 +1 @@
|
||||
global using WinUIEx;
|
||||
248
The Untamed Music Player/ViewModels/RootPlayBarViewModel.cs
Normal file
248
The Untamed Music Player/ViewModels/RootPlayBarViewModel.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
390
The Untamed Music Player/ViewModels/SettingsViewModel.cs
Normal file
390
The Untamed Music Player/ViewModels/SettingsViewModel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
51
The Untamed Music Player/ViewModels/ShellViewModel.cs
Normal file
51
The Untamed Music Player/ViewModels/ShellViewModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
The Untamed Music Player/ViewModels/专辑ViewModel.cs
Normal file
8
The Untamed Music Player/ViewModels/专辑ViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace The_Untamed_Music_Player.ViewModels;
|
||||
|
||||
public class 专辑ViewModel
|
||||
{
|
||||
public 专辑ViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
10
The Untamed Music Player/ViewModels/主页ViewModel.cs
Normal file
10
The Untamed Music Player/ViewModels/主页ViewModel.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace The_Untamed_Music_Player.ViewModels;
|
||||
|
||||
public partial class 主页ViewModel : ObservableRecipient
|
||||
{
|
||||
public 主页ViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
10
The Untamed Music Player/ViewModels/播放列表ViewModel.cs
Normal file
10
The Untamed Music Player/ViewModels/播放列表ViewModel.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace The_Untamed_Music_Player.ViewModels;
|
||||
|
||||
public partial class 播放列表ViewModel : ObservableRecipient
|
||||
{
|
||||
public 播放列表ViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
75
The Untamed Music Player/ViewModels/播放队列ViewModel.cs
Normal file
75
The Untamed Music Player/ViewModels/播放队列ViewModel.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
7
The Untamed Music Player/ViewModels/无音乐ViewModel.cs
Normal file
7
The Untamed Music Player/ViewModels/无音乐ViewModel.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace The_Untamed_Music_Player.ViewModels;
|
||||
public class 无音乐ViewModel
|
||||
{
|
||||
public 无音乐ViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
8
The Untamed Music Player/ViewModels/有音乐ViewModel.cs
Normal file
8
The Untamed Music Player/ViewModels/有音乐ViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace The_Untamed_Music_Player.ViewModels;
|
||||
|
||||
public class 有音乐ViewModel
|
||||
{
|
||||
public 有音乐ViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
75
The Untamed Music Player/ViewModels/歌曲ViewModel.cs
Normal file
75
The Untamed Music Player/ViewModels/歌曲ViewModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
The Untamed Music Player/ViewModels/歌词ViewModel.cs
Normal file
21
The Untamed Music Player/ViewModels/歌词ViewModel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
The Untamed Music Player/ViewModels/艺术家ViewModel.cs
Normal file
8
The Untamed Music Player/ViewModels/艺术家ViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace The_Untamed_Music_Player.ViewModels;
|
||||
|
||||
public class 艺术家ViewModel
|
||||
{
|
||||
public 艺术家ViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
10
The Untamed Music Player/ViewModels/音乐库ViewModel.cs
Normal file
10
The Untamed Music Player/ViewModels/音乐库ViewModel.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace The_Untamed_Music_Player.ViewModels;
|
||||
|
||||
public partial class 音乐库ViewModel : ObservableRecipient
|
||||
{
|
||||
public 音乐库ViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
375
The Untamed Music Player/Views/RootPlayBarView.xaml
Normal file
375
The Untamed Music Player/Views/RootPlayBarView.xaml
Normal 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=""/>
|
||||
<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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</Button>
|
||||
|
||||
<!-- 最小化置顶 -->
|
||||
<Button x:Uid="PlayBar_Mini"
|
||||
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
|
||||
Style="{StaticResource PlayButtonStyle}">
|
||||
<FontIcon FontSize="16" Glyph=""/>
|
||||
</Button>
|
||||
|
||||
<!-- 更多菜单 -->
|
||||
<Button x:Uid="PlayBar_More" Style="{StaticResource PlayButtonStyle}">
|
||||
<FontIcon FontSize="16" Glyph=""/>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
|
||||
<!-- 歌曲属性 -->
|
||||
<MenuFlyoutItem x:Uid="PlayBar_Property" IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
<MenuFlyoutItem.KeyboardAccelerators>
|
||||
<KeyboardAccelerator Key="F11"/>
|
||||
</MenuFlyoutItem.KeyboardAccelerators>
|
||||
</MenuFlyoutItem>
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
||||
31
The Untamed Music Player/Views/RootPlayBarView.xaml.cs
Normal file
31
The Untamed Music Player/Views/RootPlayBarView.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
244
The Untamed Music Player/Views/SettingsPage.xaml
Normal file
244
The Untamed Music Player/Views/SettingsPage.xaml
Normal 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=""/>
|
||||
</toolkit:SettingsExpander.HeaderIcon>
|
||||
<Button x:Name="addMusicFolder" Click="{x:Bind ViewModel.PickMusicFolderButton_Click}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph=""/>
|
||||
<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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</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>
|
||||
|
||||
83
The Untamed Music Player/Views/SettingsPage.xaml.cs
Normal file
83
The Untamed Music Player/Views/SettingsPage.xaml.cs
Normal 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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
72
The Untamed Music Player/Views/ShellPage.xaml
Normal file
72
The Untamed Music Player/Views/ShellPage.xaml
Normal 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=""/>
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem x:Uid="Shell_音乐库" helpers:NavigationHelper.NavigateTo="The_Untamed_Music_Player.ViewModels.音乐库ViewModel">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItemSeparator/>
|
||||
<NavigationViewItem x:Uid="Shell_播放队列" helpers:NavigationHelper.NavigateTo="The_Untamed_Music_Player.ViewModels.播放队列ViewModel">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem x:Uid="Shell_播放列表" helpers:NavigationHelper.NavigateTo="The_Untamed_Music_Player.ViewModels.播放列表ViewModel">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
</NavigationView.MenuItems>
|
||||
<Grid>
|
||||
<Frame x:Name="NavigationFrame"/>
|
||||
</Grid>
|
||||
</NavigationView>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
95
The Untamed Music Player/Views/ShellPage.xaml.cs
Normal file
95
The Untamed Music Player/Views/ShellPage.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
79
The Untamed Music Player/Views/专辑Page.xaml
Normal file
79
The Untamed Music Player/Views/专辑Page.xaml
Normal 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=""/>
|
||||
<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=""/>
|
||||
</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=""/>
|
||||
</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>
|
||||
18
The Untamed Music Player/Views/专辑Page.xaml.cs
Normal file
18
The Untamed Music Player/Views/专辑Page.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
40
The Untamed Music Player/Views/主页Page.xaml
Normal file
40
The Untamed Music Player/Views/主页Page.xaml
Normal 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>
|
||||
19
The Untamed Music Player/Views/主页Page.xaml.cs
Normal file
19
The Untamed Music Player/Views/主页Page.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
40
The Untamed Music Player/Views/播放列表Page.xaml
Normal file
40
The Untamed Music Player/Views/播放列表Page.xaml
Normal 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>
|
||||
19
The Untamed Music Player/Views/播放列表Page.xaml.cs
Normal file
19
The Untamed Music Player/Views/播放列表Page.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
308
The Untamed Music Player/Views/播放队列Page.xaml
Normal file
308
The Untamed Music Player/Views/播放队列Page.xaml
Normal 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=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem Text="加入播放队列">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutSeparator/>
|
||||
<MenuFlyoutSubItem Text="添加到">
|
||||
<MenuFlyoutSubItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutSubItem.Icon>
|
||||
<MenuFlyoutItem Text="播放队列">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutSeparator/>
|
||||
<MenuFlyoutItem Text="新建播放列表">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
</MenuFlyoutSubItem>
|
||||
<MenuFlyoutItem Text="删除">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem Text="上移">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem Text="下移">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem Text="属性">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem Text="显示专辑">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem Text="显示艺术家">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutSeparator/>
|
||||
<MenuFlyoutItem Text="选择">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
<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=""/>
|
||||
<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=""/>
|
||||
<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=""/>
|
||||
<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=""/>
|
||||
<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=""/>
|
||||
<TextBlock FontSize="12" Text="清除"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="84" Height="36"
|
||||
Padding="0">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph=""/>
|
||||
<TextBlock FontSize="12" Text="添加到"/>
|
||||
</StackPanel>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout Placement="Bottom">
|
||||
<MenuFlyoutItem Text="播放队列">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutSeparator/>
|
||||
<MenuFlyoutItem Text="新建播放列表">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</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>
|
||||
34
The Untamed Music Player/Views/播放队列Page.xaml.cs
Normal file
34
The Untamed Music Player/Views/播放队列Page.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
70
The Untamed Music Player/Views/无音乐Page.xaml
Normal file
70
The Untamed Music Player/Views/无音乐Page.xaml
Normal 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=""/>
|
||||
<TextBlock x:Uid="Settings_AddFolder"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
20
The Untamed Music Player/Views/无音乐Page.xaml.cs
Normal file
20
The Untamed Music Player/Views/无音乐Page.xaml.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
71
The Untamed Music Player/Views/有音乐Page.xaml
Normal file
71
The Untamed Music Player/Views/有音乐Page.xaml
Normal 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=""/>
|
||||
<TextBlock x:Name="AddTextBlock" x:Uid="Settings_AddFolder"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Frame x:Name="SelectFrame"
|
||||
Grid.Row="1"
|
||||
IsNavigationStackEnabled="False"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
39
The Untamed Music Player/Views/有音乐Page.xaml.cs
Normal file
39
The Untamed Music Player/Views/有音乐Page.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
236
The Untamed Music Player/Views/歌曲Page.xaml
Normal file
236
The Untamed Music Player/Views/歌曲Page.xaml
Normal 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=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem Text="加入播放队列">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutSeparator/>
|
||||
<MenuFlyoutSubItem Text="添加到">
|
||||
<MenuFlyoutSubItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutSubItem.Icon>
|
||||
<MenuFlyoutItem Text="播放队列">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutSeparator/>
|
||||
<MenuFlyoutItem Text="新建播放列表">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
</MenuFlyoutSubItem>
|
||||
<MenuFlyoutItem Text="编辑信息">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem Text="属性">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem Text="显示专辑">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem Text="显示艺术家">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutSeparator/>
|
||||
<MenuFlyoutItem Text="选择">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
<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=""/>
|
||||
</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=""/>
|
||||
</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>
|
||||
35
The Untamed Music Player/Views/歌曲Page.xaml.cs
Normal file
35
The Untamed Music Player/Views/歌曲Page.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
105
The Untamed Music Player/Views/歌词Page.xaml
Normal file
105
The Untamed Music Player/Views/歌词Page.xaml
Normal 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=""/>
|
||||
</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>
|
||||
98
The Untamed Music Player/Views/歌词Page.xaml.cs
Normal file
98
The Untamed Music Player/Views/歌词Page.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
The Untamed Music Player/Views/艺术家Page.xaml
Normal file
25
The Untamed Music Player/Views/艺术家Page.xaml
Normal 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=""/>
|
||||
<TextBlock Text=" 随机播放"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Page>
|
||||
17
The Untamed Music Player/Views/艺术家Page.xaml.cs
Normal file
17
The Untamed Music Player/Views/艺术家Page.xaml.cs
Normal 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
Reference in New Issue
Block a user