From 37d639fb92a3330e48c91306b253f119b1346f6f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 31 Mar 2026 23:24:35 +0000
Subject: [PATCH 01/17] Initial plan
From 0e191ead5da7e21d0c5acbdd429a3f19bbecf9f0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 31 Mar 2026 23:30:58 +0000
Subject: [PATCH 02/17] Pop out manual station into its own small window
Agent-Logs-Url: https://github.com/TheJoeFin/Trdo/sessions/069d6b25-7e88-4cf0-85ed-b1ea8971abf7
Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com>
---
Trdo/Controls/ManualStationWindow.xaml | 116 ++++++++++++++++++++++
Trdo/Controls/ManualStationWindow.xaml.cs | 58 +++++++++++
Trdo/Pages/PlayingPage.xaml.cs | 7 +-
Trdo/Pages/SearchStation.xaml.cs | 7 +-
Trdo/Trdo.csproj | 6 ++
5 files changed, 190 insertions(+), 4 deletions(-)
create mode 100644 Trdo/Controls/ManualStationWindow.xaml
create mode 100644 Trdo/Controls/ManualStationWindow.xaml.cs
diff --git a/Trdo/Controls/ManualStationWindow.xaml b/Trdo/Controls/ManualStationWindow.xaml
new file mode 100644
index 0000000..978374d
--- /dev/null
+++ b/Trdo/Controls/ManualStationWindow.xaml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Trdo/Controls/ManualStationWindow.xaml.cs b/Trdo/Controls/ManualStationWindow.xaml.cs
new file mode 100644
index 0000000..e28e7db
--- /dev/null
+++ b/Trdo/Controls/ManualStationWindow.xaml.cs
@@ -0,0 +1,58 @@
+using Trdo.Models;
+using Trdo.ViewModels;
+using WinUIEx;
+
+namespace Trdo.Controls;
+
+///
+/// A small standalone window for manually adding or editing a radio station.
+/// Opens as a pop-out window so that closing the tray flyout does not clear the form fields.
+///
+public sealed partial class ManualStationWindow : WindowEx
+{
+ public AddStationViewModel ViewModel { get; }
+
+ public ManualStationWindow()
+ {
+ InitializeComponent();
+
+ ViewModel = new AddStationViewModel();
+ ViewModel.SetPlayerViewModel(PlayerViewModel.Shared);
+
+ ExtendsContentIntoTitleBar = true;
+ SetTitleBar(ModernTitlebar);
+
+ Activated += ManualStationWindow_Activated;
+ }
+
+ ///
+ /// Opens the window pre-filled with the given station's data for editing.
+ ///
+ public void LoadStationForEdit(RadioStation station)
+ {
+ ViewModel.LoadStationForEdit(station);
+ }
+
+ private void ManualStationWindow_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args)
+ {
+ // Focus the station name field once the window is ready
+ if (args.WindowActivationState != Microsoft.UI.Xaml.WindowActivationState.Deactivated)
+ {
+ StationNameTextBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
+ Activated -= ManualStationWindow_Activated;
+ }
+ }
+
+ private void SaveButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ if (ViewModel.Save())
+ {
+ Close();
+ }
+ }
+
+ private void CancelButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ Close();
+ }
+}
diff --git a/Trdo/Pages/PlayingPage.xaml.cs b/Trdo/Pages/PlayingPage.xaml.cs
index 4da53e9..2daa1e1 100644
--- a/Trdo/Pages/PlayingPage.xaml.cs
+++ b/Trdo/Pages/PlayingPage.xaml.cs
@@ -356,8 +356,11 @@ private void EditStation_Click(object sender, RoutedEventArgs e)
if (sender is MenuFlyoutItem menuItem && menuItem.Tag is RadioStation station)
{
Debug.WriteLine($"[PlayingPage] Edit station clicked: {station.Name}");
- // Navigate to AddStation page in edit mode with the station data
- _shellViewModel?.NavigateToAddStationPage(station);
+ // Open a pop-out window for editing so the flyout closing doesn't clear the fields
+ ManualStationWindow editWindow = new();
+ WindowHelper.Track(editWindow);
+ editWindow.LoadStationForEdit(station);
+ editWindow.Activate();
}
}
diff --git a/Trdo/Pages/SearchStation.xaml.cs b/Trdo/Pages/SearchStation.xaml.cs
index ecc3ad6..d89b766 100644
--- a/Trdo/Pages/SearchStation.xaml.cs
+++ b/Trdo/Pages/SearchStation.xaml.cs
@@ -2,6 +2,7 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
+using Trdo.Controls;
using Trdo.Models;
using Trdo.Services;
using Trdo.ViewModels;
@@ -139,8 +140,10 @@ private void AddStationButton_Click(object sender, RoutedEventArgs e)
private void ManualEntryButton_Click(object sender, RoutedEventArgs e)
{
StopPreview();
- // Navigate to manual entry page
- _shellViewModel?.NavigateToAddStationPage();
+ // Open a pop-out window for manual station entry so the flyout closing doesn't clear the fields
+ ManualStationWindow addWindow = new();
+ WindowHelper.Track(addWindow);
+ addWindow.Activate();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
diff --git a/Trdo/Trdo.csproj b/Trdo/Trdo.csproj
index a5f24ad..cb3749a 100644
--- a/Trdo/Trdo.csproj
+++ b/Trdo/Trdo.csproj
@@ -20,6 +20,7 @@
+
@@ -119,6 +120,11 @@
MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
MSBuild:Compile
From fadef170c437ce19425a6be9621227b15072eafe Mon Sep 17 00:00:00 2001
From: Joe Finney
Date: Tue, 31 Mar 2026 19:31:02 -0500
Subject: [PATCH 03/17] Improve window placement UX with pointer anchor service
Added WindowPlacementService to capture pointer position and position windows (ManualStationWindow, TutorialWindow) near the anchor. Updated event handlers to use the service before opening flyouts or windows. Increased ManualStationWindow height to 500 and reformatted XAML bindings for readability. Added necessary using directives for the new service.
---
Trdo/App.xaml.cs | 6 +-
Trdo/Controls/ManualStationWindow.xaml | 25 +++--
Trdo/Controls/ManualStationWindow.xaml.cs | 2 +
Trdo/Controls/TutorialWindow.xaml.cs | 10 ++
Trdo/Pages/PlayingPage.xaml.cs | 12 +--
Trdo/Pages/SearchStation.xaml.cs | 2 +-
Trdo/Services/WindowPlacementService.cs | 114 ++++++++++++++++++++++
7 files changed, 153 insertions(+), 18 deletions(-)
create mode 100644 Trdo/Services/WindowPlacementService.cs
diff --git a/Trdo/App.xaml.cs b/Trdo/App.xaml.cs
index 65d57c5..a085ee6 100644
--- a/Trdo/App.xaml.cs
+++ b/Trdo/App.xaml.cs
@@ -11,7 +11,6 @@
using Trdo.Pages;
using Trdo.Services;
using Trdo.ViewModels;
-using Windows.UI;
using Windows.UI.ViewManagement;
using WinUIEx;
@@ -157,6 +156,7 @@ private void InitializeTrayIcon()
private void TrayIcon_ContextMenu(TrayIcon sender, TrayIconEventArgs args)
{
+ WindowPlacementService.CapturePointerAnchor();
args.Flyout = CreateFlyout();
}
@@ -166,6 +166,7 @@ private void TrayIcon_Selected(TrayIcon sender, TrayIconEventArgs args)
if (!_playerVm.CanPlay)
{
// No stations available, show the flyout to encourage user to add a station
+ WindowPlacementService.CapturePointerAnchor();
args.Flyout = CreateFlyout();
return;
}
@@ -190,6 +191,7 @@ private Flyout CreateFlyout()
flyout.Opened += (s, e) =>
{
+ WindowPlacementService.CapturePointerAnchor();
// Clear the back stack when flyout opens to prevent accumulation
Services.NavigationService.Instance.ClearBackStack();
};
@@ -272,7 +274,7 @@ private void UpdatePlayPauseCommandText()
}
else if (_playerVm.IsPlaying)
{
-
+
// Include now playing info if available
if (_playerVm.HasNowPlaying)
{
diff --git a/Trdo/Controls/ManualStationWindow.xaml b/Trdo/Controls/ManualStationWindow.xaml
index 978374d..b222264 100644
--- a/Trdo/Controls/ManualStationWindow.xaml
+++ b/Trdo/Controls/ManualStationWindow.xaml
@@ -9,7 +9,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Trdo - Add Station"
Width="400"
- Height="380"
+ Height="500"
IsMaximizable="False"
IsMinimizable="False"
mc:Ignorable="d">
@@ -20,9 +20,7 @@
-
+
@@ -38,7 +36,9 @@
+ Text="{x:Bind ViewModel.StationName,
+ Mode=TwoWay,
+ UpdateSourceTrigger=PropertyChanged}" />
@@ -47,7 +47,9 @@
@@ -57,7 +59,9 @@
@@ -67,7 +71,9 @@
@@ -101,7 +107,8 @@
Grid.Column="1"
HorizontalAlignment="Stretch"
Click="SaveButton_Click"
- IsEnabled="{x:Bind ViewModel.CanSave, Mode=OneWay}"
+ IsEnabled="{x:Bind ViewModel.CanSave,
+ Mode=OneWay}"
Style="{StaticResource AccentButtonStyle}">
diff --git a/Trdo/Controls/ManualStationWindow.xaml.cs b/Trdo/Controls/ManualStationWindow.xaml.cs
index e28e7db..2c7cc49 100644
--- a/Trdo/Controls/ManualStationWindow.xaml.cs
+++ b/Trdo/Controls/ManualStationWindow.xaml.cs
@@ -1,4 +1,5 @@
using Trdo.Models;
+using Trdo.Services;
using Trdo.ViewModels;
using WinUIEx;
@@ -38,6 +39,7 @@ private void ManualStationWindow_Activated(object sender, Microsoft.UI.Xaml.Wind
// Focus the station name field once the window is ready
if (args.WindowActivationState != Microsoft.UI.Xaml.WindowActivationState.Deactivated)
{
+ WindowPlacementService.PositionWindowNearAnchor(this, 400, 500);
StationNameTextBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
Activated -= ManualStationWindow_Activated;
}
diff --git a/Trdo/Controls/TutorialWindow.xaml.cs b/Trdo/Controls/TutorialWindow.xaml.cs
index 0f8327f..758c27c 100644
--- a/Trdo/Controls/TutorialWindow.xaml.cs
+++ b/Trdo/Controls/TutorialWindow.xaml.cs
@@ -13,6 +13,16 @@ public TutorialWindow()
ExtendsContentIntoTitleBar = true;
SetTitleBar(ModernTitlebar);
+ Activated += TutorialWindow_Activated;
+ }
+
+ private void TutorialWindow_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args)
+ {
+ if (args.WindowActivationState == Microsoft.UI.Xaml.WindowActivationState.Deactivated)
+ return;
+
+ WindowPlacementService.PositionWindowNearAnchor(this, 400, 600);
+ Activated -= TutorialWindow_Activated;
}
private void Button_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
diff --git a/Trdo/Pages/PlayingPage.xaml.cs b/Trdo/Pages/PlayingPage.xaml.cs
index 2daa1e1..e7998b4 100644
--- a/Trdo/Pages/PlayingPage.xaml.cs
+++ b/Trdo/Pages/PlayingPage.xaml.cs
@@ -9,7 +9,6 @@
using Trdo.Models;
using Trdo.Services;
using Trdo.ViewModels;
-using WinUIEx;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -23,9 +22,9 @@ public sealed partial class PlayingPage : Page
private const int MinIndexForScrolling = 3;
private const string FilledStar = "\uE735";
private const string OutlineStar = "\uE734";
-
+
private readonly FavoritesService _favoritesService = FavoritesService.Instance;
-
+
public PlayerViewModel ViewModel { get; }
private ShellViewModel? _shellViewModel;
@@ -117,7 +116,7 @@ private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.Pro
UpdatePlayButtonState();
UpdateStationSelection();
- if (e.PropertyName == nameof(PlayerViewModel.CurrentMetadata) ||
+ if (e.PropertyName == nameof(PlayerViewModel.CurrentMetadata) ||
e.PropertyName == nameof(PlayerViewModel.HasNowPlaying))
{
UpdateFavoriteButtonState();
@@ -321,7 +320,7 @@ private void NowPlayingInfo_Click(object sender, RoutedEventArgs e)
private void FavoriteButton_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("[PlayingPage] Favorite button clicked");
-
+
if (ViewModel.CurrentMetadata?.HasMetadata != true)
{
Debug.WriteLine("[PlayingPage] No metadata to favorite");
@@ -331,7 +330,7 @@ private void FavoriteButton_Click(object sender, RoutedEventArgs e)
string stationName = ViewModel.SelectedStation?.Name ?? "Unknown Station";
bool isFavorited = _favoritesService.ToggleFavorite(ViewModel.CurrentMetadata, stationName);
Debug.WriteLine($"[PlayingPage] Track favorite toggled. IsFavorited: {isFavorited}");
-
+
// UpdateFavoriteButtonState will be called via the FavoritesChanged event
}
@@ -357,6 +356,7 @@ private void EditStation_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"[PlayingPage] Edit station clicked: {station.Name}");
// Open a pop-out window for editing so the flyout closing doesn't clear the fields
+ WindowPlacementService.CapturePointerAnchor();
ManualStationWindow editWindow = new();
WindowHelper.Track(editWindow);
editWindow.LoadStationForEdit(station);
diff --git a/Trdo/Pages/SearchStation.xaml.cs b/Trdo/Pages/SearchStation.xaml.cs
index d89b766..070ae68 100644
--- a/Trdo/Pages/SearchStation.xaml.cs
+++ b/Trdo/Pages/SearchStation.xaml.cs
@@ -1,7 +1,6 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
-using System;
using Trdo.Controls;
using Trdo.Models;
using Trdo.Services;
@@ -141,6 +140,7 @@ private void ManualEntryButton_Click(object sender, RoutedEventArgs e)
{
StopPreview();
// Open a pop-out window for manual station entry so the flyout closing doesn't clear the fields
+ WindowPlacementService.CapturePointerAnchor();
ManualStationWindow addWindow = new();
WindowHelper.Track(addWindow);
addWindow.Activate();
diff --git a/Trdo/Services/WindowPlacementService.cs b/Trdo/Services/WindowPlacementService.cs
new file mode 100644
index 0000000..2c47938
--- /dev/null
+++ b/Trdo/Services/WindowPlacementService.cs
@@ -0,0 +1,114 @@
+using Microsoft.UI.Windowing;
+using Microsoft.UI.Xaml;
+using System.Runtime.InteropServices;
+using Windows.Graphics;
+using WinUIEx;
+
+namespace Trdo.Services;
+
+internal static partial class WindowPlacementService
+{
+ private const int WindowMargin = 12;
+ private static PointInt32? _lastAnchorPoint;
+
+ public static void CapturePointerAnchor()
+ {
+ if (GetCursorPos(out POINT point))
+ _lastAnchorPoint = new PointInt32(point.X, point.Y);
+ }
+
+ public static void PositionWindowNearAnchor(Window window, int width, int height)
+ {
+ PointInt32 anchor = GetAnchorPoint();
+ DisplayArea? displayArea = DisplayArea.GetFromPoint(anchor, DisplayAreaFallback.Nearest);
+ RectInt32 workArea = displayArea?.WorkArea ?? DisplayArea.Primary.WorkArea;
+
+ bool placeLeft = anchor.X >= workArea.X + (workArea.Width / 2);
+ bool placeAbove = anchor.Y >= workArea.Y + (workArea.Height / 2);
+
+ int x = placeLeft ? anchor.X - width - WindowMargin : anchor.X + WindowMargin;
+ int y = placeAbove ? anchor.Y - height - WindowMargin : anchor.Y + WindowMargin;
+
+ x = System.Math.Clamp(x, workArea.X, workArea.X + workArea.Width - width);
+ y = System.Math.Clamp(y, workArea.Y, workArea.Y + workArea.Height - height);
+
+ window.MoveAndResize(x, y, width, height);
+ }
+
+ private static PointInt32 GetAnchorPoint()
+ {
+ if (_lastAnchorPoint is PointInt32 anchor)
+ return anchor;
+
+ if (TryGetTaskbarAnchorPoint(out anchor))
+ return anchor;
+
+ RectInt32 workArea = DisplayArea.Primary.WorkArea;
+ return new PointInt32(workArea.X + workArea.Width - WindowMargin, workArea.Y + workArea.Height - WindowMargin);
+ }
+
+ private static bool TryGetTaskbarAnchorPoint(out PointInt32 anchor)
+ {
+ APPBARDATA appBarData = new()
+ {
+ cbSize = Marshal.SizeOf()
+ };
+
+ if (SHAppBarMessage(ABM_GETTASKBARPOS, ref appBarData) == 0)
+ {
+ anchor = default;
+ return false;
+ }
+
+ anchor = appBarData.uEdge switch
+ {
+ ABE_BOTTOM => new PointInt32(appBarData.rc.Right - WindowMargin, appBarData.rc.Top + ((appBarData.rc.Bottom - appBarData.rc.Top) / 2)),
+ ABE_TOP => new PointInt32(appBarData.rc.Right - WindowMargin, appBarData.rc.Bottom - WindowMargin),
+ ABE_LEFT => new PointInt32(appBarData.rc.Right - WindowMargin, appBarData.rc.Bottom - WindowMargin),
+ ABE_RIGHT => new PointInt32(appBarData.rc.Left + WindowMargin, appBarData.rc.Bottom - WindowMargin),
+ _ => new PointInt32(appBarData.rc.Right - WindowMargin, appBarData.rc.Bottom - WindowMargin)
+ };
+
+ return true;
+ }
+
+ private const uint ABM_GETTASKBARPOS = 0x00000005;
+ private const uint ABE_LEFT = 0;
+ private const uint ABE_TOP = 1;
+ private const uint ABE_RIGHT = 2;
+ private const uint ABE_BOTTOM = 3;
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct POINT
+ {
+ public int X;
+ public int Y;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct RECT
+ {
+ public int Left;
+ public int Top;
+ public int Right;
+ public int Bottom;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct APPBARDATA
+ {
+ public int cbSize;
+ public nint hWnd;
+ public uint uCallbackMessage;
+ public uint uEdge;
+ public RECT rc;
+ public nint lParam;
+ }
+
+ [LibraryImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static partial bool GetCursorPos(out POINT point);
+
+ [LibraryImport("shell32.dll")]
+ private static partial uint SHAppBarMessage(uint dwMessage, ref APPBARDATA pData);
+}
From b8c1b63d7b30360af3d0f616b3eb1db1f2b428ea Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Apr 2026 00:36:03 +0000
Subject: [PATCH 04/17] Initial plan
From 45252febefce94cec6500835bfc20e9460a202a0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Apr 2026 00:41:10 +0000
Subject: [PATCH 05/17] Add Play on startup setting to auto-play last station
when app opens
Agent-Logs-Url: https://github.com/TheJoeFin/Trdo/sessions/bbe887a0-8caa-4095-9da8-05b4aa6e901b
Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com>
---
Trdo/Pages/SettingsPage.xaml | 24 +++++++++++++++++
Trdo/Services/SettingsService.cs | 40 ++++++++++++++++++++++++++++
Trdo/ViewModels/PlayerViewModel.cs | 7 +++++
Trdo/ViewModels/SettingsViewModel.cs | 28 +++++++++++++++++++
4 files changed, 99 insertions(+)
diff --git a/Trdo/Pages/SettingsPage.xaml b/Trdo/Pages/SettingsPage.xaml
index d1a31c0..0b3dfc0 100644
--- a/Trdo/Pages/SettingsPage.xaml
+++ b/Trdo/Pages/SettingsPage.xaml
@@ -43,6 +43,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Trdo/Services/SettingsService.cs b/Trdo/Services/SettingsService.cs
index a16f265..b20bbca 100644
--- a/Trdo/Services/SettingsService.cs
+++ b/Trdo/Services/SettingsService.cs
@@ -9,6 +9,46 @@ public static class SettingsService
{
private const string IsFirstRunKey = "IsFirstRun";
private const string IsVolumeSliderVisibleKey = "IsVolumeSliderVisible";
+ private const string AutoPlayOnStartupKey = "AutoPlayOnStartup";
+
+ ///
+ /// Gets or sets whether the app should automatically start playing the last selected station on startup.
+ /// Defaults to false when no saved value exists.
+ ///
+ public static bool AutoPlayOnStartup
+ {
+ get
+ {
+ try
+ {
+ if (ApplicationData.Current.LocalSettings.Values.TryGetValue(AutoPlayOnStartupKey, out object? value))
+ {
+ return value switch
+ {
+ bool b => b,
+ string s when bool.TryParse(s, out bool b2) => b2,
+ _ => false
+ };
+ }
+ return false;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ set
+ {
+ try
+ {
+ ApplicationData.Current.LocalSettings.Values[AutoPlayOnStartupKey] = value;
+ }
+ catch
+ {
+ // Silently fail if unable to save
+ }
+ }
+ }
///
/// Gets or sets whether the volume slider is visible on the playing page.
diff --git a/Trdo/ViewModels/PlayerViewModel.cs b/Trdo/ViewModels/PlayerViewModel.cs
index 53f21ff..614d335 100644
--- a/Trdo/ViewModels/PlayerViewModel.cs
+++ b/Trdo/ViewModels/PlayerViewModel.cs
@@ -108,6 +108,13 @@ public PlayerViewModel()
{
Debug.WriteLine($"[PlayerViewModel] Initializing stream with URL: {_selectedStation.StreamUrl}");
InitializeStream(_selectedStation.StreamUrl);
+
+ // Auto-play on startup if the setting is enabled
+ if (SettingsService.AutoPlayOnStartup)
+ {
+ Debug.WriteLine("[PlayerViewModel] AutoPlayOnStartup is enabled, starting playback...");
+ _player.Play();
+ }
}
else
{
diff --git a/Trdo/ViewModels/SettingsViewModel.cs b/Trdo/ViewModels/SettingsViewModel.cs
index 213852c..ab06b6d 100644
--- a/Trdo/ViewModels/SettingsViewModel.cs
+++ b/Trdo/ViewModels/SettingsViewModel.cs
@@ -20,6 +20,7 @@ public partial class SettingsViewModel : INotifyPropertyChanged
private string _startupToggleText = "Off";
private string _watchdogToggleText = "Off";
private string _autoBufferToggleText = "Off";
+ private string _autoPlayOnStartupToggleText = "Off";
private StartupTask? _startupTask;
private bool _initDone;
@@ -57,6 +58,7 @@ public SettingsViewModel()
// Initialize toggle text
WatchdogToggleText = GetToggleText(_playerViewModel.WatchdogEnabled);
AutoBufferToggleText = GetToggleText(_playerViewModel.AutoBufferIncreaseEnabled);
+ AutoPlayOnStartupToggleText = GetToggleText(SettingsService.AutoPlayOnStartup);
// Initialize startup task
_ = InitializeStartupTaskAsync();
@@ -101,6 +103,32 @@ public string StartupToggleText
}
}
+ ///
+ /// Gets or sets whether the app should automatically start playing the last selected station on startup.
+ ///
+ public bool IsAutoPlayOnStartupEnabled
+ {
+ get => SettingsService.AutoPlayOnStartup;
+ set
+ {
+ if (value == SettingsService.AutoPlayOnStartup) return;
+ SettingsService.AutoPlayOnStartup = value;
+ OnPropertyChanged();
+ AutoPlayOnStartupToggleText = GetToggleText(value);
+ }
+ }
+
+ public string AutoPlayOnStartupToggleText
+ {
+ get => _autoPlayOnStartupToggleText;
+ set
+ {
+ if (value == _autoPlayOnStartupToggleText) return;
+ _autoPlayOnStartupToggleText = value;
+ OnPropertyChanged();
+ }
+ }
+
public bool IsWatchdogEnabled
{
get => _playerViewModel.WatchdogEnabled;
From 1de7e3a4ed952dc3541759b658f28d8253cebf59 Mon Sep 17 00:00:00 2001
From: Joe Finney
Date: Wed, 1 Apr 2026 00:16:34 -0500
Subject: [PATCH 06/17] Add warning InfoBar for auto-play on startup toggle
Show a warning InfoBar when enabling "Auto-play on startup" to inform users that audio will start automatically. Require explicit confirmation via an "Enable anyway" button. Refactor toggle logic to use event handler and OneWay binding. Rename "Enable Auto-Resume" to "Auto-recover" with updated description. Use shared PlayerViewModel instance in SettingsViewModel.
---
Trdo/Pages/SettingsPage.xaml | 25 +++++++++++++--
Trdo/Pages/SettingsPage.xaml.cs | 46 ++++++++++++++++++++++++++++
Trdo/ViewModels/SettingsViewModel.cs | 2 +-
3 files changed, 69 insertions(+), 4 deletions(-)
diff --git a/Trdo/Pages/SettingsPage.xaml b/Trdo/Pages/SettingsPage.xaml
index 0b3dfc0..edc234b 100644
--- a/Trdo/Pages/SettingsPage.xaml
+++ b/Trdo/Pages/SettingsPage.xaml
@@ -51,12 +51,26 @@
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="Automatically start playing the last selected station when the app opens"
TextWrapping="Wrap" />
+
+
+
+
+
-
+
-
+
+
From 66ea8ff489a82372d505c2cfc7f9a7a74c15471a Mon Sep 17 00:00:00 2001
From: Joe Finney
Date: Wed, 1 Apr 2026 00:23:33 -0500
Subject: [PATCH 07/17] Clamp window position to non-negative work area bounds
Adjusted clamping logic to use maxX and maxY with System.Math.Max, preventing negative clamp ranges when window size exceeds work area. This ensures window positions remain within valid screen bounds.
---
Trdo/Services/WindowPlacementService.cs | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/Trdo/Services/WindowPlacementService.cs b/Trdo/Services/WindowPlacementService.cs
index 2c47938..10e7576 100644
--- a/Trdo/Services/WindowPlacementService.cs
+++ b/Trdo/Services/WindowPlacementService.cs
@@ -29,8 +29,11 @@ public static void PositionWindowNearAnchor(Window window, int width, int height
int x = placeLeft ? anchor.X - width - WindowMargin : anchor.X + WindowMargin;
int y = placeAbove ? anchor.Y - height - WindowMargin : anchor.Y + WindowMargin;
- x = System.Math.Clamp(x, workArea.X, workArea.X + workArea.Width - width);
- y = System.Math.Clamp(y, workArea.Y, workArea.Y + workArea.Height - height);
+ int maxX = System.Math.Max(workArea.X, workArea.X + workArea.Width - width);
+ int maxY = System.Math.Max(workArea.Y, workArea.Y + workArea.Height - height);
+
+ x = System.Math.Clamp(x, workArea.X, maxX);
+ y = System.Math.Clamp(y, workArea.Y, maxY);
window.MoveAndResize(x, y, width, height);
}
From fafad5acbe34cda3a4008a064068352d8a0b015a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Apr 2026 05:29:10 +0000
Subject: [PATCH 08/17] Initial plan
From 02435643b6ccc3cae1cf69a2f2f6d9d5f54cdc2d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Apr 2026 05:38:53 +0000
Subject: [PATCH 09/17] Add Apple Music and YouTube Music search options with
configurable settings toggles
Agent-Logs-Url: https://github.com/TheJoeFin/Trdo/sessions/dac8c98c-2c5e-4f5f-bf1b-cd721868e8db
Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com>
---
Trdo/Pages/FavoritesPage.xaml | 26 ++++++
Trdo/Pages/FavoritesPage.xaml.cs | 53 ++++++++++++
Trdo/Pages/NowPlayingPage.xaml | 32 ++++++-
Trdo/Pages/NowPlayingPage.xaml.cs | 12 +++
Trdo/Pages/SettingsPage.xaml | 78 +++++++++++++++++
Trdo/Services/SettingsService.cs | 77 +++++++++++++++++
Trdo/ViewModels/NowPlayingViewModel.cs | 72 ++++++++++++++++
Trdo/ViewModels/SettingsViewModel.cs | 112 +++++++++++++++++++++++++
8 files changed, 460 insertions(+), 2 deletions(-)
diff --git a/Trdo/Pages/FavoritesPage.xaml b/Trdo/Pages/FavoritesPage.xaml
index 591b8c1..a15438b 100644
--- a/Trdo/Pages/FavoritesPage.xaml
+++ b/Trdo/Pages/FavoritesPage.xaml
@@ -137,6 +137,7 @@
@@ -152,6 +153,7 @@
@@ -161,6 +163,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Trdo/Pages/FavoritesPage.xaml.cs b/Trdo/Pages/FavoritesPage.xaml.cs
index aac58b9..e1077ef 100644
--- a/Trdo/Pages/FavoritesPage.xaml.cs
+++ b/Trdo/Pages/FavoritesPage.xaml.cs
@@ -61,6 +61,12 @@ private void FavoritesListView_SelectionChanged(object sender, SelectionChangedE
if (expandedContent != null)
{
expandedContent.Visibility = Visibility.Visible;
+
+ // Apply per-service visibility based on settings
+ SetButtonVisibility(container, "SpotifyButton", Trdo.Services.SettingsService.IsSpotifyEnabled);
+ SetButtonVisibility(container, "DiscogsButton", Trdo.Services.SettingsService.IsDiscogsEnabled);
+ SetButtonVisibility(container, "AppleMusicButton", Trdo.Services.SettingsService.IsAppleMusicEnabled);
+ SetButtonVisibility(container, "YouTubeMusicButton", Trdo.Services.SettingsService.IsYouTubeMusicEnabled);
}
_previouslySelectedContainer = container;
}
@@ -110,6 +116,53 @@ private async void DiscogsLink_Click(object sender, RoutedEventArgs e)
}
}
+ private async void AppleMusicLink_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is HyperlinkButton button && button.Tag is FavoriteTrack track)
+ {
+ Debug.WriteLine($"[FavoritesPage] Apple Music search for: {track.DisplayText}");
+ string searchQuery = Uri.EscapeDataString(track.DisplayText);
+
+ // Try Apple Music app first
+ string appleMusicAppUri = $"itmss://music.apple.com/search?term={searchQuery}";
+ try
+ {
+ bool success = await Launcher.LaunchUriAsync(new Uri(appleMusicAppUri));
+ if (!success)
+ {
+ string webUrl = $"https://music.apple.com/search?term={searchQuery}";
+ await Launcher.LaunchUriAsync(new Uri(webUrl));
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[FavoritesPage] Error launching Apple Music app: {ex.Message}");
+ string webUrl = $"https://music.apple.com/search?term={searchQuery}";
+ await Launcher.LaunchUriAsync(new Uri(webUrl));
+ }
+ }
+ }
+
+ private async void YouTubeMusicLink_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is HyperlinkButton button && button.Tag is FavoriteTrack track)
+ {
+ Debug.WriteLine($"[FavoritesPage] YouTube Music search for: {track.DisplayText}");
+ string searchQuery = Uri.EscapeDataString(track.DisplayText);
+ string url = $"https://music.youtube.com/search?q={searchQuery}";
+ await Launcher.LaunchUriAsync(new Uri(url));
+ }
+ }
+
+ private void SetButtonVisibility(DependencyObject container, string buttonName, bool isVisible)
+ {
+ HyperlinkButton? button = FindDescendant(container, buttonName);
+ if (button != null)
+ {
+ button.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed;
+ }
+ }
+
private T? FindDescendant(DependencyObject parent, string name = "") where T : DependencyObject
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
diff --git a/Trdo/Pages/NowPlayingPage.xaml b/Trdo/Pages/NowPlayingPage.xaml
index ee57468..5d9965b 100644
--- a/Trdo/Pages/NowPlayingPage.xaml
+++ b/Trdo/Pages/NowPlayingPage.xaml
@@ -136,7 +136,10 @@
Spacing="16">
-
+
@@ -148,13 +151,38 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Trdo/Pages/NowPlayingPage.xaml.cs b/Trdo/Pages/NowPlayingPage.xaml.cs
index 2afada0..c64f3f2 100644
--- a/Trdo/Pages/NowPlayingPage.xaml.cs
+++ b/Trdo/Pages/NowPlayingPage.xaml.cs
@@ -38,6 +38,18 @@ private async void SpotifyLink_Click(object sender, RoutedEventArgs e)
await ViewModel.SearchOnSpotify();
}
+ private async void AppleMusicLink_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("[NowPlayingPage] Apple Music link clicked");
+ await ViewModel.SearchOnAppleMusic();
+ }
+
+ private async void YouTubeMusicLink_Click(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("[NowPlayingPage] YouTube Music link clicked");
+ await ViewModel.SearchOnYouTubeMusic();
+ }
+
private void FavoriteCurrentTrack_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("[NowPlayingPage] Favorite current track clicked");
diff --git a/Trdo/Pages/SettingsPage.xaml b/Trdo/Pages/SettingsPage.xaml
index edc234b..4299153 100644
--- a/Trdo/Pages/SettingsPage.xaml
+++ b/Trdo/Pages/SettingsPage.xaml
@@ -234,6 +234,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Trdo/Services/SettingsService.cs b/Trdo/Services/SettingsService.cs
index b20bbca..c7ee5aa 100644
--- a/Trdo/Services/SettingsService.cs
+++ b/Trdo/Services/SettingsService.cs
@@ -10,6 +10,10 @@ public static class SettingsService
private const string IsFirstRunKey = "IsFirstRun";
private const string IsVolumeSliderVisibleKey = "IsVolumeSliderVisible";
private const string AutoPlayOnStartupKey = "AutoPlayOnStartup";
+ private const string IsSpotifyEnabledKey = "IsSpotifyEnabled";
+ private const string IsDiscogsEnabledKey = "IsDiscogsEnabled";
+ private const string IsAppleMusicEnabledKey = "IsAppleMusicEnabled";
+ private const string IsYouTubeMusicEnabledKey = "IsYouTubeMusicEnabled";
///
/// Gets or sets whether the app should automatically start playing the last selected station on startup.
@@ -89,6 +93,79 @@ string s when bool.TryParse(s, out bool b2) => b2,
}
}
+ ///
+ /// Gets or sets whether Spotify search links are shown.
+ /// Defaults to true when no saved value exists.
+ ///
+ public static bool IsSpotifyEnabled
+ {
+ get => GetBoolSetting(IsSpotifyEnabledKey, defaultValue: true);
+ set => SetBoolSetting(IsSpotifyEnabledKey, value);
+ }
+
+ ///
+ /// Gets or sets whether Discogs search links are shown.
+ /// Defaults to true when no saved value exists.
+ ///
+ public static bool IsDiscogsEnabled
+ {
+ get => GetBoolSetting(IsDiscogsEnabledKey, defaultValue: true);
+ set => SetBoolSetting(IsDiscogsEnabledKey, value);
+ }
+
+ ///
+ /// Gets or sets whether Apple Music search links are shown.
+ /// Defaults to true when no saved value exists.
+ ///
+ public static bool IsAppleMusicEnabled
+ {
+ get => GetBoolSetting(IsAppleMusicEnabledKey, defaultValue: true);
+ set => SetBoolSetting(IsAppleMusicEnabledKey, value);
+ }
+
+ ///
+ /// Gets or sets whether YouTube Music search links are shown.
+ /// Defaults to true when no saved value exists.
+ ///
+ public static bool IsYouTubeMusicEnabled
+ {
+ get => GetBoolSetting(IsYouTubeMusicEnabledKey, defaultValue: true);
+ set => SetBoolSetting(IsYouTubeMusicEnabledKey, value);
+ }
+
+ private static bool GetBoolSetting(string key, bool defaultValue)
+ {
+ try
+ {
+ if (ApplicationData.Current.LocalSettings.Values.TryGetValue(key, out object? value))
+ {
+ return value switch
+ {
+ bool b => b,
+ string s when bool.TryParse(s, out bool b2) => b2,
+ _ => defaultValue
+ };
+ }
+ return defaultValue;
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+
+ private static void SetBoolSetting(string key, bool value)
+ {
+ try
+ {
+ ApplicationData.Current.LocalSettings.Values[key] = value;
+ }
+ catch
+ {
+ // Silently fail if unable to save
+ }
+ }
+
///
/// Gets whether this is the first run of the application
///
diff --git a/Trdo/ViewModels/NowPlayingViewModel.cs b/Trdo/ViewModels/NowPlayingViewModel.cs
index a4e3465..686654d 100644
--- a/Trdo/ViewModels/NowPlayingViewModel.cs
+++ b/Trdo/ViewModels/NowPlayingViewModel.cs
@@ -67,6 +67,10 @@ private void OnStreamMetadataChanged(object? sender, StreamMetadata metadata)
OnPropertyChanged(nameof(ShowRawStreamTitle));
OnPropertyChanged(nameof(DiscogsSearchQuery));
OnPropertyChanged(nameof(SpotifySearchQuery));
+ OnPropertyChanged(nameof(IsSpotifyEnabled));
+ OnPropertyChanged(nameof(IsDiscogsEnabled));
+ OnPropertyChanged(nameof(IsAppleMusicEnabled));
+ OnPropertyChanged(nameof(IsYouTubeMusicEnabled));
// History is now managed by PlaylistHistoryService singleton
UpdateCurrentTrackFavoriteStatus();
@@ -234,6 +238,74 @@ private async Task OpenSpotifyWeb()
await Launcher.LaunchUriAsync(new Uri(webUrl));
}
+ ///
+ /// Opens Apple Music search with the current track information.
+ /// Tries to open the local Apple Music app first, falls back to web.
+ ///
+ public async Task SearchOnAppleMusic()
+ {
+ if (!HasMetadata)
+ return;
+
+ string query = Uri.EscapeDataString(DisplayText.Length > 0 ? DisplayText : StreamTitle);
+
+ // Try to open the Apple Music app using the itmss: URI scheme
+ string appleMusicAppUri = $"itmss://music.apple.com/search?term={query}";
+ try
+ {
+ bool success = await Launcher.LaunchUriAsync(new Uri(appleMusicAppUri));
+ if (!success)
+ {
+ Debug.WriteLine("[NowPlayingViewModel] Apple Music app not available, falling back to web");
+ await OpenAppleMusicWeb(query);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[NowPlayingViewModel] Error launching Apple Music app: {ex.Message}");
+ await OpenAppleMusicWeb(query);
+ }
+ }
+
+ private async Task OpenAppleMusicWeb(string query)
+ {
+ string webUrl = $"https://music.apple.com/search?term={query}";
+ await Launcher.LaunchUriAsync(new Uri(webUrl));
+ }
+
+ ///
+ /// Opens YouTube Music search with the current track information.
+ ///
+ public async Task SearchOnYouTubeMusic()
+ {
+ if (!HasMetadata)
+ return;
+
+ string query = Uri.EscapeDataString(DisplayText.Length > 0 ? DisplayText : StreamTitle);
+ string url = $"https://music.youtube.com/search?q={query}";
+ await Launcher.LaunchUriAsync(new Uri(url));
+ }
+
+ ///
+ /// Gets whether Spotify search links should be shown.
+ ///
+ public bool IsSpotifyEnabled => SettingsService.IsSpotifyEnabled;
+
+ ///
+ /// Gets whether Discogs search links should be shown.
+ ///
+ public bool IsDiscogsEnabled => SettingsService.IsDiscogsEnabled;
+
+ ///
+ /// Gets whether Apple Music search links should be shown.
+ ///
+ public bool IsAppleMusicEnabled => SettingsService.IsAppleMusicEnabled;
+
+ ///
+ /// Gets whether YouTube Music search links should be shown.
+ ///
+ public bool IsYouTubeMusicEnabled => SettingsService.IsYouTubeMusicEnabled;
+
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
diff --git a/Trdo/ViewModels/SettingsViewModel.cs b/Trdo/ViewModels/SettingsViewModel.cs
index ac1ddb2..71cb010 100644
--- a/Trdo/ViewModels/SettingsViewModel.cs
+++ b/Trdo/ViewModels/SettingsViewModel.cs
@@ -21,6 +21,10 @@ public partial class SettingsViewModel : INotifyPropertyChanged
private string _watchdogToggleText = "Off";
private string _autoBufferToggleText = "Off";
private string _autoPlayOnStartupToggleText = "Off";
+ private string _spotifyToggleText = "On";
+ private string _discogsToggleText = "On";
+ private string _appleMusicToggleText = "On";
+ private string _youtubeMusicToggleText = "On";
private StartupTask? _startupTask;
private bool _initDone;
@@ -59,6 +63,10 @@ public SettingsViewModel()
WatchdogToggleText = GetToggleText(_playerViewModel.WatchdogEnabled);
AutoBufferToggleText = GetToggleText(_playerViewModel.AutoBufferIncreaseEnabled);
AutoPlayOnStartupToggleText = GetToggleText(SettingsService.AutoPlayOnStartup);
+ SpotifyToggleText = GetToggleText(SettingsService.IsSpotifyEnabled);
+ DiscogsToggleText = GetToggleText(SettingsService.IsDiscogsEnabled);
+ AppleMusicToggleText = GetToggleText(SettingsService.IsAppleMusicEnabled);
+ YouTubeMusicToggleText = GetToggleText(SettingsService.IsYouTubeMusicEnabled);
// Initialize startup task
_ = InitializeStartupTaskAsync();
@@ -129,6 +137,110 @@ public string AutoPlayOnStartupToggleText
}
}
+ ///
+ /// Gets or sets whether Spotify search links are shown in Now Playing and Favorites.
+ ///
+ public bool IsSpotifyEnabled
+ {
+ get => SettingsService.IsSpotifyEnabled;
+ set
+ {
+ if (value == SettingsService.IsSpotifyEnabled) return;
+ SettingsService.IsSpotifyEnabled = value;
+ OnPropertyChanged();
+ SpotifyToggleText = GetToggleText(value);
+ }
+ }
+
+ public string SpotifyToggleText
+ {
+ get => _spotifyToggleText;
+ set
+ {
+ if (value == _spotifyToggleText) return;
+ _spotifyToggleText = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets whether Discogs search links are shown in Now Playing and Favorites.
+ ///
+ public bool IsDiscogsEnabled
+ {
+ get => SettingsService.IsDiscogsEnabled;
+ set
+ {
+ if (value == SettingsService.IsDiscogsEnabled) return;
+ SettingsService.IsDiscogsEnabled = value;
+ OnPropertyChanged();
+ DiscogsToggleText = GetToggleText(value);
+ }
+ }
+
+ public string DiscogsToggleText
+ {
+ get => _discogsToggleText;
+ set
+ {
+ if (value == _discogsToggleText) return;
+ _discogsToggleText = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets whether Apple Music search links are shown in Now Playing and Favorites.
+ ///
+ public bool IsAppleMusicEnabled
+ {
+ get => SettingsService.IsAppleMusicEnabled;
+ set
+ {
+ if (value == SettingsService.IsAppleMusicEnabled) return;
+ SettingsService.IsAppleMusicEnabled = value;
+ OnPropertyChanged();
+ AppleMusicToggleText = GetToggleText(value);
+ }
+ }
+
+ public string AppleMusicToggleText
+ {
+ get => _appleMusicToggleText;
+ set
+ {
+ if (value == _appleMusicToggleText) return;
+ _appleMusicToggleText = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets whether YouTube Music search links are shown in Now Playing and Favorites.
+ ///
+ public bool IsYouTubeMusicEnabled
+ {
+ get => SettingsService.IsYouTubeMusicEnabled;
+ set
+ {
+ if (value == SettingsService.IsYouTubeMusicEnabled) return;
+ SettingsService.IsYouTubeMusicEnabled = value;
+ OnPropertyChanged();
+ YouTubeMusicToggleText = GetToggleText(value);
+ }
+ }
+
+ public string YouTubeMusicToggleText
+ {
+ get => _youtubeMusicToggleText;
+ set
+ {
+ if (value == _youtubeMusicToggleText) return;
+ _youtubeMusicToggleText = value;
+ OnPropertyChanged();
+ }
+ }
+
public bool IsWatchdogEnabled
{
get => _playerViewModel.WatchdogEnabled;
From ec82bfb1b7458a39cf190f7c9b013d9a191d63f1 Mon Sep 17 00:00:00 2001
From: Joe Finney
Date: Wed, 1 Apr 2026 19:19:56 -0500
Subject: [PATCH 10/17] Add SVG logos for Apple Music, Discogs, and YouTube
Music
Added new SVG files: apple_music.svg, discogs.svg, and ytmusic.svg. These files provide vector logo assets for Apple Music, Discogs, and YouTube Music, with discogs.svg featuring detailed paths and gradients.
---
Trdo/Assets/apple_music.svg | 5 +++++
Trdo/Assets/discogs.svg | 42 +++++++++++++++++++++++++++++++++++++
Trdo/Assets/ytmusic.svg | 12 +++++++++++
3 files changed, 59 insertions(+)
create mode 100644 Trdo/Assets/apple_music.svg
create mode 100644 Trdo/Assets/discogs.svg
create mode 100644 Trdo/Assets/ytmusic.svg
diff --git a/Trdo/Assets/apple_music.svg b/Trdo/Assets/apple_music.svg
new file mode 100644
index 0000000..6b5affd
--- /dev/null
+++ b/Trdo/Assets/apple_music.svg
@@ -0,0 +1,5 @@
+
diff --git a/Trdo/Assets/discogs.svg b/Trdo/Assets/discogs.svg
new file mode 100644
index 0000000..957eeec
--- /dev/null
+++ b/Trdo/Assets/discogs.svg
@@ -0,0 +1,42 @@
+
+
diff --git a/Trdo/Assets/ytmusic.svg b/Trdo/Assets/ytmusic.svg
new file mode 100644
index 0000000..f9df22a
--- /dev/null
+++ b/Trdo/Assets/ytmusic.svg
@@ -0,0 +1,12 @@
+
From 3bd4896dd8e58a7116b13d015a9625897609e33f Mon Sep 17 00:00:00 2001
From: Joe Finney
Date: Wed, 1 Apr 2026 19:20:15 -0500
Subject: [PATCH 11/17] Redesign music search links & settings integration
- Use SVG icons for all music search services for visual consistency
- Add horizontally scrollable search links with settings button
- Centralize Apple Music search logic with region-aware URLs
- Redesign SettingsPage for per-service music search toggles
- Raise MusicSearchServicesChanged event for real-time UI updates
- Update NowPlayingViewModel and FavoritesPage to react to service changes
- Refactor Apple Music search to always use web, not app
- Improve XAML formatting and UI alignment
- Add and include new SVG assets for Apple Music, Discogs, YouTube Music
---
Trdo/Pages/FavoritesPage.xaml | 154 +++++++++-----
Trdo/Pages/FavoritesPage.xaml.cs | 108 +++++++---
Trdo/Pages/NowPlayingPage.xaml | 135 ++++++++-----
Trdo/Pages/NowPlayingPage.xaml.cs | 32 +++
Trdo/Pages/SettingsPage.xaml | 256 +++++++++++++++---------
Trdo/Services/MusicSearchLinkService.cs | 29 +++
Trdo/Services/SettingsService.cs | 7 +
Trdo/Trdo.csproj | 12 ++
Trdo/ViewModels/NowPlayingViewModel.cs | 47 ++---
9 files changed, 527 insertions(+), 253 deletions(-)
create mode 100644 Trdo/Services/MusicSearchLinkService.cs
diff --git a/Trdo/Pages/FavoritesPage.xaml b/Trdo/Pages/FavoritesPage.xaml
index a15438b..d8cc599 100644
--- a/Trdo/Pages/FavoritesPage.xaml
+++ b/Trdo/Pages/FavoritesPage.xaml
@@ -31,7 +31,9 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="12"
- Visibility="{x:Bind ViewModel.Favorites.Count, Mode=OneWay, Converter={StaticResource CountToEmptyStateVisibilityConverter}}">
+ Visibility="{x:Bind ViewModel.Favorites.Count,
+ Mode=OneWay,
+ Converter={StaticResource CountToEmptyStateVisibilityConverter}}">
+ Visibility="{x:Bind ViewModel.Favorites.Count,
+ Mode=OneWay,
+ Converter={StaticResource CountToHasItemsVisibilityConverter}}">
+
+
+
+
@@ -22,403 +51,359 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+ VerticalAlignment="Center"
+ Orientation="Horizontal"
+ Spacing="2">
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
From 893a78b6c8c30e6fd89cb27c3a3f9f740352072e Mon Sep 17 00:00:00 2001
From: Joe Finney
Date: Wed, 1 Apr 2026 20:53:50 -0500
Subject: [PATCH 15/17] Update app version and NuGet packages, reformat x:Bind
Bump app version to 1.9.0.0. Update several NuGet package references to latest versions. Reformat x:Bind expressions in PlayingPage.xaml for better readability without changing logic.
---
Trdo/Package.appxmanifest | 2 +-
Trdo/Pages/PlayingPage.xaml | 27 +++++++++++++++++++--------
Trdo/Trdo.csproj | 8 ++++----
3 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/Trdo/Package.appxmanifest b/Trdo/Package.appxmanifest
index 09ba118..07a2b93 100644
--- a/Trdo/Package.appxmanifest
+++ b/Trdo/Package.appxmanifest
@@ -11,7 +11,7 @@
+ Version="1.9.0.0" />
diff --git a/Trdo/Pages/PlayingPage.xaml b/Trdo/Pages/PlayingPage.xaml
index b19d889..786393a 100644
--- a/Trdo/Pages/PlayingPage.xaml
+++ b/Trdo/Pages/PlayingPage.xaml
@@ -103,8 +103,10 @@
CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="StationsListView_DragItemsCompleted"
- ItemsSource="{x:Bind ViewModel.Stations, Mode=OneWay}"
- SelectedItem="{x:Bind ViewModel.SelectedStation, Mode=TwoWay}"
+ ItemsSource="{x:Bind ViewModel.Stations,
+ Mode=OneWay}"
+ SelectedItem="{x:Bind ViewModel.SelectedStation,
+ Mode=TwoWay}"
SelectionChanged="StationsListView_SelectionChanged"
SelectionMode="Single">
@@ -162,7 +164,9 @@
Height="24"
Margin="0,0,12,0"
VerticalAlignment="Center"
- Visibility="{x:Bind FaviconUrl, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}">
+ Visibility="{x:Bind FaviconUrl,
+ Converter={StaticResource NullToVisibilityConverter},
+ Mode=OneWay}">
@@ -187,7 +191,9 @@
Padding="6"
HorizontalAlignment="Center"
VerticalAlignment="Center"
- Visibility="{x:Bind ViewModel.Stations.Count, Mode=OneWay, Converter={StaticResource CountToEmptyStateVisibilityConverter}}">
+ Visibility="{x:Bind ViewModel.Stations.Count,
+ Mode=OneWay,
+ Converter={StaticResource CountToEmptyStateVisibilityConverter}}">
+ Value="{x:Bind ViewModel.Volume,
+ Mode=TwoWay}" />
@@ -276,7 +283,9 @@
Click="NowPlayingInfo_Click"
CornerRadius="4"
ToolTipService.ToolTip="Click for details"
- Visibility="{x:Bind ViewModel.HasNowPlaying, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
+ Visibility="{x:Bind ViewModel.HasNowPlaying,
+ Mode=OneWay,
+ Converter={StaticResource BooleanToVisibilityConverter}}">
@@ -300,14 +309,16 @@
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
RepeatBehavior="Forever"
Speed="40"
- Text="{x:Bind ViewModel.NowPlaying, Mode=OneWay}" />
+ Text="{x:Bind ViewModel.NowPlaying,
+ Mode=OneWay}" />
diff --git a/Trdo/Trdo.csproj b/Trdo/Trdo.csproj
index 3bca01a..f74031a 100644
--- a/Trdo/Trdo.csproj
+++ b/Trdo/Trdo.csproj
@@ -59,10 +59,10 @@
-
-
-
-
+
+
+
+
From 6ae73bc396990dd7b2bedc8ab70a7a387509c1e1 Mon Sep 17 00:00:00 2001
From: Joe Finney
Date: Thu, 2 Apr 2026 20:49:50 -0500
Subject: [PATCH 16/17] Update default music settings and InfoBar button label
Changed default values for Apple Music and YouTube Music settings to false, disabling them by default. Updated InfoBar action button label from "Enable anyway" to "Enable" for clarity.
---
Trdo/Pages/SettingsPage.xaml | 3 ++-
Trdo/Services/SettingsService.cs | 4 ++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/Trdo/Pages/SettingsPage.xaml b/Trdo/Pages/SettingsPage.xaml
index 5326a09..1115443 100644
--- a/Trdo/Pages/SettingsPage.xaml
+++ b/Trdo/Pages/SettingsPage.xaml
@@ -82,6 +82,7 @@
+ Content="Enable" />
diff --git a/Trdo/Services/SettingsService.cs b/Trdo/Services/SettingsService.cs
index a5f14cc..d8e4724 100644
--- a/Trdo/Services/SettingsService.cs
+++ b/Trdo/Services/SettingsService.cs
@@ -123,7 +123,7 @@ public static bool IsDiscogsEnabled
///
public static bool IsAppleMusicEnabled
{
- get => GetBoolSetting(IsAppleMusicEnabledKey, defaultValue: true);
+ get => GetBoolSetting(IsAppleMusicEnabledKey, defaultValue: false);
set => SetBoolSetting(IsAppleMusicEnabledKey, value);
}
@@ -133,7 +133,7 @@ public static bool IsAppleMusicEnabled
///
public static bool IsYouTubeMusicEnabled
{
- get => GetBoolSetting(IsYouTubeMusicEnabledKey, defaultValue: true);
+ get => GetBoolSetting(IsYouTubeMusicEnabledKey, defaultValue: false);
set => SetBoolSetting(IsYouTubeMusicEnabledKey, value);
}
From 50695869630b2c5eaa76371bb93aae38fdde75d1 Mon Sep 17 00:00:00 2001
From: Joe Finney
Date: Thu, 2 Apr 2026 21:04:25 -0500
Subject: [PATCH 17/17] Bump app version to 1.9.1.0
Updated the app version in Package.appxmanifest from 1.9.0.0 to 1.9.1.0 to reflect the latest release.
---
Trdo/Package.appxmanifest | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Trdo/Package.appxmanifest b/Trdo/Package.appxmanifest
index 07a2b93..acd1508 100644
--- a/Trdo/Package.appxmanifest
+++ b/Trdo/Package.appxmanifest
@@ -11,7 +11,7 @@
+ Version="1.9.1.0" />