implemented search artists in MusicBrainz
This commit is contained in:
@@ -4,6 +4,11 @@ root = true
|
||||
# All files
|
||||
[*]
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<None Remove="Pages\FavoriteAlbums.xaml" />
|
||||
<None Remove="Pages\FavoriteArtists.xaml" />
|
||||
<None Remove="Pages\FavoriteTracks.xaml" />
|
||||
<None Remove="Pages\Search.xaml" />
|
||||
<None Remove="Pages\Settings.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -89,4 +90,10 @@
|
||||
<None Include="D:\work\Harmony\HarmonyWinUI\.editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\Search.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
<None Update="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<Page Update="Pages\Search.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Update="Pages\Settings.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
PaneDisplayMode="Left"
|
||||
PaneTitle="Harmony"
|
||||
x:Name="MainNavigation"
|
||||
SelectionChanged="MainNavigation_SelectionChanged">
|
||||
SelectionChanged="MainNavigation_SelectionChanged"
|
||||
IsBackButtonVisible="Visible"
|
||||
BackRequested="MainNavigation_BackRequested">
|
||||
|
||||
<NavigationView.MenuItems>
|
||||
|
||||
@@ -50,7 +52,8 @@
|
||||
<NavigationViewItem Content="Homepage" Tag="PageHomepage" Icon="Globe" />
|
||||
</NavigationView.FooterMenuItems>
|
||||
|
||||
<Frame x:Name="MainNavigationContentFrame" />
|
||||
<Frame x:Name="MainNavigationContentFrame"
|
||||
Navigated="MainNavigationContentFrame_Navigated"/>
|
||||
|
||||
</NavigationView>
|
||||
|
||||
|
||||
@@ -69,6 +69,19 @@ namespace HarmonyWinUI
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainNavigation_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
|
||||
{
|
||||
if (MainNavigationContentFrame.CanGoBack)
|
||||
{
|
||||
MainNavigationContentFrame.GoBack();
|
||||
}
|
||||
}
|
||||
|
||||
private void MainNavigationContentFrame_Navigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
|
||||
{
|
||||
MainNavigation.IsBackEnabled = MainNavigationContentFrame.CanGoBack;
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayListItem
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HarmonyWinUI.Models
|
||||
{
|
||||
class MusicBrainz
|
||||
{
|
||||
private String URLprefix = "https://musicbrainz.org/ws/2/";
|
||||
private static readonly HttpClient httpClient = createHttpClient();
|
||||
|
||||
public async Task<List<MusicBrainzArtist>> getArtists(String query)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(query);
|
||||
|
||||
string searchQuery = Uri.EscapeDataString($"artist:{query}");
|
||||
MusicBrainzArtistResponse? response = await getData<MusicBrainzArtistResponse>(
|
||||
$"artist?query={searchQuery}&fmt=json&limit=10"
|
||||
);
|
||||
|
||||
return response?.Artists ?? new List<MusicBrainzArtist>();
|
||||
}
|
||||
|
||||
private async Task<T?> getData<T>(String relativeURL)
|
||||
{
|
||||
Uri requestURL = new Uri(new Uri(URLprefix), relativeURL);
|
||||
|
||||
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestURL);
|
||||
using HttpResponseMessage response = await httpClient.SendAsync(
|
||||
request,
|
||||
HttpCompletionOption.ResponseHeadersRead
|
||||
);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
await using Stream jsonStream = await response.Content.ReadAsStreamAsync();
|
||||
return await JsonSerializer.DeserializeAsync<T>(jsonStream);
|
||||
}
|
||||
|
||||
private static HttpClient createHttpClient()
|
||||
{
|
||||
HttpClient client = new HttpClient
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(
|
||||
"HarmonyWinUI/1.0 (https://harmony.tpsoft.org/)"
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private sealed class MusicBrainzArtistResponse
|
||||
{
|
||||
[JsonPropertyName("artists")]
|
||||
public List<MusicBrainzArtist>? Artists { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class MusicBrainzArtist
|
||||
{
|
||||
public string? ID { get; set; }
|
||||
public string? Type { get; set; }
|
||||
[JsonPropertyName("type-id")]
|
||||
public string? TypeID { get; set; }
|
||||
public int? Score { get; set; }
|
||||
public string? Name { get; set; } = "";
|
||||
public string? Gender { get; set; }
|
||||
public string? Country { get; set; }
|
||||
public string? ImageURL { get; set; }
|
||||
|
||||
public MusicBrainzArtist() { }
|
||||
|
||||
public MusicBrainzArtist(
|
||||
string? id,
|
||||
string? type,
|
||||
string? typeID,
|
||||
int? score,
|
||||
string? name,
|
||||
string? gender,
|
||||
string? country)
|
||||
{
|
||||
this.ID = id;
|
||||
this.Type = type;
|
||||
this.TypeID = typeID;
|
||||
this.Score = score;
|
||||
this.Name = name;
|
||||
this.Gender = gender;
|
||||
this.Country = country;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace HarmonyWinUI.Models
|
||||
{
|
||||
class YTDLP
|
||||
{
|
||||
private String binPath = string.Empty;
|
||||
|
||||
public YTDLP()
|
||||
{
|
||||
this.binPath = this.findBinnary();
|
||||
System.Diagnostics.Debug.WriteLine("binPath = " + this.binPath);
|
||||
if (this.binPath.Equals(string.Empty))
|
||||
{
|
||||
throw new Exception("Binnary for YT-DLP not found.");
|
||||
}
|
||||
}
|
||||
|
||||
private String findBinnary()
|
||||
{
|
||||
DirectoryInfo? currentDirectory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
|
||||
while (currentDirectory != null)
|
||||
{
|
||||
string candidatePath = Path.Combine(
|
||||
currentDirectory.FullName,
|
||||
"vendor",
|
||||
"yt-dlp.exe"
|
||||
);
|
||||
|
||||
if (File.Exists(candidatePath))
|
||||
{
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
currentDirectory = currentDirectory.Parent;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,10 @@
|
||||
|
||||
<!-- Search -->
|
||||
<TextBox
|
||||
x:Name="SearchBox"
|
||||
PlaceholderText="🔎 Search artists, albums and songs..."
|
||||
HorizontalAlignment="Stretch" />
|
||||
HorizontalAlignment="Stretch"
|
||||
KeyDown="SearchBox_KeyDown"/>
|
||||
|
||||
<!-- Last Artists -->
|
||||
<StackPanel Spacing="12">
|
||||
@@ -44,14 +46,14 @@
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Last Songs -->
|
||||
<!-- Last Tracks -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock
|
||||
Text="Last songs"
|
||||
Text="Last tracks"
|
||||
Style="{StaticResource TitleTextBlockStyle}" />
|
||||
|
||||
<ListView>
|
||||
<!-- Songs -->
|
||||
<!-- Tracks -->
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
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.
|
||||
@@ -27,5 +16,22 @@ namespace HarmonyWinUI.Pages
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void SearchBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
string searchText = SearchBox.Text;
|
||||
Search(searchText);
|
||||
}
|
||||
}
|
||||
|
||||
private void Search(string searchText)
|
||||
{
|
||||
// Implement your search logic here
|
||||
// For example, you can navigate to a search results page or filter a list of items
|
||||
System.Diagnostics.Debug.WriteLine($"Searching for: {searchText}");
|
||||
Frame.Navigate(typeof(Search), searchText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page
|
||||
x:Class="HarmonyWinUI.Pages.Search"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:HarmonyWinUI.Pages"
|
||||
xmlns:models="using:HarmonyWinUI.Models"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<local:ImageUrlToImageSourceConverter x:Key="ImageUrlToImageSourceConverter" />
|
||||
</Page.Resources>
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel
|
||||
Padding="24"
|
||||
Spacing="24">
|
||||
|
||||
<TextBlock Text="Search" FontFamily="Arial"
|
||||
FontSize="24" TextWrapping="WrapWholeWords"
|
||||
CharacterSpacing="200" Foreground="LightGray" />
|
||||
|
||||
<TextBlock x:Name="TextBlockQuery" Text="Query: " FontFamily="Arial"
|
||||
FontSize="14" TextWrapping="WrapWholeWords"
|
||||
CharacterSpacing="100" Foreground="LightGray" />
|
||||
|
||||
<!-- Artists -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock
|
||||
Text="Artists"
|
||||
Style="{StaticResource TitleTextBlockStyle}" />
|
||||
|
||||
<ListView
|
||||
x:Name="ArtistsListView"
|
||||
ItemsSource="{x:Bind Artists}">
|
||||
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:MusicBrainzArtist">
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="12"
|
||||
Padding="4">
|
||||
|
||||
<Grid
|
||||
Width="48"
|
||||
Height="48">
|
||||
<Border
|
||||
Background="{ThemeResource SystemControlBackgroundBaseMediumBrush}"
|
||||
CornerRadius="24">
|
||||
<FontIcon
|
||||
FontSize="24"
|
||||
Glyph="" />
|
||||
</Border>
|
||||
|
||||
<Image
|
||||
Source="{Binding ImageURL, Converter={StaticResource ImageUrlToImageSourceConverter}}"
|
||||
Stretch="UniformToFill" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Text="{x:Bind Name}"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Albums -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock
|
||||
Text="Albums"
|
||||
Style="{StaticResource TitleTextBlockStyle}" />
|
||||
|
||||
<ListView>
|
||||
<!-- Albums -->
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Tracks -->
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock
|
||||
Text="Tracks"
|
||||
Style="{StaticResource TitleTextBlockStyle}" />
|
||||
|
||||
<ListView>
|
||||
<!-- Tracks -->
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Page>
|
||||
@@ -0,0 +1,114 @@
|
||||
using HarmonyWinUI.Models;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace HarmonyWinUI.Pages;
|
||||
|
||||
public sealed class ImageUrlToImageSourceConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is string imageUrl
|
||||
&& Uri.TryCreate(imageUrl, UriKind.Absolute, out Uri? imageUri))
|
||||
{
|
||||
return new BitmapImage(imageUri);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class Search : Page
|
||||
{
|
||||
public ObservableCollection<MusicBrainzArtist> Artists { get; } = new();
|
||||
private string? pendingSearchText;
|
||||
|
||||
public Search()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += Search_Loaded;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
pendingSearchText = e.Parameter as string;
|
||||
}
|
||||
|
||||
private async void Search_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (pendingSearchText is not string searchText)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pendingSearchText = null;
|
||||
await SearchText(searchText);
|
||||
}
|
||||
|
||||
private async Task SearchText(string searchText)
|
||||
{
|
||||
TextBlockQuery.Text = $"Query: '{searchText}'";
|
||||
var mb = new MusicBrainz();
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var artists = await mb.getArtists(searchText);
|
||||
|
||||
this.Artists.Clear();
|
||||
foreach (var artist in artists)
|
||||
{
|
||||
this.Artists.Add(artist);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception exception) when (
|
||||
exception is HttpRequestException
|
||||
or IOException
|
||||
or JsonException
|
||||
or TaskCanceledException
|
||||
)
|
||||
{
|
||||
ContentDialog errorDialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = XamlRoot,
|
||||
Title = "MusicBrainz connection failed",
|
||||
Content = $"{exception.GetType().Name}: {exception.Message}",
|
||||
PrimaryButtonText = "Retry",
|
||||
CloseButtonText = "Cancel",
|
||||
DefaultButton = ContentDialogButton.Primary
|
||||
};
|
||||
|
||||
ContentDialogResult result = await errorDialog.ShowAsync();
|
||||
if (result != ContentDialogResult.Primary)
|
||||
{
|
||||
this.Artists.Clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user