更新背景Service

This commit is contained in:
LanZhan
2025-09-17 13:24:32 +08:00
parent 385e7a4999
commit 7c41f38c56
33 changed files with 261 additions and 299 deletions

View File

@@ -30,7 +30,7 @@ public partial class App : Application
if ((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."
$"{typeof(T)} 需要在 App.xaml.cs 的 ConfigureServices 中注册"
);
}
@@ -116,7 +116,7 @@ public partial class App : Application
UnhandledException += App_UnhandledException;
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
MainWindow = new MainWindow();

View File

@@ -1,3 +1,4 @@
using Microsoft.UI.Xaml;
using The_Untamed_Music_Player.Services;
using Windows.UI;
@@ -42,7 +43,7 @@ public interface IColorExtractionService
/// <summary>
/// 动态背景服务接口
/// </summary>
public interface IDynamicBackgroundService
public interface IDynamicBackgroundService : IDisposable
{
/// <summary>
/// 是否启用
@@ -58,16 +59,11 @@ public interface IDynamicBackgroundService
/// 初始化动态背景服务
/// </summary>
/// <param name="targetElement">目标元素</param>
void Initialize(Microsoft.UI.Xaml.FrameworkElement targetElement);
Task InitializeAsync(FrameworkElement? targetElement = null);
/// <summary>
/// 手动更新背景
/// </summary>
/// <returns></returns>
Task UpdateBackgroundAsync();
/// <summary>
/// 清理资源
/// </summary>
void Dispose();
}

View File

@@ -2,28 +2,67 @@ using Windows.UI;
namespace The_Untamed_Music_Player.Contracts.Services;
public interface IMaterialSelectorService
public interface IMaterialSelectorService : IDisposable
{
MaterialType Material { get; }
bool IsFallBack { get; }
byte LuminosityOpacity { get; }
Color TintColor { get; }
Task InitializeAsync();
Task<(byte, Color)> SetMaterial(MaterialType material);
Task<(byte, Color)> SetMaterial(
MaterialType material,
bool firstStart = false,
bool forced = false
);
void SetIsFallBack(bool isFallBack);
void SetLuminosityOpacity(byte opacity);
void SetTintColor(Color color);
void SetLuminosityOpacity(byte opacity, bool firstStart = false);
void SetTintColor(Color color, bool firstStart = false);
}
public enum MaterialType
{
/// <summary>
/// 无背景
/// </summary>
None = 0,
/// <summary>
/// 云母
/// </summary>
Mica = 1,
/// <summary>
/// 云母Alt
/// </summary>
MicaAlt = 2,
/// <summary>
/// 桌面亚克力
/// </summary>
DesktopAcrylic = 3,
/// <summary>
/// 基础亚克力
/// </summary>
AcrylicBase = 4,
/// <summary>
/// 薄亚克力
/// </summary>
AcrylicThin = 5,
/// <summary>
/// 模糊
/// </summary>
Blur = 6,
/// <summary>
/// 透明
/// </summary>
Transparent = 7,
/// <summary>
/// 变色
/// </summary>
Animated = 8,
}

View File

@@ -4,8 +4,8 @@ namespace The_Untamed_Music_Player.Contracts.Services;
public interface IThemeSelectorService
{
ElementTheme Theme { get; }
Task InitializeAsync();
Task SetThemeAsync(ElementTheme theme);
Task SetRequestedThemeAsync();
ElementTheme Theme { get; set; }
void Initialize();
void SetThemeAsync(ElementTheme theme);
void SetRequestedThemeAsync();
}

View File

@@ -54,7 +54,7 @@ public sealed partial class EditAlbumInfoDialog : ContentDialog, INotifyProperty
_year = $"{info.Year}";
Cover = info.Cover;
_isSaveCoverButtonEnabled = Cover is not null;
RequestedTheme = Data.MainViewModel!.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
RequestedTheme = ThemeSelectorService.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
InitializeComponent();
}

View File

@@ -132,7 +132,7 @@ public sealed partial class EditPlaylistInfoDialog : ContentDialog, INotifyPrope
_coverPaths = [.. info.CoverPaths]; // 注意要创建副本
IsDeleteCoverButtonEnabled = Cover is not null;
IsSaveCoverButtonEnabled = Cover is not null && _isCoverEdited;
RequestedTheme = Data.MainViewModel!.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
RequestedTheme = ThemeSelectorService.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
InitializeComponent();
}

View File

@@ -83,7 +83,7 @@ public sealed partial class EditSongInfoDialog : ContentDialog, INotifyPropertyC
_lyric = detailedInfo.Lyric;
Cover = detailedInfo.Cover;
IsSaveCoverButtonEnabled = Cover is not null;
RequestedTheme = Data.MainViewModel!.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
RequestedTheme = ThemeSelectorService.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
InitializeComponent();
LyricEditor.Document.SetText(TextSetOptions.None, _lyric);
}

View File

@@ -1,6 +1,6 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Services;
namespace The_Untamed_Music_Player.Controls;
@@ -8,7 +8,7 @@ public sealed partial class EqualizerDialog : ContentDialog
{
public EqualizerDialog()
{
RequestedTheme = Data.MainViewModel!.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
RequestedTheme = ThemeSelectorService.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
InitializeComponent();
}
}

View File

@@ -96,7 +96,7 @@ public sealed partial class ImportPlaylistDialog : ContentDialog, INotifyPropert
public ImportPlaylistDialog()
{
RequestedTheme = Data.MainViewModel!.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
RequestedTheme = ThemeSelectorService.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
InitializeComponent();
}

View File

@@ -1,6 +1,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Services;
namespace The_Untamed_Music_Player.Controls;
@@ -10,7 +11,7 @@ public sealed partial class NewPlaylistInfoDialog : ContentDialog
public NewPlaylistInfoDialog()
{
RequestedTheme = Data.MainViewModel!.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
RequestedTheme = ThemeSelectorService.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
InitializeComponent();
}

View File

@@ -2,7 +2,7 @@ using System.Diagnostics;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Services;
namespace The_Untamed_Music_Player.Controls;
@@ -13,7 +13,7 @@ public sealed partial class PropertiesDialog : ContentDialog
public PropertiesDialog(IDetailedSongInfoBase info)
{
_song = info;
RequestedTheme = Data.MainViewModel!.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
RequestedTheme = ThemeSelectorService.IsDarkTheme ? ElementTheme.Dark : ElementTheme.Light;
InitializeComponent();
}

View File

@@ -5,6 +5,7 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
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.Messages;
using The_Untamed_Music_Player.Models;
@@ -22,13 +23,15 @@ public sealed partial class MainWindow : WindowEx, IRecipient<LogMessage>
private readonly DispatcherQueue dispatcherQueue;
private readonly UISettings settings;
private readonly ILogger _logger = LoggingService.CreateLogger<MainWindow>();
private InfoBarManager? _infoBarManager;
private readonly InfoBarManager? _infoBarManager;
public MainViewModel ViewModel { get; }
public MainWindow()
{
InitializeComponent();
Data.MainWindow = this;
ViewModel = App.GetService<MainViewModel>();
AppWindow.SetIcon(Path.Combine(AppContext.BaseDirectory, "Assets/AppIcon/Icon.ico"));
Title = "AppDisplayName".GetLocalized();
@@ -38,10 +41,7 @@ public sealed partial class MainWindow : WindowEx, IRecipient<LogMessage>
settings = new UISettings();
settings.ColorValuesChanged += Settings_ColorValuesChanged;
Data.MainWindow = this;
ShellFrame.Navigate(typeof(ShellPage));
ViewModel = App.GetService<MainViewModel>();
// 初始化InfoBar管理器
_infoBarManager = new InfoBarManager(
@@ -135,13 +135,12 @@ public sealed partial class MainWindow : WindowEx, IRecipient<LogMessage>
try
{
Data.MusicPlayer.Dispose();
ViewModel.CleanupDynamicBackgroundService(); // 清理背景服务
ViewModel.CleanupSystemBackdrop(); // 清理系统背景
Data.DesktopLyricWindow?.Close(); // 关闭桌面歌词窗口
Data.DesktopLyricWindow?.Dispose();
StrongReferenceMessenger.Default.Unregister<LogMessage>(this); // 清理消息接收
_infoBarManager?.Dispose(); // 清理InfoBar管理器
_infoBarManager = null;
App.GetService<IMaterialSelectorService>().Dispose();
App.GetService<IDynamicBackgroundService>().Dispose();
LoggingService.Shutdown(); // 关闭日志服务
}
catch (Exception ex)

View File

@@ -1,4 +1,3 @@
using Microsoft.UI.Xaml.Media;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.ViewModels;
@@ -8,11 +7,6 @@ namespace The_Untamed_Music_Player.Models;
public static class Data
{
/// <summary>
/// 是否是第一次使用本软件
/// </summary>
public static bool? NotFirstUsed { get; set; } = null;
/// <summary>
/// 是否正在下载或更改音乐
/// </summary>
@@ -23,20 +17,13 @@ public static class Data
/// </summary>
public static bool IsFileActivationLaunch { get; set; } = false;
public static LocalAlbumInfo? SelectedLocalAlbum { get; set; }
public static LocalArtistInfo? SelectedLocalArtist { get; set; }
public static PlaylistInfo? SelectedPlaylist { get; set; }
public static IBriefOnlineAlbumInfo? SelectedOnlineAlbum { get; set; }
public static IBriefOnlineArtistInfo? SelectedOnlineArtist { get; set; }
public static IBriefOnlinePlaylistInfo? SelectedOnlinePlaylist { get; set; }
/// <summary>
/// 软件显示名称
/// </summary>
public static readonly string AppDisplayName = "AppDisplayName".GetLocalized();
/// <summary>
/// 播放器支持的音频文件类型
/// 支持的音频文件类型
/// </summary>
public static readonly string[] SupportedAudioTypes =
[
@@ -55,6 +42,9 @@ public static class Data
".bwf",
];
/// <summary>
/// 支持的封面图片类型
/// </summary>
public static readonly string[] SupportedCoverTypes =
[
".png",
@@ -69,47 +59,34 @@ public static class Data
".tiff",
];
/// <summary>
/// 是否为独占模式
/// </summary>
public static bool IsExclusiveMode { get; set; }
#region
public static LocalAlbumInfo? SelectedLocalAlbum { get; set; }
public static LocalArtistInfo? SelectedLocalArtist { get; set; }
public static PlaylistInfo? SelectedPlaylist { get; set; }
public static IBriefOnlineAlbumInfo? SelectedOnlineAlbum { get; set; }
public static IBriefOnlineArtistInfo? SelectedOnlineArtist { get; set; }
public static IBriefOnlinePlaylistInfo? SelectedOnlinePlaylist { get; set; }
#endregion
/// <summary>
/// 是否为如果当前位于音乐库歌曲页面且使用文件夹排序方式,点击歌曲仅会将其所在文件夹内的歌曲加入播放队列
/// </summary>
public static bool IsOnlyAddSpecificFolder { get; set; }
/// <summary>
/// 歌词字体
/// </summary>
public static FontFamily SelectedFontFamily { get; set; } = new("Microsoft YaHei");
/// <summary>
/// 歌词字号
/// </summary>
public static double SelectedCurrentFontSize { get; set; } = 50.0;
public static double SelectedNotCurrentFontSize { get; set; } = 20.0;
/// <summary>
/// 是否显示歌词背景
/// </summary>
public static bool IsWindowBackgroundFollowsCover { get; set; } = false;
public static OnlineMusicLibrary OnlineMusicLibrary { get; set; } = new();
public static MusicLibrary MusicLibrary { get; set; } = new();
public static OnlineMusicLibrary OnlineMusicLibrary { get; set; } = new();
public static PlaylistLibrary PlaylistLibrary { get; set; } = new();
public static MusicPlayer MusicPlayer { get; set; } = new();
#region Views
public static MainWindow? MainWindow { get; set; }
public static ShellPage? ShellPage { get; set; }
public static HomePage HomePage { get; set; } = null!;
public static HomePage? HomePage { get; set; }
public static LyricPage? LyricPage { get; set; }
public static RootPlayBarView? RootPlayBarView { get; set; }
public static DesktopLyricWindow? DesktopLyricWindow { get; set; }
public static MainViewModel? MainViewModel { get; set; }
#endregion
#region ViewModels
public static SettingsViewModel? SettingsViewModel { get; set; }
public static ShellViewModel? ShellViewModel { get; set; }
public static RootPlayBarViewModel? RootPlayBarViewModel { get; set; }
public static LocalSongsViewModel? LocalSongsViewModel { get; set; }
public static LocalAlbumsViewModel? LocalAlbumsViewModel { get; set; }
#endregion
}

View File

@@ -22,7 +22,7 @@ public partial class LyricSlice(double time, string content) : ObservableObject
} = false;
[ObservableProperty]
public partial double FontSize { get; set; } = Data.SelectedNotCurrentFontSize;
public partial double FontSize { get; set; } = Settings.LyricPageNotCurrentFontSize;
[ObservableProperty]
public partial Thickness Margin { get; set; } = new(0, 20, 0, 20);
@@ -32,7 +32,9 @@ public partial class LyricSlice(double time, string content) : ObservableObject
public void UpdateStyle()
{
FontSize = IsCurrent ? Data.SelectedCurrentFontSize : Data.SelectedNotCurrentFontSize;
FontSize = IsCurrent
? Settings.LyricPageCurrentFontSize
: Settings.LyricPageNotCurrentFontSize;
Margin = IsCurrent ? new Thickness(0, 40, 0, 40) : new Thickness(0, 20, 0, 20);
Opacity = IsCurrent ? 1.0 : 0.5;
}

View File

@@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Messages;
using The_Untamed_Music_Player.Services;
@@ -109,7 +110,8 @@ public partial class MusicLibrary : ObservableRecipient
_logger.ZLogInformation(ex, $"加载音乐文件夹失败:{path}");
}
}
Data.SettingsViewModel?.NotifyEmptyFolderMessageVisibilityChanged();
Data.SettingsViewModel?.EmptyFolderMessageVisibility =
Folders.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
}
_librarySemaphore.Release();
}

View File

@@ -1443,11 +1443,8 @@ public partial class MusicPlayer
ShuffleMode = await _localSettingsService.ReadSettingAsync<bool>("ShuffleMode");
RepeatMode = await _localSettingsService.ReadSettingAsync<byte>("RepeatMode");
IsMute = await _localSettingsService.ReadSettingAsync<bool>("IsMute");
while (!Data.NotFirstUsed.HasValue)
{
await Task.Delay(100);
}
if (Data.NotFirstUsed.Value)
if (Settings.NotFirstUsed)
{
CurrentVolume = await _localSettingsService.ReadSettingAsync<double>(
"CurrentVolume"

View File

@@ -220,25 +220,15 @@ public static class Settings
?? "Microsoft YaHei"
);
var themeName = await _localSettingsService.ReadSettingAsync<string>(nameof(Theme));
if (Enum.TryParse(themeName, out ElementTheme cacheTheme))
{
Theme = cacheTheme;
}
else
{
Theme = ElementTheme.Default;
}
Theme = Enum.TryParse<ElementTheme>(themeName, out var cacheTheme)
? cacheTheme
: ElementTheme.Default;
var materialName = await _localSettingsService.ReadSettingAsync<string>(
nameof(Material)
);
if (Enum.TryParse(materialName, out MaterialType cacheMaterial))
{
Material = cacheMaterial;
}
else
{
Material = MaterialType.DesktopAcrylic;
}
Material = Enum.TryParse<MaterialType>(materialName, out var cacheMaterial)
? cacheMaterial
: MaterialType.DesktopAcrylic;
if (NotFirstUsed)
{

View File

@@ -1,5 +1,6 @@
using The_Untamed_Music_Player.Activation;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Models;
using ZLinq;
namespace The_Untamed_Music_Player.Services;
@@ -12,18 +13,21 @@ public class ActivationService(IEnumerable<IActivationHandler> activationHandler
App.GetService<IThemeSelectorService>();
private readonly IMaterialSelectorService _materialSelectorService =
App.GetService<IMaterialSelectorService>();
private readonly IDynamicBackgroundService _dynamicBackgroundService =
App.GetService<IDynamicBackgroundService>();
public async Task ActivateAsync(object activationArgs)
{
await InitializeAsync(); // 在激活之前执行任务
await InitializeAsync(); // 在激活之前执行任务
await HandleActivationAsync(activationArgs); // 通过 ActivationHandlers 处理激活
App.MainWindow?.Activate(); // 打开 MainWindow
await StartupAsync(); // 在激活之后执行任务
await StartupAsync(); // 在激活之后执行任务
}
private async Task InitializeAsync()
{
await _themeSelectorService.InitializeAsync().ConfigureAwait(false);
await Settings.InitializeAsync().ConfigureAwait(false);
_themeSelectorService.Initialize();
}
private async Task HandleActivationAsync(object activationArgs)
@@ -40,7 +44,8 @@ public class ActivationService(IEnumerable<IActivationHandler> activationHandler
private async Task StartupAsync()
{
await _themeSelectorService.SetRequestedThemeAsync();
_themeSelectorService.SetRequestedThemeAsync();
await _materialSelectorService.InitializeAsync();
await _dynamicBackgroundService.InitializeAsync();
}
}

View File

@@ -15,7 +15,7 @@ namespace The_Untamed_Music_Player.Services;
/// <summary>
/// 动态背景服务,根据当前播放歌曲的封面颜色动态改变窗口背景
/// </summary>
public class DynamicBackgroundService(IColorExtractionService colorExtractionService)
public partial class DynamicBackgroundService(IColorExtractionService colorExtractionService)
: IDynamicBackgroundService
{
private readonly ILogger _logger = LoggingService.CreateLogger<DynamicBackgroundService>();
@@ -37,7 +37,7 @@ public class DynamicBackgroundService(IColorExtractionService colorExtractionSer
ClearBackground();
}
}
} = Data.IsWindowBackgroundFollowsCover;
}
/// <summary>
/// 背景更新事件
@@ -48,27 +48,15 @@ public class DynamicBackgroundService(IColorExtractionService colorExtractionSer
/// 初始化动态背景服务
/// </summary>
/// <param name="targetElement">目标元素通常是MainWindow的根容器</param>
public void Initialize(FrameworkElement targetElement)
public async Task InitializeAsync(FrameworkElement? targetElement = null)
{
_targetElement = targetElement;
_compositor = ElementCompositionPreview.GetElementVisual(targetElement).Compositor;
IsEnabled = Settings.IsWindowBackgroundFollowsCover;
_targetElement = targetElement ?? Data.MainWindow!.GetBackgroundGrid();
_compositor = ElementCompositionPreview.GetElementVisual(_targetElement).Compositor;
// 监听当前歌曲变化
Data.MusicPlayer.PropertyChanged += OnMusicPlayerPropertyChanged;
}
/// <summary>
/// 清理资源
/// </summary>
public void Dispose()
{
Data.MusicPlayer.PropertyChanged -= OnMusicPlayerPropertyChanged;
// ClearBackground();
_backgroundVisual?.Dispose();
_currentGradientBrush?.Dispose();
_targetElement?.SizeChanged -= OnTargetElementSizeChanged;
await UpdateBackgroundAsync();
}
/// <summary>
@@ -358,4 +346,16 @@ public class DynamicBackgroundService(IColorExtractionService colorExtractionSer
(byte)((b + m) * 255)
);
}
/// <summary>
/// 清理资源
/// </summary>
public void Dispose()
{
Data.MusicPlayer.PropertyChanged -= OnMusicPlayerPropertyChanged;
_backgroundVisual?.Dispose();
_currentGradientBrush?.Dispose();
_targetElement?.SizeChanged -= OnTargetElementSizeChanged;
GC.SuppressFinalize(this);
}
}

View File

@@ -19,7 +19,6 @@ public class MaterialSelectorService : IMaterialSelectorService
IsInputActive = true,
};
private ISystemBackdropControllerWithTargets? _currentBackdropController;
public bool _firstStart = true;
public MaterialType Material
{
get;
@@ -67,11 +66,19 @@ public class MaterialSelectorService : IMaterialSelectorService
IsFallBack = Settings.IsFallBack;
LuminosityOpacity = Settings.LuminosityOpacity;
TintColor = Settings.TintColor;
await SetMaterial(Material);
await SetMaterial(Material, true, true);
}
public async Task<(byte, Color)> SetMaterial(MaterialType material)
public async Task<(byte, Color)> SetMaterial(
MaterialType material,
bool firstStart = false,
bool forced = false
)
{
if (Material == material && !forced)
{
return (LuminosityOpacity, TintColor);
}
_mainWindow.SystemBackdrop = null;
_currentBackdropController?.RemoveAllSystemBackdropTargets();
_currentBackdropController?.Dispose();
@@ -111,11 +118,10 @@ public class MaterialSelectorService : IMaterialSelectorService
};
}
if (_firstStart && ThemeSelectorService.IsDarkTheme == Settings.PreviousIsDarkTheme)
if (firstStart && ThemeSelectorService.IsDarkTheme == Settings.PreviousIsDarkTheme)
{
SetLuminosityOpacity(LuminosityOpacity);
SetTintColor(TintColor);
_firstStart = false;
SetLuminosityOpacity(LuminosityOpacity, true);
SetTintColor(TintColor, true);
}
else
{
@@ -128,8 +134,12 @@ public class MaterialSelectorService : IMaterialSelectorService
public void SetIsFallBack(bool isFallBack) => IsFallBack = isFallBack;
public void SetLuminosityOpacity(byte opacity)
public void SetLuminosityOpacity(byte opacity, bool forced = false)
{
if (LuminosityOpacity == opacity && !forced)
{
return;
}
if (_currentBackdropController is MicaController micaController)
{
micaController.LuminosityOpacity = opacity / 100f;
@@ -141,8 +151,12 @@ public class MaterialSelectorService : IMaterialSelectorService
LuminosityOpacity = opacity;
}
public void SetTintColor(Color color)
public void SetTintColor(Color color, bool forced = false)
{
if (TintColor == color && !forced)
{
return;
}
if (_currentBackdropController is MicaController micaController)
{
micaController.TintColor = color;
@@ -203,11 +217,11 @@ public class MaterialSelectorService : IMaterialSelectorService
{
color = Material switch
{
MaterialType.Mica => Color.FromArgb(255, 32, 32, 32),
MaterialType.MicaAlt => Color.FromArgb(255, 10, 10, 10),
MaterialType.DesktopAcrylic => Color.FromArgb(255, 44, 44, 44),
MaterialType.AcrylicBase => Color.FromArgb(255, 32, 32, 32),
MaterialType.AcrylicThin => Color.FromArgb(255, 84, 84, 84),
MaterialType.Mica => ColorFromHex(0xFF202020),
MaterialType.MicaAlt => ColorFromHex(0xFF0A0A0A),
MaterialType.DesktopAcrylic => ColorFromHex(0xFF2C2C2C),
MaterialType.AcrylicBase => ColorFromHex(0xFF202020),
MaterialType.AcrylicThin => ColorFromHex(0xFF545454),
_ => TintColor,
};
}
@@ -215,11 +229,11 @@ public class MaterialSelectorService : IMaterialSelectorService
{
color = Material switch
{
MaterialType.Mica => Color.FromArgb(255, 243, 243, 243),
MaterialType.MicaAlt => Color.FromArgb(255, 218, 218, 218),
MaterialType.DesktopAcrylic => Color.FromArgb(255, 252, 252, 252),
MaterialType.AcrylicBase => Color.FromArgb(255, 243, 243, 243),
MaterialType.AcrylicThin => Color.FromArgb(255, 211, 211, 211),
MaterialType.Mica => ColorFromHex(0xFFF3F3F3),
MaterialType.MicaAlt => ColorFromHex(0xFFDADADA),
MaterialType.DesktopAcrylic => ColorFromHex(0xFFFCFCFC),
MaterialType.AcrylicBase => ColorFromHex(0xFFF3F3F3),
MaterialType.AcrylicThin => ColorFromHex(0xFFD3D3D3),
_ => TintColor,
};
}
@@ -234,6 +248,15 @@ public class MaterialSelectorService : IMaterialSelectorService
TintColor = color;
}
private static Color ColorFromHex(uint hex)
{
var a = (byte)((hex >> 24) & 0xFF);
var r = (byte)((hex >> 16) & 0xFF);
var g = (byte)((hex >> 8) & 0xFF);
var b = (byte)(hex & 0xFF);
return Color.FromArgb(a, r, g, b);
}
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
if (IsFallBack)
@@ -252,4 +275,14 @@ public class MaterialSelectorService : IMaterialSelectorService
SetConfigurationSourceTheme();
ChangeTheme();
}
public void Dispose()
{
_currentBackdropController?.RemoveAllSystemBackdropTargets();
_currentBackdropController?.Dispose();
_currentBackdropController = null;
_mainWindow.Activated -= MainWindow_Activated;
((FrameworkElement)_mainWindow.Content).ActualThemeChanged -= Window_ThemeChanged;
GC.SuppressFinalize(this);
}
}

View File

@@ -1,57 +1,46 @@
using Microsoft.UI.Xaml;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
namespace The_Untamed_Music_Player.Services;
public class ThemeSelectorService : IThemeSelectorService
{
private const string SettingsKey = "AppBackgroundRequestedTheme";
private readonly ILocalSettingsService _localSettingsService =
App.GetService<ILocalSettingsService>();
public ElementTheme Theme
{
get;
set
{
field = value;
Settings.Theme = value;
}
}
public static bool IsDarkTheme =>
((FrameworkElement)App.MainWindow!.Content).ActualTheme == ElementTheme.Dark
|| (
((FrameworkElement)App.MainWindow!.Content).ActualTheme == ElementTheme.Default
&& App.Current.RequestedTheme == ApplicationTheme.Dark
);
public ElementTheme Theme { get; set; } = ElementTheme.Default;
public async Task InitializeAsync()
public void Initialize()
{
Theme = await LoadThemeFromSettingsAsync();
Theme = Settings.Theme;
}
public async Task SetThemeAsync(ElementTheme theme)
public void SetThemeAsync(ElementTheme theme)
{
Theme = theme;
await SetRequestedThemeAsync();
await SaveThemeInSettingsAsync(Theme);
SetRequestedThemeAsync();
}
public async Task SetRequestedThemeAsync()
public void 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}");
}
}

View File

@@ -136,7 +136,7 @@ public partial class HomeViewModel : ObservableObject
_selectorBar?.SelectedItem = _selectorBar.Items[currentSelectedIndex];
_ = Data.OnlineMusicLibrary.Search();
Data.HomePage.GetFrame()
Data.HomePage?.GetFrame()
.Navigate(
page,
null,

View File

@@ -235,7 +235,7 @@ public partial class LocalSongsViewModel
{
if (_isGrouped)
{
if ((SortMode is 10 or 11) && Data.IsOnlyAddSpecificFolder)
if ((SortMode is 10 or 11) && Settings.IsOnlyAddSpecificFolder)
{
return
[

View File

@@ -1,66 +1,6 @@
using Microsoft.UI;
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using The_Untamed_Music_Player.Contracts.Services;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using Windows.UI;
using WinRT;
using WinUIEx;
namespace The_Untamed_Music_Player.ViewModels;
public class MainViewModel
{
private readonly ILocalSettingsService _localSettingsService =
App.GetService<ILocalSettingsService>();
private IDynamicBackgroundService _dynamicBackgroundService = null!;
private readonly MainWindow _mainWindow;
public bool IsDarkTheme { get; set; }
public MainViewModel()
{
_mainWindow = Data.MainWindow ?? new();
IsDarkTheme =
((FrameworkElement)_mainWindow.Content).ActualTheme == ElementTheme.Dark
|| (
((FrameworkElement)_mainWindow.Content).ActualTheme == ElementTheme.Default
&& App.Current.RequestedTheme == ApplicationTheme.Dark
);
InitializeDynamicBackground();
((FrameworkElement)_mainWindow.Content).ActualThemeChanged += Window_ThemeChanged;
Data.MainViewModel = this;
}
public void InitializeDynamicBackground()
{
_dynamicBackgroundService = App.GetService<IDynamicBackgroundService>();
// 初始化动态背景服务,使用根网格作为目标元素
_dynamicBackgroundService.Initialize(_mainWindow.GetBackgroundGrid());
// 如果当前已有正在播放的歌曲,立即更新背景
_ = _dynamicBackgroundService.UpdateBackgroundAsync();
}
private void Window_ThemeChanged(FrameworkElement sender, object args)
{
IsDarkTheme =
((FrameworkElement)_mainWindow.Content).ActualTheme == ElementTheme.Dark
|| (
((FrameworkElement)_mainWindow.Content).ActualTheme == ElementTheme.Default
&& App.Current.RequestedTheme == ApplicationTheme.Dark
);
}
/// <summary>
/// 清理动态背景服务
/// </summary>
public void CleanupDynamicBackgroundService()
{
_dynamicBackgroundService?.Dispose();
}
public MainViewModel() { }
}

View File

@@ -1,7 +1,6 @@
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
@@ -41,8 +40,8 @@ public partial class SettingsViewModel
/// <summary>
/// 是否显示文件夹为空信息
/// </summary>
public Visibility EmptyFolderMessageVisibility =>
Data.MusicLibrary.Folders?.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
[ObservableProperty]
public partial Visibility EmptyFolderMessageVisibility { get; set; } = Visibility.Collapsed;
/// <summary>
/// 歌曲下载位置
@@ -129,12 +128,12 @@ public partial class SettingsViewModel
public partial ElementTheme ElementTheme { get; set; }
[RelayCommand]
public async Task SwitchThemeAsync(ElementTheme theme)
public void SwitchTheme(ElementTheme theme)
{
if (ElementTheme != theme)
{
ElementTheme = theme;
await _themeSelectorService.SetThemeAsync(theme);
_themeSelectorService.SetThemeAsync(theme);
}
}
@@ -202,6 +201,8 @@ public partial class SettingsViewModel
TintColor = _materialSelectorService.TintColor;
VersionDescription = GetVersionDescription();
EmptyFolderMessageVisibility =
Data.MusicLibrary.Folders.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
LoadSongDownloadLocationAsync();
LoadFonts();
IsExportPlaylistsButtonEnabled = Data.PlaylistLibrary.Playlists.Count > 0;
@@ -213,14 +214,6 @@ public partial class SettingsViewModel
IsExportPlaylistsButtonEnabled = message.HasPlaylist;
}
/// <summary>
/// 通知 EmptyFolderMessageVisibility 属性发生了变化(供外部调用)
/// </summary>
public void NotifyEmptyFolderMessageVisibilityChanged()
{
OnPropertyChanged(nameof(EmptyFolderMessageVisibility));
}
public async void PickMusicFolderButton_Click(object sender, RoutedEventArgs e)
{
(sender as Button)!.IsEnabled = false;
@@ -236,7 +229,8 @@ public partial class SettingsViewModel
)
{
Data.MusicLibrary.Folders.Add(folder.Path);
OnPropertyChanged(nameof(EmptyFolderMessageVisibility));
EmptyFolderMessageVisibility =
Data.MusicLibrary.Folders.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
await SaveFoldersAsync();
await Data.MusicLibrary.LoadLibraryAgainAsync(); // 重新加载音乐库
}
@@ -246,7 +240,8 @@ public partial class SettingsViewModel
public async void RemoveMusicFolder(string folder)
{
Data.MusicLibrary.Folders.Remove(folder);
OnPropertyChanged(nameof(EmptyFolderMessageVisibility));
EmptyFolderMessageVisibility =
Data.MusicLibrary.Folders.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
await SaveFoldersAsync();
await Data.MusicLibrary.LoadLibraryAgainAsync();
}
@@ -463,18 +458,22 @@ public partial class SettingsViewModel
public async void MaterialComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var (opacity, color) = await _materialSelectorService.SetMaterial(
(MaterialType)SelectedMaterial
(MaterialType)SelectedMaterial,
false,
false
);
LuminosityOpacity = opacity;
TintColor = color;
}
public async void ResetButton_Click(object sender, RoutedEventArgs e)
public async void ResetMaterialButton_Click(object sender, RoutedEventArgs e)
{
IsFallBack = true;
SelectedMaterial = 3;
SelectedMaterial = (byte)MaterialType.DesktopAcrylic;
var (opacity, color) = await _materialSelectorService.SetMaterial(
(MaterialType)SelectedMaterial
(MaterialType)SelectedMaterial,
false,
true
);
LuminosityOpacity = opacity;
TintColor = color;
@@ -486,12 +485,12 @@ public partial class SettingsViewModel
RangeBaseValueChangedEventArgs e
)
{
_materialSelectorService.SetLuminosityOpacity(LuminosityOpacity);
_materialSelectorService.SetLuminosityOpacity(LuminosityOpacity, false);
}
public void TintColorPicker_ColorChanged(ColorPicker sender, ColorChangedEventArgs args)
{
_materialSelectorService.SetTintColor(args.NewColor);
_materialSelectorService.SetTintColor(args.NewColor, false);
}
public void FontFamilyComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)

View File

@@ -24,7 +24,7 @@
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Behavior="Bouncing" Direction="Left"
FontFamily="{x:Bind model:Data.SelectedFontFamily}"
FontFamily="{x:Bind model:Settings.FontFamily}"
FontSize="32" Foreground="#FDFDFD"
Loaded="LyricContent_Loaded"
SizeChanged="LyricContentTextBlock_SizeChanged"

View File

@@ -33,7 +33,7 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
private readonly TextBlock _measureTextBlock = new()
{
FontSize = 32,
FontFamily = Data.SelectedFontFamily,
FontFamily = Settings.FontFamily,
};
public DesktopLyricViewModel ViewModel { get; }
@@ -58,7 +58,7 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
var exStyle = GetWindowLong(_hWnd, GWL_EXSTYLE);
exStyle |= WS_EX_TOOLWINDOW; // 添加工具窗口样式
exStyle &= ~WS_EX_APPWINDOW; // 移除应用窗口样式
_ = SetWindowLong(_hWnd, GWL_EXSTYLE, exStyle);
SetWindowLong(_hWnd, GWL_EXSTYLE, exStyle);
SetTopmost(true);
@@ -147,16 +147,12 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
if (isClickThrough)
{
// 添加 WS_EX_TRANSPARENT 使窗口点击穿透
_ = SetWindowLong(_hWnd, GWL_EXSTYLE, currentStyle | WS_EX_LAYERED | WS_EX_TRANSPARENT);
SetWindowLong(_hWnd, GWL_EXSTYLE, currentStyle | WS_EX_LAYERED | WS_EX_TRANSPARENT);
}
else
{
// 移除 WS_EX_TRANSPARENT 使窗口可接收点击
_ = SetWindowLong(
_hWnd,
GWL_EXSTYLE,
(currentStyle | WS_EX_LAYERED) & ~WS_EX_TRANSPARENT
);
SetWindowLong(_hWnd, GWL_EXSTYLE, (currentStyle | WS_EX_LAYERED) & ~WS_EX_TRANSPARENT);
}
}
@@ -167,24 +163,10 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
const uint SWP_NOSIZE = 0x0001;
const uint SWP_NOACTIVATE = 0x0010;
const uint flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE;
var position = value ? new nint(-1) : new nint(-2);
SetWindowPos(_hWnd, position, 0, 0, 0, 0, flags);
}
private void Window_Closed(object sender, WindowEventArgs args)
{
Data.RootPlayBarViewModel?.IsDesktopLyricWindowStarted = false;
if (_updateTimer250ms is not null)
{
_updateTimer250ms.Stop();
_updateTimer250ms = null;
}
}
public void Dispose() { }
private void AnimatedBorder_PointerPressed(object sender, PointerRoutedEventArgs e)
{
_isDragging = true;
@@ -227,7 +209,7 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
{
Text = "TEST测试",
FontSize = 32,
FontFamily = Data.SelectedFontFamily,
FontFamily = Settings.FontFamily,
};
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
(sender as MarqueeText)!.Height = textBlock.DesiredSize.Height;
@@ -282,4 +264,13 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
_logger.ZLogInformation(ex, $"调整灵动词岛宽度时发生错误");
}
}
private void Window_Closed(object sender, WindowEventArgs args)
{
Data.RootPlayBarViewModel?.IsDesktopLyricWindowStarted = false;
_updateTimer250ms?.Stop();
_updateTimer250ms = null;
}
public void Dispose() { }
}

View File

@@ -41,7 +41,7 @@
<TextBlock x:Name="LyricTextBlock"
Margin="{x:Bind Margin, Mode=OneWay}"
FontFamily="{x:Bind model:Data.SelectedFontFamily}"
FontFamily="{x:Bind model:Settings.FontFamily}"
FontSize="{x:Bind FontSize, Mode=OneWay}"
Opacity="{x:Bind Opacity, Mode=OneWay}"
SizeChanged="TextBlock_SizeChanged"
@@ -128,6 +128,19 @@
<ColumnDefinition Width="1.2*"/>
</Grid.ColumnDefinitions>
<Border x:Name="ShadowTarget"/>
<ScrollViewer x:Name="LyricViewer" VerticalScrollBarVisibility="Hidden">
<StackPanel>
<StackPanel Height="600"/>
<ListView x:Name="LyricView"
helper:ListViewExtensions.ItemCornerRadius="15"
IsItemClickEnabled="True"
ItemClick="{x:Bind ViewModel.ListView_ItemClick}"
ItemTemplate="{StaticResource LyricViewTemplate}"
ItemsSource="{x:Bind model:Data.MusicPlayer.CurrentLyric, Mode=OneWay}"
SelectionMode="None"/>
<StackPanel Height="600"/>
</StackPanel>
</ScrollViewer>
<Grid x:Name="ReferenceGrid"
Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Stretch"
@@ -203,19 +216,6 @@
Offset="20,20"/>
</ui:Effects.Shadow>
</Border>
<ScrollViewer x:Name="LyricViewer" VerticalScrollBarVisibility="Hidden">
<StackPanel>
<StackPanel Height="600"/>
<ListView x:Name="LyricView"
helper:ListViewExtensions.ItemCornerRadius="15"
IsItemClickEnabled="True"
ItemClick="{x:Bind ViewModel.ListView_ItemClick}"
ItemTemplate="{StaticResource LyricViewTemplate}"
ItemsSource="{x:Bind model:Data.MusicPlayer.CurrentLyric, Mode=OneWay}"
SelectionMode="None"/>
<StackPanel Height="600"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Grid>
</Page>

View File

@@ -128,8 +128,8 @@ public sealed partial class LyricPage : Page, IDisposable
private void TextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
{
var textblock = sender as TextBlock;
if (textblock!.FontSize == Data.SelectedCurrentFontSize)
var textblock = (sender as TextBlock)!;
if (Math.Abs(textblock.FontSize - Settings.LyricPageCurrentFontSize) < 1e-3)
{
var currentScrollPosition = LyricViewer.VerticalOffset;
var point = new Point(0, currentScrollPosition);

View File

@@ -6,6 +6,7 @@ using Microsoft.UI.Xaml.Media.Animation;
using The_Untamed_Music_Player.Controls;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Services;
using The_Untamed_Music_Player.ViewModels;
namespace The_Untamed_Music_Player.Views;
@@ -217,7 +218,7 @@ public sealed partial class PlayListsPage : Page
{
XamlRoot = XamlRoot,
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
RequestedTheme = Data.MainViewModel!.IsDarkTheme
RequestedTheme = ThemeSelectorService.IsDarkTheme
? ElementTheme.Dark
: ElementTheme.Light,
Title = titleTextBlock,

View File

@@ -6,6 +6,7 @@ using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Controls;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Services;
using The_Untamed_Music_Player.ViewModels;
using ZLinq;
@@ -280,7 +281,7 @@ public sealed partial class PlayQueuePage : Page
{
XamlRoot = XamlRoot,
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
RequestedTheme = Data.MainViewModel!.IsDarkTheme
RequestedTheme = ThemeSelectorService.IsDarkTheme
? ElementTheme.Dark
: ElementTheme.Light,
Title = titleTextBlock,

View File

@@ -259,7 +259,7 @@
<toolkit:SettingsExpander x:Uid="Settings_WindowMaterial" HeaderIcon="{ui:FontIcon FontFamily={StaticResource UntamedFontFamily}, Glyph=&#xEABC;}">
<StackPanel Orientation="Horizontal" Spacing="10">
<Button x:Uid="Settings_Reset" Click="{x:Bind ViewModel.ResetButton_Click}"/>
<Button x:Uid="Settings_Reset" Click="{x:Bind ViewModel.ResetMaterialButton_Click}"/>
<ComboBox x:Name="MaterialComboBox" x:Uid="Settings_ChooseAMaterial"
ItemsSource="{x:Bind ViewModel.Materials}"
SelectedIndex="{x:Bind ViewModel.SelectedMaterial, Mode=TwoWay}"

View File

@@ -3,7 +3,7 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using The_Untamed_Music_Player.Controls;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Services;
using The_Untamed_Music_Player.ViewModels;
using Windows.Storage;
using Windows.System;
@@ -34,7 +34,7 @@ public sealed partial class SettingsPage : Page
{
XamlRoot = XamlRoot,
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
RequestedTheme = Data.MainViewModel!.IsDarkTheme
RequestedTheme = ThemeSelectorService.IsDarkTheme
? ElementTheme.Dark
: ElementTheme.Light,
Title = titleTextBlock,
@@ -66,7 +66,7 @@ public sealed partial class SettingsPage : Page
{
XamlRoot = XamlRoot,
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
RequestedTheme = Data.MainViewModel!.IsDarkTheme
RequestedTheme = ThemeSelectorService.IsDarkTheme
? ElementTheme.Dark
: ElementTheme.Light,
Title = titleTextBlock,
@@ -104,7 +104,7 @@ public sealed partial class SettingsPage : Page
{
XamlRoot = XamlRoot,
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
RequestedTheme = Data.MainViewModel!.IsDarkTheme
RequestedTheme = ThemeSelectorService.IsDarkTheme
? ElementTheme.Dark
: ElementTheme.Light,
Title = titleTextBlock,