XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

HaloPixelToolBox

【WinUI3】基于USB HID通讯的花再音响工具箱

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/HaloPixelToolBox

adding spotify support

070f4e7
Bartholomew Ho <qiangqiang101@hotmail.com>
提交于

代码差异

8 个文件 +672 -2
Modified HaloPixelToolBox/HaloPixelToolBox/App.xaml.cs +2 -1
@@ -1,4 +1,4 @@
1 using HaloPixelToolBox.Interface.Services;
1 using HaloPixelToolBox.Interface.Services;
2 2 using HaloPixelToolBox.Profiles.CrossVersionProfiles;
3 3 using HaloPixelToolBox.Utilities;
4 4 using Microsoft.UI.Dispatching;
@@ -43,6 +43,7 @@ public partial class App : Application
43 43 AppThemeHelper.Theme = SystemProfile.Theme;
44 44 PageManager.RegisterPage(typeof(AppShellPage));
45 45 PageManager.RegisterPage(typeof(CloudMusicLyricsToolPage));
46 PageManager.RegisterPage(typeof(SpotifyLyricsToolPage));
46 47 PageManager.RegisterPage(typeof(MainPage));
47 48 PageManager.RegisterPage(typeof(SettingPage));
48 49 UnhandledException += App_UnhandledException;
Modified HaloPixelToolBox/HaloPixelToolBox/HaloPixelToolBox.csproj +4 -1
@@ -1,4 +1,4 @@
1 <Project Sdk="Microsoft.NET.Sdk">
1 <Project Sdk="Microsoft.NET.Sdk">
2 2 <PropertyGroup>
3 3 <OutputType>WinExe</OutputType>
4 4 <TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
@@ -109,6 +109,9 @@
109 109 <Page Update="Views\CloudMusicLyricsToolPage.xaml">
110 110 <Generator>MSBuild:Compile</Generator>
111 111 </Page>
112 <Page Update="Views\SpotifyLyricsToolPage.xaml">
113 <Generator>MSBuild:Compile</Generator>
114 </Page>
112 115 </ItemGroup>
113 116 <ItemGroup>
114 117 <Page Update="Views\MainPage.xaml">
Added HaloPixelToolBox/HaloPixelToolBox/Profiles/CrossVersionProfiles/SpotifyLyricsProfile.cs +40 -0
@@ -0,0 +1,40 @@
1 using HaloPixelToolBox.Core.Models;
2 using XFEExtension.NetCore.AutoConfig;
3 using XFEExtension.NetCore.WinUIHelper.Utilities.Helper;
4
5 namespace HaloPixelToolBox.Profiles.CrossVersionProfiles;
6
7 public partial class SpotifyLyricsProfile : XFEProfile
8 {
9 public SpotifyLyricsProfile() => ProfilePath = $@"{AppPathHelper.LocalProfile}\{nameof(SpotifyLyricsProfile)}";
10
11 /// <summary>
12 /// 切换回默认显示内容的时间
13 /// </summary>
14 [ProfileProperty]
15 private int switchBackTimeout = 60;
16
17 /// <summary>
18 /// 是否启用Spotify歌词
19 /// </summary>
20 [ProfileProperty]
21 private bool enableSpotifyLyrics = false;
22
23 /// <summary>
24 /// 当暂停时切换回默认显示内容
25 /// </summary>
26 [ProfileProperty]
27 private bool switchBackWhenPause = true;
28
29 /// <summary>
30 /// 默认歌词显示布局
31 /// </summary>
32 [ProfileProperty]
33 private HaloPixelTextLayout defaultHaloPixelTextLayout = HaloPixelTextLayout.Center;
34
35 /// <summary>
36 /// 默认暂停界面
37 /// </summary>
38 [ProfileProperty]
39 private HaloPixelUIModel defaultHaloPixelUIModel = HaloPixelUIModel.Clock;
40 }
Added HaloPixelToolBox/HaloPixelToolBox/Utilities/SpotifyLyricsReader.cs +321 -0
@@ -0,0 +1,321 @@
1 using System;
2 using System.Collections.Generic;
3 using System.Net.Http;
4 using System.Net.Http.Json;
5 using System.Text.Json.Serialization;
6 using System.Text.RegularExpressions;
7 using System.Threading.Tasks;
8 using Windows.Media.Control;
9
10 namespace HaloPixelToolBox.Utilities;
11
12 public class LyricLine
13 {
14 public TimeSpan Timestamp { get; set; }
15 public string Text { get; set; } = string.Empty;
16 }
17
18 public static class LrcParser
19 {
20 private static readonly Regex LrcTimeRegex = new Regex(@"\[(\d+):(\d+)(?:\.(\d+))?\]", RegexOptions.Compiled);
21
22 public static List<LyricLine> Parse(string lrcContent)
23 {
24 var lines = new List<LyricLine>();
25 if (string.IsNullOrWhiteSpace(lrcContent))
26 return lines;
27
28 var rawLines = lrcContent.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
29 foreach (var line in rawLines)
30 {
31 var match = LrcTimeRegex.Match(line);
32 if (match.Success)
33 {
34 var minutes = int.Parse(match.Groups[1].Value);
35 var seconds = int.Parse(match.Groups[2].Value);
36 var fractionStr = match.Groups[3].Value;
37 var milliseconds = 0;
38 if (!string.IsNullOrEmpty(fractionStr))
39 {
40 if (fractionStr.Length == 2)
41 milliseconds = int.Parse(fractionStr) * 10;
42 else if (fractionStr.Length == 3)
43 milliseconds = int.Parse(fractionStr);
44 else
45 milliseconds = int.Parse(fractionStr.Substring(0, 3));
46 }
47
48 var timestamp = new TimeSpan(0, 0, minutes, seconds, milliseconds);
49 var text = line.Substring(match.Length).Trim();
50 lines.Add(new LyricLine { Timestamp = timestamp, Text = text });
51 }
52 }
53 lines.Sort((a, b) => a.Timestamp.CompareTo(b.Timestamp));
54 return lines;
55 }
56 }
57
58 public class LrclibResponse
59 {
60 [JsonPropertyName("plainLyrics")]
61 public string? PlainLyrics { get; set; }
62
63 [JsonPropertyName("syncedLyrics")]
64 public string? SyncedLyrics { get; set; }
65 }
66
67 public class SpotifyLyricsReader
68 {
69 private static readonly HttpClient HttpClient = new HttpClient();
70
71 private GlobalSystemMediaTransportControlsSessionManager? _manager;
72 private GlobalSystemMediaTransportControlsSession? _currentSession;
73
74 private string _currentTitle = string.Empty;
75 private string _currentArtist = string.Empty;
76 private string _currentAlbum = string.Empty;
77 private double _currentDuration = 0;
78 private List<LyricLine> _lyricLines = new();
79
80 public string CurrentTitle => _currentTitle;
81 public string CurrentArtist => _currentArtist;
82
83 static SpotifyLyricsReader()
84 {
85 HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("HaloPixelToolBox/1.2.7 (https://github.com/WePro/HaloPixelToolBox)");
86 }
87
88 public bool Initialize()
89 {
90 try
91 {
92 var task = Task.Run(async () =>
93 {
94 _manager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
95 _manager.SessionsChanged += OnSessionsChanged;
96 UpdateCurrentSession();
97 return _currentSession != null;
98 });
99 return task.Result;
100 }
101 catch (Exception ex)
102 {
103 Console.WriteLine($"[ERROR] SpotifyLyricsReader Initialize failed: {ex.Message}");
104 return false;
105 }
106 }
107
108 private void OnSessionsChanged(GlobalSystemMediaTransportControlsSessionManager sender, SessionsChangedEventArgs args)
109 {
110 UpdateCurrentSession();
111 }
112
113 private void UpdateCurrentSession()
114 {
115 if (_manager == null) return;
116
117 var sessions = _manager.GetSessions();
118 GlobalSystemMediaTransportControlsSession? spotifySession = null;
119 foreach (var s in sessions)
120 {
121 if (s.SourceAppUserModelId.Contains("Spotify", StringComparison.OrdinalIgnoreCase))
122 {
123 spotifySession = s;
124 break;
125 }
126 }
127
128 if (spotifySession != _currentSession)
129 {
130 if (_currentSession != null)
131 {
132 _currentSession.MediaPropertiesChanged -= OnMediaPropertiesChanged;
133 _currentSession.PlaybackInfoChanged -= OnPlaybackInfoChanged;
134 _currentSession.TimelinePropertiesChanged -= OnTimelinePropertiesChanged;
135 }
136
137 _currentSession = spotifySession;
138
139 if (_currentSession != null)
140 {
141 _currentSession.MediaPropertiesChanged += OnMediaPropertiesChanged;
142 _currentSession.PlaybackInfoChanged += OnPlaybackInfoChanged;
143 _currentSession.TimelinePropertiesChanged += OnTimelinePropertiesChanged;
144 _ = UpdateTrackAsync(_currentSession);
145 }
146 else
147 {
148 _currentTitle = string.Empty;
149 _currentArtist = string.Empty;
150 _currentAlbum = string.Empty;
151 _currentDuration = 0;
152 _lyricLines.Clear();
153 }
154 }
155 }
156
157 private async void OnMediaPropertiesChanged(GlobalSystemMediaTransportControlsSession sender, MediaPropertiesChangedEventArgs args)
158 {
159 await UpdateTrackAsync(sender);
160 }
161
162 private void OnPlaybackInfoChanged(GlobalSystemMediaTransportControlsSession sender, PlaybackInfoChangedEventArgs args)
163 {
164 // Playback status might have changed, could trigger UI updates
165 }
166
167 private void OnTimelinePropertiesChanged(GlobalSystemMediaTransportControlsSession sender, TimelinePropertiesChangedEventArgs args)
168 {
169 // Timeline properties updated
170 }
171
172 private async Task UpdateTrackAsync(GlobalSystemMediaTransportControlsSession session)
173 {
174 try
175 {
176 var media = await session.TryGetMediaPropertiesAsync();
177 if (media == null) return;
178
179 var timeline = session.GetTimelineProperties();
180 double duration = timeline?.EndTime.TotalSeconds ?? 0;
181
182 if (_currentTitle == media.Title && _currentArtist == media.Artist)
183 {
184 return; // Same track
185 }
186
187 _currentTitle = media.Title;
188 _currentArtist = media.Artist;
189 _currentAlbum = media.AlbumTitle;
190 _currentDuration = duration;
191 _lyricLines.Clear();
192
193 Console.WriteLine($"[SpotifyLyricsReader] Track changed: {_currentArtist} - {_currentTitle} ({_currentAlbum}, {duration}s)");
194
195 var lyricsRes = await FetchLyricsAsync(_currentTitle, _currentArtist, _currentAlbum, _currentDuration);
196 if (lyricsRes != null && !string.IsNullOrEmpty(lyricsRes.SyncedLyrics))
197 {
198 _lyricLines = LrcParser.Parse(lyricsRes.SyncedLyrics);
199 Console.WriteLine($"[SpotifyLyricsReader] Loaded {_lyricLines.Count} synced lines.");
200 }
201 else if (lyricsRes != null && !string.IsNullOrEmpty(lyricsRes.PlainLyrics))
202 {
203 Console.WriteLine("[SpotifyLyricsReader] No synced lyrics, plain lyrics available.");
204 }
205 else
206 {
207 Console.WriteLine("[SpotifyLyricsReader] No lyrics found.");
208 }
209 }
210 catch (Exception ex)
211 {
212 Console.WriteLine($"[ERROR] UpdateTrackAsync failed: {ex.Message}");
213 }
214 }
215
216 private async Task<LrclibResponse?> FetchLyricsAsync(string title, string artist, string album, double duration)
217 {
218 try
219 {
220 string url = $"https://lrclib.net/api/get?track_name={Uri.EscapeDataString(title)}&artist_name={Uri.EscapeDataString(artist)}";
221 if (!string.IsNullOrEmpty(album))
222 url += $"&album_name={Uri.EscapeDataString(album)}";
223 if (duration > 0)
224 url += $"&duration={(int)duration}";
225
226 var response = await HttpClient.GetAsync(url);
227 if (response.IsSuccessStatusCode)
228 {
229 return await response.Content.ReadFromJsonAsync<LrclibResponse>();
230 }
231
232 // Fallback: search without album/duration if direct match fails
233 if (!string.IsNullOrEmpty(album) || duration > 0)
234 {
235 string fallbackUrl = $"https://lrclib.net/api/get?track_name={Uri.EscapeDataString(title)}&artist_name={Uri.EscapeDataString(artist)}";
236 var fallbackRes = await HttpClient.GetAsync(fallbackUrl);
237 if (fallbackRes.IsSuccessStatusCode)
238 {
239 return await fallbackRes.Content.ReadFromJsonAsync<LrclibResponse>();
240 }
241 }
242 }
243 catch (Exception ex)
244 {
245 Console.WriteLine($"[ERROR] FetchLyricsAsync failed: {ex.Message}");
246 }
247 return null;
248 }
249
250 public bool TryReadLyrics(out string lyrics)
251 {
252 lyrics = string.Empty;
253 if (_currentSession == null)
254 {
255 lyrics = "Spotify未就绪";
256 return false;
257 }
258
259 var playback = _currentSession.GetPlaybackInfo();
260 if (playback.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed ||
261 playback.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Stopped)
262 {
263 lyrics = "播放已停止";
264 return false;
265 }
266
267 if (_lyricLines == null || _lyricLines.Count == 0)
268 {
269 if (!string.IsNullOrWhiteSpace(_currentTitle))
270 {
271 lyrics = $"{_currentArtist} - {_currentTitle}";
272 return true;
273 }
274 lyrics = "无歌词信息";
275 return false;
276 }
277
278 var timeline = _currentSession.GetTimelineProperties();
279 if (timeline == null)
280 {
281 lyrics = $"{_currentArtist} - {_currentTitle}";
282 return true;
283 }
284
285 TimeSpan currentPosition;
286 if (playback.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing)
287 {
288 var elapsed = DateTimeOffset.Now - timeline.LastUpdatedTime;
289 if (elapsed < TimeSpan.Zero) elapsed = TimeSpan.Zero;
290 currentPosition = timeline.Position + elapsed;
291 }
292 else
293 {
294 currentPosition = timeline.Position;
295 }
296
297 string currentText = string.Empty;
298 foreach (var line in _lyricLines)
299 {
300 if (line.Timestamp <= currentPosition)
301 {
302 currentText = line.Text;
303 }
304 else
305 {
306 break;
307 }
308 }
309
310 if (string.IsNullOrEmpty(currentText))
311 {
312 lyrics = $"{_currentArtist} - {_currentTitle}";
313 }
314 else
315 {
316 lyrics = currentText;
317 }
318
319 return true;
320 }
321 }
Added HaloPixelToolBox/HaloPixelToolBox/ViewModels/SpotifyLyricsToolPageViewModel.cs +180 -0
@@ -0,0 +1,180 @@
1 using CommunityToolkit.Mvvm.ComponentModel;
2 using HaloPixelToolBox.Core.Utilities;
3 using HaloPixelToolBox.Profiles.CrossVersionProfiles;
4 using HaloPixelToolBox.Utilities;
5 using System;
6 using System.Diagnostics;
7 using System.Threading.Tasks;
8 using XFEExtension.NetCore.StringExtension;
9 using XFEExtension.NetCore.WinUIHelper.Implements;
10 using XFEExtension.NetCore.WinUIHelper.Interface.Services;
11 using XFEExtension.NetCore.WinUIHelper.Utilities;
12
13 namespace HaloPixelToolBox.ViewModels;
14
15 public partial class SpotifyLyricsToolPageViewModel : ServiceBaseViewModelBase<string>
16 {
17 [ObservableProperty]
18 private bool deviceReady;
19 [ObservableProperty]
20 private bool spotifyReady;
21 [ObservableProperty]
22 private bool enableSpotifyLyrics = SpotifyLyricsProfile.EnableSpotifyLyrics;
23 [ObservableProperty]
24 private bool switchBackWhenPause = SpotifyLyricsProfile.SwitchBackWhenPause;
25 [ObservableProperty]
26 private int switchBackTimeout = SpotifyLyricsProfile.SwitchBackTimeout;
27 [ObservableProperty]
28 private string spotifyTrackInfo = "未检测到播放中的歌曲";
29
30 public HaloPixelDevice Device { get; set; } = new();
31 public SpotifyLyricsReader Reader { get; set; }
32
33 public ISettingService SettingService { get; } = ServiceManager.GetService<ISettingService>();
34
35 partial void OnEnableSpotifyLyricsChanged(bool value) => SpotifyLyricsProfile.EnableSpotifyLyrics = value;
36 partial void OnSwitchBackWhenPauseChanged(bool value) => SpotifyLyricsProfile.SwitchBackWhenPause = value;
37 partial void OnSwitchBackTimeoutChanged(int value) => SpotifyLyricsProfile.SwitchBackTimeout = value;
38
39 public SpotifyLyricsToolPageViewModel()
40 {
41 Console.WriteLine("初始化Spotify歌词读取器");
42 Reader = new SpotifyLyricsReader();
43
44 Console.WriteLine("准备启动Spotify后台线程");
45 Task.Run(async () =>
46 {
47 try
48 {
49 Console.WriteLine("正在搜索花再设备...");
50 while (!DeviceReady)
51 {
52 var ready = Device.Initialize();
53 AutoNavigationParameterService.CurrentPage?.DispatcherQueue.TryEnqueue(() =>
54 {
55 DeviceReady = ready;
56 });
57 await Task.Delay(500);
58 }
59 Console.WriteLine("花再设备已连接");
60 }
61 catch (Exception ex)
62 {
63 Console.WriteLine($"[ERROR]搜索花再设备时发生错误:{ex.Message}");
64 Console.WriteLine($"[TRACE]{ex.StackTrace}");
65 }
66 });
67
68 Task.Run(async () =>
69 {
70 try
71 {
72 Console.WriteLine("正在搜索Spotify...");
73 while (!SpotifyReady)
74 {
75 var ready = Reader.Initialize();
76 AutoNavigationParameterService.CurrentPage?.DispatcherQueue.TryEnqueue(() =>
77 {
78 SpotifyReady = ready;
79 SpotifyTrackInfo = !string.IsNullOrEmpty(Reader.CurrentTitle) ? $"{Reader.CurrentArtist} - {Reader.CurrentTitle}" : "未检测到播放中的歌曲";
80 });
81 await Task.Delay(500);
82 }
83 Console.WriteLine("Spotify已准备就绪");
84 }
85 catch (Exception ex)
86 {
87 Console.WriteLine($"[ERROR]搜索Spotify时发生错误:{ex.Message}");
88 Console.WriteLine($"[TRACE]{ex.StackTrace}");
89 }
90 });
91
92 Task.Run(async () =>
93 {
94 Console.WriteLine("启动Spotify歌词主线程");
95 Console.WriteLine("等待花再设备...");
96 while (!DeviceReady)
97 await Task.Delay(500);
98
99 if (DeviceReady)
100 {
101 Console.WriteLine("花再设备已就绪,显示启动信息");
102 Device.SetTextLayout(Core.Models.HaloPixelTextLayout.Center);
103 Device.ShowText("Spotify歌词同步已就绪");
104 await Task.Delay(3000);
105 }
106
107 while (true)
108 {
109 bool isClockUI = false;
110 int time = 0;
111 try
112 {
113 if (DeviceReady && SpotifyReady && EnableSpotifyLyrics)
114 {
115 Console.WriteLine("[DEBUG]设备均在线,准备进入主循环");
116 string lastRead = string.Empty;
117 bool scrolled = false;
118 while (true)
119 {
120 try
121 {
122 if (!DeviceReady || !SpotifyReady || !EnableSpotifyLyrics)
123 break;
124
125 // Update track info text in UI
126 AutoNavigationParameterService.CurrentPage?.DispatcherQueue.TryEnqueue(() =>
127 {
128 SpotifyTrackInfo = !string.IsNullOrEmpty(Reader.CurrentTitle) ? $"{Reader.CurrentArtist} - {Reader.CurrentTitle}" : "无播放中的歌曲";
129 });
130
131 if (Reader.TryReadLyrics(out var lyrics) && lastRead != lyrics)
132 {
133 Console.WriteLine($"已读取到歌词:{lyrics}");
134 lastRead = lyrics;
135 isClockUI = false;
136 time = 0;
137 if (scrolled)
138 {
139 Device.ShowText(string.Empty);
140 await Task.Delay(100);
141 scrolled = false;
142 }
143 Device.SetTextLayout(SpotifyLyricsProfile.DefaultHaloPixelTextLayout);
144 Device.ShowText(lyrics);
145 Debug.WriteLine(lyrics.DisplayLength());
146 if (lyrics.DisplayLength() > 30)
147 {
148 scrolled = true;
149 await Task.Delay(500);
150 Device.SetTextLayout(Core.Models.HaloPixelTextLayout.ScrollRightToLeft);
151 }
152 }
153 await Task.Delay(50);
154 time += 50;
155 if (!isClockUI && time >= SpotifyLyricsProfile.SwitchBackTimeout * 1000)
156 {
157 isClockUI = true;
158 Device.SetUIModel(SpotifyLyricsProfile.DefaultHaloPixelUIModel);
159 Console.WriteLine("已切换至时钟界面");
160 }
161 }
162 catch (Exception ex)
163 {
164 Console.WriteLine($"[ERROR]Spotify歌词主循环发生错误:{ex.Message}");
165 Console.WriteLine($"[TRACE]{ex.StackTrace}");
166 }
167 }
168 }
169 await Task.Delay(500);
170 }
171 catch (Exception ex)
172 {
173 Console.WriteLine($"[ERROR]Spotify歌词主线程发生错误:{ex.Message}");
174 Console.WriteLine($"[TRACE]{ex.StackTrace}");
175 }
176 }
177 });
178 Console.WriteLine("Spotify后台线程启动完成");
179 }
180 }
Modified HaloPixelToolBox/HaloPixelToolBox/Views/AppShellPage.xaml +5 -0
@@ -49,6 +49,11 @@
49 49 <FontIcon Glyph="&#xE8D6;"/>
50 50 </NavigationViewItem.Icon>
51 51 </NavigationViewItem>
52 <NavigationViewItem Content="Spotify歌词" add:NavigationAddition.NavigateTo="HaloPixelToolBox.Views.SpotifyLyricsToolPage">
53 <NavigationViewItem.Icon>
54 <FontIcon Glyph="&#xE8D6;"/>
55 </NavigationViewItem.Icon>
56 </NavigationViewItem>
52 57 <!--在此处添加导航栏页面-->
53 58 </NavigationView.MenuItems>
54 59
Added HaloPixelToolBox/HaloPixelToolBox/Views/SpotifyLyricsToolPage.xaml +87 -0
@@ -0,0 +1,87 @@
1 <?xml version="1.0" encoding="utf-8"?>
2 <Page
3 x:Class="HaloPixelToolBox.Views.SpotifyLyricsToolPage"
4 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
5 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6 xmlns:local="using:HaloPixelToolBox.Views"
7 xmlns:controls="using:CommunityToolkit.WinUI.Controls"
8 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
9 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
10 mc:Ignorable="d">
11 <Page.Resources>
12 <Style x:Key="SettingsSectionHeaderTextBlockStyle"
13 BasedOn="{StaticResource BodyStrongTextBlockStyle}"
14 TargetType="TextBlock">
15 <Style.Setters>
16 <Setter Property="Margin" Value="1,30,0,6" />
17 </Style.Setters>
18 </Style>
19 </Page.Resources>
20
21 <Grid>
22 <ScrollView>
23 <StackPanel Orientation="Vertical" Spacing="10" Margin="0,0,0,20">
24 <TextBlock Text="设备状态" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"/>
25 <Grid RowSpacing="10" ColumnSpacing="5">
26 <Grid.ColumnDefinitions>
27 <ColumnDefinition Width="Auto"/>
28 <ColumnDefinition Width="Auto"/>
29 </Grid.ColumnDefinitions>
30 <Grid.RowDefinitions>
31 <RowDefinition/>
32 <RowDefinition/>
33 </Grid.RowDefinitions>
34 <TextBlock Text="花再 Halo Pixel 状态:" VerticalAlignment="Center" HorizontalAlignment="Right" Style="{ThemeResource BodyStrongTextBlockStyle}"/>
35 <Grid Grid.Column="1">
36 <Border CornerRadius="90" Height="25" Width="25" Visibility="{x:Bind ViewModel.DeviceReady, Mode=OneWay, Converter={StaticResource BooleanInverseConverter}}" Background="{ThemeResource SystemFillColorCriticalBrush}"/>
37 <Border CornerRadius="90" Height="25" Width="25" Visibility="{x:Bind ViewModel.DeviceReady, Mode=OneWay}" Background="{ThemeResource SystemFillColorSuccessBrush}"/>
38 </Grid>
39 <TextBlock Grid.Row="1" Text="Spotify 状态:" VerticalAlignment="Center" HorizontalAlignment="Right" Style="{ThemeResource BodyStrongTextBlockStyle}"/>
40 <Grid Grid.Column="1" Grid.Row="1">
41 <Border CornerRadius="90" Height="25" Width="25" Visibility="{x:Bind ViewModel.SpotifyReady, Mode=OneWay, Converter={StaticResource BooleanInverseConverter}}" Background="{ThemeResource SystemFillColorCriticalBrush}"/>
42 <Border CornerRadius="90" Height="25" Width="25" Visibility="{x:Bind ViewModel.SpotifyReady, Mode=OneWay}" Background="{ThemeResource SystemFillColorSuccessBrush}"/>
43 </Grid>
44 </Grid>
45
46 <TextBlock Text="播放信息" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"/>
47 <TextBlock Text="{x:Bind ViewModel.SpotifyTrackInfo, Mode=OneWay}" Style="{StaticResource BodyTextBlockStyle}"/>
48
49 <TextBlock Text="功能" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"/>
50 <controls:SettingsCard Header="启用歌词同步" Description="启用后,Spotify的歌词会实时显示在花再 Halo Pixel 音响上">
51 <ToggleSwitch IsOn="{x:Bind ViewModel.EnableSpotifyLyrics, Mode=TwoWay}"/>
52 </controls:SettingsCard>
53
54 <TextBlock Text="个性化" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"/>
55 <controls:SettingsExpander Header="暂停后切换回默认界面" Description="一段时间后自动切换回时钟界面等音响自带页面">
56 <ToggleSwitch x:Name="switchBackWhenPauseToggleSwitch" IsOn="{x:Bind ViewModel.SwitchBackWhenPause, Mode=TwoWay}"/>
57 <controls:SettingsExpander.Items>
58 <controls:SettingsCard Header="暂停延迟" Description="设置多长时间后,音响切换回默认界面" IsEnabled="{x:Bind switchBackWhenPauseToggleSwitch.IsOn, Mode=OneWay}">
59 <NumberBox SpinButtonPlacementMode="Compact" Value="{x:Bind ViewModel.SwitchBackTimeout, Mode=TwoWay}"/>
60 </controls:SettingsCard>
61 <controls:SettingsCard Header="暂停界面" Description="设置暂停后切换回的默认界面" IsEnabled="{x:Bind switchBackWhenPauseToggleSwitch.IsOn, Mode=OneWay}">
62 <ComboBox x:Name="defaultHaloPixelUIModelComboBox" Tag="HaloPixelToolBox.Profiles.CrossVersionProfiles.SpotifyLyricsProfile.DefaultHaloPixelUIModel">
63 <ComboBoxItem Content="时钟类" Tag="Clock"/>
64 <ComboBoxItem Content="游戏类" Tag="Game"/>
65 <ComboBoxItem Content="打工类" Tag="Work"/>
66 <ComboBoxItem Content="读书类" Tag="Read"/>
67 <ComboBoxItem Content="猫咪类" Tag="Cats"/>
68 <ComboBoxItem Content="狗狗类" Tag="Dogs"/>
69 <ComboBoxItem Content="热更类" Tag="Memes"/>
70 <ComboBoxItem Content="赛博类" Tag="Cyber"/>
71 <ComboBoxItem Content="频谱类" Tag="Waves"/>
72 </ComboBox>
73 </controls:SettingsCard>
74 </controls:SettingsExpander.Items>
75 </controls:SettingsExpander>
76 <controls:SettingsCard Header="歌词位置" Description="设置歌词的默认显示位置">
77 <ComboBox x:Name="defaultHaloPixelTextLayoutComboBox" Tag="HaloPixelToolBox.Profiles.CrossVersionProfiles.SpotifyLyricsProfile.DefaultHaloPixelTextLayout">
78 <ComboBoxItem Content="左对齐" Tag="Left"/>
79 <ComboBoxItem Content="右对齐" Tag="Right"/>
80 <ComboBoxItem Content="居中" Tag="Center"/>
81 <ComboBoxItem Content="伸展" Tag="Stretch"/>
82 </ComboBox>
83 </controls:SettingsCard>
84 </StackPanel>
85 </ScrollView>
86 </Grid>
87 </Page>
Added HaloPixelToolBox/HaloPixelToolBox/Views/SpotifyLyricsToolPage.xaml.cs +33 -0
@@ -0,0 +1,33 @@
1 using HaloPixelToolBox.Core.Models;
2 using Microsoft.UI.Xaml.Controls;
3 using Microsoft.UI.Xaml.Navigation;
4 using System;
5 using XFEExtension.NetCore.WinUIHelper.Utilities.Helper;
6
7 namespace HaloPixelToolBox.Views;
8
9 public sealed partial class SpotifyLyricsToolPage : Page
10 {
11 public static SpotifyLyricsToolPage? Current { get; set; }
12 public SpotifyLyricsToolPageViewModel ViewModel { get; set; } = new();
13
14 public SpotifyLyricsToolPage()
15 {
16 Console.WriteLine("正在初始化Spotify歌词界面...");
17 Current = this;
18 InitializeComponent();
19 ViewModel.AutoNavigationParameterService.Initialize(this);
20 ViewModel.SettingService.AddComboBox(defaultHaloPixelTextLayoutComboBox, ProfileHelper.GetEnumProfileSaveFunc<HaloPixelTextLayout>(), ProfileHelper.GetEnumProfileLoadFuncForComboBox());
21 ViewModel.SettingService.AddComboBox(defaultHaloPixelUIModelComboBox, ProfileHelper.GetEnumProfileSaveFunc<HaloPixelUIModel>(), ProfileHelper.GetEnumProfileLoadFuncForComboBox());
22 ViewModel.SettingService.Initialize();
23 ViewModel.SettingService.RegisterEvents();
24 NavigationCacheMode = NavigationCacheMode.Enabled;
25 Console.WriteLine("Spotify歌词界面初始化完成");
26 }
27
28 protected override void OnNavigatedTo(NavigationEventArgs e)
29 {
30 Console.WriteLine("导航到Spotify歌词页面");
31 ViewModel.AutoNavigationParameterService.OnParameterChange(e.Parameter);
32 }
33 }