diff --git a/MusicX.Core/Helpers/IIdentifiable.cs b/MusicX.Core/Helpers/IIdentifiable.cs index db157fae..675ba0b6 100644 --- a/MusicX.Core/Helpers/IIdentifiable.cs +++ b/MusicX.Core/Helpers/IIdentifiable.cs @@ -1,5 +1,5 @@ -using MusicX.Core.Models.General; -using MusicX.Core.Models; +using MusicX.Core.Models; +using MusicX.Core.Models.General; namespace MusicX.Core.Helpers; @@ -9,8 +9,9 @@ public interface IIdentifiable public static void Process(ResponseData data) { - if (data.Block != null) ProcessBlock(data.Block, data); - + if (data.Block != null) + ProcessBlock(data.Block, data); + if (data.Replacements?.ReplacementsModels is { Count: > 0 } replacementsModels) foreach (var block in replacementsModels.SelectMany(b => b.ToBlocks)) { @@ -23,17 +24,19 @@ public static void Process(ResponseData data) FillPlaylistOwnerName(playlist, data); } - if (data.Playlist != null) FillPlaylistOwnerName(data.Playlist, data); - - if (data.Section != null) ProcessSection(data.Section, data); + if (data.Playlist != null) + FillPlaylistOwnerName(data.Playlist, data); + + if (data.Section != null) + ProcessSection(data.Section, data); } private static IEnumerable IntersectById(IEnumerable? enumerable, IEnumerable ids) where T : IIdentifiable => enumerable?.IntersectBy(ids, static b => b.Identifier) ?? []; - + private static IEnumerable IntersectById(IEnumerable? enumerable, IEnumerable ids) where T : IIdentifiable => enumerable?.IntersectBy(ids, static b => long.Parse(b.Identifier)) ?? []; - + private static IEnumerable IntersectById(IEnumerable? enumerable, IEnumerable ids) where T : IIdentifiable => enumerable?.IntersectBy(ids, static b => int.Parse(b.Identifier)) ?? []; @@ -66,7 +69,7 @@ private static void ProcessBlock(Block block, ResponseData data) foreach (var recommendedPlaylist in block.RecommendedPlaylists) { recommendedPlaylist.Audios.AddRange(IntersectById(data.Audios, recommendedPlaylist.AudiosIds)); - recommendedPlaylist.Playlist = IntersectById(data.Playlists, new []{ ((IIdentifiable)recommendedPlaylist).Identifier }).FirstOrDefault()!; + recommendedPlaylist.Playlist = IntersectById(data.Playlists, new[] { ((IIdentifiable)recommendedPlaylist).Identifier }).FirstOrDefault()!; } foreach (var audio in block.Audios) { @@ -89,8 +92,9 @@ private static void RemoveEmptySeparators(Section section) { var snippetsBannerIndex = section.Blocks.FindIndex(b => b is { Layout.Name: "snippets_banner" }); - if (snippetsBannerIndex < 0) return; - + if (snippetsBannerIndex < 0) + return; + section.Blocks.RemoveAt(snippetsBannerIndex); section.Blocks.RemoveAt(snippetsBannerIndex); // excess separator } @@ -98,24 +102,32 @@ private static void RemoveEmptySeparators(Section section) private static void RemoveAds(Section section) { section.Blocks.RemoveAll(block => - block is { DataType: "radiostations" } or { Layout.Title: "Радиостанции" or "Эфиры" } || + block is { DataType: "radiostations" } or { DataType: "audio_stream_mixes" } or + { DataType: "audio_content_cards" } or { DataType: "empty" } or + { Layout.Title: "Радиостанции" or "Эфиры" or "Популярные подкасты" } || ( - block is { Banners.Count: > 0 } && + block is { Banners.Count: > 0 } && block.Banners.RemoveAll(banner => banner.ClickAction?.Action.Url.Contains("subscription") is true || banner.ClickAction?.Action.Url.Contains("combo") is true || - banner.ClickAction?.Action.Url.Contains("https://vk.com/app") is true || - banner.ClickAction?.Action.Url.Contains("https://vk.com/vk_music") is true) > 0 && + banner.ClickAction?.Action.Url + .Contains("https://vk.com/app") is true || + banner.ClickAction?.Action.Url.Contains("https://vk.com/vk_music") is + true) > 0 && block.Banners.Count == 0 ) || ( - block is { Links.Count: > 0 } && - block.Links.RemoveAll(link => link.Url.Contains("audio_offline") || - link.Url.Contains("radiostations") || - link.Url.Contains("music_transfer") || - link.Url.Contains("subscription")) > 0 && + block is { Links.Count: > 0 } && + block.Links.RemoveAll(link => link.Url.Contains("audio_offline") || + link.Url.Contains("radiostations") || + link.Url.Contains("music_transfer") || + link.Url.Contains("subscription") || + link.Url.Contains("audiobooks_favorites")) > 0 && block.Links.Count == 0 ) ); + + if (section.Blocks.FirstOrDefault() is { Layout.Name: "separator" }) + section.Blocks.RemoveAt(0); } private static void MergeRefBlocks(Section section) @@ -123,13 +135,15 @@ private static void MergeRefBlocks(Section section) for (var i = 0; i < section.Blocks.Count; i++) { var block = section.Blocks[i]; - - if (block is not { DataType: "action", Layout.Name: "horizontal_buttons" }) continue; + + if (block is not { DataType: "action", Layout.Name: "horizontal_buttons" }) + continue; var refBlockIndex = section.Blocks.FindIndex(b => b.DataType == block.Actions[0].RefDataType && b.Layout?.Name == block.Actions[0].RefLayoutName) - 1; - if (refBlockIndex < 0 || block.Actions[0].Action.Type != "open_section") continue; + if (refBlockIndex < 0 || block.Actions[0].Action.Type != "open_section") + continue; section.Blocks[refBlockIndex].Actions.AddRange(block.Actions); section.Blocks.RemoveAt(i); @@ -140,7 +154,7 @@ private static void MergeRefBlocks(Section section) private static void FillPlaylistOwnerName(Playlist playlist, ResponseData data) { var ownerId = playlist.Original?.OwnerId ?? playlist.OwnerId; - + if (ownerId < 0) { var value = data.Groups?.FirstOrDefault(b => b.Id == -ownerId); diff --git a/MusicX.Core/Models/Playlist.cs b/MusicX.Core/Models/Playlist.cs index b83775c3..94841111 100644 --- a/MusicX.Core/Models/Playlist.cs +++ b/MusicX.Core/Models/Playlist.cs @@ -19,7 +19,7 @@ public class Playlist : IIdentifiable public string? OwnerName { get; set; } [JsonProperty("type")] - public long Type { get; set; } + public string Type { get; set; } [JsonProperty("title")] public string? Title { get; set; } diff --git a/MusicX.Core/Services/VkService.cs b/MusicX.Core/Services/VkService.cs index e06833d9..9bda110a 100644 --- a/MusicX.Core/Services/VkService.cs +++ b/MusicX.Core/Services/VkService.cs @@ -23,7 +23,7 @@ public class VkService public readonly IVkApiCategories vkApi; private readonly IVkApiInvoke apiInvoke; private readonly Logger logger; - private readonly string vkApiVersion = "5.220"; + private readonly string vkApiVersion = "5.243"; public bool IsAuth = false; private readonly IVkTokenStore tokenStore; diff --git a/MusicX/Controls/BlockControl.xaml b/MusicX/Controls/BlockControl.xaml index b69467d2..88635f9b 100644 --- a/MusicX/Controls/BlockControl.xaml +++ b/MusicX/Controls/BlockControl.xaml @@ -98,6 +98,9 @@ + + + diff --git a/MusicX/Controls/Blocks/LinksBlockControl.xaml.cs b/MusicX/Controls/Blocks/LinksBlockControl.xaml.cs index 8e0ba667..c473f49c 100644 --- a/MusicX/Controls/Blocks/LinksBlockControl.xaml.cs +++ b/MusicX/Controls/Blocks/LinksBlockControl.xaml.cs @@ -18,7 +18,7 @@ private static void BlockChanged(DependencyObject d, DependencyPropertyChangedEv if (d is not LinksBlockControl control || e.NewValue is not BlockViewModel block) return; - if (block.Layout?.Name == "list") + if (block.Layout?.Name is "list" or "entity_double_grid") { foreach (var link in block.Links) { diff --git a/MusicX/Controls/Blocks/MusicCategoryBlockControl.xaml b/MusicX/Controls/Blocks/MusicCategoryBlockControl.xaml index 546d3fd7..91e629f6 100644 --- a/MusicX/Controls/Blocks/MusicCategoryBlockControl.xaml +++ b/MusicX/Controls/Blocks/MusicCategoryBlockControl.xaml @@ -8,11 +8,16 @@ xmlns:controls="clr-namespace:MusicX.Controls" xmlns:shaders="clr-namespace:MusicX.Shaders" xmlns:appearance="clr-namespace:Wpf.Ui.Appearance;assembly=Wpf.Ui" + xmlns:blocks="clr-namespace:MusicX.Controls.Blocks" + xmlns:converters="clr-namespace:MusicX.Converters" + xmlns:patches="clr-namespace:MusicX.Patches" Name="Control" mc:Ignorable="d" d:DataContext="{d:DesignInstance models:Block}" d:DesignHeight="450" d:DesignWidth="800"> + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MusicX/Controls/Blocks/MusicCategoryBlockControl.xaml.cs b/MusicX/Controls/Blocks/MusicCategoryBlockControl.xaml.cs index 228873a0..0258c140 100644 --- a/MusicX/Controls/Blocks/MusicCategoryBlockControl.xaml.cs +++ b/MusicX/Controls/Blocks/MusicCategoryBlockControl.xaml.cs @@ -12,77 +12,91 @@ using NLog; using Wpf.Ui.Appearance; -namespace MusicX.Controls.Blocks +namespace MusicX.Controls.Blocks; + +/// +/// Логика взаимодействия для MusicCategoryBlockControl.xaml +/// +public partial class MusicCategoryBlockControl : UserControl { - /// - /// Логика взаимодействия для MusicCategoryBlockControl.xaml - /// - public partial class MusicCategoryBlockControl : UserControl - { - private readonly VkService vkService; - private readonly Services.NavigationService navigationService; + private readonly VkService vkService; + private readonly Services.NavigationService navigationService; - public static readonly DependencyProperty AppThemeProperty = - DependencyProperty.Register(nameof(AppTheme), typeof(ApplicationTheme), typeof(MusicCategoryBlockControl)); + public static readonly DependencyProperty AppThemeProperty = + DependencyProperty.Register(nameof(AppTheme), typeof(ApplicationTheme), typeof(MusicCategoryBlockControl)); - public ApplicationTheme AppTheme - { - get => (ApplicationTheme)GetValue(AppThemeProperty); - set => SetValue(AppThemeProperty, value); - } + public ApplicationTheme AppTheme + { + get => (ApplicationTheme)GetValue(AppThemeProperty); + set => SetValue(AppThemeProperty, value); + } - public MusicCategoryBlockControl() - { - InitializeComponent(); - Unloaded += OnUnloaded; + public static readonly DependencyProperty LayoutProperty = DependencyProperty.Register( + nameof(Layout), typeof(MusicCategoryLayout), typeof(MusicCategoryBlockControl), new PropertyMetadata(MusicCategoryLayout.List)); + + public MusicCategoryLayout Layout + { + get => (MusicCategoryLayout)GetValue(LayoutProperty); + set => SetValue(LayoutProperty, value); + } - AppTheme = ApplicationThemeManager.GetAppTheme(); - ApplicationThemeManager.Changed += ApplicationThemeOnChanged; + public MusicCategoryBlockControl() + { + InitializeComponent(); + Unloaded += OnUnloaded; - vkService = StaticService.Container.GetRequiredService(); - navigationService = StaticService.Container.GetRequiredService(); - } + AppTheme = ApplicationThemeManager.GetAppTheme(); + ApplicationThemeManager.Changed += ApplicationThemeOnChanged; - private void OnUnloaded(object sender, RoutedEventArgs e) - { - ApplicationThemeManager.Changed -= ApplicationThemeOnChanged; - } + vkService = StaticService.Container.GetRequiredService(); + navigationService = StaticService.Container.GetRequiredService(); + } - private void ApplicationThemeOnChanged(ApplicationTheme currentApplicationTheme, Color systemAccent) - { - AppTheme = currentApplicationTheme; - } + private void OnUnloaded(object sender, RoutedEventArgs e) + { + ApplicationThemeManager.Changed -= ApplicationThemeOnChanged; + } + + private void ApplicationThemeOnChanged(ApplicationTheme currentApplicationTheme, Color systemAccent) + { + AppTheme = currentApplicationTheme; + } - public IList Links => (DataContext as BlockViewModel)?.Links ?? new(); + public IList Links => (DataContext as BlockViewModel)?.Links ?? new(); - private async Task OpenPage(Link link) + private async Task OpenPage(Link link) + { + try { - try + if (link.Meta?.ContentType is "custom") { - if (link.Meta?.ContentType is "custom") - { - navigationService.OpenSection(link.Meta.TrackCode); - return; - } - - var music = await vkService.GetAudioCatalogAsync(link.Url); - navigationService.OpenSection(music.Catalog.DefaultSection); - + navigationService.OpenSection(link.Meta.TrackCode); return; } - catch(Exception ex) - { - var logger = StaticService.Container.GetRequiredService(); - logger.Error(ex, "Failed to open link {LinkType} {Link}", link.Meta?.ContentType, link.Url); - } + + var music = await vkService.GetAudioCatalogAsync(link.Url); + navigationService.OpenSection(music.Catalog.DefaultSection); + return; } - - private async void CardAction_Click(object sender, RoutedEventArgs e) + catch(Exception ex) { - if (sender is Control { DataContext: Link link }) - await OpenPage(link); + var logger = StaticService.Container.GetRequiredService(); + logger.Error(ex, "Failed to open link {LinkType} {Link}", link.Meta?.ContentType, link.Url); } + + } + + private async void CardAction_Click(object sender, RoutedEventArgs e) + { + if (sender is Control { DataContext: Link link }) + await OpenPage(link); } } + +public enum MusicCategoryLayout +{ + List, + Grid +} \ No newline at end of file diff --git a/MusicX/Controls/Blocks/TitleBlockControl.xaml.cs b/MusicX/Controls/Blocks/TitleBlockControl.xaml.cs index e6e2f806..d262202d 100644 --- a/MusicX/Controls/Blocks/TitleBlockControl.xaml.cs +++ b/MusicX/Controls/Blocks/TitleBlockControl.xaml.cs @@ -94,7 +94,8 @@ private async void MoreButton_Click(object sender, RoutedEventArgs e) var button = ViewModel.Buttons[0]; - navigationService.OpenSection(button.SectionId); + if (!string.IsNullOrEmpty(button.SectionId)) + navigationService.OpenSection(button.SectionId); } catch (Exception ex) { diff --git a/MusicX/Controls/LinkControl.xaml.cs b/MusicX/Controls/LinkControl.xaml.cs index fdb4fbb2..64fe8055 100644 --- a/MusicX/Controls/LinkControl.xaml.cs +++ b/MusicX/Controls/LinkControl.xaml.cs @@ -9,6 +9,8 @@ using MusicX.Core.Models; using MusicX.Core.Services; using MusicX.Services; +using MusicX.ViewModels; +using MusicX.Views; using NLog; using Wpf.Ui.Controls; @@ -162,6 +164,27 @@ private async void CardAction_Click(object sender, RoutedEventArgs e) navigationService.OpenSection(curator.Catalog.DefaultSection); } + + if (Link.Meta.ContentType == "audio_playlists") + { + const string playlistUrl = "https://vk.com/music/playlist/"; + if (Link.Url.StartsWith(playlistUrl)) + { + var (playlistId, ownerId, accessKey, _) = PlaylistData.Parse(Link.Url[playlistUrl.Length..]); + navigationService.OpenExternalPage(new PlaylistView(playlistId, ownerId, accessKey)); + return; + } + + if (Link.Url == "https://vk.com/audio?catalog=my_audios") + { + navigationService.OpenMenuSection("Музыка"); + return; + } + + var catalog = await vkService.GetAudioCatalogAsync(Link.Url); + + navigationService.OpenSection(catalog.Catalog.DefaultSection); + } } catch(Exception ex) { diff --git a/MusicX/Converters/MarginMultiplyConverter.cs b/MusicX/Converters/MarginMultiplyConverter.cs new file mode 100644 index 00000000..d662f057 --- /dev/null +++ b/MusicX/Converters/MarginMultiplyConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace MusicX.Converters; + +public class MarginMultiplyConverter : IValueConverter +{ + public Thickness Margin { get; set; } + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is int index) + return new Thickness(index * Margin.Left, index * Margin.Top, index * Margin.Right, index * Margin.Bottom); + + return Margin; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/MusicX/RootWindow.xaml.cs b/MusicX/RootWindow.xaml.cs index 97c5d5b1..d8c87496 100644 --- a/MusicX/RootWindow.xaml.cs +++ b/MusicX/RootWindow.xaml.cs @@ -362,7 +362,7 @@ private void NavigationServiceOnExternalSectionOpened(object? sender, SectionVie } private void NavigationServiceOnMenuSectionOpened(object? sender, string s) { - navigationBar.Items.First(b => b.Tag is string tag && tag == s) + navigationBar.Items.First(b => (b.Tag is string tag && tag == s) || (b.Content is string str && str == s)) .RaiseEvent(new(ButtonBase.ClickEvent)); } diff --git a/MusicX/Styles/CardActionStyles.xaml b/MusicX/Styles/CardActionStyles.xaml index dd1df0ec..affc9859 100644 --- a/MusicX/Styles/CardActionStyles.xaml +++ b/MusicX/Styles/CardActionStyles.xaml @@ -3,6 +3,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">