返回提交历史
Modified
HaloPixelToolBox/HaloPixelToolBox/App.xaml.cs
+2
-1
Modified
HaloPixelToolBox/HaloPixelToolBox/HaloPixelToolBox.csproj
+4
-1
Added
HaloPixelToolBox/HaloPixelToolBox/Profiles/CrossVersionProfiles/SpotifyLyricsProfile.cs
+40
-0
Added
HaloPixelToolBox/HaloPixelToolBox/Utilities/SpotifyLyricsReader.cs
+321
-0
Added
HaloPixelToolBox/HaloPixelToolBox/ViewModels/SpotifyLyricsToolPageViewModel.cs
+180
-0
Modified
HaloPixelToolBox/HaloPixelToolBox/Views/AppShellPage.xaml
+5
-0
Added
HaloPixelToolBox/HaloPixelToolBox/Views/SpotifyLyricsToolPage.xaml
+87
-0
Added
HaloPixelToolBox/HaloPixelToolBox/Views/SpotifyLyricsToolPage.xaml.cs
+33
-0
XFEstudio/HaloPixelToolBox
adding spotify support
070f4e7
代码差异
8 个文件
+672
-2
@@ -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;
@@ -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">
@@ -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
}
@@ -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
}
@@ -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
}
@@ -49,6 +49,11 @@
49
49
<FontIcon Glyph=""/>
50
50
</NavigationViewItem.Icon>
51
51
</NavigationViewItem>
52
<NavigationViewItem Content="Spotify歌词" add:NavigationAddition.NavigateTo="HaloPixelToolBox.Views.SpotifyLyricsToolPage">
53
<NavigationViewItem.Icon>
54
<FontIcon Glyph=""/>
55
</NavigationViewItem.Icon>
56
</NavigationViewItem>
52
57
<!--在此处添加导航栏页面-->
53
58
</NavigationView.MenuItems>
54
59
@@ -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>
@@ -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
}