diff --git a/HarmonyWinUI/.editorconfig b/HarmonyWinUI/.editorconfig
index ed024d0..83c28bd 100644
--- a/HarmonyWinUI/.editorconfig
+++ b/HarmonyWinUI/.editorconfig
@@ -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]
diff --git a/HarmonyWinUI/HarmonyWinUI.csproj b/HarmonyWinUI/HarmonyWinUI.csproj
index 8ba262f..881f798 100644
--- a/HarmonyWinUI/HarmonyWinUI.csproj
+++ b/HarmonyWinUI/HarmonyWinUI.csproj
@@ -36,6 +36,7 @@
+
@@ -89,4 +90,10 @@
+
+
+ MSBuild:Compile
+
+
+
diff --git a/HarmonyWinUI/HarmonyWinUI.csproj.user b/HarmonyWinUI/HarmonyWinUI.csproj.user
index b80b9f8..73cd313 100644
--- a/HarmonyWinUI/HarmonyWinUI.csproj.user
+++ b/HarmonyWinUI/HarmonyWinUI.csproj.user
@@ -17,6 +17,9 @@
Designer
+
+ Designer
+
Designer
diff --git a/HarmonyWinUI/MainWindow.xaml b/HarmonyWinUI/MainWindow.xaml
index c444bf0..4c194f1 100644
--- a/HarmonyWinUI/MainWindow.xaml
+++ b/HarmonyWinUI/MainWindow.xaml
@@ -30,8 +30,10 @@
PaneDisplayMode="Left"
PaneTitle="Harmony"
x:Name="MainNavigation"
- SelectionChanged="MainNavigation_SelectionChanged">
-
+ SelectionChanged="MainNavigation_SelectionChanged"
+ IsBackButtonVisible="Visible"
+ BackRequested="MainNavigation_BackRequested">
+
@@ -40,7 +42,7 @@
-
+
@@ -50,7 +52,8 @@
-
+
@@ -100,7 +103,7 @@
RadiusY="6"
VerticalAlignment="Center"
HorizontalAlignment="Center"
- Fill="{ThemeResource SystemControlBackgroundBaseMediumBrush}"/>
+ Fill="{ThemeResource SystemControlBackgroundBaseMediumBrush}"/>
+ Margin="12,0,0,6"/>
diff --git a/HarmonyWinUI/MainWindow.xaml.cs b/HarmonyWinUI/MainWindow.xaml.cs
index a6accdb..0295883 100644
--- a/HarmonyWinUI/MainWindow.xaml.cs
+++ b/HarmonyWinUI/MainWindow.xaml.cs
@@ -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
diff --git a/HarmonyWinUI/Models/MusicBrainz.cs b/HarmonyWinUI/Models/MusicBrainz.cs
new file mode 100644
index 0000000..6aec890
--- /dev/null
+++ b/HarmonyWinUI/Models/MusicBrainz.cs
@@ -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> getArtists(String query)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(query);
+
+ string searchQuery = Uri.EscapeDataString($"artist:{query}");
+ MusicBrainzArtistResponse? response = await getData(
+ $"artist?query={searchQuery}&fmt=json&limit=10"
+ );
+
+ return response?.Artists ?? new List();
+ }
+
+ private async Task getData(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(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? 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;
+ }
+
+ }
+}
diff --git a/HarmonyWinUI/Models/YTDLP.cs b/HarmonyWinUI/Models/YTDLP.cs
new file mode 100644
index 0000000..96db544
--- /dev/null
+++ b/HarmonyWinUI/Models/YTDLP.cs
@@ -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;
+ }
+ }
+}
diff --git a/HarmonyWinUI/Pages/Dashboard.xaml b/HarmonyWinUI/Pages/Dashboard.xaml
index 57dc36e..a0e102f 100644
--- a/HarmonyWinUI/Pages/Dashboard.xaml
+++ b/HarmonyWinUI/Pages/Dashboard.xaml
@@ -19,14 +19,16 @@
+ x:Name="SearchBox"
+ PlaceholderText="🔎 Search artists, albums and songs..."
+ HorizontalAlignment="Stretch"
+ KeyDown="SearchBox_KeyDown"/>
+ Text="Last artists"
+ Style="{StaticResource TitleTextBlockStyle}" />
@@ -36,30 +38,30 @@
+ Text="Last albums"
+ Style="{StaticResource TitleTextBlockStyle}" />
-
+
+ Text="Last tracks"
+ Style="{StaticResource TitleTextBlockStyle}" />
-
+
+ Text="New releases"
+ Style="{StaticResource TitleTextBlockStyle}" />
diff --git a/HarmonyWinUI/Pages/Dashboard.xaml.cs b/HarmonyWinUI/Pages/Dashboard.xaml.cs
index 256b9ff..3e464c6 100644
--- a/HarmonyWinUI/Pages/Dashboard.xaml.cs
+++ b/HarmonyWinUI/Pages/Dashboard.xaml.cs
@@ -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);
+ }
}
}
diff --git a/HarmonyWinUI/Pages/Search.xaml b/HarmonyWinUI/Pages/Search.xaml
new file mode 100644
index 0000000..15541d1
--- /dev/null
+++ b/HarmonyWinUI/Pages/Search.xaml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HarmonyWinUI/Pages/Search.xaml.cs b/HarmonyWinUI/Pages/Search.xaml.cs
new file mode 100644
index 0000000..a623537
--- /dev/null
+++ b/HarmonyWinUI/Pages/Search.xaml.cs
@@ -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();
+ }
+}
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+public sealed partial class Search : Page
+{
+ public ObservableCollection 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;
+ }
+ }
+ }
+ }
+}