mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
fix: stats dashboard ui
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.2.237.0" />
|
||||
Version="1.2.238.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -6,5 +6,6 @@ namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(250);
|
||||
public static readonly TimeSpan AnimationDuration = TimeSpan.FromMilliseconds(350);
|
||||
public static readonly TimeSpan WaitingDuration = TimeSpan.FromMilliseconds(300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,47 +28,59 @@
|
||||
|
||||
<Grid Margin="0,20,0,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="36,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ProgressBar
|
||||
Grid.Row="0"
|
||||
Background="Transparent"
|
||||
IsIndeterminate="{x:Bind ViewModel.IsLoading, Mode=OneWay}" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<ComboBox
|
||||
x:Uid="StatsDashboardControlTimeRange"
|
||||
Header="Time Range"
|
||||
SelectedIndex="{x:Bind ViewModel.SelectedTimeRange, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlToday" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisWeek" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisMonth" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisQuarter" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisYear" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlCustom" />
|
||||
</ComboBox>
|
||||
<controls:WrapPanel
|
||||
Grid.Row="1"
|
||||
Margin="36,12"
|
||||
HorizontalSpacing="12"
|
||||
Orientation="Horizontal"
|
||||
VerticalSpacing="12">
|
||||
<ComboBox
|
||||
x:Uid="StatsDashboardControlTimeRange"
|
||||
Header="Time Range"
|
||||
SelectedIndex="{x:Bind ViewModel.SelectedTimeRange, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlToday" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisWeek" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisMonth" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisQuarter" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisYear" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlCustom" />
|
||||
</ComboBox>
|
||||
|
||||
<StackPanel
|
||||
Margin="0,0,0,5"
|
||||
VerticalAlignment="Bottom"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Visibility="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<CalendarDatePicker x:Uid="StatsDashboardControlStart" Date="{x:Bind ViewModel.CustomStartDate, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
Margin="0,26,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="-" />
|
||||
<CalendarDatePicker x:Uid="StatsDashboardControlEnd" Date="{x:Bind ViewModel.CustomEndDate, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<CalendarDatePicker
|
||||
x:Uid="StatsDashboardControlStart"
|
||||
Date="{x:Bind ViewModel.CustomStartDate, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}" />
|
||||
<TimePicker
|
||||
VerticalAlignment="Bottom"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}"
|
||||
Time="{x:Bind ViewModel.CustomStartTime, Mode=TwoWay}" />
|
||||
<CalendarDatePicker
|
||||
x:Uid="StatsDashboardControlEnd"
|
||||
Date="{x:Bind ViewModel.CustomEndDate, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}" />
|
||||
<TimePicker
|
||||
VerticalAlignment="Bottom"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}"
|
||||
Time="{x:Bind ViewModel.CustomEndTime, Mode=TwoWay}" />
|
||||
<Button
|
||||
VerticalAlignment="Bottom"
|
||||
Command="{x:Bind ViewModel.RefreshDataCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}" />
|
||||
</controls:WrapPanel>
|
||||
|
||||
<ScrollViewer Grid.Row="1" Padding="36,0">
|
||||
<ScrollViewer Grid.Row="2" Padding="36,0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
@@ -367,11 +379,11 @@
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
<!--<Button
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Command="{x:Bind ViewModel.GenerateTestDataCommand}"
|
||||
Content="Generate test data" />
|
||||
Content="Generate test data" />-->
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -254,15 +254,15 @@ namespace BetterLyrics.WinUI3.Services.GSMTCService
|
||||
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
if (CurrentIsPlaying)
|
||||
{
|
||||
_scrobbleStopwatch.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_scrobbleStopwatch.Stop();
|
||||
if (CurrentIsPlaying)
|
||||
{
|
||||
_scrobbleStopwatch.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_scrobbleStopwatch.Stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||
using BetterLyrics.WinUI3.Services.PlayHistoryService;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.WinUI;
|
||||
using LiveChartsCore;
|
||||
using LiveChartsCore.Kernel;
|
||||
using LiveChartsCore.Kernel.Sketches;
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using LiveChartsCore.Themes;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.Views.Windows;
|
||||
@@ -25,7 +27,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class StatsDashboardControlViewModel : ObservableObject
|
||||
public partial class StatsDashboardControlViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IPlayHistoryService _playHistoryService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
@@ -33,13 +35,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private string _localizedTimesValue;
|
||||
|
||||
[ObservableProperty] public partial bool IsLoading { get; set; }
|
||||
private readonly DispatcherQueueTimer _timer;
|
||||
|
||||
[ObservableProperty] public partial bool IsLoading { get; set; } = false;
|
||||
|
||||
// 时间筛选
|
||||
[ObservableProperty] public partial StatsRange SelectedTimeRange { get; set; }
|
||||
[ObservableProperty] public partial bool IsCustomRangeSelected { get; set; }
|
||||
[ObservableProperty] public partial DateTimeOffset? CustomStartDate { get; set; }
|
||||
[ObservableProperty] public partial DateTimeOffset? CustomEndDate { get; set; }
|
||||
[ObservableProperty] public partial StatsRange SelectedTimeRange { get; set; } = StatsRange.Today;
|
||||
[ObservableProperty] public partial bool IsCustomRangeSelected { get; set; } = false;
|
||||
[ObservableProperty] public partial DateTimeOffset? CustomStartDate { get; set; } = DateTime.Now;
|
||||
[ObservableProperty] public partial DateTimeOffset? CustomEndDate { get; set; } = DateTime.Now;
|
||||
[ObservableProperty] public partial TimeSpan CustomStartTime { get; set; } = TimeSpan.Zero;
|
||||
[ObservableProperty] public partial TimeSpan CustomEndTime { get; set; } = TimeSpan.Zero;
|
||||
|
||||
// 顶部基础数据
|
||||
[ObservableProperty] public partial TimeSpan TotalDuration { get; set; }
|
||||
@@ -69,19 +75,20 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
_localizedTimesValue = _localizationService.GetLocalizedString("StatsDashboardControlTimes");
|
||||
|
||||
SelectedTimeRange = StatsRange.Today;
|
||||
_timer = _dispatcherQueue.CreateTimer();
|
||||
|
||||
CustomStartDate = DateTimeOffset.Now.AddDays(-7);
|
||||
CustomEndDate = DateTimeOffset.Now;
|
||||
UpdateDateRange();
|
||||
}
|
||||
|
||||
async partial void OnSelectedTimeRangeChanged(StatsRange value)
|
||||
partial void OnSelectedTimeRangeChanged(StatsRange value)
|
||||
{
|
||||
IsCustomRangeSelected = value == StatsRange.Custom;
|
||||
await LoadDataAsync();
|
||||
UpdateDateRange();
|
||||
}
|
||||
async partial void OnCustomEndDateChanged(DateTimeOffset? value) => await LoadDataAsync();
|
||||
async partial void OnCustomStartDateChanged(DateTimeOffset? value) => await LoadDataAsync();
|
||||
partial void OnCustomEndDateChanged(DateTimeOffset? value) => LoadData();
|
||||
partial void OnCustomStartDateChanged(DateTimeOffset? value) => LoadData();
|
||||
partial void OnCustomStartTimeChanged(TimeSpan value) => LoadData();
|
||||
partial void OnCustomEndTimeChanged(TimeSpan value) => LoadData();
|
||||
|
||||
private void ProcessHourlyStats(List<PlayHistoryItem> logs)
|
||||
{
|
||||
@@ -135,11 +142,24 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private (DateTime? Start, DateTime? End) CalculateDateRange()
|
||||
{
|
||||
if (IsCustomRangeSelected)
|
||||
{
|
||||
return (CustomStartDate?.UtcDateTime, CustomEndDate?.UtcDateTime);
|
||||
}
|
||||
if (CustomStartDate == null || CustomEndDate == null) return (null, null);
|
||||
|
||||
return (
|
||||
new DateTime(
|
||||
DateOnly.FromDateTime(CustomStartDate.Value.LocalDateTime),
|
||||
TimeOnly.FromTimeSpan(CustomStartTime),
|
||||
DateTimeKind.Local)
|
||||
.ToUniversalTime(),
|
||||
new DateTime(
|
||||
DateOnly.FromDateTime(CustomEndDate.Value.LocalDateTime),
|
||||
TimeOnly.FromTimeSpan(CustomEndTime),
|
||||
DateTimeKind.Local)
|
||||
.ToUniversalTime()
|
||||
);
|
||||
}
|
||||
|
||||
private void UpdateDateRange()
|
||||
{
|
||||
DateTime nowLocal = DateTime.Now;
|
||||
DateTime startLocal = nowLocal.Date;
|
||||
|
||||
@@ -152,6 +172,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
int dayOfWeek = (int)nowLocal.DayOfWeek;
|
||||
if (dayOfWeek == 0) dayOfWeek = 7;
|
||||
startLocal = nowLocal.Date.AddDays(-(dayOfWeek - 1));
|
||||
startLocal = new DateTime(startLocal.Year, startLocal.Month, startLocal.Day);
|
||||
break;
|
||||
case StatsRange.ThisMonth:
|
||||
startLocal = new DateTime(nowLocal.Year, nowLocal.Month, 1);
|
||||
@@ -165,60 +186,82 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
break;
|
||||
}
|
||||
|
||||
return (startLocal.ToUniversalTime(), nowLocal.ToUniversalTime());
|
||||
CustomStartDate = startLocal.Date;
|
||||
CustomEndDate = nowLocal.Date;
|
||||
|
||||
CustomStartTime = startLocal.TimeOfDay;
|
||||
CustomEndTime = nowLocal.TimeOfDay;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadDataAsync()
|
||||
private void RefreshData()
|
||||
{
|
||||
if (IsLoading) return;
|
||||
IsLoading = true;
|
||||
|
||||
try
|
||||
if (IsCustomRangeSelected)
|
||||
{
|
||||
var (start, end) = CalculateDateRange();
|
||||
LoadData();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateDateRange();
|
||||
}
|
||||
}
|
||||
|
||||
if (start == null || end == null)
|
||||
[RelayCommand]
|
||||
public void LoadData()
|
||||
{
|
||||
_timer.Debounce(async () =>
|
||||
{
|
||||
if (IsLoading) return;
|
||||
IsLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
start = end = DateTime.Now.ToUniversalTime();
|
||||
await Task.Delay(Constants.Time.WaitingDuration);
|
||||
|
||||
var (start, end) = CalculateDateRange();
|
||||
|
||||
if (start == null || end == null)
|
||||
{
|
||||
start = end = DateTime.Now.ToUniversalTime();
|
||||
}
|
||||
|
||||
var durationTask = _playHistoryService.GetTotalListeningDurationAsync(start.Value, end.Value);
|
||||
var logsTask = _playHistoryService.GetLogsByDateRangeAsync(start.Value, end.Value);
|
||||
var topSongsTask = _playHistoryService.GetTopSongsAsync(start.Value, end.Value, 10);
|
||||
var topArtistsTask = _playHistoryService.GetTopArtistsAsync(start.Value, end.Value, 10);
|
||||
var playersTask = _playHistoryService.GetPlayerDistributionAsync(start.Value, end.Value);
|
||||
|
||||
await Task.WhenAll(durationTask, logsTask, topSongsTask, topArtistsTask, playersTask);
|
||||
|
||||
TotalDuration = await durationTask;
|
||||
var logs = await logsTask;
|
||||
TotalTracksPlayed = logs.Count;
|
||||
|
||||
TopSongs = [.. await topSongsTask];
|
||||
|
||||
var pStats = await playersTask;
|
||||
UpdatePlayerStats(pStats);
|
||||
|
||||
TopArtists = [.. await topArtistsTask];
|
||||
|
||||
ProcessHourlyStats(logs);
|
||||
}
|
||||
|
||||
var durationTask = _playHistoryService.GetTotalListeningDurationAsync(start.Value, end.Value);
|
||||
var logsTask = _playHistoryService.GetLogsByDateRangeAsync(start.Value, end.Value);
|
||||
var topSongsTask = _playHistoryService.GetTopSongsAsync(start.Value, end.Value, 10);
|
||||
var topArtistsTask = _playHistoryService.GetTopArtistsAsync(start.Value, end.Value, 10);
|
||||
var playersTask = _playHistoryService.GetPlayerDistributionAsync(start.Value, end.Value);
|
||||
|
||||
await Task.WhenAll(durationTask, logsTask, topSongsTask, topArtistsTask, playersTask);
|
||||
|
||||
TotalDuration = await durationTask;
|
||||
var logs = await logsTask;
|
||||
TotalTracksPlayed = logs.Count;
|
||||
|
||||
TopSongs = [.. await topSongsTask];
|
||||
|
||||
var pStats = await playersTask;
|
||||
UpdatePlayerStats(pStats);
|
||||
|
||||
TopArtists = [.. await topArtistsTask];
|
||||
|
||||
ProcessHourlyStats(logs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error loading stats: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error loading stats: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}, Constants.Time.DebounceTimeout);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task GenerateTestDataAsync()
|
||||
{
|
||||
await _playHistoryService.GenerateTestDataAsync(1000);
|
||||
await LoadDataAsync(); // 生成完刷新
|
||||
LoadData(); // 生成完刷新
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user