add search function for music gallery

This commit is contained in:
Zhe Fang
2025-07-24 10:37:46 -04:00
parent e92130af85
commit 8cf9830644
17 changed files with 626 additions and 253 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -40,15 +40,18 @@
<PackageReference Include="CommunityToolkit.Labs.WinUI.Shimmer" Version="0.1.250703-build.2173" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.MetadataControl" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="ICU4N" Version="60.1.0-alpha.438" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.7" />

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum SongOrderType
{
Title,
Album,
Artist
}
}

View File

@@ -1,5 +1,6 @@
using ATL;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -12,19 +13,15 @@ namespace BetterLyrics.WinUI3.Helper
{
public static class CollectionHelper
{
public static ObservableCollection<GroupInfoList> GetGroupedByTitleAsync(this ICollection<Track> tracks)
public static ObservableCollection<GroupInfoList> GetGroupedBy<T>(
this IEnumerable<T> items,
Func<T, object> groupKeySelector,
Func<object, object>? orderSelector = null)
{
// Grab Contact objects from pre-existing list (list is returned from function GetContactsAsync())
var query = from item in tracks
// Group the items returned from the query, sort and select the ones you want to keep
group item by item.Title.Substring(0, 1).ToUpper() into g
var query = from item in items
group item by groupKeySelector(item) into g
orderby g.Key
// GroupInfoList is a simple custom class that has an IEnumerable type attribute, and
// a key attribute. The IGrouping-typed variable g now holds the Contact objects,
// and these objects will be used to create a new GroupInfoList object.
select new GroupInfoList(g) { Key = g.Key };
select new GroupInfoList(g.Cast<object>(), orderSelector) { Key = g.Key };
return new ObservableCollection<GroupInfoList>(query);
}

View File

@@ -1,5 +1,6 @@
using BetterLyrics.WinUI3.Helper;
using CommunityToolkit.Mvvm.DependencyInjection;
using ICU4N.Text;
using Lyricify.Lyrics.Helpers.General;
using NTextCat;
using System;
@@ -15,6 +16,7 @@ namespace BetterLyrics.WinUI3.Services
private static readonly RankedLanguageIdentifierFactory _factory = new();
private static readonly RankedLanguageIdentifier _identifier;
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private static readonly Transliterator _transliterator = Transliterator.GetInstance("Any-Latin; Latin-ASCII;");
public static List<Models.LanguageInfo> SupportedTargetLanguages =>
[
@@ -124,5 +126,20 @@ namespace BetterLyrics.WinUI3.Services
if (found == -1) found = 7; // 默认使用英语
return found;
}
public static string GetOrderChar(string text)
{
if (string.IsNullOrWhiteSpace(text)) return "#";
char c = text[0];
if (char.IsLetter(c) && c < 128) // 英文
return char.ToUpper(c).ToString();
// 使用 ICU4N 转写为拉丁字母
string latin = _transliterator.Transliterate(text);
if (!string.IsNullOrEmpty(latin) && char.IsLetter(latin[0]))
return char.ToUpper(latin[0]).ToString();
return "#";
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.System;
namespace BetterLyrics.WinUI3.Helper
{
public class LauncherHelper
{
public static async Task SelectAndShowFile(string filePath)
{
var file = await StorageFile.GetFileFromPathAsync(filePath);
var folder = await file.GetParentAsync();
var folderOptions = new FolderLauncherOptions();
folderOptions.ItemsToSelect.Add(file);
await Launcher.LaunchFolderAsync(folder, folderOptions);
}
}
}

View File

@@ -6,13 +6,20 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public partial class GroupInfoList(IEnumerable<object> items) : List<object>(items)
public partial class GroupInfoList : List<object>
{
public required object Key { get; set; }
public GroupInfoList(IEnumerable<object> items, Func<object, object>? orderSelector = null)
: base(orderSelector != null
? items.OrderBy(orderSelector)
: items)
{
}
public override string ToString()
{
return "Group " + Key.ToString();
return $"{Key}";
}
}
}

View File

@@ -0,0 +1,25 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace BetterLyrics.WinUI3.TemplateSelector;
public class SongOrderTemplateSelector : DataTemplateSelector
{
public DataTemplate ByTitleTemplate { get; set; }
public DataTemplate ByAlbumTemplate { get; set; }
public DataTemplate ByArtistTemplate { get; set; }
public SongOrderType SongOrderType { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
return SongOrderType switch
{
SongOrderType.Title => ByTitleTemplate,
SongOrderType.Album => ByAlbumTemplate,
SongOrderType.Artist => ByArtistTemplate,
_ => ByTitleTemplate
};
}
}

View File

@@ -94,6 +94,9 @@ namespace BetterLyrics.WinUI3
[ObservableProperty]
public partial string LockHotKey { get; set; } = "";
[ObservableProperty]
public partial bool IsMusicGalleryPageExpanded { get; set; } = false;
private void AutoHideOrShowWindow()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();

View File

@@ -1,10 +1,12 @@
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using System;
@@ -29,36 +31,67 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly MediaTimelineController _timelineController = new();
private readonly SystemMediaTransportControls _smtc;
private List<Track> _tracks = [];
private List<Track> _filteredTracks = [];
[ObservableProperty]
public partial ObservableCollection<GroupInfoList> TracksByTitle { get; set; } = [];
public partial bool IsLocalMediaNotFound { get; set; }
[ObservableProperty]
public partial ObservableCollection<GroupInfoList> GroupedTracks { get; set; } = [];
[ObservableProperty]
public partial ObservableCollection<Track> TrackSearchSuggestions { get; set; } = [];
[ObservableProperty]
public partial SongOrderType SongOrderType { get; set; } = SongOrderType.Title;
[ObservableProperty]
public partial bool IsDataLoading { get; set; } = false;
[ObservableProperty]
public partial Track TrackRightTapped { get; set; } = new();
[ObservableProperty]
public partial int SelectedSongIndex { get; set; } = -1;
[ObservableProperty]
public partial string SongSearchQuery { get; set; } = string.Empty;
public MusicGalleryViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService) : base(settingsService)
{
_mediaPlayer.MediaOpened += MediaPlayer_MediaOpened;
_timelineController = _mediaPlayer.TimelineController = new();
_timelineController.PositionChanged += TimelineController_PositionChanged;
_smtc = _mediaPlayer.SystemMediaTransportControls;
_mediaPlayer.CommandManager.IsEnabled = false;
_smtc.IsEnabled = true;
_smtc.IsPlayEnabled = true;
_smtc.IsPauseEnabled = true;
_smtc.IsNextEnabled = true;
_smtc.IsPreviousEnabled = true;
_smtc.ButtonPressed += Smtc_ButtonPressed;
_smtc.PlaybackPositionChangeRequested += Smtc_PlaybackPositionChangeRequested;
_libWatcherService = libWatcherService;
_libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged;
}
private void Smtc_PlaybackPositionChangeRequested(SystemMediaTransportControls sender, PlaybackPositionChangeRequestedEventArgs args)
{
_timelineController.Position = args.RequestedPlaybackPosition;
}
private void MediaPlayer_MediaOpened(MediaPlayer sender, object args)
{
_timelineController.Start();
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
}
private void TimelineController_PositionChanged(MediaTimelineController sender, object args)
{
_smtc.UpdateTimelineProperties(new SystemMediaTransportControlsTimelineProperties()
{
Position = sender.Position,
EndTime = sender.Duration ?? TimeSpan.Zero
EndTime = _mediaPlayer.PlaybackSession.NaturalDuration
});
}
@@ -68,14 +101,13 @@ namespace BetterLyrics.WinUI3.ViewModels
{
case SystemMediaTransportControlsButton.Play:
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
_mediaPlayer.Play();
_timelineController.Resume();
break;
case SystemMediaTransportControlsButton.Pause:
_smtc.PlaybackStatus = MediaPlaybackStatus.Paused;
_mediaPlayer.Pause();
_timelineController.Pause();
break;
case SystemMediaTransportControlsButton.Next:
//Next
break;
case SystemMediaTransportControlsButton.Previous:
//Previous
@@ -90,58 +122,108 @@ namespace BetterLyrics.WinUI3.ViewModels
public void RefreshSongs()
{
IsDataLoading = true;
_tracks.Clear();
Task.Run(() =>
_dispatcherQueueTimer.Debounce(() =>
{
foreach (var folder in _settingsService.LocalMediaFolders)
IsDataLoading = true;
_tracks.Clear();
Task.Run(() =>
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
foreach (var folder in _settingsService.LocalMediaFolders)
{
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
Track track = new(file);
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
{
Track track = new(file);
if (track.Duration <= 0) continue;
_tracks.Add(track);
});
}
}
}
}
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
TracksByTitle.AddRange(_tracks.GetGroupedByTitleAsync());
IsDataLoading = false;
ApplySongSearchQuery();
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
IsLocalMediaNotFound = !_filteredTracks.Any();
ApplySongOrderType();
IsDataLoading = false;
});
});
});
}, TimeSpan.FromMilliseconds(100));
}
public void PlaySongAt(int? index)
public void ApplySongSearchQuery()
{
if (index.HasValue)
if (string.IsNullOrWhiteSpace(SongSearchQuery))
{
var track = _tracks.ElementAtOrDefault(index.Value);
if (track != null)
{
_mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(track.Path));
var updater = _smtc.DisplayUpdater;
updater.AppMediaId = Package.Current.Id.FullName;
updater.Type = MediaPlaybackType.Music;
updater.MusicProperties.Title = track.Title;
updater.MusicProperties.Artist = track.Artist;
updater.MusicProperties.AlbumTitle = track.Album;
if (track.EmbeddedPictures.FirstOrDefault()?.PictureData is byte[] pictureData)
{
updater.Thumbnail = ImageHelper.ByteArrayToRandomAccessStreamReference(pictureData);
}
_timelineController.Duration = TimeSpan.FromSeconds(track.Duration);
_timelineController.Start();
updater.Update();
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
}
_filteredTracks = _tracks;
return;
}
_filteredTracks = new List<Track>(
_tracks.Where(t => t.Title.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
t.Artist.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
t.Album.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase))
);
}
private void ApplySongOrderType()
{
switch (SongOrderType)
{
case SongOrderType.Title:
GroupedTracks = _filteredTracks.GetGroupedBy(
t => LanguageHelper.GetOrderChar(t.Title),
o => ((Track)o).Title
);
break;
case SongOrderType.Artist:
GroupedTracks = _filteredTracks.GetGroupedBy(
t => LanguageHelper.GetOrderChar(t.Artist),
o => ((Track)o).Artist
);
break;
case SongOrderType.Album:
GroupedTracks = _filteredTracks.GetGroupedBy(
t => LanguageHelper.GetOrderChar(t.Album),
o => ((Track)o).Album
);
break;
}
SelectedSongIndex = -1;
}
public void PlayTrack(Track? track)
{
if (track == null) return;
_smtc.IsEnabled = true;
_mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(track.Path));
var updater = _smtc.DisplayUpdater;
updater.AppMediaId = Package.Current.Id.FullName;
updater.Type = MediaPlaybackType.Music;
updater.MusicProperties.Title = track.Title;
updater.MusicProperties.Artist = track.Artist;
updater.MusicProperties.AlbumTitle = track.Album;
if (track.EmbeddedPictures.FirstOrDefault()?.PictureData is byte[] pictureData)
{
updater.Thumbnail = ImageHelper.ByteArrayToRandomAccessStreamReference(pictureData);
}
updater.Update();
}
partial void OnSongOrderTypeChanged(SongOrderType value)
{
ApplySongOrderType();
IsLocalMediaNotFound = !_filteredTracks.Any();
}
partial void OnSongSearchQueryChanged(string value)
{
ApplySongSearchQuery();
IsLocalMediaNotFound = !_filteredTracks.Any();
ApplySongOrderType();
}
public void Receive(PropertyChangedMessage<ObservableCollection<LocalMediaFolder>> message)

View File

@@ -345,13 +345,13 @@ namespace BetterLyrics.WinUI3.ViewModels
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
}
public void ToggleLocalLyricsFolder(LocalMediaFolder folder)
public void ToggleLocalLyricsFolder()
{
_settingsService.LocalMediaFolders = [.. LocalMediaFolders];
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
}
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
public void ToggleLyricsSearchProvider()
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
Broadcast(

View File

@@ -92,7 +92,7 @@
<ToolTipService.ToolTip>
<ToolTip x:Name="TimelineOffsetToolTip" x:Uid="LyricsPageTimelineOffsetButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<Button.ContextFlyout>
<Flyout x:Name="TimelineOffsetFlyout" ShouldConstrainToRootBounds="False">
<StackPanel>
<Slider
@@ -122,7 +122,7 @@
</CheckBox>
</StackPanel>
</Flyout>
</Button.DataContext>
</Button.ContextFlyout>
</Button>
</StackPanel>
@@ -244,7 +244,7 @@
</FontIcon.OpacityTransition>
</FontIcon>
</Grid>
<Button.DataContext>
<Button.ContextFlyout>
<Flyout x:Name="VolumeFlyout" ShouldConstrainToRootBounds="False">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
@@ -273,7 +273,7 @@
<ToolTipService.ToolTip>
<ToolTip x:Name="TranslationToolTip" x:Uid="LyricsPageTranslationButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<Button.ContextFlyout>
<Flyout x:Name="TranslationFlyout" ShouldConstrainToRootBounds="False">
<StackPanel>
<ToggleSwitch x:Uid="LyricsPageTranslationEnabled" IsOn="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}" />
@@ -283,7 +283,7 @@
IsOn="{x:Bind ViewModel.ShowTranslationOnly, Mode=TwoWay}" />
</StackPanel>
</Flyout>
</Button.DataContext>
</Button.ContextFlyout>
</Button>
<!-- Display type -->
@@ -297,7 +297,7 @@
<ToolTipService.ToolTip>
<ToolTip x:Name="PresentationTypeToolTip" x:Uid="LyricsPageDisplayTypeButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<Button.ContextFlyout>
<Flyout x:Name="DisplayTypeSwitchFlyout" ShouldConstrainToRootBounds="false">
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
@@ -311,7 +311,7 @@
<RadioButton x:Uid="MainPageSplitView" Click="SplitViewRadioButton_Click" />
</RadioButtons>
</Flyout>
</Button.DataContext>
</Button.ContextFlyout>
</Button>
<!-- Settings -->

View File

@@ -22,6 +22,12 @@
<local:LyricsPage />
<local:MusicGalleryPage x:Name="MusicGalleryPage" Padding="64">
<local:MusicGalleryPage.TranslationTransition>
<Vector3Transition />
</local:MusicGalleryPage.TranslationTransition>
</local:MusicGalleryPage>
<!-- Top command -->
<Grid
x:Name="TopCommandGrid"

View File

@@ -77,12 +77,12 @@ namespace BetterLyrics.WinUI3.Views
_settingsService.StandardWindowWidth = 1600;
_settingsService.StandardWindowHeight = 800;
}
AppWindow.MoveAndResize(new Windows.Graphics.RectInt32(
_settingsService.StandardWindowLeft,
_settingsService.StandardWindowTop,
_settingsService.StandardWindowWidth,
_settingsService.StandardWindowHeight
));
//AppWindow.MoveAndResize(new Windows.Graphics.RectInt32(
// _settingsService.StandardWindowLeft,
// _settingsService.StandardWindowTop,
// _settingsService.StandardWindowWidth,
// _settingsService.StandardWindowHeight
//));
break;
case AutoStartWindowType.DockMode:
DockFlyoutItem.IsChecked = true;
@@ -332,11 +332,25 @@ namespace BetterLyrics.WinUI3.Views
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateMusicGalleryPageTranslation();
}
private void MusicGalleryButton_Click(object sender, RoutedEventArgs e)
{
WindowHelper.OpenWindow<MusicGalleryWindow>();
ViewModel.IsMusicGalleryPageExpanded = !ViewModel.IsMusicGalleryPageExpanded;
UpdateMusicGalleryPageTranslation();
}
private void UpdateMusicGalleryPageTranslation()
{
if (ViewModel.IsMusicGalleryPageExpanded)
{
MusicGalleryPage.Translation = new System.Numerics.Vector3(0, 0, 0);
}
else
{
MusicGalleryPage.Translation = new System.Numerics.Vector3(0, (float)RootGrid.ActualHeight, 0);
}
}
}
}

View File

@@ -12,190 +12,370 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:media="using:CommunityToolkit.WinUI.Media"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:templateselector="using:BetterLyrics.WinUI3.TemplateSelector"
xmlns:ui="using:CommunityToolkit.WinUI"
Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}"
Loaded="Page_Loaded"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="ByTitleTemplate" x:DataType="atl:Track">
<Grid
Padding="12"
RightTapped="SongListVireItemGrid_RightTapped"
Tapped="SongListVireItemGrid_Tapped">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1.5*" />
<ColumnDefinition Width="1.5*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1.2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<!-- 歌曲名 -->
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding Title}"
TextWrapping="Wrap" />
<!-- 歌手名 -->
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Artist}"
TextWrapping="Wrap" />
<!-- 专辑名 -->
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Album}"
TextWrapping="Wrap" />
<!-- 年份 -->
<TextBlock
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Year}"
TextWrapping="Wrap" />
<!-- 流派 -->
<TextBlock
Grid.Column="4"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Genre}"
TextWrapping="Wrap" />
<!-- 歌曲时长 -->
<TextBlock
Grid.Column="5"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
TextAlignment="Right"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="ByAlbumTemplate" x:DataType="atl:Track">
<Grid
Padding="12"
RightTapped="SongListVireItemGrid_RightTapped"
Tapped="SongListVireItemGrid_Tapped">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1.5*" />
<ColumnDefinition Width="1.5*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1.2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<!-- 专辑名 -->
<TextBlock
Grid.Column="0"
Text="{Binding Album}"
TextWrapping="Wrap" />
<!-- 歌曲名 -->
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Title}"
TextWrapping="Wrap" />
<!-- 歌手名 -->
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Artist}"
TextWrapping="Wrap" />
<!-- 年份 -->
<TextBlock
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Year}"
TextWrapping="Wrap" />
<!-- 流派 -->
<TextBlock
Grid.Column="4"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Genre}"
TextWrapping="Wrap" />
<!-- 歌曲时长 -->
<TextBlock
Grid.Column="5"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
TextAlignment="Right"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="ByArtistTemplate" x:DataType="atl:Track">
<Grid
Padding="12"
RightTapped="SongListVireItemGrid_RightTapped"
Tapped="SongListVireItemGrid_Tapped">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1.5*" />
<ColumnDefinition Width="1.5*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1.2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<!-- 歌手名 -->
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding Artist}"
TextWrapping="Wrap" />
<!-- 歌曲名 -->
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Title}"
TextWrapping="Wrap" />
<!-- 专辑名 -->
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Album}"
TextWrapping="Wrap" />
<!-- 年份 -->
<TextBlock
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Year}"
TextWrapping="Wrap" />
<!-- 流派 -->
<TextBlock
Grid.Column="4"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Genre}"
TextWrapping="Wrap" />
<!-- 歌曲时长 -->
<TextBlock
Grid.Column="5"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
TextAlignment="Right"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
<templateselector:SongOrderTemplateSelector
x:Key="SongOrderTemplateSelector"
ByAlbumTemplate="{StaticResource ByAlbumTemplate}"
ByArtistTemplate="{StaticResource ByArtistTemplate}"
ByTitleTemplate="{StaticResource ByTitleTemplate}"
SongOrderType="{x:Bind ViewModel.SongOrderType, Mode=OneWay}" />
<CollectionViewSource
x:Name="TracksByTitleCVS"
IsSourceGrouped="True"
Source="{x:Bind ViewModel.TracksByTitle, Mode=OneWay}" />
Source="{x:Bind ViewModel.GroupedTracks, Mode=OneWay}" />
</Page.Resources>
<Grid>
<Grid Margin="0,12,0,0">
<AutoSuggestBox
x:Name="SongSearchBox"
Margin="36,0"
VerticalAlignment="Top"
PlaceholderText="搜索歌曲"
QueryIcon="Find"
QuerySubmitted="SongSearchBox_QuerySubmitted"
SuggestionChosen="SongSearchBox_SuggestionChosen"
TextChanged="SongSearchBox_TextChanged" />
<controls:Segmented
x:Name="Segmented"
Margin="36,48,36,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SelectedIndex="0"
SelectionMode="Single">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{Binding ElementName=Segmented, Path=SelectedIndex, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Tag" Value="Song" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{Binding ElementName=Segmented, Path=SelectedIndex, Mode=OneWay}"
ComparisonCondition="Equal"
Value="1">
<interactivity:ChangePropertyAction PropertyName="Tag" Value="Album" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{Binding ElementName=Segmented, Path=SelectedIndex, Mode=OneWay}"
ComparisonCondition="Equal"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Tag" Value="Artist" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<controls:SegmentedItem Content="歌曲" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC4F;}" />
<controls:SegmentedItem Content="专辑" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE93C;}" />
<controls:SegmentedItem Content="艺术家" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEFA9;}" />
</controls:Segmented>
<Grid x:Name="SongViewer" Margin="0,36,0,0">
<controls:SwitchPresenter Margin="0,96,0,0" Value="{Binding ElementName=Segmented, Path=Tag, Mode=OneWay}">
<controls:SwitchPresenter.ContentTransitions>
<TransitionCollection>
<PopupThemeTransition />
</TransitionCollection>
</controls:SwitchPresenter.ContentTransitions>
<Grid Margin="12" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="16" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<AutoSuggestBox
x:Name="SongSearchBox"
Grid.Column="0"
HorizontalAlignment="Stretch"
PlaceholderText="搜索歌曲"
QueryIcon="Find"
Text="{x:Bind ViewModel.SongSearchQuery, Mode=TwoWay}" />
<StackPanel
Grid.Column="2"
Orientation="Horizontal"
Spacing="12">
<TextBlock
VerticalAlignment="Center"
Style="{StaticResource BodyStrongTextBlockStyle}"
Text="排序方式" />
<controls:Segmented
x:Name="Segmented"
SelectedIndex="{x:Bind ViewModel.SongOrderType, Converter={StaticResource EnumToIntConverter}, Mode=TwoWay}"
SelectionMode="Single">
<controls:SegmentedItem Content="歌曲" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC4F;}" />
<controls:SegmentedItem Content="专辑" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE93C;}" />
<controls:SegmentedItem Content="艺术家" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEFA9;}" />
</controls:Segmented>
</StackPanel>
</Grid>
<controls:Case Value="Song">
<SemanticZoom>
<SemanticZoom.ZoomedInView>
<GridView
ItemsSource="{x:Bind TracksByTitleCVS.View, Mode=OneWay}"
ScrollViewer.IsHorizontalScrollChainingEnabled="False"
SelectionMode="None">
<GridView.GroupStyle>
<GroupStyle />
</GridView.GroupStyle>
</GridView>
</SemanticZoom.ZoomedInView>
<SemanticZoom Margin="0,48,0,0">
<SemanticZoom.ZoomedInView>
<ListView
x:Name="SongListView"
ItemTemplateSelector="{StaticResource SongOrderTemplateSelector}"
ItemsSource="{x:Bind TracksByTitleCVS.View, Mode=OneWay}">
<ListView.ContextFlyout>
<Flyout Placement="Right" ShouldConstrainToRootBounds="False">
<StackPanel Spacing="12">
<SemanticZoom.ZoomedOutView>
<ListView ItemsSource="{x:Bind TracksByTitleCVS.View.CollectionGroups, Mode=OneWay}" SelectionChanged="SongListView_SelectionChanged">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel AreStickyGroupHeadersEnabled="True" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="models:GroupInfoList">
<Border AutomationProperties.AccessibilityView="Raw">
<TextBlock
AutomationProperties.AccessibilityView="Raw"
Style="{ThemeResource TitleTextBlockStyle}"
Text="{x:Bind Key}" />
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="36,0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="atl:Track">
<Grid Padding="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<!-- 歌曲名 -->
<ColumnDefinition Width="1.5*" />
<!-- 歌手名 -->
<ColumnDefinition Width="1.5*" />
<!-- 专辑名 -->
<ColumnDefinition Width="1*" />
<!-- 年份 -->
<ColumnDefinition Width="1.2*" />
<!-- 流派 -->
<ColumnDefinition Width="1*" />
<!-- 歌曲时长 -->
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Spacing="6">
<!--<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="&#xE946;" />-->
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="文件信息" />
</StackPanel>
<!-- 歌曲名 -->
<ScrollViewer Width="300" Height="300">
<StackPanel Spacing="12">
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="标题" />
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Title, Mode=OneWay}" />
</StackPanel>
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="艺术家" />
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Artist, Mode=OneWay}" />
</StackPanel>
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="专辑" />
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Album, Mode=OneWay}" />
</StackPanel>
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="年份" />
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Year, Mode=OneWay}" />
</StackPanel>
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="时长" />
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, Mode=OneWay}" />
</StackPanel>
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="比特率" />
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Bitrate, Mode=OneWay}" />
</StackPanel>
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="采样率" />
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.SampleRate, Mode=OneWay}" />
</StackPanel>
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="位深" />
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.BitDepth, Mode=OneWay}" />
</StackPanel>
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="格式" />
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.AudioFormat.Name, Mode=OneWay}" />
</StackPanel>
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="编码" />
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Encoder, Mode=OneWay}" />
</StackPanel>
<StackPanel>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="路径" />
<HyperlinkButton Click="SongPathHyperlinkButton_Click" Content="{x:Bind ViewModel.TrackRightTapped.Path, Mode=OneWay}" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</StackPanel>
</Flyout>
</ListView.ContextFlyout>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel AreStickyGroupHeadersEnabled="True" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="models:GroupInfoList">
<Border AutomationProperties.AccessibilityView="Raw">
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding Title}"
TextWrapping="Wrap" />
<!-- 歌手名 -->
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Artist}"
TextWrapping="Wrap" />
<!-- 专辑名 -->
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Album}"
TextWrapping="Wrap" />
<!-- 年份 -->
<TextBlock
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Year}"
TextWrapping="Wrap" />
<!-- 流派 -->
<TextBlock
Grid.Column="4"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Genre}"
TextWrapping="Wrap" />
<!-- 歌曲时长 -->
<TextBlock
Grid.Column="5"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
TextAlignment="Right"
TextWrapping="Wrap" />
</Grid>
AutomationProperties.AccessibilityView="Raw"
Style="{ThemeResource TitleTextBlockStyle}"
Text="{Binding}" />
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
</controls:Case>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="36,0" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
</SemanticZoom.ZoomedInView>
<controls:Case Value="Album">
<ListView />
</controls:Case>
<SemanticZoom.ZoomedOutView>
<GridView
MaxWidth="500"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ItemsSource="{x:Bind TracksByTitleCVS.View.CollectionGroups, Mode=OneWay}"
ScrollViewer.IsHorizontalScrollChainingEnabled="False"
SelectionMode="None">
<GridView.ItemTemplate>
<DataTemplate x:DataType="models:GroupInfoList">
<TextBlock Style="{ThemeResource TitleTextBlockStyle}" Text="{Binding}" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
<controls:Case Value="Artist">
<ListView />
</controls:Case>
<Grid Margin="0,48,0,0" Visibility="{x:Bind ViewModel.IsLocalMediaNotFound, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image MaxWidth="200" Source="/Assets/Planet.png" />
<TextBlock HorizontalAlignment="Center" Text="未在媒体库内找到任何歌曲" />
</StackPanel>
</Grid>
</controls:SwitchPresenter>
</Grid>
<Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}" Visibility="{x:Bind ViewModel.IsDataLoading, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
@@ -209,10 +389,10 @@
<RowDefinition Height="12" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<labs:Shimmer Grid.Row="0" CornerRadius="12" />
<labs:Shimmer Grid.Row="2" CornerRadius="12" />
<labs:Shimmer Grid.Row="4" CornerRadius="12" />
<labs:Shimmer Grid.Row="6" CornerRadius="12" />
<labs:Shimmer Grid.Row="0" CornerRadius="6" />
<labs:Shimmer Grid.Row="2" CornerRadius="6" />
<labs:Shimmer Grid.Row="4" CornerRadius="6" />
<labs:Shimmer Grid.Row="6" CornerRadius="6" />
</Grid>
<ProgressRing IsActive="{x:Bind ViewModel.IsDataLoading, Mode=OneWay}" />
</Grid>

View File

@@ -1,3 +1,6 @@
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
@@ -14,6 +17,7 @@ using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.System;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -26,35 +30,31 @@ namespace BetterLyrics.WinUI3.Views
public sealed partial class MusicGalleryPage : Page
{
public MusicGalleryViewModel ViewModel => (MusicGalleryViewModel)DataContext;
public MusicGalleryPage()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<MusicGalleryViewModel>();
}
private void SongListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ViewModel.PlaySongAt((sender as ListView)?.SelectedIndex);
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
ViewModel.RefreshSongs();
}
private void SongSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
private void SongListVireItemGrid_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
ViewModel.TrackRightTapped = (Track)((FrameworkElement)sender).DataContext;
}
private void SongSearchBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
private async void SongPathHyperlinkButton_Click(object sender, RoutedEventArgs e)
{
await LauncherHelper.SelectAndShowFile($"{((HyperlinkButton)sender).Content}");
}
private void SongSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
private void SongListVireItemGrid_Tapped(object sender, TappedRoutedEventArgs e)
{
ViewModel.PlayTrack((Track)((FrameworkElement)sender).DataContext);
}
}
}

View File

@@ -28,7 +28,7 @@ namespace BetterLyrics.WinUI3.Views
{
if (toggleSwitch.DataContext is LocalMediaFolder localLyricsFolder)
{
ViewModel.ToggleLocalLyricsFolder(localLyricsFolder);
ViewModel.ToggleLocalLyricsFolder();
}
}
}
@@ -47,7 +47,7 @@ namespace BetterLyrics.WinUI3.Views
{
if (toggleSwitch.DataContext is LyricsSearchProviderInfo providerInfo)
{
ViewModel.ToggleLyricsSearchProvider(providerInfo);
ViewModel.ToggleLyricsSearchProvider();
}
}
}